如何获取NSStatusItem的屏幕位置

Joh*_*ast 13 macos cocoa objective-c nsstatusitem

我有一个关于NSStatusItemmac osx中for cocoa 的问题.如果你看一下名为snippets的mac app(请参阅http://snippetsapp.com/上的电影).您将看到,一旦您单击状态栏图标,图标下方就会出现完美对齐的视图/面板或甚至是窗口.

我的问题是......如何计算你的位置NSWindow就像这个应用程序一样?

我尝试过以下方法:

  1. 子类 NSMenu
  2. 为菜单的第一项设置视图显示(工作但足够)
  3. 使用addSubview而不是图标来实现NSStatusItem这个效果,但是不能高于20px

Pet*_*sey 11

为NSStatusItem提供一个视图,然后获取该视图窗口的框架.这在技术上被视为未记录的好处,所以如果它有一天中断(例如,如果他们开始将窗口保持在屏幕外)则不要感到惊讶.

我不知道你的意思是"不能再高于20px".


Ste*_*teg 8

为了做到这一点,没有自定义视图的麻烦,我尝试了以下(这是有效的).在设置为状态项的操作的方法中,即在用户单击状态项时调用的方法,可以通过以下方式检索状态项的框架:

[[[NSApp currentEvent] window] frame]
Run Code Online (Sandbox Code Playgroud)

为我服务


TyR*_*TyR 0

注意:请不要使用这个,至少不要用于定位 NSStatusItem。

当我发布这篇文章时,这种疯狂的图像匹配技术是在没有未记录的 API 的情况下解决这个问题的唯一方法。现在,您应该使用奥斯卡的解决方案。


如果您愿意使用图像分析来查找菜单栏上的状态项,这里有一个 NSScreen 类别,它正是这样做的。

这样做可能看起来很疯狂,但它速度快,相对较小,并且是在没有未记录的 API 的情况下查找状态项的唯一方法。

如果您传入状态项的当前图像,此方法应该找到它。

@implementation NSScreen (LTStatusItemLocator)

// Find the location of IMG on the screen's status bar.
// If the image is not found, returns NSZeroPoint
- (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG
{
    CGColorSpaceRef     csK = CGColorSpaceCreateDeviceGray();
    NSPoint             ret = NSZeroPoint;
    CGDirectDisplayID   screenID = 0;
    CGImageRef          displayImg = NULL;
    CGImageRef          compareImg = NULL;
    CGRect              screenRect = CGRectZero;
    CGRect              barRect = CGRectZero;
    uint8_t             *bm_bar = NULL;
    uint8_t             *bm_bar_ptr;
    uint8_t             *bm_compare = NULL;
    uint8_t             *bm_compare_ptr;
    size_t              bm_compare_w, bm_compare_h;
    BOOL                inverted = NO;
    int                 numberOfScanLines = 0;
    CGFloat             *meanValues = NULL;

    int                 presumptiveMatchIdx = -1;
    CGFloat             presumptiveMatchMeanVal = 999;


    // If the computer is set to Dark Mode, set the "inverted" flag
    NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
    id style = globalPrefs[@"AppleInterfaceStyle"];
    if ([style isKindOfClass:[NSString class]]) {
        inverted = (NSOrderedSame == [style caseInsensitiveCompare:@"dark"]);
    }

    screenID = (CGDirectDisplayID)[self.deviceDescription[@"NSScreenNumber"] integerValue];

    screenRect = CGDisplayBounds(screenID);

    // Get the menubar rect
    barRect = CGRectMake(0, 0, screenRect.size.width, 22);

    displayImg = CGDisplayCreateImageForRect(screenID, barRect);
    if (!displayImg) {
        NSLog(@"Unable to create image from display");
        CGColorSpaceRelease(csK);
        return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers
    }

    size_t bar_w = CGImageGetWidth(displayImg);
    size_t bar_h = CGImageGetHeight(displayImg);

    // Determine scale factor based on the CGImageRef we got back from the display
    CGFloat scaleFactor = (CGFloat)bar_h / (CGFloat)22;

    // Greyscale bitmap for menu bar
    bm_bar = malloc(1 * bar_w * bar_h);
    {
        CGContextRef bmCxt = NULL;

        bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);

        // Draw the menu bar in grey
        CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg);

        uint8_t minVal = 0xff;
        uint8_t maxVal = 0x00;
        // Walk the bitmap
        uint64_t running = 0;
        for (int yi = bar_h / 2; yi == bar_h / 2; yi++)
        {
            bm_bar_ptr = bm_bar + (bar_w * yi);
            for (int xi = 0; xi < bar_w; xi++)
            {
                uint8_t v = *bm_bar_ptr++;
                if (v < minVal) minVal = v;
                if (v > maxVal) maxVal = v;
                running += v;
            }
        }
        running /= bar_w;
        uint8_t threshold = minVal + ((maxVal - minVal) / 2);
        //threshold = running;


        // Walk the bitmap
        bm_bar_ptr = bm_bar;
        for (int yi = 0; yi < bar_h; yi++)
        {
            for (int xi = 0; xi < bar_w; xi++)
            {
                // Threshold all the pixels. Values > 50% go white, values <= 50% go black
                // (opposite if Dark Mode)

                // Could unroll this loop as an optimization, but probably not worthwhile
                *bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00);
                bm_bar_ptr++;
            }
        }


        CGImageRelease(displayImg);
        displayImg = CGBitmapContextCreateImage(bmCxt);

        CGContextRelease(bmCxt);
    }


    {
        CGContextRef bmCxt = NULL;
        CGImageRef img_cg = NULL;

        bm_compare_w = scaleFactor * IMG.size.width;
        bm_compare_h = scaleFactor * 22;

        // Create out comparison bitmap - the image that was passed in
        bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);

        CGContextSetBlendMode(bmCxt, kCGBlendModeNormal);

        NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height);
        NSRect imgRect = imgRect_og;
        img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil];

        CGContextClearRect(bmCxt, imgRect);
        CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor);
        CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999));

        CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor);
        CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height) / 2.);

        // Draw the image in grey
        CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor);
        CGContextDrawImage(bmCxt, imgRect, img_cg);

        compareImg = CGBitmapContextCreateImage(bmCxt);


        CGContextRelease(bmCxt);
    }




    {
        // We start at the right of the menu bar, and scan left until we find a good match
        int numberOfScanLines = barRect.size.width - IMG.size.width;

        bm_compare = malloc(1 * bm_compare_w * bm_compare_h);
        // We use the meanValues buffer to keep track of how well the image matched for each point in the scan
        meanValues = calloc(sizeof(CGFloat), numberOfScanLines);

        // Walk the menubar image from right to left, pixel by pixel
        for (int scanx = 0; scanx < numberOfScanLines; scanx++)
        {

            // Optimization, if we recently found a really good match, bail on the loop and return it
            if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) {
                break;
            }

            CGFloat xOffset = numberOfScanLines - scanx;
            CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor);
            CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect);

            CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
            CGContextSetBlendMode(compareCxt, kCGBlendModeCopy);

            // Draw the image from our menubar
            CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop);

            // Blend mode difference is like an XOR
            CGContextSetBlendMode(compareCxt, kCGBlendModeDifference);

            // Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly
            CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg);

            CGContextFlush(compareCxt);

            // Walk through the result image, to determine overall blackness
            bm_compare_ptr = bm_compare;
            for (int i = 0; i < bm_compare_w * bm_compare_h; i++)
            {
                meanValues[scanx] += (CGFloat)(*bm_compare_ptr);
                bm_compare_ptr++;
            }
            meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h));

            // If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this
            // a presumptive match. Mark it as such, but continue looking to see if there's an even better match.
            if (meanValues[scanx] < 0.07) {
                if (meanValues[scanx] < presumptiveMatchMeanVal) {
                    presumptiveMatchMeanVal = meanValues[scanx];
                    presumptiveMatchIdx = scanx;
                }
            }

            CGImageRelease(displayCrop);
            CGContextRelease(compareCxt);

        }
    }


    // After we're done scanning the whole menubar (or we bailed because we found a good match),
    // return the origin point.
    // If we didn't match well enough, return NSZeroPoint
    if (presumptiveMatchIdx >= 0) {
        ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame));
        ret.x -= (IMG.size.width + presumptiveMatchIdx);
        ret.y -= 22;
    }


    CGImageRelease(displayImg);
    CGImageRelease(compareImg);
    CGColorSpaceRelease(csK);

    if (bm_bar) free(bm_bar);
    if (bm_compare) free(bm_compare);
    if (meanValues) free(meanValues);

    return ret;
}

@end
Run Code Online (Sandbox Code Playgroud)

  • 我知道这很疯狂。它仍然是一个很好的图像匹配算法,所以就按它的本来面目吧。当我发布这篇文章时,这实际上是在没有未记录的 API 的情况下解决这个问题的唯一方法。现在情况已不再如此,所以不要将其用于此目的。 (2认同)