SDL  2.0
SDL_cocoaevents.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 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  NSMenu *viewMenu;
279  NSMenuItem *menuItem;
280  NSMenu *mainMenu;
281 
282  if (NSApp == nil) {
283  return;
284  }
285 
286  mainMenu = [[NSMenu alloc] init];
287 
288  /* Create the main menu bar */
289  [NSApp setMainMenu:mainMenu];
290 
291  [mainMenu release]; /* we're done with it, let NSApp own it. */
292  mainMenu = nil;
293 
294  /* Create the application menu */
295  appName = GetApplicationName();
296  appleMenu = [[NSMenu alloc] initWithTitle:@""];
297 
298  /* Add menu items */
299  title = [@"About " stringByAppendingString:appName];
300  [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
301 
302  [appleMenu addItem:[NSMenuItem separatorItem]];
303 
304  [appleMenu addItemWithTitle:@"Preferences…" action:nil keyEquivalent:@","];
305 
306  [appleMenu addItem:[NSMenuItem separatorItem]];
307 
308  serviceMenu = [[NSMenu alloc] initWithTitle:@""];
309  menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
310  [menuItem setSubmenu:serviceMenu];
311 
312  [NSApp setServicesMenu:serviceMenu];
313  [serviceMenu release];
314 
315  [appleMenu addItem:[NSMenuItem separatorItem]];
316 
317  title = [@"Hide " stringByAppendingString:appName];
318  [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
319 
320  menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
321  [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
322 
323  [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
324 
325  [appleMenu addItem:[NSMenuItem separatorItem]];
326 
327  title = [@"Quit " stringByAppendingString:appName];
328  [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
329 
330  /* Put menu into the menubar */
331  menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
332  [menuItem setSubmenu:appleMenu];
333  [[NSApp mainMenu] addItem:menuItem];
334  [menuItem release];
335 
336  /* Tell the application object that this is now the application menu */
337  [NSApp setAppleMenu:appleMenu];
338  [appleMenu release];
339 
340 
341  /* Create the window menu */
342  windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
343 
344  /* Add menu items */
345  [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
346 
347  [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
348 
349  /* Put menu into the menubar */
350  menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
351  [menuItem setSubmenu:windowMenu];
352  [[NSApp mainMenu] addItem:menuItem];
353  [menuItem release];
354 
355  /* Tell the application object that this is now the window menu */
356  [NSApp setWindowsMenu:windowMenu];
357  [windowMenu release];
358 
359 
360  /* Add the fullscreen view toggle menu option, if supported */
361  if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) {
362  /* Create the view menu */
363  viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
364 
365  /* Add menu items */
366  menuItem = [viewMenu addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
367  [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
368 
369  /* Put menu into the menubar */
370  menuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
371  [menuItem setSubmenu:viewMenu];
372  [[NSApp mainMenu] addItem:menuItem];
373  [menuItem release];
374 
375  [viewMenu release];
376  }
377 }
378 
379 void
380 Cocoa_RegisterApp(void)
381 { @autoreleasepool
382 {
383  /* This can get called more than once! Be careful what you initialize! */
384 
385  if (NSApp == nil) {
386  [SDLApplication sharedApplication];
387  SDL_assert(NSApp != nil);
388 
389  s_bShouldHandleEventsInSDLApplication = SDL_TRUE;
390 
392  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
393  }
394 
395  if ([NSApp mainMenu] == nil) {
396  CreateApplicationMenus();
397  }
398  [NSApp finishLaunching];
399  if ([NSApp delegate]) {
400  /* The SDL app delegate calls this in didFinishLaunching if it's
401  * attached to the NSApp, otherwise we need to call it manually.
402  */
403  [SDLApplication registerUserDefaults];
404  }
405  }
406  if (NSApp && !appDelegate) {
407  appDelegate = [[SDLAppDelegate alloc] init];
408 
409  /* If someone else has an app delegate, it means we can't turn a
410  * termination into SDL_Quit, and we can't handle application:openFile:
411  */
412  if (![NSApp delegate]) {
413  [(NSApplication *)NSApp setDelegate:appDelegate];
414  } else {
415  appDelegate->seenFirstActivate = YES;
416  }
417  }
418 }}
419 
420 void
422 { @autoreleasepool
423 {
424 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
425  /* Update activity every 30 seconds to prevent screensaver */
428  Uint32 now = SDL_GetTicks();
429  if (!data->screensaver_activity ||
430  SDL_TICKS_PASSED(now, data->screensaver_activity + 30000)) {
431  UpdateSystemActivity(UsrActivity);
432  data->screensaver_activity = now;
433  }
434  }
435 #endif
436 
437  for ( ; ; ) {
438  NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES ];
439  if ( event == nil ) {
440  break;
441  }
442 
443  if (!s_bShouldHandleEventsInSDLApplication) {
444  Cocoa_DispatchEvent(event);
445  }
446 
447  // Pass events down to SDLApplication to be handled in sendEvent:
448  [NSApp sendEvent:event];
449  }
450 }}
451 
452 void
454 { @autoreleasepool
455 {
457 
458  if (!data->screensaver_use_iopm) {
459  return;
460  }
461 
462  if (data->screensaver_assertion) {
463  IOPMAssertionRelease(data->screensaver_assertion);
464  data->screensaver_assertion = 0;
465  }
466 
467  if (_this->suspend_screensaver) {
468  /* FIXME: this should ideally describe the real reason why the game
469  * called SDL_DisableScreenSaver. Note that the name is only meant to be
470  * seen by OS X power users. there's an additional optional human-readable
471  * (localized) reason parameter which we don't set.
472  */
473  NSString *name = [GetApplicationName() stringByAppendingString:@" using SDL_DisableScreenSaver"];
474  IOPMAssertionCreateWithDescription(kIOPMAssertPreventUserIdleDisplaySleep,
475  (CFStringRef) name,
476  NULL, NULL, NULL, 0, NULL,
477  &data->screensaver_assertion);
478  }
479 }}
480 
481 #endif /* SDL_VIDEO_DRIVER_COCOA */
482 
483 /* vi: set ts=4 sw=4 expandtab: */
void Cocoa_RegisterApp(void)
GLuint id
void Cocoa_HandleKeyEvent(_THIS, NSEvent *event)
GLuint num
int SDL_SendDropFile(SDL_Window *window, const char *file)
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
void Cocoa_SuspendScreenSaver(_THIS)
IOPMAssertionID screensaver_assertion
uint32_t Uint32
Definition: SDL_stdinc.h:181
int SDL_SendDropComplete(SDL_Window *window)
GLuint const GLchar * name
#define SDL_GetHintBoolean
static SDL_VideoDevice * _this
Definition: SDL_video.c:121
static SDL_AudioDeviceID device
Definition: loopwave.c:37
#define SDL_HINT_MAC_BACKGROUND_APP
When set don&#39;t force the SDL app to become a foreground process.
Definition: SDL_hints.h:705
Uint32 screensaver_activity
Uint32 SDL_GetTicks(void)
Get the number of milliseconds since the SDL library initialization.
#define _THIS
struct _cl_event * event
void Cocoa_PumpEvents(_THIS)
BOOL screensaver_use_iopm
SDL_VideoDisplay * displays
Definition: SDL_sysvideo.h:312
SDL_Window * windows
Definition: SDL_sysvideo.h:313
double floor(double x)
Definition: s_floor.c:29
void Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
SDL_Window * fullscreen_window
Definition: SDL_sysvideo.h:134
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 SDL_assert(condition)
Definition: SDL_assert.h:169
#define NULL
Definition: begin_code.h:164
SDL_bool
Definition: SDL_stdinc.h:139
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
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
The type used to identify a window.
Definition: SDL_sysvideo.h:73
void terminate(int sig)
Definition: testlock.c:45
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1571
SDL_VideoDevice * SDL_GetVideoDevice(void)
Definition: SDL_video.c:586
SDL_bool suspend_screensaver
Definition: SDL_sysvideo.h:310
GLuint GLsizei GLsizei * length
Uint32 flags
Definition: SDL_sysvideo.h:83
#define SDL_TICKS_PASSED(A, B)
Compare SDL ticks values, and return true if A has passed B.
Definition: SDL_timer.h:56
GLuint in
int SDL_SendQuit(void)
Definition: SDL_quit.c:137