src/video/cocoa/SDL_cocoaevents.m
author Peter Kosyh <p.kosyh@gmail.com>
Mon, 11 Nov 2019 22:23:33 -0500
changeset 13237 f5298e605071
parent 12503 806492103856
permissions -rw-r--r--
haiku: mouse_relative fix

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