Kakao'daki durum çubuğuna ekledikten sonra NSStatusItem
çerçevesinin elde edilmesi mümkün mü? Uygulamam başlatıldığında, sistem durum çubuğuna bir öğe ekliyorum ve nerede konumlandırılacağını bilmek istiyorum, mümkün. Eğer durum öğe üzerinde özel bir görünüm kurduysanızNSStatusItem
cevap
:
NSRect statusRect = [[statusItem view] frame];
NSLog(@"%@", [NSString stringWithFormat:@"%.1fx%.1f",statusRect.size.width, statusRect.size.height]);
Aksi takdirde mevcut ve belgelenmiş API'leri kullanarak mümkün olduğunu sanmıyorum.
Düzeltme: Eklenen yorumlar.
böyle pencere ivar kesmek olabilir:
@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
Bu özel bir görünüm olmadan durum öğeler için yararlıdır.
Lionüzerinde test şu iş gibi görünüyor - iOS uygulamaları için benzer çözümler gördük ve hala standart SDK yöntemlerle çünkü sözde onlar uygulama mağazasına teslim izin verir.
NSRect frame = [[statusBarItem valueForKey:@"window"] frame];
Bu, çalışır ve durum öğesi için özel bir görünüm belirlemediyseniz kullanışlıdır. – Fabian
Bu, NSStatusItem'in (statusBarItem öğesinin ne olduğunu varsayarak) KVC'nin 'window' özelliği olarak kullanabileceği bir şey olduğunu varsayar. Durum böyle değil. Bu zaman aşımına uğradığında bir istisna (bu anahtar için KVC uyumlu değil) alırsınız. Ayrıca, bu App Store incelemesine de son vermedim. Ya da yapmadılarsa, bir gün KVC'yi nasıl kullandığınızı kontrol etmeye başlayabilirler (özel yöntemler/ivar'lara eriştiğiniz yerlerde bu gibi kullanımları). –
Bu, App Store inceleme sürecinden reddedilmenizi olası hale getiren özel bir API çağrısı mı? –
10.10 ile NSStatusItem
özel bir görünüm ayarlamadan durum maddenin pozisyon almak için kullanılabilecek bir button
özelliğine sahiptir.
NSStatusBarButton *statusBarButton = [myStatusItem button];
NSRect rectInWindow = [statusBarButton convertRect:[statusBarButton bounds] toView:nil];
NSRect screenRect = [[statusBarButton window] convertRectToScreen:rectInWindow];
NSLog(@"%@", NSStringFromRect(screenRect));
Harika bir ipucu, ancak 10.10 henüz oldukça popüler değil. :( –
Bunu özel bir API olmadan yapmak mümkündür. İşte NSScreen için bir kategori. Bu durum öğesinin menü çubuğundaki görüntüsünü bulmak için görüntü analizi kullanır. Neyse ki bilgisayarlar gerçekten hızlı. :)
Durum öğesinin görüntüsünün neye benzediğini bildiğiniz ve onu bir NSImage olarak geçirebiliyorsanız, bu yöntem bunu bulmalıdır.
Normal modun yanı sıra karanlık modda çalışır. Geçirdiğiniz resmin siyah olması gerektiğini unutmayın. Renkli görüntüler muhtemelen çok iyi çalışmayacaktır.
@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
Benim için çalışmadı. Bir NSStatusItem varsayılan bir görünüme sahip görünmüyor, bu nedenle [statusItem view] null değerini döndürür. – blutfink
Belgede belirttiği gibi, "NSStatusItem'in görünümü değil, durum çubuğunda alıcının konumunda görüntülenen özel görünümü döndürür." –
Bu, yalnızca durum öğesinde özel bir görünüm oluşturduysanız çalışır. – Fabian