src/video/cocoa/SDL_cocoaevents.m
author Alfred Reynolds <alfred@valvesoftware.com>
Wed, 29 Jul 2015 17:19:15 -0700
changeset 9820 c0bcc39a3491
parent 9619 b94b6d0bff0f
child 9822 371d82c6bf94
permissions -rw-r--r--
SDL
- add a new SDL_HINT_MAC_BACKGROUND_APP hint, when set or set to 1 don't force the app to be foreground
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2015 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #if SDL_VIDEO_DRIVER_COCOA
    24 #include "SDL_timer.h"
    25 
    26 #include "SDL_cocoavideo.h"
    27 #include "../../events/SDL_events_c.h"
    28 #include "SDL_assert.h"
    29 #include "SDL_hints.h"
    30 
    31 /* This define was added in the 10.9 SDK. */
    32 #ifndef kIOPMAssertPreventUserIdleDisplaySleep
    33 #define kIOPMAssertPreventUserIdleDisplaySleep kIOPMAssertionTypePreventUserIdleDisplaySleep
    34 #endif
    35 
    36 @interface SDLApplication : NSApplication
    37 
    38 - (void)terminate:(id)sender;
    39 
    40 @end
    41 
    42 @implementation SDLApplication
    43 
    44 // Override terminate to handle Quit and System Shutdown smoothly.
    45 - (void)terminate:(id)sender
    46 {
    47     SDL_SendQuit();
    48 }
    49 
    50 @end // SDLApplication
    51 
    52 /* setAppleMenu disappeared from the headers in 10.4 */
    53 @interface NSApplication(NSAppleMenu)
    54 - (void)setAppleMenu:(NSMenu *)menu;
    55 @end
    56 
    57 @interface SDLAppDelegate : NSObject <NSApplicationDelegate> {
    58 @public
    59     BOOL seenFirstActivate;
    60 }
    61 
    62 - (id)init;
    63 @end
    64 
    65 @implementation SDLAppDelegate : NSObject
    66 - (id)init
    67 {
    68     self = [super init];
    69     if (self) {
    70         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    71 
    72         seenFirstActivate = NO;
    73 
    74         [center addObserver:self
    75                    selector:@selector(windowWillClose:)
    76                        name:NSWindowWillCloseNotification
    77                      object:nil];
    78 
    79         [center addObserver:self
    80                    selector:@selector(focusSomeWindow:)
    81                        name:NSApplicationDidBecomeActiveNotification
    82                      object:nil];
    83     }
    84 
    85     return self;
    86 }
    87 
    88 - (void)dealloc
    89 {
    90     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    91 
    92     [center removeObserver:self name:NSWindowWillCloseNotification object:nil];
    93     [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil];
    94 
    95     [super dealloc];
    96 }
    97 
    98 - (void)windowWillClose:(NSNotification *)notification;
    99 {
   100     NSWindow *win = (NSWindow*)[notification object];
   101 
   102     if (![win isKeyWindow]) {
   103         return;
   104     }
   105 
   106     /* HACK: Make the next window in the z-order key when the key window is
   107      * closed. The custom event loop and/or windowing code we have seems to
   108      * prevent the normal behavior: https://bugzilla.libsdl.org/show_bug.cgi?id=1825
   109      */
   110 
   111     /* +[NSApp orderedWindows] never includes the 'About' window, but we still
   112      * want to try its list first since the behavior in other apps is to only
   113      * make the 'About' window key if no other windows are on-screen.
   114      */
   115     for (NSWindow *window in [NSApp orderedWindows]) {
   116         if (window != win && [window canBecomeKeyWindow]) {
   117             if ([window respondsToSelector:@selector(isOnActiveSpace)]) {
   118                 if (![window isOnActiveSpace]) {
   119                     continue;
   120                 }
   121             }
   122             [window makeKeyAndOrderFront:self];
   123             return;
   124         }
   125     }
   126 
   127     /* If a window wasn't found above, iterate through all visible windows
   128      * (including the 'About' window, if it's shown) and make the first one key.
   129      * Note that +[NSWindow windowNumbersWithOptions:] was added in 10.6.
   130      */
   131     if ([NSWindow respondsToSelector:@selector(windowNumbersWithOptions:)]) {
   132         /* Get all visible windows in the active Space, in z-order. */
   133         for (NSNumber *num in [NSWindow windowNumbersWithOptions:0]) {
   134             NSWindow *window = [NSApp windowWithWindowNumber:[num integerValue]];
   135             if (window && window != win && [window canBecomeKeyWindow]) {
   136                 [window makeKeyAndOrderFront:self];
   137                 return;
   138             }
   139         }
   140     }
   141 }
   142 
   143 - (void)focusSomeWindow:(NSNotification *)aNotification
   144 {
   145     /* HACK: Ignore the first call. The application gets a
   146      * applicationDidBecomeActive: a little bit after the first window is
   147      * created, and if we don't ignore it, a window that has been created with
   148      * SDL_WINDOW_MINIMIZED will ~immediately be restored.
   149      */
   150     if (!seenFirstActivate) {
   151         seenFirstActivate = YES;
   152         return;
   153     }
   154 
   155     SDL_VideoDevice *device = SDL_GetVideoDevice();
   156     if (device && device->windows) {
   157         SDL_Window *window = device->windows;
   158         int i;
   159         for (i = 0; i < device->num_displays; ++i) {
   160             SDL_Window *fullscreen_window = device->displays[i].fullscreen_window;
   161             if (fullscreen_window) {
   162                 if (fullscreen_window->flags & SDL_WINDOW_MINIMIZED) {
   163                     SDL_RestoreWindow(fullscreen_window);
   164                 }
   165                 return;
   166             }
   167         }
   168 
   169         if (window->flags & SDL_WINDOW_MINIMIZED) {
   170             SDL_RestoreWindow(window);
   171         } else {
   172             SDL_RaiseWindow(window);
   173         }
   174     }
   175 }
   176 
   177 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
   178 {
   179     return (BOOL)SDL_SendDropFile([filename UTF8String]);
   180 }
   181 @end
   182 
   183 static SDLAppDelegate *appDelegate = nil;
   184 
   185 static NSString *
   186 GetApplicationName(void)
   187 {
   188     NSString *appName;
   189 
   190     /* Determine the application name */
   191     appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
   192     if (!appName) {
   193         appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
   194     }
   195 
   196     if (![appName length]) {
   197         appName = [[NSProcessInfo processInfo] processName];
   198     }
   199 
   200     return appName;
   201 }
   202 
   203 static void
   204 CreateApplicationMenus(void)
   205 {
   206     NSString *appName;
   207     NSString *title;
   208     NSMenu *appleMenu;
   209     NSMenu *serviceMenu;
   210     NSMenu *windowMenu;
   211     NSMenu *viewMenu;
   212     NSMenuItem *menuItem;
   213     NSMenu *mainMenu;
   214 
   215     if (NSApp == nil) {
   216         return;
   217     }
   218 
   219     mainMenu = [[NSMenu alloc] init];
   220 
   221     /* Create the main menu bar */
   222     [NSApp setMainMenu:mainMenu];
   223 
   224     [mainMenu release];  /* we're done with it, let NSApp own it. */
   225     mainMenu = nil;
   226 
   227     /* Create the application menu */
   228     appName = GetApplicationName();
   229     appleMenu = [[NSMenu alloc] initWithTitle:@""];
   230 
   231     /* Add menu items */
   232     title = [@"About " stringByAppendingString:appName];
   233     [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
   234 
   235     [appleMenu addItem:[NSMenuItem separatorItem]];
   236 
   237     [appleMenu addItemWithTitle:@"Preferences…" action:nil keyEquivalent:@","];
   238 
   239     [appleMenu addItem:[NSMenuItem separatorItem]];
   240 
   241     serviceMenu = [[NSMenu alloc] initWithTitle:@""];
   242     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
   243     [menuItem setSubmenu:serviceMenu];
   244 
   245     [NSApp setServicesMenu:serviceMenu];
   246     [serviceMenu release];
   247 
   248     [appleMenu addItem:[NSMenuItem separatorItem]];
   249 
   250     title = [@"Hide " stringByAppendingString:appName];
   251     [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
   252 
   253     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
   254     [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
   255 
   256     [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
   257 
   258     [appleMenu addItem:[NSMenuItem separatorItem]];
   259 
   260     title = [@"Quit " stringByAppendingString:appName];
   261     [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
   262 
   263     /* Put menu into the menubar */
   264     menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
   265     [menuItem setSubmenu:appleMenu];
   266     [[NSApp mainMenu] addItem:menuItem];
   267     [menuItem release];
   268 
   269     /* Tell the application object that this is now the application menu */
   270     [NSApp setAppleMenu:appleMenu];
   271     [appleMenu release];
   272 
   273 
   274     /* Create the window menu */
   275     windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
   276 
   277     /* Add menu items */
   278     [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
   279 
   280     [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
   281 
   282     /* Put menu into the menubar */
   283     menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
   284     [menuItem setSubmenu:windowMenu];
   285     [[NSApp mainMenu] addItem:menuItem];
   286     [menuItem release];
   287 
   288     /* Tell the application object that this is now the window menu */
   289     [NSApp setWindowsMenu:windowMenu];
   290     [windowMenu release];
   291 
   292 
   293     /* Add the fullscreen view toggle menu option, if supported */
   294     if ([NSApp respondsToSelector:@selector(setPresentationOptions:)]) {
   295         /* Create the view menu */
   296         viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
   297 
   298         /* Add menu items */
   299         menuItem = [viewMenu addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
   300         [menuItem setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask];
   301 
   302         /* Put menu into the menubar */
   303         menuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
   304         [menuItem setSubmenu:viewMenu];
   305         [[NSApp mainMenu] addItem:menuItem];
   306         [menuItem release];
   307 
   308         [viewMenu release];
   309     }
   310 }
   311 
   312 void
   313 Cocoa_RegisterApp(void)
   314 { @autoreleasepool
   315 {
   316     /* This can get called more than once! Be careful what you initialize! */
   317 
   318     if (NSApp == nil) {
   319         [SDLApplication sharedApplication];
   320         SDL_assert(NSApp != nil);
   321 
   322         const char *hint = SDL_GetHint(SDL_HINT_MAC_BACKGROUND_APP);
   323 		if (!hint && *hint != '0') {
   324 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6
   325 			if ([NSApp respondsToSelector:@selector(setActivationPolicy:)]) {
   326 #endif
   327 				[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
   328 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6
   329 			} else {
   330 				ProcessSerialNumber psn = {0, kCurrentProcess};
   331 				TransformProcessType(&psn, kProcessTransformToForegroundApplication);
   332 			}
   333 #endif
   334             [NSApp activateIgnoringOtherApps:YES];
   335 		}
   336 		
   337         if ([NSApp mainMenu] == nil) {
   338             CreateApplicationMenus();
   339         }
   340         [NSApp finishLaunching];
   341         NSDictionary *appDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:
   342             [NSNumber numberWithBool:NO], @"AppleMomentumScrollSupported",
   343             [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
   344             [NSNumber numberWithBool:YES], @"ApplePersistenceIgnoreState",
   345             nil];
   346         [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
   347         [appDefaults release];
   348     }
   349     if (NSApp && !appDelegate) {
   350         appDelegate = [[SDLAppDelegate alloc] init];
   351 
   352         /* If someone else has an app delegate, it means we can't turn a
   353          * termination into SDL_Quit, and we can't handle application:openFile:
   354          */
   355         if (![NSApp delegate]) {
   356             [(NSApplication *)NSApp setDelegate:appDelegate];
   357         } else {
   358             appDelegate->seenFirstActivate = YES;
   359         }
   360     }
   361 }}
   362 
   363 void
   364 Cocoa_PumpEvents(_THIS)
   365 { @autoreleasepool
   366 {
   367     /* Update activity every 30 seconds to prevent screensaver */
   368     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
   369     if (_this->suspend_screensaver && !data->screensaver_use_iopm) {
   370         Uint32 now = SDL_GetTicks();
   371         if (!data->screensaver_activity ||
   372             SDL_TICKS_PASSED(now, data->screensaver_activity + 30000)) {
   373             UpdateSystemActivity(UsrActivity);
   374             data->screensaver_activity = now;
   375         }
   376     }
   377 
   378     for ( ; ; ) {
   379         NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ];
   380         if ( event == nil ) {
   381             break;
   382         }
   383 
   384         switch ([event type]) {
   385         case NSLeftMouseDown:
   386         case NSOtherMouseDown:
   387         case NSRightMouseDown:
   388         case NSLeftMouseUp:
   389         case NSOtherMouseUp:
   390         case NSRightMouseUp:
   391         case NSLeftMouseDragged:
   392         case NSRightMouseDragged:
   393         case NSOtherMouseDragged: /* usually middle mouse dragged */
   394         case NSMouseMoved:
   395         case NSScrollWheel:
   396             Cocoa_HandleMouseEvent(_this, event);
   397             break;
   398         case NSKeyDown:
   399         case NSKeyUp:
   400         case NSFlagsChanged:
   401             Cocoa_HandleKeyEvent(_this, event);
   402             break;
   403         default:
   404             break;
   405         }
   406         /* Pass through to NSApp to make sure everything stays in sync */
   407         [NSApp sendEvent:event];
   408     }
   409 }}
   410 
   411 void
   412 Cocoa_SuspendScreenSaver(_THIS)
   413 { @autoreleasepool
   414 {
   415     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
   416 
   417     if (!data->screensaver_use_iopm) {
   418         return;
   419     }
   420 
   421     if (data->screensaver_assertion) {
   422         IOPMAssertionRelease(data->screensaver_assertion);
   423         data->screensaver_assertion = 0;
   424     }
   425 
   426     if (_this->suspend_screensaver) {
   427         /* FIXME: this should ideally describe the real reason why the game
   428          * called SDL_DisableScreenSaver. Note that the name is only meant to be
   429          * seen by OS X power users. there's an additional optional human-readable
   430          * (localized) reason parameter which we don't set.
   431          */
   432         NSString *name = [GetApplicationName() stringByAppendingString:@" using SDL_DisableScreenSaver"];
   433         IOPMAssertionCreateWithDescription(kIOPMAssertPreventUserIdleDisplaySleep,
   434                                            (CFStringRef) name,
   435                                            NULL, NULL, NULL, 0, NULL,
   436                                            &data->screensaver_assertion);
   437     }
   438 }}
   439 
   440 #endif /* SDL_VIDEO_DRIVER_COCOA */
   441 
   442 /* vi: set ts=4 sw=4 expandtab: */