src/video/cocoa/SDL_cocoaevents.m
author Sam Lantinga
Sat, 22 Feb 2014 17:55:58 -0800
changeset 8239 05cbb1cb4f27
parent 8237 6cc44a5d9eef
child 8250 e671ec6b22dd
permissions -rw-r--r--
Fixed bug 2347 - On OSX, an SDL app prevents system shutdown.

Tim McDaniel

On OSX, an SDL app forces a system shutdown to be cancelled. This happens because [SDLAppDelegate applicationShouldTerminate] returns NSTerminateCancel. A better approach is to subclass NSApplication and override terminate to do nothing except call SDL_SendQuit. In response to a system shutdown notification, this allows the normal SDL Quit event processing to occur, and if the app then terminates, system shutdown occurs normally. Please see the attached patch, based on SDL 2.0.1.
slouken@1931
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@8149
     3
  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
slouken@1931
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@1931
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@1931
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@1931
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
slouken@6044
    22
slouken@6044
    23
#if SDL_VIDEO_DRIVER_COCOA
slouken@3120
    24
#include "SDL_timer.h"
slouken@1931
    25
slouken@1931
    26
#include "SDL_cocoavideo.h"
slouken@2738
    27
#include "../../events/SDL_events_c.h"
slouken@1931
    28
icculus@3624
    29
#if !defined(UsrActivity) && defined(__LP64__) && !defined(__POWER__)
icculus@3624
    30
/*
icculus@3624
    31
 * Workaround for a bug in the 10.5 SDK: By accident, OSService.h does
icculus@3624
    32
 * not include Power.h at all when compiling in 64bit mode. This has
icculus@3624
    33
 * been fixed in 10.6, but for 10.5, we manually define UsrActivity
icculus@3624
    34
 * to ensure compilation works.
icculus@3624
    35
 */
icculus@3624
    36
#define UsrActivity 1
icculus@3624
    37
#endif
slouken@3120
    38
slouken@8239
    39
@interface SDLApplication : NSApplication
slouken@8239
    40
slouken@8239
    41
- (void)terminate:(id)sender;
slouken@8239
    42
slouken@8239
    43
@end
slouken@8239
    44
slouken@8239
    45
@implementation SDLApplication
slouken@8239
    46
slouken@8239
    47
// Override terminate to handle Quit and System Shutdown smoothly.
slouken@8239
    48
- (void)terminate:(id)sender
slouken@8239
    49
{
slouken@8239
    50
    SDL_SendQuit();
slouken@8239
    51
}
slouken@8239
    52
slouken@8239
    53
@end // SDLApplication
slouken@8239
    54
slouken@1931
    55
/* setAppleMenu disappeared from the headers in 10.4 */
slouken@1931
    56
@interface NSApplication(NSAppleMenu)
slouken@1931
    57
- (void)setAppleMenu:(NSMenu *)menu;
slouken@1931
    58
@end
slouken@1931
    59
jorgen@7469
    60
@interface SDLAppDelegate : NSObject {
jorgen@7801
    61
@public
jorgen@7469
    62
    BOOL seenFirstActivate;
jorgen@7469
    63
}
jorgen@7469
    64
jorgen@7469
    65
- (id)init;
slouken@1937
    66
@end
slouken@1937
    67
slouken@1937
    68
@implementation SDLAppDelegate : NSObject
jorgen@7469
    69
- (id)init
jorgen@7469
    70
{
jorgen@7469
    71
    self = [super init];
jorgen@7469
    72
jorgen@7469
    73
    if (self) {
jorgen@7469
    74
        seenFirstActivate = NO;
jorgen@7801
    75
        [[NSNotificationCenter defaultCenter] addObserver:self
jorgen@7801
    76
                                                 selector:@selector(focusSomeWindow:)
jorgen@7801
    77
                                                     name:NSApplicationDidBecomeActiveNotification
jorgen@7801
    78
                                                   object:nil];
jorgen@7469
    79
    }
jorgen@7469
    80
jorgen@7469
    81
    return self;
jorgen@7469
    82
}
jorgen@7469
    83
jorgen@7801
    84
- (void)dealloc
jorgen@7801
    85
{
jorgen@7801
    86
    [[NSNotificationCenter defaultCenter] removeObserver:self];
jorgen@7801
    87
    [super dealloc];
jorgen@7801
    88
}
jorgen@7801
    89
jorgen@7801
    90
