sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_cocoaevents.m (17978B)


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