首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何获取NSStatusItem的屏幕位置

如何获取NSStatusItem的屏幕位置
EN

Stack Overflow用户
提问于 2009-08-19 18:13:54
回答 6查看 5.8K关注 0票数 14

我有一个关于mac中可可的NSStatusItem的问题。如果你看一看mac上的名为snippets的应用程序(参见http://snippetsapp.com/上的电影)。你会看到,一旦你点击你的状态栏图标,一个完全对齐的视图/面板甚至窗口就会出现在图标的下方。

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

我尝试过以下几种方法:

  1. 子类NSMenu
  2. Set菜单第一项的视图大小(有效但足够了)
  3. 使用addSubview而不是addSubview to NSStatusItem这有效,但不能超过20px的
EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2016-06-29 05:05:54

注意:请不要使用此,至少不要用于查找NSStatusItem。

回到我发布这篇文章的时候,这种疯狂的图像匹配技术是解决这个问题的唯一方法,而不需要未经记录的API。现在,您应该使用Oskar的解决方案。

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

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

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

代码语言:javascript
复制
@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
票数 0
EN

Stack Overflow用户

发布于 2009-08-19 19:37:31

给NSStatusItem一个视图,然后获取该视图窗口的框架。这在技术上被算作UndocumentedGoodness,所以如果有一天它崩溃了,不要感到惊讶(例如,如果他们开始将窗口保持在屏幕之外)。

我不知道你说的“不能再高到20px”是什么意思。

票数 11
EN

Stack Overflow用户

发布于 2012-03-29 22:18:43

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

代码语言:javascript
复制
[[[NSApp currentEvent] window] frame]

对我来说是一种享受

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1301701

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档