- (void)focusSomeWindow:(NSNotification *)aNotification
jorgen@7466
    91
{
jorgen@7469
    92
    /* HACK: Ignore the first call. The application gets a
jorgen@7469
    93
     * applicationDidBecomeActive: a little bit after the first window is
jorgen@7469
    94
     * created, and if we don't ignore it, a window that has been created with
jorgen@7469
    95
     * SDL_WINDOW_MINIZED will ~immediately be restored.
jorgen@7469
    96
     */
jorgen@7469
    97
    if (!seenFirstActivate) {
jorgen@7469
    98
        seenFirstActivate = YES;
jorgen@7469
    99
        return;
jorgen@7469
   100
    }
jorgen@7469
   101
jorgen@7466
   102
    SDL_VideoDevice *device = SDL_GetVideoDevice();
jorgen@7466
   103
    if (device && device->windows)
jorgen@7466
   104
    {
jorgen@7466
   105
        SDL_Window *window = device->windows;
jorgen@7466
   106
        int i;
jorgen@7466
   107
        for (i = 0; i < device->num_displays; ++i)
jorgen@7466
   108
        {
jorgen@7466
   109
            SDL_Window *fullscreen_window = device->displays[i].fullscreen_window;
jorgen@7466
   110
            if (fullscreen_window)
jorgen@7466
   111
            {
jorgen@7466
   112
                if (fullscreen_window->flags & SDL_WINDOW_MINIMIZED) {
jorgen@7466
   113
                    SDL_RestoreWindow(fullscreen_window);
jorgen@7466
   114
                }
jorgen@7466
   115
                return;
jorgen@7466
   116
            }
jorgen@7466
   117
        }
jorgen@7466
   118
jorgen@7466
   119
        if (window->flags & SDL_WINDOW_MINIMIZED) {
jorgen@7466
   120
            SDL_RestoreWindow(window);
jorgen@7466
   121
        } else {
jorgen@7466
   122
            SDL_RaiseWindow(window);
jorgen@7466
   123
        }
jorgen@7466
   124
    }
jorgen@7466
   125
}
jorgen@7466
   126
slouken@6091
   127
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
slouken@6091
   128
{
slouken@6091
   129
    return (BOOL)SDL_SendDropFile([filename UTF8String]);
slouken@6091
   130
}
slouken@1937
   131
@end
slouken@1937
   132
jorgen@7801
   133
static SDLAppDelegate *appDelegate = nil;
jorgen@7801
   134
slouken@1931
   135
static NSString *
slouken@1931
   136
GetApplicationName(void)
slouken@1931
   137
{
slouken@8237
   138
    NSString *appName;
slouken@1931
   139
slouken@1931
   140
    /* Determine the application name */
slouken@8237
   141
    appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
slouken@8237
   142
    if (!appName)
slouken@8237
   143
        appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
slouken@7191
   144
slouken@1931
   145
    if (![appName length])
slouken@1931
   146
        appName = [[NSProcessInfo processInfo] processName];
slouken@1931
   147
slouken@1931
   148
    return appName;
slouken@1931
   149
}
slouken@1931
   150
slouken@1931
   151
static void
slouken@1931
   152
