2011-03-24 3 views
8

NSStatusItem의 프레임을 코코아의 상태 표시 줄에 추가 한 후 프레임을 가져올 수 있습니까? 내 앱이 시작되면 시스템 상태 표시 줄에 항목을 추가하고 위치를 알 수 있기를 원합니다. 이 상태 항목에서 사용자 정의보기를 설정 한 경우NSStatusItem의 프레임을 얻는 방법

답변

1

:

NSRect statusRect = [[statusItem view] frame]; 
NSLog(@"%@", [NSString stringWithFormat:@"%.1fx%.1f",statusRect.size.width, statusRect.size.height]); 

그렇지 않으면 나는 그것이 가능하고 문서화 된 API를 사용하여 가능하다고 생각하지 않습니다.

편집 : 통합 의견.

+2

나를 위해 작동하지 않았습니다. NSStatusItem에는 기본보기가 없어서 [statusItem view]는 null을 반환합니다. – blutfink

+0

문서에서 "상태 표시 줄의 수신자 위치에 표시되는 사용자 정의보기를 반환합니다."이며 NSStatusItem의보기가 아닙니다. –

+1

상태 항목에 대한 사용자 정의보기를 설정 한 경우에만 작동합니다. – Fabian

-3

당신은 다음과 같이 윈도우 바르를 해킹 할 수

@interface NSStatusItem (Hack) 

- (NSRect)hackFrame; 

@end 

@implementation NSStatusItem (Hack) 

- (NSRect)hackFrame 
{ 
    int objSize = class_getInstanceSize([NSObject class]) ; 
    id * _ffWindow = (void *)self + objSize + sizeof(NSStatusBar*) + sizeof(CGFloat) ; 
    NSWindow * window = *_ffWindow ; 

    return [window frame] ; 
} 

@end 

이 사용자 정의보기가없는 상태 항목에 유용합니다.

사자

25

에서 테스트 다음은 작동하는 것 같다 - 나는 아이폰 OS 애플리케이션을위한 유사한 솔루션을 본 당신은 여전히 ​​표준 SDK 방법을 사용하고 있기 때문에 아마도 그들은 앱 스토어에 제출을 허용합니다.

NSRect frame = [[statusBarItem valueForKey:@"window"] frame]; 
+2

이 기능은 상태 항목에 대한 사용자 정의보기를 설정하지 않은 경우에 유용합니다. – Fabian

+1

이것은 NSStatusItem ('statusBarItem'이 무엇인지 가정)이 KVC가'window' 속성으로 사용할 수있는 것을 가지고 있다고 가정합니다. 그 경우는 보장되지 않습니다. 이 키에 대해 KVC와 호환되지 않는 예외가 발생합니다. 또한이 앱 스토어 검토를 통과하지 않을 것입니다. 이미없는 경우 KVC 사용 방법을 확인하기 시작할 수도 있습니다 (이 방법을 사용하여 개인 방법/ivars에 액세스하는 경우). –

+1

App Store 리뷰 프로세스에서 거부 될 수있는 비공개 API 호출입니까? –

24

는 10.10으로, NSStatusItem 사용자 정의보기를 설정하지 않고 상태 항목의 위치를 ​​가져 오는 데 사용할 수 button 속성이 있습니다.

NSStatusBarButton *statusBarButton = [myStatusItem button]; 
NSRect rectInWindow = [statusBarButton convertRect:[statusBarButton bounds] toView:nil]; 
NSRect screenRect = [[statusBarButton window] convertRectToScreen:rectInWindow]; 
NSLog(@"%@", NSStringFromRect(screenRect)); 
+0

굉장한 팁이지만 10.10은 아직 널리 사용되지 않습니다. :( –

0

개인용 API가 없어도 가능합니다. 다음은 NSScreen 카테고리입니다. 이미지 분석을 사용하여 메뉴 막대에서 상태 항목의 이미지를 찾습니다. 다행히 컴퓨터는 정말 빠릅니다. :)

상태 항목의 이미지가 어떤 모양인지 알고 NSImage로 전달할 수있는 한이 메서드는이를 찾습니다.

일반 모드는 물론 어두운 모드에서도 작동합니다. 전달하는 이미지는 검은 색이어야합니다. 컬러 이미지는 아마도 그렇게 잘 작동하지 않을 것입니다.

@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 
관련 문제