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