CreateApplicationMenus(void)
slouken@1931
   153
{
slouken@1931
   154
    NSString *appName;
slouken@1931
   155
    NSString *title;
slouken@1931
   156
    NSMenu *appleMenu;
slouken@6515
   157
    NSMenu *serviceMenu;
slouken@1931
   158
    NSMenu *windowMenu;
slouken@7952
   159
    NSMenu *viewMenu;
slouken@1931
   160
    NSMenuItem *menuItem;
slouken@7191
   161
slouken@7511
   162
    if (NSApp == nil) {
jorgen@7508
   163
        return;
jorgen@7508
   164
    }
jorgen@7508
   165
    
slouken@1931
   166
    /* Create the main menu bar */
slouken@1931
   167
    [NSApp setMainMenu:[[NSMenu alloc] init]];
slouken@1931
   168
slouken@1931
   169
    /* Create the application menu */
slouken@1931
   170
    appName = GetApplicationName();
slouken@1931
   171
    appleMenu = [[NSMenu alloc] initWithTitle:@""];
slouken@7191
   172
slouken@1931
   173
    /* Add menu items */
slouken@1931
   174
    title = [@"About " stringByAppendingString:appName];
slouken@1931
   175
    [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
slouken@1931
   176
slouken@1931
   177
    [appleMenu addItem:[NSMenuItem separatorItem]];
slouken@1931
   178
slouken@6515
   179
    [appleMenu addItemWithTitle:@"Preferences…" action:nil keyEquivalent:@","];
slouken@6515
   180
slouken@6515
   181
    [appleMenu addItem:[NSMenuItem separatorItem]];
slouken@6515
   182
slouken@6515
   183
    serviceMenu = [[NSMenu alloc] initWithTitle:@""];
slouken@6515
   184
    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
slouken@6515
   185
    [menuItem setSubmenu:serviceMenu];
slouken@6515
   186
slouken@6515
   187
    [NSApp setServicesMenu:serviceMenu];
slouken@6836
   188
    [serviceMenu release];
slouken@5377
   189
slouken@5377
   190
    [appleMenu addItem:[NSMenuItem separatorItem]];
slouken@5377
   191
slouken@1931
   192
    title = [@"Hide " stringByAppendingString:appName];
slouken@6515
   193
    [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
slouken@1931
   194
slouken@6515
   195
    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
slouken@1931
   196
    [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
slouken@1931
   197
slouken@1931
   198
    [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
slouken@1931
   199
slouken@1931
   200
    [appleMenu addItem:[NSMenuItem separatorItem]];
slouken@1931
   201
slouken@1931
   202
    title = [@"Quit " stringByAppendingString:appName];
slouken@6515
   203
    [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
slouken@7191
   204
slouken@1931
   205
    /* Put menu into the menubar */
slouken@1931
   206
    menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
slouken@1931
   207
    [menuItem setSubmenu:appleMenu];
slouken@1931
   208
    [[NSApp mainMenu] addItem:menuItem];
slouken@1931
   209
    [menuItem release];
slouken@1931
   210
slouken@1931
   211
    /* Tell the application object that this is now the application menu */
slouken@1931
   212
    [NSApp setAppleMenu:appleMenu];
slouken@1931
   213
    [appleMenu release];
slouken@1931
   214
slouken@1931
   215
slouken@1931
   216
    /* Create the window menu */
slouken@1931
   217
    windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
slouken@7191
   218
slouken@6515
   219
    /* Add menu items */
slouken@6515
   220
    [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
slouken@7191
   221
slouken@6515
   222
    [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
slouken@6515
   223
slouken@1931
   224
    /* Put menu into the menubar */
slouken@1931
   225
    menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
slouken@1931
   226
    [menuItem setSubmenu:windowMenu];
slouken@1931
   227
    [[NSApp mainMenu] addItem:menuItem];
slouken@1931
   228
    [menuItem release];
slouken@7191
   229
slouken@1931
   230
    /* Tell the application object that this is now the window menu */
slouken@1931
   231
    [NSApp setWindowsMenu:windowMenu];
slouken@1931
   232
    [windowMenu release];
slouken@7952
   233
slouken@7952
   234
slouken@7952
   235
    /* Add the fullscreen view toggle menu option, if supported */
slouken@7952
   236
    if ([NSApp respondsToSelector:@selector(setPresentationOptions:)]) {
slouken@7952
   237
        /* Create the view menu */
slouken@7952
   238
        viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
slouken@7952
   239
slouken@7952
   240
        /* Add menu items */
slouken@7952
   241
        menuItem = [viewMenu addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
slouken@7952
   242
        [menuItem setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask];
slouken@7952
   243
slouken@7952
   244
        /* Put menu into the menubar */
slouken@7952
   245
        menuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
slouken@7952
   246
        [menuItem setSubmenu:viewMenu];
slouken@7952
   247
        [[NSApp mainMenu] addItem:menuItem];
slouken@7952
   248
        [menuItem release];
slouken@7952
   249
slouken@7952
   250
        [viewMenu release];
slouken@7952
   251
    }
slouken@1931
   252
}
slouken@1931
   253
slouken@1931
   254
void
slouken@1931
   255
Cocoa_RegisterApp(void)
slouken@1931
   256
{
icculus@6639
   257
    /* This can get called more than once! Be careful what you initialize! */
slouken@1931
   258
    ProcessSerialNumber psn;
slouken@6848
   259
    NSAutoreleasePool *pool;
slouken@1931
   260
slouken@1931
   261
    if (!GetCurrentProcess(&psn)) {
slouken@1931
   262
        TransformProcessType(&psn, kProcessTransformToForegroundApplication);
slouken@1931
   263
        SetFrontProcess(&psn);
slouken@1931
   264
    }
slouken@1931
   265
slouken@6848
   266
    pool = [[NSAutoreleasePool alloc] init];
slouken@6848
   267
    if (NSApp == nil) {
slouken@8239
   268
        [SDLApplication sharedApplication];
slouken@1931
   269
slouken@6848
   270
        if ([NSApp mainMenu] == nil) {
slouken@6848
   271
            CreateApplicationMenus();
slouken@1931
   272
        }
slouken@6848
   273
        [NSApp finishLaunching];
slouken@7739
   274
        NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:@"NO" forKey:@"AppleMomentumScrollSupported"];
slouken@7739
   275
        [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
slouken@7739
   276
slouken@1931
   277
    }
jorgen@7801
   278
    if (NSApp && !appDelegate) {
jorgen@7801
   279
        appDelegate = [[SDLAppDelegate alloc] init];
jorgen@7801
   280
jorgen@7801
   281
        /* If someone else has an app delegate, it means we can't turn a
jorgen@7801
   282
         * termination into SDL_Quit, and we can't handle application:openFile:
jorgen@7801
   283
         */
jorgen@7801
   284
        if (![NSApp delegate]) {
jorgen@7801
   285
            [NSApp setDelegate:appDelegate];
jorgen@7801
   286
        } else {
jorgen@7801
   287
            appDelegate->seenFirstActivate = YES;
jorgen@7801
   288
        }
slouken@6848
   289
    }
slouken@6848
   290
    [pool release];
slouken@1931
   291
}
slouken@1931
   292
slouken@1931
   293
void
slouken@1931
   294
Cocoa_PumpEvents(_THIS)
slouken@1931
   295
{
slouken@6848
   296
    NSAutoreleasePool *pool;
slouken@6848
   297
slouken@3025
   298
    /* Update activity every 30 seconds to prevent screensaver */
slouken@3025
   299
    if (_this->suspend_screensaver) {
slouken@3025
   300
        SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
slouken@3025
   301
        Uint32 now = SDL_GetTicks();
slouken@3025
   302
        if (!data->screensaver_activity ||
slouken@7857
   303
            SDL_TICKS_PASSED(now, data->screensaver_activity + 30000)) {
slouken@3025
   304
            UpdateSystemActivity(UsrActivity);
slouken@3025
   305
            data->screensaver_activity = now;
slouken@3025
   306
        }
slouken@3025
   307
    }
slouken@3025
   308
slouken@6848
   309
    pool = [[NSAutoreleasePool alloc] init];
slouken@6848
   310
    for ( ; ; ) {
slouken@6848
   311
        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ];
slouken@6848
   312
        if ( event == nil ) {
slouken@6848
   313
            break;
slouken@1931
   314
        }
slouken@7191
   315
slouken@6848
   316
        switch ([event type]) {
slouken@6848
   317
        case NSLeftMouseDown:
slouken@6848
   318
        case NSOtherMouseDown:
slouken@6848
   319
        case NSRightMouseDown:
slouken@6848
   320
        case NSLeftMouseUp:
slouken@6848
   321
        case NSOtherMouseUp:
slouken@6848
   322
        case NSRightMouseUp:
slouken@6848
   323
        case NSLeftMouseDragged:
slouken@6848
   324
        case NSRightMouseDragged:
slouken@6848
   325
        case NSOtherMouseDragged: /* usually middle mouse dragged */
slouken@6848
   326
        case NSMouseMoved:
slouken@6848
   327
        case NSScrollWheel:
slouken@6848
   328
            Cocoa_HandleMouseEvent(_this, event);
slouken@6848
   329
            break;
slouken@6848
   330
        case NSKeyDown:
slouken@6848
   331
        case NSKeyUp:
slouken@6848
   332
        case NSFlagsChanged:
slouken@6848
   333
            Cocoa_HandleKeyEvent(_this, event);
slouken@6848
   334
            break;
slouken@6848
   335
        default:
slouken@6848
   336
            break;
slouken@6848
   337
        }
slouken@6848
   338
        /* Pass through to NSApp to make sure everything stays in sync */
slouken@6848
   339
        [NSApp sendEvent:event];
slouken@1931
   340
    }
slouken@6848
   341
    [pool release];
slouken@1931
   342
}
slouken@1931
   343
slouken@6044
   344
#endif /* SDL_VIDEO_DRIVER_COCOA */
slouken@6044
   345
slouken@1931
   346
/* vi: set ts=4 sw=4 expandtab: */