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 {
50  SDL_SendQuit();
51 }
52 
53 static SDL_bool s_bShouldHandleEventsInSDLApplication = SDL_FALSE;
54 
55 static 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:
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 
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 
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 
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
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 
445 void
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 
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: */
terminate
void terminate(int sig)
Definition: testlock.c:45
sort_controllers.filename
string filename
Definition: sort_controllers.py:8
Cocoa_SuspendScreenSaver
void Cocoa_SuspendScreenSaver(_THIS)
Uint32
uint32_t Uint32
Definition: SDL_stdinc.h:203
SDL_VideoDevice::driverdata
void * driverdata
Definition: SDL_sysvideo.h:381
SDL_WINDOW_MINIMIZED
@ SDL_WINDOW_MINIMIZED
Definition: SDL_video.h:106
in
GLuint in
Definition: SDL_opengl_glext.h:7940
NULL
#define NULL
Definition: begin_code.h:167
SDL_timer.h
Cocoa_HandleKeyEvent
void Cocoa_HandleKeyEvent(_THIS, NSEvent *event)
num
GLuint num
Definition: SDL_opengl_glext.h:4956
SDL_cocoavideo.h
length
GLuint GLsizei GLsizei * length
Definition: SDL_opengl_glext.h:669
data
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
SDL_Window
The type used to identify a window.
Definition: SDL_sysvideo.h:73
SDL_HINT_MAC_BACKGROUND_APP
#define SDL_HINT_MAC_BACKGROUND_APP
When set don't force the SDL app to become a foreground process.
Definition: SDL_hints.h:821
SDL_GetHintBoolean
#define SDL_GetHintBoolean
Definition: SDL_dynapi_overrides.h:608
event
struct _cl_event * event
Definition: SDL_opengl_glext.h:2649
SDL_FALSE
@ SDL_FALSE
Definition: SDL_stdinc.h:163
_this
static SDL_VideoDevice * _this
Definition: SDL_video.c:118
window
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
name
GLuint const GLchar * name
Definition: SDL_opengl_glext.h:660
SDL_SendDropFile
int SDL_SendDropFile(SDL_Window *window, const char *file)
Definition: SDL_dropevents.c:80
SDL_assert.h
SDL_GetTicks
Uint32 SDL_GetTicks(void)
Get the number of milliseconds since the SDL library initialization.
_THIS
#define _THIS
Definition: SDL_alsa_audio.h:31
SDL_SendDropComplete
int SDL_SendDropComplete(SDL_Window *window)
Definition: SDL_dropevents.c:92
SDL_assert
#define SDL_assert(condition)
Definition: SDL_assert.h:169
Cocoa_HandleMouseEvent
void Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
SDL_VideoDevice
Definition: SDL_sysvideo.h:148
id
GLuint id
Definition: SDL_opengl_glext.h:528
Cocoa_RegisterApp
void Cocoa_RegisterApp(void)
SDL_TRUE
@ SDL_TRUE
Definition: SDL_stdinc.h:164
SDL_hints.h
Cocoa_PumpEvents
void Cocoa_PumpEvents(_THIS)
SDL_TICKS_PASSED
#define SDL_TICKS_PASSED(A, B)
Compare SDL ticks values, and return true if A has passed B.
Definition: SDL_timer.h:56
SDL_GetVideoDevice
SDL_VideoDevice * SDL_GetVideoDevice(void)
Definition: SDL_video.c:583
void
const SDL_PRINTF_FORMAT_STRING char int const SDL_PRINTF_FORMAT_STRING char int const SDL_PRINTF_FORMAT_STRING char int const SDL_PRINTF_FORMAT_STRING char const char const SDL_SCANF_FORMAT_STRING char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
Definition: SDL_dynapi_procs.h:89
device
static SDL_AudioDeviceID device
Definition: loopwave.c:37
floor
double floor(double x)
Definition: s_floor.c:33
SDL_Window::flags
Uint32 flags
Definition: SDL_sysvideo.h:83
type
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1571
i
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
SDL_SendQuit
int SDL_SendQuit(void)
Definition: SDL_quit.c:201
SDL_VideoDevice::suspend_screensaver
SDL_bool suspend_screensaver
Definition: SDL_sysvideo.h:314
SDL_VideoData
Definition: SDL_androidvideo.h:36
SDL_bool
SDL_bool
Definition: SDL_stdinc.h:161