// 8 july 2014 #import "objc_darwin.h" #import "_cgo_export.h" #import "modalqueue.h" #import #define toNSWindow(x) ((NSWindow *) (x)) static Class areaClass; @interface goApplication : NSApplication @end @implementation goApplication // by default, NSApplication eats some key events // this prevents that from happening with Area // see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html - (void)sendEvent:(NSEvent *)e { NSEventType type; type = [e type]; if (type == NSKeyDown || type == NSKeyUp || type == NSFlagsChanged) { id focused; focused = [[e window] firstResponder]; if (focused != nil && [focused isKindOfClass:areaClass]) switch (type) { case NSKeyDown: [focused keyDown:e]; return; case NSKeyUp: [focused keyUp:e]; return; case NSFlagsChanged: [focused flagsChanged:e]; return; } // else fall through } // otherwise, let NSApplication do it [super sendEvent:e]; } // ok AppKit, wanna play hardball? let's play hardball. // because I can neither break out of the special version of the NSModalPanelRunLoopMode that the regular terminate: puts us in nor avoid the exit(0); call included, I'm taking control // note that this is called AFTER applicationShouldTerminate: - (void)terminate:(id)sender { // DO ABSOLUTELY NOTHING // the magic is [NSApp run] will just... stop. } @end @interface appDelegateClass : NSObject @end @implementation appDelegateClass - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app { NSArray *windows; NSUInteger i, n; windows = [NSApp windows]; n = [windows count]; for (i = 0; i < n; i++) { NSWindow *w; w = toNSWindow([windows objectAtIndex:i]); if (![[w delegate] windowShouldClose:w]) // stop at the first rejection; thanks Lyle42 in irc.freenode.net/#macdev return NSTerminateCancel; } // all windows closed; stop gracefully for Go // note that this is designed for our special terminate: above return NSTerminateNow; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app { return NO; } @end appDelegateClass *appDelegate; id getAppDelegate(void) { return appDelegate; } void uiinit(char **errmsg) { areaClass = getAreaClass(); appDelegate = [appDelegateClass new]; [goApplication sharedApplication]; // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! // see https://github.com/andlabs/ui/issues/6 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setDelegate:appDelegate]; } void uimsgloop(void) { [NSApp run]; // NSLog(@"you shouldn't see this under normal circumstances, but screw the rules, I have SUBCLASSING"); } // don't use [NSApp terminate:]; that quits the program void uistop(void) { NSEvent *e; [NSApp stop:NSApp]; // stop: won't register until another event has passed; let's synthesize one e = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:0 context:[NSGraphicsContext currentContext] subtype:0 data1:0 data2:0]; [NSApp postEvent:e atStart:NO]; // let pending events take priority } // we use the modal queue because dispatch_suspend()/dispatch_resume() can't be used with the main queue // thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch and blocks for this void issue(void *what) { if (queueIfModal(what)) return; dispatch_async(dispatch_get_main_queue(), ^{ doissue(what); }); }