src/video/cocoa/SDL_cocoaevents.m
author Sam Lantinga <slouken@libsdl.org>
Thu, 12 Dec 2019 19:07:26 -0800
changeset 13347 99ecd178999f
parent 13309 a72231930f12
permissions -rw-r--r--
Fixed binding D-pad on NES30 controller
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2019 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 bool
   271 LoadMainMenuNibIfAvailable(void)
   272 {
   273     NSDictionary *infoDict;
   274     NSString *mainNibFileName;
   275     bool success = false;
   276     
   277     infoDict = [[NSBundle mainBundle] infoDictionary];
   278     if (infoDict) {
   279         mainNibFileName = [infoDict valueForKey:@"NSMainNibFile"];
   280         
   281         if (mainNibFileName) {
   282             success = [[NSBundle mainBundle] loadNibNamed:mainNibFileName owner:[NSApplication sharedApplication] topLevelObjects:nil];
   283         }
   284     }
   285     
   286     return success;
   287 }
   288 
   289 static void
   290 CreateApplicationMenus(void)
   291 {
   292     NSString *appName;
   293     NSString *title;
   294     NSMenu *appleMenu;
   295     NSMenu *serviceMenu;
   296     NSMenu *windowMenu;
   297     NSMenuItem *menuItem;
   298     NSMenu *mainMenu;
   299 
   300     if (NSApp == nil) {
   301         return;
   302     }
   303     
   304     mainMenu = [[NSMenu alloc] init];
   305 
   306     /* Create the main menu bar */
   307     [NSApp setMainMenu:mainMenu];
   308 
   309     [mainMenu release];  /* we're done with it, let NSApp own it. */
   310     mainMenu = nil;
   311 
   312     /* Create the application menu */
   313     appName = GetApplicationName();
   314     appleMenu = [[NSMenu alloc] initWithTitle:@""];
   315 
   316     /* Add menu items */
   317     title = [@"About " stringByAppendingString:appName];
   318     [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
   319 
   320     [appleMenu addItem:[NSMenuItem separatorItem]];
   321 
   322     [appleMenu addItemWithTitle:@"Preferences…" action:nil keyEquivalent:@","];
   323 
   324     [appleMenu addItem:[NSMenuItem separatorItem]];
   325 
   326     serviceMenu = [[NSMenu alloc] initWithTitle:@""];
   327     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
   328     [menuItem setSubmenu:serviceMenu];
   329 
   330     [NSApp setServicesMenu:serviceMenu];
   331     [serviceMenu release];
   332 
   333     [appleMenu addItem:[NSMenuItem separatorItem]];
   334 
   335     title = [@"Hide " stringByAppendingString:appName];
   336     [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
   337 
   338     menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
   339     [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
   340 
   341     [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
   342 
   343     [appleMenu addItem:[NSMenuItem separatorItem]];
   344 
   345     title = [@"Quit " stringByAppendingString:appName];
   346     [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
   347 
   348     /* Put menu into the menubar */
   349     menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
   350     [menuItem setSubmenu:appleMenu];
   351     [[NSApp mainMenu] addItem:menuItem];
   352     [menuItem release];
   353 
   354     /* Tell the application object that this is now the application menu */
   355     [NSApp setAppleMenu:appleMenu];
   356     [appleMenu release];
   357 
   358 
   359     /* Create the window menu */
   360     windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
   361 
   362     /* Add menu items */
   363     [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
   364 
   365     [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
   366 
   367     [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
   368     
   369     /* Add the fullscreen toggle menu option, if supported */
   370     if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) {
   371         /* Cocoa should update the title to Enter or Exit Full Screen automatically.
   372          * But if not, then just fallback to Toggle Full Screen.
   373          */
   374         menuItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
   375         [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
   376         [windowMenu addItem:menuItem];
   377         [menuItem release];
   378     }
   379 
   380     /* Put menu into the menubar */
   381     menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
   382     [menuItem setSubmenu:windowMenu];
   383     [[NSApp mainMenu] addItem:menuItem];
   384     [menuItem release];
   385 
   386     /* Tell the application object that this is now the window menu */
   387     [NSApp setWindowsMenu:windowMenu];
   388     [windowMenu release];
   389 }
   390 
   391 void
   392 Cocoa_RegisterApp(void)
   393 { @autoreleasepool
   394 {
   395     /* This can get called more than once! Be careful what you initialize! */
   396 
   397     if (NSApp == nil) {
   398         [SDLApplication sharedApplication];
   399         SDL_assert(NSApp != nil);
   400 
   401         s_bShouldHandleEventsInSDLApplication = SDL_TRUE;
   402 
   403         if (!SDL_GetHintBoolean(SDL_HINT_MAC_BACKGROUND_APP, SDL_FALSE)) {
   404             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
   405         }
   406 
   407         /* If there aren't already menus in place, look to see if there's
   408          * a nib we should use. If not, then manually create the basic
   409          * menus we meed.
   410          */
   411         if ([NSApp mainMenu] == nil) {
   412             bool nibLoaded;
   413             
   414             nibLoaded = LoadMainMenuNibIfAvailable();
   415             if (!nibLoaded) {
   416                 CreateApplicationMenus();
   417             }
   418         }
   419         [NSApp finishLaunching];
   420         if ([NSApp delegate]) {
   421             /* The SDL app delegate calls this in didFinishLaunching if it's
   422              * attached to the NSApp, otherwise we need to call it manually.
   423              */
   424             [SDLApplication registerUserDefaults];
   425         }
   426     }
   427     if (NSApp && !appDelegate) {
   428         appDelegate = [[SDLAppDelegate alloc] init];
   429 
   430         /* If someone else has an app delegate, it means we can't turn a
   431          * termination into SDL_Quit, and we can't handle application:openFile:
   432          */
   433         if (![NSApp delegate]) {
   434             [(NSApplication *)NSApp setDelegate:appDelegate];
   435         } else {
   436             appDelegate->seenFirstActivate = YES;
   437         }
   438     }
   439 }}
   440 
   441 void
   442 Cocoa_PumpEvents(_THIS)
   443 { @autoreleasepool
   444 {
   445 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
   446     /* Update activity every 30 seconds to prevent screensaver */
   447     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
   448     if (_this->suspend_screensaver && !data->screensaver_use_iopm) {
   449         Uint32 now = SDL_GetTicks();
   450         if (!data->screensaver_activity ||
   451             SDL_TICKS_PASSED(now, data->screensaver_activity + 30000)) {
   452             UpdateSystemActivity(UsrActivity);
   453             data->screensaver_activity = now;
   454         }
   455     }
   456 #endif
   457 
   458     for ( ; ; ) {
   459         NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ];
   460         if ( event == nil ) {
   461             break;
   462         }
   463 
   464         if (!s_bShouldHandleEventsInSDLApplication) {
   465             Cocoa_DispatchEvent(event);
   466         }
   467 
   468         // Pass events down to SDLApplication to be handled in sendEvent:
   469         [NSApp sendEvent:event];
   470     }
   471 }}
   472 
   473 void
   474 Cocoa_SuspendScreenSaver(_THIS)
   475 { @autoreleasepool
   476 {
   477     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
   478 
   479     if (!data->screensaver_use_iopm) {
   480         return;
   481     }
   482 
   483     if (data->screensaver_assertion) {
   484         IOPMAssertionRelease(data->screensaver_assertion);
   485         data->screensaver_assertion = 0;
   486     }
   487 
   488     if (_this->suspend_screensaver) {
   489         /* FIXME: this should ideally describe the real reason why the game
   490          * called SDL_DisableScreenSaver. Note that the name is only meant to be
   491          * seen by OS X power users. there's an additional optional human-readable
   492          * (localized) reason parameter which we don't set.
   493          */
   494         NSString *name = [GetApplicationName() stringByAppendingString:@" using SDL_DisableScreenSaver"];
   495         IOPMAssertionCreateWithDescription(kIOPMAssertPreventUserIdleDisplaySleep,
   496                                            (CFStringRef) name,
   497                                            NULL, NULL, NULL, 0, NULL,
   498                                            &data->screensaver_assertion);
   499     }
   500 }}
   501 
   502 #endif /* SDL_VIDEO_DRIVER_COCOA */
   503 
   504 /* vi: set ts=4 sw=4 expandtab: */