SDL 2.0
SDL_cocoaevents.m
Go to the documentation of this file.
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{
51}
52
53static SDL_bool s_bShouldHandleEventsInSDLApplication = SDL_FALSE;
54
55static void Cocoa_DispatchEvent(NSEvent *theEvent)
56{
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:
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
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 */
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
250static SDLAppDelegate *appDelegate = nil;
251
252static NSString *
253GetApplicationName(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
270static void
271CreateApplicationMenus(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
372void
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
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
413void
415{ @autoreleasepool
416{
417#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
418 /* Update activity every 30 seconds to prevent screensaver */
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
445void
447{ @autoreleasepool
448{
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
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: */
#define _THIS
#define SDL_assert(condition)
Definition: SDL_assert.h:169
void Cocoa_RegisterApp(void)
void Cocoa_SuspendScreenSaver(_THIS)
void Cocoa_PumpEvents(_THIS)
void Cocoa_HandleKeyEvent(_THIS, NSEvent *event)
void Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
int SDL_SendDropFile(SDL_Window *window, const char *file)
int SDL_SendDropComplete(SDL_Window *window)
#define SDL_GetHintBoolean
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
int SDL_SendQuit(void)
Definition: SDL_quit.c:201
#define SDL_HINT_MAC_BACKGROUND_APP
When set don't force the SDL app to become a foreground process.
Definition: SDL_hints.h:821
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1571
struct _cl_event * event
GLuint id
GLuint in
GLuint const GLchar * name
GLuint GLsizei GLsizei * length
GLuint num
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
uint32_t Uint32
Definition: SDL_stdinc.h:203
SDL_VideoDevice * SDL_GetVideoDevice(void)
Definition: SDL_video.c:583
Uint32 SDL_GetTicks(void)
Get the number of milliseconds since the SDL library initialization.
#define SDL_TICKS_PASSED(A, B)
Compare SDL ticks values, and return true if A has passed B.
Definition: SDL_timer.h:56
static SDL_VideoDevice * _this
Definition: SDL_video.c:118
@ SDL_WINDOW_MINIMIZED
Definition: SDL_video.h:106
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
#define NULL
Definition: begin_code.h:167
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
static SDL_AudioDeviceID device
Definition: loopwave.c:37
double floor(double x)
Definition: s_floor.c:33
SDL_bool suspend_screensaver
Definition: SDL_sysvideo.h:314
The type used to identify a window.
Definition: SDL_sysvideo.h:74
Uint32 flags
Definition: SDL_sysvideo.h:83
void terminate(int sig)
Definition: testlock.c:45