src/video/cocoa/SDL_cocoaevents.m
author Sam Lantinga <slouken@libsdl.org>
Wed, 03 Jan 2018 10:03:25 -0800
changeset 11811 5d94cb6b24d3
parent 11697 2b03795fbd49
child 12201 8bdc4d340419
permissions -rw-r--r--
Updated copyright for 2018
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 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 - (void)sendEvent:(NSEvent *)theEvent;
    40 
    41 + (void)registerUserDefaults;
    42 
    43 @end
    44 
    45 @implementation SDLApplication
    46 
    47 // Override terminate to handle Quit and System Shutdown smoothly.
    48 - (void)terminate:(id)sender
    49 {
    50     SDL_SendQuit();
    51 }
    52 
    53 static SDL_bool s_bShouldHandleEventsInSDLApplication = SDL_FALSE;
    54 
    55 static void Cocoa_DispatchEvent(NSEvent *theEvent)
    56 {
    57     SDL_VideoDevice *_this = SDL_GetVideoDevice();
    58 
    59     switch ([theEvent type]) {
    60         case NSEventTypeLeftMouseDown:
    61         case NSEventTypeOtherMouseDown:
    62         case NSEventTypeRightMouseDown:
    63         case NSEventTypeLeftMouseUp:
    64         case NSEventTypeOtherMouseUp:
    65         case NSEventTypeRightMouseUp:
    66         case NSEventTypeLeftMouseDragged:
    67         case NSEventTypeRightMouseDragged:
    68         case NSEventTypeOtherMouseDragged: /* usually middle mouse dragged */
    69         case NSEventTypeMouseMoved:
    70         case NSEventTypeScrollWheel:
    71             Cocoa_HandleMouseEvent(_this, theEvent);
    72             break;
    73         case NSEventTypeKeyDown:
    74         case NSEventTypeKeyUp:
    75         case NSEventTypeFlagsChanged:
    76             Cocoa_HandleKeyEvent(_this, theEvent);
    77             break;
    78         default:
    79             break;
    80     }
    81 }
    82 
    83 // Dispatch events here so that we can handle events caught by
    84 // nextEventMatchingMask in SDL, as well as events caught by other
    85 // processes (such as CEF) that are passed down to NSApp.
    86 - (void)sendEvent:(NSEvent *)theEvent
    87 {
    88     if (s_bShouldHandleEventsInSDLApplication) {
    89         Cocoa_DispatchEvent(theEvent);
    90     }
    91 
    92     [super sendEvent:theEvent];
    93 }
    94 
    95 + (void)registerUserDefaults
    96 {
    97     NSDictionary *appDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:
    98                                  [NSNumber numberWithBool:NO], @"AppleMomentumScrollSupported",
    99                                  [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
   100                                  [NSNumber numberWithBool:YES], @"ApplePersistenceIgnoreState",
   101                                  nil];
   102     [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
   103     [appDefaults release];
   104 }
   105 
   106 @end // SDLApplication
   107 
   108 /* setAppleMenu disappeared from the headers in 10.4 */
   109 @interface NSApplication(NSAppleMenu)
   110 - (void)setAppleMenu:(NSMenu *)menu;
   111 @end
   112 
   113 @interface SDLAppDelegate : NSObject <NSApplicationDelegate> {
   114 @public
   115     BOOL seenFirstActivate;
   116 }
   117 
   118 - (id)init;
   119 @end
   120 
   121 @implementation SDLAppDelegate : NSObject
   122 - (id)init
   123 {
   124     self = [super init];
   125     if (self) {
   126         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   127 
   128         seenFirstActivate = NO;
   129 
   130         [center addObserver:self
   131                    selector:@selector(windowWillClose:)
   132                        name:NSWindowWillCloseNotification
   133                      object:nil];
   134 
   135         [center addObserver:self
   136                    selector:@selector(focusSomeWindow:)
   137                        name:NSApplicationDidBecomeActiveNotification
   138                      object:nil];
   139     }
   140 
   141     return self;
   142 }
   143 
   144 - (void)dealloc
   145 {
   146     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   147 
   148     [center removeObserver:self name:NSWindowWillCloseNotification object:nil];
   149     [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil];
   150 
   151     [super dealloc];
   152 }
   153 
   154 - (void)windowWillClose:(NSNotification *)notification;
   155 {
   156     NSWindow *win = (NSWindow*)[notification object];
   157 
   158     if (![win isKeyWindow]) {
   159         return;
   160     }
   161 
   162     /* HACK: Make the next window in the z-order key when the key window is
   163      * closed. The custom event loop and/or windowing code we have seems to
   164      * prevent the normal behavior: https://bugzilla.libsdl.org/show_bug.cgi?id=1825
   165      */
   166 
   167     /* +[NSApp orderedWindows] never includes the 'About' window, but we still
   168      * want to try its list first since the behavior in other apps is to only
   169      * make the 'About' window key if no other windows are on-screen.
   170      */
   171     for (NSWindow *window in [NSApp orderedWindows]) {
   172         if (window != win && [window canBecomeKeyWindow]) {
   173             if (![window isOnActiveSpace]) {
   174                 continue;
   175             }
   176             [window makeKeyAndOrderFront:self];
   177             return;
   178         }
   179     }
   180 
   181     /* If a window wasn't found above, iterate through all visible windows in
   182      * the active Space in z-order (including the 'About' window, if it's shown)
   183      * and make the first one key.
   184      */
   185     for (NSNumber *num in [NSWindow windowNumbersWithOptions:0]) {
   186         NSWindow *window = [NSApp windowWithWindowNumber:[num integerValue]];
   187         if (window && window != win && [window canBecomeKeyWindow]) {
   188             [window makeKeyAndOrderFront:self];
   189             return;
   190         }
   191     }
   192 }
   193 
   194 - (void)focusSomeWindow:(NSNotification *)aNotification
   195 {
   196     /* HACK: Ignore the first call. The application gets a
   197      * applicationDidBecomeActive: a little bit after the first window is
   198      * created, and if we don't ignore it, a window that has been created with
   199      * SDL_WINDOW_MINIMIZED will ~immediately be restored.
   200      */
   201     if (!seenFirstActivate) {
   202         seenFirstActivate = YES;
   203         return;
   204     }
   205 
   206     SDL_VideoDevice *device = SDL_GetVideoDevice();
   207     if (device && device->windows) {
   208         SDL_Window *window = device->windows;
   209         int i;
   210         for (i = 0; i < device->num_displays; ++i) {
   211             SDL_Window *fullscreen_window = device->displays[i].fullscreen_window;
   212             if (fullscreen_window) {
   213                 if (fullscreen_window->flags & SDL_WINDOW_MINIMIZED) {
   214                     SDL_RestoreWindow(fullscreen_window);
   215                 }
   216                 return;
   217             }
   218         }
   219 
   220         if (window->flags & SDL_WINDOW_MINIMIZED) {
   221             SDL_RestoreWindow(window);
   222         } else {
   223             SDL_RaiseWindow(window);
   224         }
   225     }
   226 }
   227 
   228 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
   229 {
   230     return (BOOL)SDL_SendDropFile(NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL);
   231 }
   232 
   233 - (void)applicationDidFinishLaunching:(NSNotification *)notification
   234 {
   235     /* The menu bar of SDL apps which don't have the typical .app bundle
   236      * structure fails to work the first time a window is created (until it's
   237      * de-focused and re-focused), if this call is in Cocoa_RegisterApp instead
   238      * of here. https://bugzilla.libsdl.org/show_bug.cgi?id=3051
   239      */
   240     if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, SDL_FALSE)) {
   241         [NSApp activateIgnoringOtherApps:YES];
   242     }
   243 
   244     /* If we call this before NSApp activation, macOS might print a complaint
   245      * about ApplePersistenceIgnoreState. */
   246     [SDLApplication registerUserDefaults];
   247 }
   248 @end
   249 
   250 static SDLAppDelegate *appDelegate = nil;
   251 
   252 static NSString *
   253 GetApplicationName(void)
   254 {
   255     NSString *appName;
   256 
   257     /* Determine the application name */
   258     appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
   259     if (!appName) {
   260         appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
   261     }
   262 
   263     if (![appName length]) {
   264         appName = [[NSProcessInfo processInfo] processName];
   265     }
   266 
   267     return appName;
   268 }
   269 
   270 static void
   271 CreateApplicationMenus(void)
   272 {
   273     NSString *appName;
   274     NSString *title;
   275     NSMenu *appleMenu;
   276     NSMenu *serviceMenu;
   277     NSMenu *windowMenu;
   278     NSMenu *viewMenu;
   279     NSMenuItem *menuItem;
   280     NSMenu *mainMenu;
   281 
   282     if (NSApp == nil) {
   283         return;
   284     }
   285 
   286     mainMenu = [[NSMenu alloc] init];
   287 
   288     /* Create the main menu bar */
   289     [NSApp setMainMenu:mainMenu];
   290 
   291     [mainMenu release];  /* we're done with it, let NSApp own it. */
   292     mainMenu = nil;
   293 
   294     /* Create the application menu */
   295     appName = GetApplicationName();
   296     appleMenu = [[NSMenu alloc] initWithTitle:@""];
   297 
   298     /* Add menu items */
   299     title = [@"About " stringByAppendingString:appName];
   300     [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
   301 
   302     [appleMenu addItem:[NSMenuItem separatorItem]];
   303 
   304     [appleMenu addItemWithTitle:@"Preferences…" action:nil keyEquivalent:@","];
   305 
   306     [appleMenu addItem:[NSMenuItem separatorItem]];
   307 
   308     serviceMenu = [[NSMenu alloc] initWithTitle:@""];
   309     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
   310     [menuItem setSubmenu:serviceMenu];
   311 
   312     [NSApp setServicesMenu:serviceMenu];
   313     [serviceMenu release];
   314 
   315     [appleMenu addItem:[NSMenuItem separatorItem]];
   316 
   317     title = [@"Hide " stringByAppendingString:appName];
   318     [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
   319 
   320     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
   321     [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
   322 
   323     [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
   324 
   325     [appleMenu addItem:[NSMenuItem separatorItem]];
   326 
   327     title = [@"Quit " stringByAppendingString:appName];
   328     [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
   329 
   330     /* Put menu into the menubar */
   331     menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
   332     [menuItem setSubmenu:appleMenu];
   333     [[NSApp mainMenu] addItem:menuItem];
   334     [menuItem release];
   335 
   336     /* Tell the application object that this is now the application menu */
   337     [NSApp setAppleMenu:appleMenu];
   338     [appleMenu release];
   339 
   340 
   341     /* Create the window menu */
   342     windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
   343 
   344     /* Add menu items */
   345     [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
   346 
   347     [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
   348 
   349     /* Put menu into the menubar */
   350     menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
   351     [menuItem setSubmenu:windowMenu];
   352     [[NSApp mainMenu] addItem:menuItem];
   353     [menuItem release];
   354 
   355     /* Tell the application object that this is now the window menu */
   356     [NSApp setWindowsMenu:windowMenu];
   357     [windowMenu release];
   358 
   359 
   360     /* Add the fullscreen view toggle menu option, if supported */
   361     if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) {
   362         /* Create the view menu */
   363         viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
   364 
   365         /* Add menu items */
   366         menuItem = [viewMenu addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
   367         [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
   368 
   369         /* Put menu into the menubar */
   370         menuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
   371         [menuItem setSubmenu:viewMenu];
   372         [[NSApp mainMenu] addItem:menuItem];
   373         [menuItem release];
   374 
   375         [viewMenu release];
   376     }
   377 }
   378 
   379 void
   380 Cocoa_RegisterApp(void)
   381 { @autoreleasepool
   382 {
   383     /* This can get called more than once! Be careful what you initialize! */
   384 
   385     if (NSApp == nil) {
   386         [SDLApplication sharedApplication];
   387         SDL_assert(NSApp != nil);
   388 
   389         s_bShouldHandleEventsInSDLApplication = SDL_TRUE;
   390 
   391         if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, SDL_FALSE)) {
   392             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
   393 		}
   394 		
   395         if ([NSApp mainMenu] == nil) {
   396             CreateApplicationMenus();
   397         }
   398         [NSApp finishLaunching];
   399         if ([NSApp delegate]) {
   400             /* The SDL app delegate calls this in didFinishLaunching if it's
   401              * attached to the NSApp, otherwise we need to call it manually.
   402              */
   403             [SDLApplication registerUserDefaults];
   404         }
   405     }
   406     if (NSApp && !appDelegate) {
   407         appDelegate = [[SDLAppDelegate alloc] init];
   408 
   409         /* If someone else has an app delegate, it means we can't turn a
   410          * termination into SDL_Quit, and we can't handle application:openFile:
   411          */
   412         if (![NSApp delegate]) {
   413             [(NSApplication *)NSApp setDelegate:appDelegate];
   414         } else {
   415             appDelegate->seenFirstActivate = YES;
   416         }
   417     }
   418 }}
   419 
   420 void
   421 Cocoa_PumpEvents(_THIS)
   422 { @autoreleasepool
   423 {
   424 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
   425     /* Update activity every 30 seconds to prevent screensaver */
   426     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
   427     if (_this->suspend_screensaver && !data->screensaver_use_iopm) {
   428         Uint32 now = SDL_GetTicks();
   429         if (!data->screensaver_activity ||
   430             SDL_TICKS_PASSED(now, data->screensaver_activity + 30000)) {
   431             UpdateSystemActivity(UsrActivity);
   432             data->screensaver_activity = now;
   433         }
   434     }
   435 #endif
   436 
   437     for ( ; ; ) {
   438         NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ];
   439         if ( event == nil ) {
   440             break;
   441         }
   442 
   443         if (!s_bShouldHandleEventsInSDLApplication) {
   444             Cocoa_DispatchEvent(event);
   445         }
   446 
   447         // Pass events down to SDLApplication to be handled in sendEvent:
   448         [NSApp sendEvent:event];
   449     }
   450 }}
   451 
   452 void
   453 Cocoa_SuspendScreenSaver(_THIS)
   454 { @autoreleasepool
   455 {
   456     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
   457 
   458     if (!data->screensaver_use_iopm) {
   459         return;
   460     }
   461 
   462     if (data->screensaver_assertion) {
   463         IOPMAssertionRelease(data->screensaver_assertion);
   464         data->screensaver_assertion = 0;
   465     }
   466 
   467     if (_this->suspend_screensaver) {
   468         /* FIXME: this should ideally describe the real reason why the game
   469          * called SDL_DisableScreenSaver. Note that the name is only meant to be
   470          * seen by OS X power users. there's an additional optional human-readable
   471          * (localized) reason parameter which we don't set.
   472          */
   473         NSString *name = [GetApplicationName() stringByAppendingString:@" using SDL_DisableScreenSaver"];
   474         IOPMAssertionCreateWithDescription(kIOPMAssertPreventUserIdleDisplaySleep,
   475                                            (CFStringRef) name,
   476                                            NULL, NULL, NULL, 0, NULL,
   477                                            &data->screensaver_assertion);
   478     }
   479 }}
   480 
   481 #endif /* SDL_VIDEO_DRIVER_COCOA */
   482 
   483 /* vi: set ts=4 sw=4 expandtab: */