by ampatspell
in Code
Few weeks ago I finished my first Mac application — Saucejas.app. It is small (about 1000 lines of code) app which renders pretty image and video slideshow to secondary display using CoreAnimation.
Application itself doesn’t have notably pretty UI or implementation. It was written basically in one day for use in Saucejas show as a informative & mood-setting background. Here is video featuring Saucejas and previous version of app in background which I wrote earlier in Processing.
Nevertheless it was a really good exercise for me and here are few things I’ve learned:
plist editor is not the easiest to use tool for NSArray of NSDictionaries of 2-3 key-value pairs — consider different format for large lists or create custom editor for plistNSSize SCProportionalSizeForTargetSize(NSSize size, NSSize target) { if(NSEqualSizes(size, target)) { return size; } float widthFactor = target.width / size.width; float heightFactor = target.height / size.height; float scalingFactor; if (widthFactor < heightFactor) { scalingFactor = widthFactor; } else { scalingFactor = heightFactor; } return NSMakeSize(size.width * scalingFactor, size.height * scalingFactor); } @implementation NSImage (SCAdditions) - (NSImage *)imageByScalingProportionallyToSize:(NSSize)target { NSSize size = self.size; NSSize proportionalSize = SCProportionalSizeForTargetSize(size, target); NSImage *scaledImage = [[NSImage alloc] initWithSize:proportionalSize]; [scaledImage setCacheMode:NSImageCacheNever]; [scaledImage lockFocus]; [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; [self drawInRect:NSMakeRect(.0f, .0f, proportionalSize.width, proportionalSize.height) fromRect:NSMakeRect(.0f, .0f, size.width, size.height) operation:NSCompositeSourceOver fraction:1.0f]; [scaledImage unlockFocus]; return [scaledImage autorelease]; } @end
NSImage can also be done in background threadNSImageCacheNever otherwise all scaled NSImages are cached// Periodically called by NSTimer - (void)disableScreenSaverTimerDidUpdate:(NSTimer *)timer { UpdateSystemActivity(OverallAct); }
#import <IOKit/graphics/IOGraphicsLib.h> @implementation SCDisplay + (NSArray *)displays { NSMutableArray *displays = [NSMutableArray array]; CGDirectDisplayID *onlineDisplays = malloc( sizeof(CGDirectDisplayID) * kSCMaxDisplayCount); CGDisplayCount displayCount; CGDisplayErr listErr = CGGetActiveDisplayList(kSCMaxDisplayCount, onlineDisplays, &displayCount); if(listErr == kCGErrorSuccess) { for(int i=0; i<displayCount; i++) { CGDirectDisplayID displayId = onlineDisplays[i]; CGRect bounds = CGDisplayBounds(displayId); NSString *name = nil; io_service_t port = CGDisplayIOServicePort(displayId); CFDictionaryRef infoRef = IODisplayCreateInfoDictionary(port, kIODisplayOnlyPreferredName); if(infoRef != NULL) { CFDictionaryRef productNameDictionaryRef = CFDictionaryGetValue(infoRef, CFSTR(kDisplayProductName)); if(productNameDictionaryRef != NULL) { CFIndex count = CFDictionaryGetCount(productNameDictionaryRef); if(count > 0) { const void **values = (const void **)malloc (sizeof(void *) * count); CFDictionaryGetKeysAndValues(productNameDictionaryRef, NULL, values); CFTypeRef nameRef = (CFTypeRef)values[0]; name = [NSString stringWithString:(NSString *)nameRef]; free(values); } } CFRelease(infoRef); } if(!name) { name = @"Unknown"; } SCDisplay *display = [[SCDisplay alloc] initWithDirectDisplayId:displayId bounds:bounds name:name]; [displays addObject:display]; [display release]; } } else { NSLog(@"%@ %s • CGGetOnlineDisplayList failed: %u", [self className], _cmd, listErr); } free(onlineDisplays); return displays; } @end
// CGDirectDisplayID displayId_; from CGGetActiveDisplayList // Caputure CGDisplayErr captureErr = CGDisplayCapture(self.displayId); if(captureErr == kCGErrorSuccess) { self.captured = YES; } else { NSLog(@"%@ %s • CGDisplayCapture failed: %u", [self className], _cmd, captureErr); } // Release CGDisplayErr err = CGDisplayRelease(self.displayId); if(err == kCGErrorSuccess) { self.captured = NO; released = YES; } else { NSLog(@"%@ %s • CGDisplayRelease failed: %u", [self className], _cmd, err); }
NSWindow on captured displayCGFloat mainDisplayHeight = [[NSScreen mainScreen] frame].size.height; CGRect displayBounds = self.display.bounds; // from CGDisplayBounds // secondary displays are "below" main screen NSRect rect = NSMakeRect(displayBounds.origin.x, mainDisplayHeight - displayBounds.size.height, displayBounds.size.width, displayBounds.size.height); NSWindow *window = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; [window setLevel:CGShieldingWindowLevel()];
-[NSScreen frame] & -[NSScreen visibleFrame] maybe they calculate correct rect for windowNSScreen from displayId I assume -[NSScreen deviceDescription] can be usedReferences: