1 //========================================================================
2 // GLFW 3.4 macOS - www.glfw.org
3 //------------------------------------------------------------------------
4 // Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
6 // This software is provided 'as-is', without any express or implied
7 // warranty. In no event will the authors be held liable for any damages
8 // arising from the use of this software.
10 // Permission is granted to anyone to use this software for any purpose,
11 // including commercial applications, and to alter it and redistribute it
12 // freely, subject to the following restrictions:
14 // 1. The origin of this software must not be misrepresented; you must not
15 // claim that you wrote the original software. If you use this software
16 // in a product, an acknowledgment in the product documentation would
17 // be appreciated but is not required.
19 // 2. Altered source versions must be plainly marked as such, and must not
20 // be misrepresented as being the original software.
22 // 3. This notice may not be removed or altered from any source
25 //========================================================================
26 // It is fine to use C99 in this file because it will not be built with VS
27 //========================================================================
30 #include <sys/param.h> // For MAXPATHLEN
32 // Needed for _NSGetProgname
33 #include <crt_externs.h>
35 // Change to our application bundle's resources directory, if present
37 static void changeToResourcesDirectory(void)
39 char resourcesPath[MAXPATHLEN];
41 CFBundleRef bundle = CFBundleGetMainBundle();
45 CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle);
47 CFStringRef last = CFURLCopyLastPathComponent(resourcesURL);
48 if (CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo)
51 CFRelease(resourcesURL);
57 if (!CFURLGetFileSystemRepresentation(resourcesURL,
59 (UInt8*) resourcesPath,
62 CFRelease(resourcesURL);
66 CFRelease(resourcesURL);
71 // Set up the menu bar (manually)
72 // This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
73 // could go away at any moment, lots of stuff that really should be
74 // localize(d|able), etc. Add a nib to save us this horror.
76 static void createMenuBar(void)
79 NSString* appName = nil;
80 NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary];
81 NSString* nameKeys[] =
83 @"CFBundleDisplayName",
85 @"CFBundleExecutable",
88 // Try to figure out what the calling application is called
90 for (i = 0; i < sizeof(nameKeys) / sizeof(nameKeys[0]); i++)
92 id name = bundleInfo[nameKeys[i]];
94 [name isKindOfClass:[NSString class]] &&
95 ![name isEqualToString:@""])
104 char** progname = _NSGetProgname();
105 if (progname && *progname)
106 appName = @(*progname);
108 appName = @"GLFW Application";
111 NSMenu* bar = [[NSMenu alloc] init];
112 [NSApp setMainMenu:bar];
114 NSMenuItem* appMenuItem =
115 [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
116 NSMenu* appMenu = [[NSMenu alloc] init];
117 [appMenuItem setSubmenu:appMenu];
119 [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName]
120 action:@selector(orderFrontStandardAboutPanel:)
122 [appMenu addItem:[NSMenuItem separatorItem]];
123 NSMenu* servicesMenu = [[NSMenu alloc] init];
124 [NSApp setServicesMenu:servicesMenu];
125 [[appMenu addItemWithTitle:@"Services"
127 keyEquivalent:@""] setSubmenu:servicesMenu];
128 [servicesMenu release];
129 [appMenu addItem:[NSMenuItem separatorItem]];
130 [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
131 action:@selector(hide:)
133 [[appMenu addItemWithTitle:@"Hide Others"
134 action:@selector(hideOtherApplications:)
136 setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
137 [appMenu addItemWithTitle:@"Show All"
138 action:@selector(unhideAllApplications:)
140 [appMenu addItem:[NSMenuItem separatorItem]];
141 [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
142 action:@selector(terminate:)
145 NSMenuItem* windowMenuItem =
146 [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
148 NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
149 [NSApp setWindowsMenu:windowMenu];
150 [windowMenuItem setSubmenu:windowMenu];
152 [windowMenu addItemWithTitle:@"Minimize"
153 action:@selector(performMiniaturize:)
155 [windowMenu addItemWithTitle:@"Zoom"
156 action:@selector(performZoom:)
158 [windowMenu addItem:[NSMenuItem separatorItem]];
159 [windowMenu addItemWithTitle:@"Bring All to Front"
160 action:@selector(arrangeInFront:)
163 // TODO: Make this appear at the bottom of the menu (for consistency)
164 [windowMenu addItem:[NSMenuItem separatorItem]];
165 [[windowMenu addItemWithTitle:@"Enter Full Screen"
166 action:@selector(toggleFullScreen:)
168 setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
170 // Prior to Snow Leopard, we need to use this oddly-named semi-private API
171 // to get the application menu working properly.
172 SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:");
173 [NSApp performSelector:setAppleMenuSelector withObject:appMenu];
176 // Create key code translation tables
178 static void createKeyTables(void)
182 memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
183 memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes));
185 _glfw.ns.keycodes[0x1D] = GLFW_KEY_0;
186 _glfw.ns.keycodes[0x12] = GLFW_KEY_1;
187 _glfw.ns.keycodes[0x13] = GLFW_KEY_2;
188 _glfw.ns.keycodes[0x14] = GLFW_KEY_3;
189 _glfw.ns.keycodes[0x15] = GLFW_KEY_4;
190 _glfw.ns.keycodes[0x17] = GLFW_KEY_5;
191 _glfw.ns.keycodes[0x16] = GLFW_KEY_6;
192 _glfw.ns.keycodes[0x1A] = GLFW_KEY_7;
193 _glfw.ns.keycodes[0x1C] = GLFW_KEY_8;
194 _glfw.ns.keycodes[0x19] = GLFW_KEY_9;
195 _glfw.ns.keycodes[0x00] = GLFW_KEY_A;
196 _glfw.ns.keycodes[0x0B] = GLFW_KEY_B;
197 _glfw.ns.keycodes[0x08] = GLFW_KEY_C;
198 _glfw.ns.keycodes[0x02] = GLFW_KEY_D;
199 _glfw.ns.keycodes[0x0E] = GLFW_KEY_E;
200 _glfw.ns.keycodes[0x03] = GLFW_KEY_F;
201 _glfw.ns.keycodes[0x05] = GLFW_KEY_G;
202 _glfw.ns.keycodes[0x04] = GLFW_KEY_H;
203 _glfw.ns.keycodes[0x22] = GLFW_KEY_I;
204 _glfw.ns.keycodes[0x26] = GLFW_KEY_J;
205 _glfw.ns.keycodes[0x28] = GLFW_KEY_K;
206 _glfw.ns.keycodes[0x25] = GLFW_KEY_L;
207 _glfw.ns.keycodes[0x2E] = GLFW_KEY_M;
208 _glfw.ns.keycodes[0x2D] = GLFW_KEY_N;
209 _glfw.ns.keycodes[0x1F] = GLFW_KEY_O;
210 _glfw.ns.keycodes[0x23] = GLFW_KEY_P;
211 _glfw.ns.keycodes[0x0C] = GLFW_KEY_Q;
212 _glfw.ns.keycodes[0x0F] = GLFW_KEY_R;
213 _glfw.ns.keycodes[0x01] = GLFW_KEY_S;
214 _glfw.ns.keycodes[0x11] = GLFW_KEY_T;
215 _glfw.ns.keycodes[0x20] = GLFW_KEY_U;
216 _glfw.ns.keycodes[0x09] = GLFW_KEY_V;
217 _glfw.ns.keycodes[0x0D] = GLFW_KEY_W;
218 _glfw.ns.keycodes[0x07] = GLFW_KEY_X;
219 _glfw.ns.keycodes[0x10] = GLFW_KEY_Y;
220 _glfw.ns.keycodes[0x06] = GLFW_KEY_Z;
222 _glfw.ns.keycodes[0x27] = GLFW_KEY_APOSTROPHE;
223 _glfw.ns.keycodes[0x2A] = GLFW_KEY_BACKSLASH;
224 _glfw.ns.keycodes[0x2B] = GLFW_KEY_COMMA;
225 _glfw.ns.keycodes[0x18] = GLFW_KEY_EQUAL;
226 _glfw.ns.keycodes[0x32] = GLFW_KEY_GRAVE_ACCENT;
227 _glfw.ns.keycodes[0x21] = GLFW_KEY_LEFT_BRACKET;
228 _glfw.ns.keycodes[0x1B] = GLFW_KEY_MINUS;
229 _glfw.ns.keycodes[0x2F] = GLFW_KEY_PERIOD;
230 _glfw.ns.keycodes[0x1E] = GLFW_KEY_RIGHT_BRACKET;
231 _glfw.ns.keycodes[0x29] = GLFW_KEY_SEMICOLON;
232 _glfw.ns.keycodes[0x2C] = GLFW_KEY_SLASH;
233 _glfw.ns.keycodes[0x0A] = GLFW_KEY_WORLD_1;
235 _glfw.ns.keycodes[0x33] = GLFW_KEY_BACKSPACE;
236 _glfw.ns.keycodes[0x39] = GLFW_KEY_CAPS_LOCK;
237 _glfw.ns.keycodes[0x75] = GLFW_KEY_DELETE;
238 _glfw.ns.keycodes[0x7D] = GLFW_KEY_DOWN;
239 _glfw.ns.keycodes[0x77] = GLFW_KEY_END;
240 _glfw.ns.keycodes[0x24] = GLFW_KEY_ENTER;
241 _glfw.ns.keycodes[0x35] = GLFW_KEY_ESCAPE;
242 _glfw.ns.keycodes[0x7A] = GLFW_KEY_F1;
243 _glfw.ns.keycodes[0x78] = GLFW_KEY_F2;
244 _glfw.ns.keycodes[0x63] = GLFW_KEY_F3;
245 _glfw.ns.keycodes[0x76] = GLFW_KEY_F4;
246 _glfw.ns.keycodes[0x60] = GLFW_KEY_F5;
247 _glfw.ns.keycodes[0x61] = GLFW_KEY_F6;
248 _glfw.ns.keycodes[0x62] = GLFW_KEY_F7;
249 _glfw.ns.keycodes[0x64] = GLFW_KEY_F8;
250 _glfw.ns.keycodes[0x65] = GLFW_KEY_F9;
251 _glfw.ns.keycodes[0x6D] = GLFW_KEY_F10;
252 _glfw.ns.keycodes[0x67] = GLFW_KEY_F11;
253 _glfw.ns.keycodes[0x6F] = GLFW_KEY_F12;
254 _glfw.ns.keycodes[0x69] = GLFW_KEY_F13;
255 _glfw.ns.keycodes[0x6B] = GLFW_KEY_F14;
256 _glfw.ns.keycodes[0x71] = GLFW_KEY_F15;
257 _glfw.ns.keycodes[0x6A] = GLFW_KEY_F16;
258 _glfw.ns.keycodes[0x40] = GLFW_KEY_F17;
259 _glfw.ns.keycodes[0x4F] = GLFW_KEY_F18;
260 _glfw.ns.keycodes[0x50] = GLFW_KEY_F19;
261 _glfw.ns.keycodes[0x5A] = GLFW_KEY_F20;
262 _glfw.ns.keycodes[0x73] = GLFW_KEY_HOME;
263 _glfw.ns.keycodes[0x72] = GLFW_KEY_INSERT;
264 _glfw.ns.keycodes[0x7B] = GLFW_KEY_LEFT;
265 _glfw.ns.keycodes[0x3A] = GLFW_KEY_LEFT_ALT;
266 _glfw.ns.keycodes[0x3B] = GLFW_KEY_LEFT_CONTROL;
267 _glfw.ns.keycodes[0x38] = GLFW_KEY_LEFT_SHIFT;
268 _glfw.ns.keycodes[0x37] = GLFW_KEY_LEFT_SUPER;
269 _glfw.ns.keycodes[0x6E] = GLFW_KEY_MENU;
270 _glfw.ns.keycodes[0x47] = GLFW_KEY_NUM_LOCK;
271 _glfw.ns.keycodes[0x79] = GLFW_KEY_PAGE_DOWN;
272 _glfw.ns.keycodes[0x74] = GLFW_KEY_PAGE_UP;
273 _glfw.ns.keycodes[0x7C] = GLFW_KEY_RIGHT;
274 _glfw.ns.keycodes[0x3D] = GLFW_KEY_RIGHT_ALT;
275 _glfw.ns.keycodes[0x3E] = GLFW_KEY_RIGHT_CONTROL;
276 _glfw.ns.keycodes[0x3C] = GLFW_KEY_RIGHT_SHIFT;
277 _glfw.ns.keycodes[0x36] = GLFW_KEY_RIGHT_SUPER;
278 _glfw.ns.keycodes[0x31] = GLFW_KEY_SPACE;
279 _glfw.ns.keycodes[0x30] = GLFW_KEY_TAB;
280 _glfw.ns.keycodes[0x7E] = GLFW_KEY_UP;
282 _glfw.ns.keycodes[0x52] = GLFW_KEY_KP_0;
283 _glfw.ns.keycodes[0x53] = GLFW_KEY_KP_1;
284 _glfw.ns.keycodes[0x54] = GLFW_KEY_KP_2;
285 _glfw.ns.keycodes[0x55] = GLFW_KEY_KP_3;
286 _glfw.ns.keycodes[0x56] = GLFW_KEY_KP_4;
287 _glfw.ns.keycodes[0x57] = GLFW_KEY_KP_5;
288 _glfw.ns.keycodes[0x58] = GLFW_KEY_KP_6;
289 _glfw.ns.keycodes[0x59] = GLFW_KEY_KP_7;
290 _glfw.ns.keycodes[0x5B] = GLFW_KEY_KP_8;
291 _glfw.ns.keycodes[0x5C] = GLFW_KEY_KP_9;
292 _glfw.ns.keycodes[0x45] = GLFW_KEY_KP_ADD;
293 _glfw.ns.keycodes[0x41] = GLFW_KEY_KP_DECIMAL;
294 _glfw.ns.keycodes[0x4B] = GLFW_KEY_KP_DIVIDE;
295 _glfw.ns.keycodes[0x4C] = GLFW_KEY_KP_ENTER;
296 _glfw.ns.keycodes[0x51] = GLFW_KEY_KP_EQUAL;
297 _glfw.ns.keycodes[0x43] = GLFW_KEY_KP_MULTIPLY;
298 _glfw.ns.keycodes[0x4E] = GLFW_KEY_KP_SUBTRACT;
300 for (scancode = 0; scancode < 256; scancode++)
302 // Store the reverse translation for faster key name lookup
303 if (_glfw.ns.keycodes[scancode] >= 0)
304 _glfw.ns.scancodes[_glfw.ns.keycodes[scancode]] = scancode;
308 // Retrieve Unicode data for the current keyboard layout
310 static GLFWbool updateUnicodeDataNS(void)
312 if (_glfw.ns.inputSource)
314 CFRelease(_glfw.ns.inputSource);
315 _glfw.ns.inputSource = NULL;
316 _glfw.ns.unicodeData = nil;
319 _glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
320 if (!_glfw.ns.inputSource)
322 _glfwInputError(GLFW_PLATFORM_ERROR,
323 "Cocoa: Failed to retrieve keyboard layout input source");
327 _glfw.ns.unicodeData =
328 TISGetInputSourceProperty(_glfw.ns.inputSource,
329 kTISPropertyUnicodeKeyLayoutData);
330 if (!_glfw.ns.unicodeData)
332 _glfwInputError(GLFW_PLATFORM_ERROR,
333 "Cocoa: Failed to retrieve keyboard layout Unicode data");
340 // Load HIToolbox.framework and the TIS symbols we need from it
342 static GLFWbool initializeTIS(void)
344 // This works only because Cocoa has already loaded it properly
345 _glfw.ns.tis.bundle =
346 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HIToolbox"));
347 if (!_glfw.ns.tis.bundle)
349 _glfwInputError(GLFW_PLATFORM_ERROR,
350 "Cocoa: Failed to load HIToolbox.framework");
354 CFStringRef* kPropertyUnicodeKeyLayoutData =
355 CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
356 CFSTR("kTISPropertyUnicodeKeyLayoutData"));
357 _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource =
358 CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
359 CFSTR("TISCopyCurrentKeyboardLayoutInputSource"));
360 _glfw.ns.tis.GetInputSourceProperty =
361 CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
362 CFSTR("TISGetInputSourceProperty"));
363 _glfw.ns.tis.GetKbdType =
364 CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
365 CFSTR("LMGetKbdType"));
367 if (!kPropertyUnicodeKeyLayoutData ||
368 !TISCopyCurrentKeyboardLayoutInputSource ||
369 !TISGetInputSourceProperty ||
372 _glfwInputError(GLFW_PLATFORM_ERROR,
373 "Cocoa: Failed to load TIS API symbols");
377 _glfw.ns.tis.kPropertyUnicodeKeyLayoutData =
378 *kPropertyUnicodeKeyLayoutData;
380 return updateUnicodeDataNS();
383 @interface GLFWHelper : NSObject
386 @implementation GLFWHelper
388 - (void)selectedKeyboardInputSourceChanged:(NSObject* )object
390 updateUnicodeDataNS();
393 - (void)doNothing:(id)object
399 @interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
402 @implementation GLFWApplicationDelegate
404 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
408 for (window = _glfw.windowListHead; window; window = window->next)
409 _glfwInputWindowCloseRequest(window);
411 return NSTerminateCancel;
414 - (void)applicationDidChangeScreenParameters:(NSNotification *) notification
418 for (window = _glfw.windowListHead; window; window = window->next)
420 if (window->context.client != GLFW_NO_API)
421 [window->context.nsgl.object update];
424 _glfwPollMonitorsNS();
427 - (void)applicationWillFinishLaunching:(NSNotification *)notification
429 if (_glfw.hints.init.ns.menubar)
431 // In case we are unbundled, make us a proper UI application
432 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
434 // Menu bar setup must go between sharedApplication and finishLaunching
435 // in order to properly emulate the behavior of NSApplicationMain
437 if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
439 [[NSBundle mainBundle] loadNibNamed:@"MainMenu"
441 topLevelObjects:&_glfw.ns.nibObjects];
448 - (void)applicationDidFinishLaunching:(NSNotification *)notification
450 _glfwPlatformPostEmptyEvent();
454 - (void)applicationDidHide:(NSNotification *)notification
458 for (i = 0; i < _glfw.monitorCount; i++)
459 _glfwRestoreVideoModeNS(_glfw.monitors[i]);
462 @end // GLFWApplicationDelegate
465 //////////////////////////////////////////////////////////////////////////
466 ////// GLFW internal API //////
467 //////////////////////////////////////////////////////////////////////////
469 void* _glfwLoadLocalVulkanLoaderNS(void)
471 CFBundleRef bundle = CFBundleGetMainBundle();
476 CFBundleCopyAuxiliaryExecutableURL(bundle, CFSTR("libvulkan.1.dylib"));
483 if (CFURLGetFileSystemRepresentation(url, true, (UInt8*) path, sizeof(path) - 1))
484 handle = _glfw_dlopen(path);
491 //////////////////////////////////////////////////////////////////////////
492 ////// GLFW platform API //////
493 //////////////////////////////////////////////////////////////////////////
495 int _glfwPlatformInit(void)
499 _glfw.ns.helper = [[GLFWHelper alloc] init];
501 [NSThread detachNewThreadSelector:@selector(doNothing:)
502 toTarget:_glfw.ns.helper
505 [NSApplication sharedApplication];
507 _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
508 if (_glfw.ns.delegate == nil)
510 _glfwInputError(GLFW_PLATFORM_ERROR,
511 "Cocoa: Failed to create application delegate");
515 [NSApp setDelegate:_glfw.ns.delegate];
517 NSEvent* (^block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
519 if ([event modifierFlags] & NSEventModifierFlagCommand)
520 [[NSApp keyWindow] sendEvent:event];
525 _glfw.ns.keyUpMonitor =
526 [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
529 if (_glfw.hints.init.ns.chdir)
530 changeToResourcesDirectory();
532 // Press and Hold prevents some keys from emitting repeated characters
533 NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
534 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
536 [[NSNotificationCenter defaultCenter]
537 addObserver:_glfw.ns.helper
538 selector:@selector(selectedKeyboardInputSourceChanged:)
539 name:NSTextInputContextKeyboardSelectionDidChangeNotification
544 _glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
545 if (!_glfw.ns.eventSource)
548 CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0);
550 if (!initializeTIS())
555 _glfwPollMonitorsNS();
557 if (![[NSRunningApplication currentApplication] isFinishedLaunching])
565 void _glfwPlatformTerminate(void)
569 if (_glfw.ns.inputSource)
571 CFRelease(_glfw.ns.inputSource);
572 _glfw.ns.inputSource = NULL;
573 _glfw.ns.unicodeData = nil;
576 if (_glfw.ns.eventSource)
578 CFRelease(_glfw.ns.eventSource);
579 _glfw.ns.eventSource = NULL;
582 if (_glfw.ns.delegate)
584 [NSApp setDelegate:nil];
585 [_glfw.ns.delegate release];
586 _glfw.ns.delegate = nil;
591 [[NSNotificationCenter defaultCenter]
592 removeObserver:_glfw.ns.helper
593 name:NSTextInputContextKeyboardSelectionDidChangeNotification
595 [[NSNotificationCenter defaultCenter]
596 removeObserver:_glfw.ns.helper];
597 [_glfw.ns.helper release];
598 _glfw.ns.helper = nil;
601 if (_glfw.ns.keyUpMonitor)
602 [NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
604 free(_glfw.ns.clipboardString);
606 _glfwTerminateNSGL();
611 const char* _glfwPlatformGetVersionString(void)
613 return _GLFW_VERSION_NUMBER " Cocoa NSGL EGL OSMesa"
614 #if defined(_GLFW_BUILD_DLL)