cocoa_init.m 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  1. //========================================================================
  2. // GLFW 3.4 macOS - www.glfw.org
  3. //------------------------------------------------------------------------
  4. // Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
  5. //
  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.
  9. //
  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:
  13. //
  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.
  18. //
  19. // 2. Altered source versions must be plainly marked as such, and must not
  20. // be misrepresented as being the original software.
  21. //
  22. // 3. This notice may not be removed or altered from any source
  23. // distribution.
  24. //
  25. //========================================================================
  26. // It is fine to use C99 in this file because it will not be built with VS
  27. //========================================================================
  28. #include "internal.h"
  29. #include "../kitty/monotonic.h"
  30. #include <sys/param.h> // For MAXPATHLEN
  31. #include <pthread.h>
  32. // Needed for _NSGetProgname
  33. #include <crt_externs.h>
  34. // Change to our application bundle's resources directory, if present
  35. //
  36. static void changeToResourcesDirectory(void)
  37. {
  38. char resourcesPath[MAXPATHLEN];
  39. CFBundleRef bundle = CFBundleGetMainBundle();
  40. if (!bundle)
  41. return;
  42. CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle);
  43. CFStringRef last = CFURLCopyLastPathComponent(resourcesURL);
  44. if (CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo)
  45. {
  46. CFRelease(last);
  47. CFRelease(resourcesURL);
  48. return;
  49. }
  50. CFRelease(last);
  51. if (!CFURLGetFileSystemRepresentation(resourcesURL,
  52. true,
  53. (UInt8*) resourcesPath,
  54. MAXPATHLEN))
  55. {
  56. CFRelease(resourcesURL);
  57. return;
  58. }
  59. CFRelease(resourcesURL);
  60. chdir(resourcesPath);
  61. }
  62. // Set up the menu bar (manually)
  63. // This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
  64. // could go away at any moment, lots of stuff that really should be
  65. // localize(d|able), etc. Add a nib to save us this horror.
  66. //
  67. static void createMenuBar(void)
  68. {
  69. size_t i;
  70. NSString* appName = nil;
  71. NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary];
  72. NSString* nameKeys[] =
  73. {
  74. @"CFBundleDisplayName",
  75. @"CFBundleName",
  76. @"CFBundleExecutable",
  77. };
  78. // Try to figure out what the calling application is called
  79. for (i = 0; i < sizeof(nameKeys) / sizeof(nameKeys[0]); i++)
  80. {
  81. id name = bundleInfo[nameKeys[i]];
  82. if (name &&
  83. [name isKindOfClass:[NSString class]] &&
  84. ![name isEqualToString:@""])
  85. {
  86. appName = name;
  87. break;
  88. }
  89. }
  90. if (!appName)
  91. {
  92. char** progname = _NSGetProgname();
  93. if (progname && *progname)
  94. appName = @(*progname);
  95. else
  96. appName = @"GLFW Application";
  97. }
  98. NSMenu* bar = [[NSMenu alloc] init];
  99. [NSApp setMainMenu:bar];
  100. NSMenuItem* appMenuItem =
  101. [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
  102. NSMenu* appMenu = [[NSMenu alloc] init];
  103. [appMenuItem setSubmenu:appMenu];
  104. [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName]
  105. action:@selector(orderFrontStandardAboutPanel:)
  106. keyEquivalent:@""];
  107. [appMenu addItem:[NSMenuItem separatorItem]];
  108. NSMenu* servicesMenu = [[NSMenu alloc] init];
  109. [NSApp setServicesMenu:servicesMenu];
  110. [[appMenu addItemWithTitle:@"Services"
  111. action:NULL
  112. keyEquivalent:@""] setSubmenu:servicesMenu];
  113. [servicesMenu release];
  114. [appMenu addItem:[NSMenuItem separatorItem]];
  115. [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
  116. action:@selector(hide:)
  117. keyEquivalent:@"h"];
  118. [[appMenu addItemWithTitle:@"Hide Others"
  119. action:@selector(hideOtherApplications:)
  120. keyEquivalent:@"h"]
  121. setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
  122. [appMenu addItemWithTitle:@"Show All"
  123. action:@selector(unhideAllApplications:)
  124. keyEquivalent:@""];
  125. [appMenu addItem:[NSMenuItem separatorItem]];
  126. [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
  127. action:@selector(terminate:)
  128. keyEquivalent:@"q"];
  129. NSMenuItem* windowMenuItem =
  130. [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
  131. [bar release];
  132. NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
  133. [NSApp setWindowsMenu:windowMenu];
  134. [windowMenuItem setSubmenu:windowMenu];
  135. [windowMenu addItemWithTitle:@"Minimize"
  136. action:@selector(performMiniaturize:)
  137. keyEquivalent:@"m"];
  138. [windowMenu addItemWithTitle:@"Zoom"
  139. action:@selector(performZoom:)
  140. keyEquivalent:@""];
  141. [windowMenu addItem:[NSMenuItem separatorItem]];
  142. [windowMenu addItemWithTitle:@"Bring All to Front"
  143. action:@selector(arrangeInFront:)
  144. keyEquivalent:@""];
  145. // TODO: Make this appear at the bottom of the menu (for consistency)
  146. [windowMenu addItem:[NSMenuItem separatorItem]];
  147. [[windowMenu addItemWithTitle:@"Enter Full Screen"
  148. action:@selector(toggleFullScreen:)
  149. keyEquivalent:@"f"]
  150. setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
  151. // Prior to Snow Leopard, we need to use this oddly-named semi-private API
  152. // to get the application menu working properly.
  153. SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:");
  154. [NSApp performSelector:setAppleMenuSelector withObject:appMenu];
  155. }
  156. // Retrieve Unicode data for the current keyboard layout
  157. //
  158. static bool updateUnicodeDataNS(void)
  159. {
  160. if (_glfw.ns.inputSource)
  161. {
  162. CFRelease(_glfw.ns.inputSource);
  163. _glfw.ns.inputSource = NULL;
  164. _glfw.ns.unicodeData = nil;
  165. }
  166. for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next)
  167. window->ns.deadKeyState = 0;
  168. _glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
  169. if (!_glfw.ns.inputSource)
  170. {
  171. _glfwInputError(GLFW_PLATFORM_ERROR,
  172. "Cocoa: Failed to retrieve keyboard layout input source");
  173. return false;
  174. }
  175. _glfw.ns.unicodeData =
  176. TISGetInputSourceProperty(_glfw.ns.inputSource,
  177. kTISPropertyUnicodeKeyLayoutData);
  178. if (!_glfw.ns.unicodeData)
  179. {
  180. _glfwInputError(GLFW_PLATFORM_ERROR,
  181. "Cocoa: Failed to retrieve keyboard layout Unicode data");
  182. return false;
  183. }
  184. return true;
  185. }
  186. // Load HIToolbox.framework and the TIS symbols we need from it
  187. //
  188. static bool initializeTIS(void)
  189. {
  190. // This works only because Cocoa has already loaded it properly
  191. _glfw.ns.tis.bundle =
  192. CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HIToolbox"));
  193. if (!_glfw.ns.tis.bundle)
  194. {
  195. _glfwInputError(GLFW_PLATFORM_ERROR,
  196. "Cocoa: Failed to load HIToolbox.framework");
  197. return false;
  198. }
  199. CFStringRef* kPropertyUnicodeKeyLayoutData =
  200. CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
  201. CFSTR("kTISPropertyUnicodeKeyLayoutData"));
  202. *(void **)&_glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource =
  203. CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
  204. CFSTR("TISCopyCurrentKeyboardLayoutInputSource"));
  205. *(void **)&_glfw.ns.tis.GetInputSourceProperty =
  206. CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
  207. CFSTR("TISGetInputSourceProperty"));
  208. *(void **)&_glfw.ns.tis.GetKbdType =
  209. CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
  210. CFSTR("LMGetKbdType"));
  211. if (!kPropertyUnicodeKeyLayoutData ||
  212. !TISCopyCurrentKeyboardLayoutInputSource ||
  213. !TISGetInputSourceProperty ||
  214. !LMGetKbdType)
  215. {
  216. _glfwInputError(GLFW_PLATFORM_ERROR,
  217. "Cocoa: Failed to load TIS API symbols");
  218. return false;
  219. }
  220. _glfw.ns.tis.kPropertyUnicodeKeyLayoutData =
  221. *kPropertyUnicodeKeyLayoutData;
  222. return updateUnicodeDataNS();
  223. }
  224. static void
  225. display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFlags flags, void *userInfo UNUSED)
  226. {
  227. if (flags & kCGDisplayBeginConfigurationFlag) {
  228. return;
  229. }
  230. if (flags & kCGDisplaySetModeFlag) {
  231. // GPU possibly changed
  232. }
  233. }
  234. static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
  235. @interface GLFWHelper : NSObject
  236. @end
  237. @implementation GLFWHelper
  238. - (void)selectedKeyboardInputSourceChanged:(NSObject* )object
  239. {
  240. (void)object;
  241. updateUnicodeDataNS();
  242. }
  243. - (void)doNothing:(id)object
  244. {
  245. (void)object;
  246. }
  247. // watch for settings change and rebuild global_shortcuts using key/value observing on NSUserDefaults
  248. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  249. {
  250. (void)keyPath; (void)object; (void)change; (void)context;
  251. if (global_shortcuts != nil) {
  252. [global_shortcuts release];
  253. global_shortcuts = nil;
  254. }
  255. }
  256. @end // GLFWHelper
  257. // Delegate for application related notifications {{{
  258. @interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
  259. @end
  260. @implementation GLFWApplicationDelegate
  261. - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
  262. {
  263. (void)sender;
  264. if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(0);
  265. return NSTerminateCancel;
  266. }
  267. - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
  268. return YES;
  269. }
  270. static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
  271. - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
  272. {
  273. (void)sender;
  274. if (!handle_reopen_callback) return YES;
  275. if (handle_reopen_callback(flag)) return YES;
  276. return NO;
  277. }
  278. - (void)applicationDidChangeScreenParameters:(NSNotification *) notification
  279. {
  280. (void)notification;
  281. _GLFWwindow* window;
  282. for (window = _glfw.windowListHead; window; window = window->next)
  283. {
  284. if (window->context.client != GLFW_NO_API)
  285. [window->context.nsgl.object update];
  286. }
  287. _glfwPollMonitorsNS();
  288. }
  289. static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
  290. - (void)applicationWillFinishLaunching:(NSNotification *)notification
  291. {
  292. (void)notification;
  293. if (_glfw.hints.init.ns.menubar)
  294. {
  295. // In case we are unbundled, make us a proper UI application
  296. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  297. // Menu bar setup must go between sharedApplication and finishLaunching
  298. // in order to properly emulate the behavior of NSApplicationMain
  299. if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
  300. {
  301. [[NSBundle mainBundle] loadNibNamed:@"MainMenu"
  302. owner:NSApp
  303. topLevelObjects:&_glfw.ns.nibObjects];
  304. }
  305. else
  306. createMenuBar();
  307. }
  308. if (finish_launching_callback)
  309. finish_launching_callback();
  310. }
  311. - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
  312. (void)sender;
  313. if (!filename || !_glfw.ns.url_open_callback) return NO;
  314. const char *url = NULL;
  315. @try {
  316. url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
  317. } @catch(NSException *exc) {
  318. NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason);
  319. return NO;
  320. }
  321. if (!url) return NO;
  322. return _glfw.ns.url_open_callback(url);
  323. }
  324. - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
  325. (void)sender;
  326. if (!_glfw.ns.url_open_callback || !filenames) return;
  327. for (id x in filenames) {
  328. NSString *filename = x;
  329. const char *url = NULL;
  330. @try {
  331. url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
  332. } @catch(NSException *exc) {
  333. NSLog(@"Converting openFiles filename: %@ failed with error: %@", filename, exc.reason);
  334. }
  335. if (url) _glfw.ns.url_open_callback(url);
  336. }
  337. }
  338. // Remove openFile and openFiles when the minimum supported macOS version is 10.13
  339. - (void)application:(NSApplication *)sender openURLs:(NSArray<NSURL *> *)urls
  340. {
  341. (void)sender;
  342. if (!_glfw.ns.url_open_callback || !urls) return;
  343. for (id x in urls) {
  344. NSURL *ns_url = x;
  345. const char *url = NULL;
  346. @try {
  347. url = [[ns_url absoluteString] UTF8String];
  348. } @catch(NSException *exc) {
  349. NSLog(@"Converting openURLs url: %@ failed with error: %@", ns_url, exc.reason);
  350. }
  351. if (url) _glfw.ns.url_open_callback(url);
  352. }
  353. }
  354. - (void)applicationDidFinishLaunching:(NSNotification *)notification
  355. {
  356. (void)notification;
  357. [NSApp stop:nil];
  358. CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
  359. _glfwCocoaPostEmptyEvent();
  360. }
  361. - (void)applicationWillTerminate:(NSNotification *)aNotification
  362. {
  363. (void)aNotification;
  364. CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL);
  365. }
  366. - (void)applicationDidHide:(NSNotification *)notification
  367. {
  368. (void)notification;
  369. int i;
  370. for (i = 0; i < _glfw.monitorCount; i++)
  371. _glfwRestoreVideoModeNS(_glfw.monitors[i]);
  372. }
  373. @end // GLFWApplicationDelegate
  374. // }}}
  375. @interface GLFWApplication : NSApplication
  376. - (void)tick_callback;
  377. - (void)render_frame_received:(id)displayIDAsID;
  378. @end
  379. @implementation GLFWApplication
  380. - (void)tick_callback
  381. {
  382. _glfwDispatchTickCallback();
  383. }
  384. - (void)render_frame_received:(id)displayIDAsID
  385. {
  386. CGDirectDisplayID displayID = [(NSNumber*)displayIDAsID unsignedIntValue];
  387. _glfwDispatchRenderFrame(displayID);
  388. }
  389. @end
  390. //////////////////////////////////////////////////////////////////////////
  391. ////// GLFW internal API //////
  392. //////////////////////////////////////////////////////////////////////////
  393. void* _glfwLoadLocalVulkanLoaderNS(void)
  394. {
  395. CFBundleRef bundle = CFBundleGetMainBundle();
  396. if (!bundle)
  397. return NULL;
  398. CFURLRef url =
  399. CFBundleCopyAuxiliaryExecutableURL(bundle, CFSTR("libvulkan.1.dylib"));
  400. if (!url)
  401. return NULL;
  402. char path[PATH_MAX];
  403. void* handle = NULL;
  404. if (CFURLGetFileSystemRepresentation(url, true, (UInt8*) path, sizeof(path) - 1))
  405. handle = _glfw_dlopen(path);
  406. CFRelease(url);
  407. return handle;
  408. }
  409. //////////////////////////////////////////////////////////////////////////
  410. ////// GLFW platform API //////
  411. //////////////////////////////////////////////////////////////////////////
  412. /**
  413. * Apple Symbolic HotKeys Ids
  414. * To find this symbolic hot keys indices do:
  415. * 1. open Terminal
  416. * 2. restore defaults in System Preferences > Keyboard > Shortcuts
  417. * 3. defaults read com.apple.symbolichotkeys > current.txt
  418. * 4. enable/disable given symbolic hot key in System Preferences > Keyboard > Shortcuts
  419. * 5. defaults read com.apple.symbolichotkeys | diff -C 5 current.txt -
  420. * 6. restore defaults in System Preferences > Keyboard > Shortcuts
  421. */
  422. typedef enum AppleShortcutNames {
  423. // launchpad & dock
  424. kSHKTurnDockHidingOnOrOff = 52, // Opt, Cmd, D
  425. kSHKShowLaunchpad = 160, //
  426. // display
  427. kSHKDecreaseDisplayBrightness1 = 53, // F14 (Fn)
  428. kSHKDecreaseDisplayBrightness2 = 55, // F14 (Fn, Ctrl)
  429. kSHKIncreaseDisplayBrightness1 = 54, // F15 (Fn)
  430. kSHKIncreaseDisplayBrightness2 = 56, // F15 (Fn, Ctrl)
  431. // mission control
  432. kSHKMissionControl = 32, // Ctrl, Arrow Up
  433. kSHKShowNotificationCenter = 163, //
  434. kSHKTurnDoNotDisturbOnOrOff = 175, //
  435. kSHKApplicationWindows = 33, // Ctrl, Arrow Down
  436. kSHKShowDesktop = 36, // F11
  437. kSHKMoveLeftASpace = 79, // Ctrl, Arrow Left
  438. kSHKMoveRightASpace = 81, // Ctrl, Arrow Right
  439. kSHKSwitchToDesktop1 = 118, // Ctrl, 1
  440. kSHKSwitchToDesktop2 = 119, // Ctrl, 2
  441. kSHKSwitchToDesktop3 = 120, // Ctrl, 3
  442. kSHKSwitchToDesktop4 = 121, // Ctrl, 4
  443. kSHKQuickNote = 190, // Fn, Q
  444. // keyboard
  445. kSHKChangeTheWayTabMovesFocus = 13, // Ctrl, F7
  446. kSHKTurnKeyboardAccessOnOrOff = 12, // Ctrl, F1
  447. kSHKMoveFocusToTheMenuBar = 7, // Ctrl, F2
  448. kSHKMoveFocusToTheDock = 8, // Ctrl, F3
  449. kSHKMoveFocusToActiveOrNextWindow = 9, // Ctrl, F4
  450. kSHKMoveFocusToTheWindowToolbar = 10, // Ctrl, F5
  451. kSHKMoveFocusToTheFloatingWindow = 11, // Ctrl, F6
  452. kSHKMoveFocusToNextWindow = 27, // Cmd, `
  453. kSHKMoveFocusToStatusMenus = 57, // Ctrl, F8
  454. // input sources
  455. kSHKSelectThePreviousInputSource = 60, // Ctrl, Space bar
  456. kSHKSelectNextSourceInInputMenu = 61, // Ctrl, Opt, Space bar
  457. // screenshots
  458. kSHKSavePictureOfScreenAsAFile = 28, // Shift, Cmd, 3
  459. kSHKCopyPictureOfScreenToTheClipboard = 29, // Ctrl, Shift, Cmd, 3
  460. kSHKSavePictureOfSelectedAreaAsAFile = 30, // Shift, Cmd, 4
  461. kSHKCopyPictureOfSelectedAreaToTheClipboard = 31, // Ctrl, Shift, Cmd, 4
  462. kSHKScreenshotAndRecordingOptions = 184, // Shift, Cmd, 5
  463. // spotlight
  464. kSHKShowSpotlightSearch = 64, // Cmd, Space bar
  465. kSHKShowFinderSearchWindow = 65, // Opt, Cmd, Space bar
  466. // accessibility
  467. kSHKTurnZoomOnOrOff = 15, // Opt, Cmd, 8
  468. kSHKTurnImageSmoothingOnOrOff = 23, // Opt, Cmd, Backslash "\"
  469. kSHKZoomOut = 19, // Opt, Cmd, -
  470. kSHKZoomIn = 17, // Opt, Cmd, =
  471. kSHKTurnFocusFollowingOnOrOff = 179, //
  472. kSHKIncreaseContrast = 25, // Ctrl, Opt, Cmd, .
  473. kSHKDecreaseContrast = 26, // Ctrl, Opt, Cmd, ,
  474. kSHKInvertColors = 21, // Ctrl, Opt, Cmd, 8
  475. kSHKTurnVoiceOverOnOrOff = 59, // Cmd, F5
  476. kSHKShowAccessibilityControls = 162, // Opt, Cmd, F5
  477. // app shortcuts
  478. kSHKShowHelpMenu = 98, // Shift, Cmd, /
  479. // deprecated (Not shown on macOS Monterey)
  480. kSHKMoveFocusToTheWindowDrawer = 51, // Opt, Cmd, `
  481. kSHKShowDashboard = 62, // F12
  482. kSHKLookUpInDictionary = 70, // Shift, Cmd, E
  483. kSHKHideAndShowFrontRow = 73, // Cmd, Esc
  484. kSHKActivateSpaces = 75, // F8
  485. // unknown
  486. kSHKUnknown = 0, //
  487. } AppleShortcutNames;
  488. static bool
  489. is_shiftable_shortcut(int scv) {
  490. return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow;
  491. }
  492. #define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl | NSEventModifierFlagFunction))
  493. static void
  494. build_global_shortcuts_lookup(void) {
  495. // dump these in a terminal with: defaults read com.apple.symbolichotkeys
  496. NSMutableDictionary<NSString*, NSNumber*> *temp = [NSMutableDictionary dictionaryWithCapacity:128]; // will be autoreleased
  497. NSMutableSet<NSNumber*> *temp_configured = [NSMutableSet setWithCapacity:128]; // will be autoreleased
  498. NSMutableSet<NSNumber*> *temp_missing_value = [NSMutableSet setWithCapacity:128]; // will be autoreleased
  499. NSDictionary *apple_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.symbolichotkeys"];
  500. if (apple_settings) {
  501. NSDictionary<NSString*, id> *symbolic_hotkeys = [apple_settings objectForKey:@"AppleSymbolicHotKeys"];
  502. if (symbolic_hotkeys) {
  503. for (NSString *key in symbolic_hotkeys) {
  504. id obj = symbolic_hotkeys[key];
  505. if (![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSDictionary class]]) continue;
  506. NSInteger sc = [key integerValue];
  507. NSDictionary *sc_value = obj;
  508. id enabled = [sc_value objectForKey:@"enabled"];
  509. if (!enabled || ![enabled isKindOfClass:[NSNumber class]]) continue;
  510. [temp_configured addObject:@(sc)];
  511. if (![enabled boolValue]) continue;
  512. id v = [sc_value objectForKey:@"value"];
  513. if (!v || ![v isKindOfClass:[NSDictionary class]]) {
  514. if ([enabled boolValue]) [temp_missing_value addObject:@(sc)];
  515. continue;
  516. }
  517. NSDictionary *value = v;
  518. id t = [value objectForKey:@"type"];
  519. if (!t || ![t isKindOfClass:[NSString class]] || ![t isEqualToString:@"standard"]) continue;
  520. id p = [value objectForKey:@"parameters"];
  521. if (!p || ![p isKindOfClass:[NSArray class]] || [(NSArray*)p count] < 2) continue;
  522. NSArray<NSNumber*> *parameters = p;
  523. NSInteger ch = [parameters[0] isKindOfClass:[NSNumber class]] ? [parameters[0] integerValue] : 0xffff;
  524. NSInteger vk = [parameters[1] isKindOfClass:[NSNumber class]] ? [parameters[1] integerValue] : 0xffff;
  525. NSEventModifierFlags mods = ([parameters count] > 2 && [parameters[2] isKindOfClass:[NSNumber class]]) ? [parameters[2] unsignedIntegerValue] : 0;
  526. mods = USEFUL_MODS(mods);
  527. static char buf[64];
  528. #define S(x, k) snprintf(buf, sizeof(buf) - 1, #x":%lx:%ld", (unsigned long)mods, (long)k)
  529. if (ch == 0xffff) { if (vk == 0xffff) continue; S(v, vk); } else S(c, ch);
  530. temp[@(buf)] = @(sc);
  531. // the move to next window shortcuts also respond to the same shortcut + shift
  532. if (is_shiftable_shortcut([key intValue]) && !(mods & NSEventModifierFlagShift)) {
  533. mods |= NSEventModifierFlagShift;
  534. if (ch == 0xffff) S(v, vk); else S(c, ch);
  535. temp[@(buf)] = @(sc);
  536. }
  537. #undef S
  538. }
  539. }
  540. }
  541. // Add global shortcut definitions when the default enabled shortcut is not defined,
  542. // or when the default enabled shortcut is not disabled and is missing a value.
  543. // Here are the shortcuts that are enabled by default in the standard ANSI (US) layout.
  544. // macOS provides separate configurations for some languages or keyboards.
  545. // In general, the rules here will not take effect.
  546. static char buf[64];
  547. #define S(i, t, m, k) if ([temp_configured member:@(i)] == nil || [temp_missing_value member:@(i)] != nil) { \
  548. snprintf(buf, sizeof(buf) - 1, #t":%lx:%ld", (unsigned long)m, (long)k); \
  549. temp[@(buf)] = @(i); \
  550. }
  551. // launchpad & dock
  552. S(kSHKTurnDockHidingOnOrOff, c, (NSEventModifierFlagOption | NSEventModifierFlagCommand), 'd'); // Opt, Cmd, D
  553. // mission control
  554. S(kSHKMissionControl, v, NSEventModifierFlagControl, 126); // Ctrl, Arrow Up
  555. S(kSHKApplicationWindows, v, NSEventModifierFlagControl, 125); // Ctrl, Arrow Down
  556. // keyboard
  557. S(kSHKMoveFocusToTheMenuBar, v, NSEventModifierFlagControl, 120); // Ctrl, F2
  558. S(kSHKMoveFocusToTheDock, v, NSEventModifierFlagControl, 99); // Ctrl, F3
  559. S(kSHKMoveFocusToActiveOrNextWindow, v, NSEventModifierFlagControl, 118); // Ctrl, F4
  560. S(kSHKMoveFocusToActiveOrNextWindow, v, (NSEventModifierFlagShift | NSEventModifierFlagControl), 118); // Shift, Ctrl, F4
  561. S(kSHKMoveFocusToNextWindow, c, NSEventModifierFlagCommand, 96); // Cmd, `
  562. S(kSHKMoveFocusToNextWindow, c, (NSEventModifierFlagShift | NSEventModifierFlagCommand), 96); // Shift, Cmd, `
  563. S(kSHKMoveFocusToStatusMenus, v, NSEventModifierFlagControl, 100); // Ctrl, F8
  564. // input sources
  565. S(kSHKSelectThePreviousInputSource, c, NSEventModifierFlagControl, 32); // Ctrl, Space bar
  566. S(kSHKSelectNextSourceInInputMenu, c, (NSEventModifierFlagControl | NSEventModifierFlagOption), 32); // Ctrl, Opt, Space bar
  567. // spotlight
  568. S(kSHKShowSpotlightSearch, c, NSEventModifierFlagCommand, 32); // Cmd, Space bar
  569. S(kSHKShowFinderSearchWindow, c, (NSEventModifierFlagOption | NSEventModifierFlagCommand), 32); // Opt, Cmd, Space bar
  570. #undef S
  571. global_shortcuts = [[NSDictionary dictionaryWithDictionary:temp] retain];
  572. /* NSLog(@"global_shortcuts: %@", global_shortcuts); */
  573. }
  574. static int
  575. is_active_apple_global_shortcut(NSEvent *event) {
  576. if (global_shortcuts == nil) build_global_shortcuts_lookup();
  577. NSEventModifierFlags modifierFlags = USEFUL_MODS([event modifierFlags]);
  578. static char lookup_key[64];
  579. #define LOOKUP(t, k) \
  580. snprintf(lookup_key, sizeof(lookup_key) - 1, #t":%lx:%ld", (unsigned long)modifierFlags, (long)k); \
  581. NSNumber *sc = global_shortcuts[@(lookup_key)]; \
  582. if (sc != nil) return [sc intValue]; \
  583. if ([event.charactersIgnoringModifiers length] == 1) {
  584. if (modifierFlags & NSEventModifierFlagShift) {
  585. const uint32_t ch_without_shift = vk_to_unicode_key_with_current_layout([event keyCode]);
  586. if (ch_without_shift < GLFW_FKEY_FIRST || ch_without_shift > GLFW_FKEY_LAST) {
  587. LOOKUP(c, ch_without_shift);
  588. }
  589. }
  590. const unichar ch = [event.charactersIgnoringModifiers characterAtIndex:0];
  591. LOOKUP(c, ch);
  592. }
  593. unsigned short vk = [event keyCode];
  594. if (vk != 0xffff) {
  595. LOOKUP(v, vk);
  596. }
  597. #undef LOOKUP
  598. return kSHKUnknown;
  599. }
  600. static bool
  601. is_useful_apple_global_shortcut(int sc) {
  602. switch(sc) {
  603. // launchpad & dock
  604. case kSHKTurnDockHidingOnOrOff: // Opt, Cmd, D
  605. case kSHKShowLaunchpad: //
  606. // display
  607. case kSHKDecreaseDisplayBrightness1: // F14 (Fn)
  608. case kSHKDecreaseDisplayBrightness2: // F14 (Fn, Ctrl)
  609. case kSHKIncreaseDisplayBrightness1: // F15 (Fn)
  610. case kSHKIncreaseDisplayBrightness2: // F14 (Fn, Ctrl)
  611. // mission control
  612. case kSHKMissionControl: // Ctrl, Arrow Up
  613. case kSHKShowNotificationCenter: //
  614. case kSHKTurnDoNotDisturbOnOrOff: //
  615. case kSHKApplicationWindows: // Ctrl, Arrow Down
  616. case kSHKShowDesktop: // F11
  617. case kSHKMoveLeftASpace: // Ctrl, Arrow Left
  618. case kSHKMoveRightASpace: // Ctrl, Arrow Right
  619. case kSHKSwitchToDesktop1: // Ctrl, 1
  620. case kSHKSwitchToDesktop2: // Ctrl, 2
  621. case kSHKSwitchToDesktop3: // Ctrl, 3
  622. case kSHKSwitchToDesktop4: // Ctrl, 4
  623. case kSHKQuickNote: // Fn, Q
  624. // keyboard
  625. /* case kSHKChangeTheWayTabMovesFocus: // Ctrl, F7 */
  626. /* case kSHKTurnKeyboardAccessOnOrOff: // Ctrl, F1 */
  627. case kSHKMoveFocusToTheMenuBar: // Ctrl, F2
  628. case kSHKMoveFocusToTheDock: // Ctrl, F3
  629. case kSHKMoveFocusToActiveOrNextWindow: // Ctrl, F4
  630. /* case kSHKMoveFocusToTheWindowToolbar: // Ctrl, F5 */
  631. /* case kSHKMoveFocusToTheFloatingWindow: // Ctrl, F6 */
  632. case kSHKMoveFocusToNextWindow: // Cmd, `
  633. case kSHKMoveFocusToStatusMenus: // Ctrl, F8
  634. // input sources
  635. case kSHKSelectThePreviousInputSource: // Ctrl, Space bar
  636. case kSHKSelectNextSourceInInputMenu: // Ctrl, Opt, Space bar
  637. // screenshots
  638. /* case kSHKSavePictureOfScreenAsAFile: // Shift, Cmd, 3 */
  639. /* case kSHKCopyPictureOfScreenToTheClipboard: // Ctrl, Shift, Cmd, 3 */
  640. /* case kSHKSavePictureOfSelectedAreaAsAFile: // Shift, Cmd, 4 */
  641. /* case kSHKCopyPictureOfSelectedAreaToTheClipboard: // Ctrl, Shift, Cmd, 4 */
  642. /* case kSHKScreenshotAndRecordingOptions: // Shift, Cmd, 5 */
  643. // spotlight
  644. case kSHKShowSpotlightSearch: // Cmd, Space bar
  645. case kSHKShowFinderSearchWindow: // Opt, Cmd, Space bar
  646. // accessibility
  647. /* case kSHKTurnZoomOnOrOff: // Opt, Cmd, 8 */
  648. /* case kSHKTurnImageSmoothingOnOrOff: // Opt, Cmd, Backslash "\" */
  649. /* case kSHKZoomOut: // Opt, Cmd, - */
  650. /* case kSHKZoomIn: // Opt, Cmd, = */
  651. /* case kSHKTurnFocusFollowingOnOrOff: // */
  652. /* case kSHKIncreaseContrast: // Ctrl, Opt, Cmd, . */
  653. /* case kSHKDecreaseContrast: // Ctrl, Opt, Cmd, , */
  654. /* case kSHKInvertColors: // Ctrl, Opt, Cmd, 8 */
  655. /* case kSHKTurnVoiceOverOnOrOff: // Cmd, F5 */
  656. /* case kSHKShowAccessibilityControls: // Opt, Cmd, F5 */
  657. // app shortcuts
  658. /* case kSHKShowHelpMenu: // Shift, Cmd, / */
  659. // deprecated (Not shown on macOS Monterey)
  660. /* case kSHKMoveFocusToTheWindowDrawer: // Opt, Cmd, ` */
  661. /* case kSHKShowDashboard: // F12 */
  662. /* case kSHKLookUpInDictionary: // Shift, Cmd, E */
  663. /* case kSHKHideAndShowFrontRow: // Cmd, Esc */
  664. /* case kSHKActivateSpaces: // F8 */
  665. return true;
  666. default:
  667. return false;
  668. }
  669. }
  670. static bool
  671. is_apple_jis_layout_function_key(NSEvent *event) {
  672. return [event keyCode] == 0x66 /* kVK_JIS_Eisu */ || [event keyCode] == 0x68 /* kVK_JIS_Kana */;
  673. }
  674. GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
  675. GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
  676. handle_reopen_callback = callback;
  677. return previous;
  678. }
  679. GLFWAPI GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback) {
  680. GLFWapplicationwillfinishlaunchingfun previous = finish_launching_callback;
  681. finish_launching_callback = callback;
  682. return previous;
  683. }
  684. int _glfwPlatformInit(void)
  685. {
  686. @autoreleasepool {
  687. _glfw.ns.helper = [[GLFWHelper alloc] init];
  688. [NSThread detachNewThreadSelector:@selector(doNothing:)
  689. toTarget:_glfw.ns.helper
  690. withObject:nil];
  691. if (NSApp)
  692. _glfw.ns.finishedLaunching = true;
  693. [GLFWApplication sharedApplication];
  694. _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
  695. if (_glfw.ns.delegate == nil)
  696. {
  697. _glfwInputError(GLFW_PLATFORM_ERROR,
  698. "Cocoa: Failed to create application delegate");
  699. return false;
  700. }
  701. [NSApp setDelegate:_glfw.ns.delegate];
  702. static struct {
  703. unsigned short virtual_key_code;
  704. NSEventModifierFlags input_source_switch_modifiers;
  705. NSTimeInterval timestamp;
  706. } last_keydown_shortcut_event;
  707. last_keydown_shortcut_event.virtual_key_code = 0xffff;
  708. last_keydown_shortcut_event.input_source_switch_modifiers = 0;
  709. NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
  710. {
  711. debug_key("---------------- key down -------------------\n");
  712. debug_key("%s\n", [[event description] UTF8String]);
  713. if (!_glfw.ignoreOSKeyboardProcessing) {
  714. // first check if there is a global menu bar shortcut
  715. if ([[NSApp mainMenu] performKeyEquivalent:event]) {
  716. debug_key("keyDown triggered global menu bar action ignoring\n");
  717. last_keydown_shortcut_event.virtual_key_code = [event keyCode];
  718. last_keydown_shortcut_event.input_source_switch_modifiers = 0;
  719. last_keydown_shortcut_event.timestamp = [event timestamp];
  720. return nil;
  721. }
  722. // now check if there is a useful apple shortcut
  723. int global_shortcut = is_active_apple_global_shortcut(event);
  724. if (is_useful_apple_global_shortcut(global_shortcut)) {
  725. debug_key("keyDown triggered global macOS shortcut ignoring\n");
  726. last_keydown_shortcut_event.virtual_key_code = [event keyCode];
  727. // record the modifier keys if switching to the next input source
  728. last_keydown_shortcut_event.input_source_switch_modifiers = (global_shortcut == kSHKSelectNextSourceInInputMenu) ? USEFUL_MODS([event modifierFlags]) : 0;
  729. last_keydown_shortcut_event.timestamp = [event timestamp];
  730. return event;
  731. }
  732. // check for JIS keyboard layout function keys
  733. if (is_apple_jis_layout_function_key(event)) {
  734. debug_key("keyDown triggered JIS layout function key ignoring\n");
  735. last_keydown_shortcut_event.virtual_key_code = [event keyCode];
  736. last_keydown_shortcut_event.input_source_switch_modifiers = 0;
  737. last_keydown_shortcut_event.timestamp = [event timestamp];
  738. return event;
  739. }
  740. }
  741. last_keydown_shortcut_event.virtual_key_code = 0xffff;
  742. NSWindow *kw = [NSApp keyWindow];
  743. if (kw && kw.contentView) [kw.contentView keyDown:event];
  744. else debug_key("keyDown ignored as no keyWindow present\n");
  745. return nil;
  746. };
  747. NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
  748. {
  749. debug_key("----------------- key up --------------------\n");
  750. debug_key("%s\n", [[event description] UTF8String]);
  751. if (last_keydown_shortcut_event.virtual_key_code != 0xffff && last_keydown_shortcut_event.virtual_key_code == [event keyCode]) {
  752. // ignore as the corresponding key down event triggered a menu bar or macOS shortcut
  753. last_keydown_shortcut_event.virtual_key_code = 0xffff;
  754. debug_key("keyUp ignored as corresponds to previous keyDown that triggered a shortcut\n");
  755. return nil;
  756. }
  757. NSWindow *kw = [NSApp keyWindow];
  758. if (kw && kw.contentView) [kw.contentView keyUp:event];
  759. else debug_key("keyUp ignored as no keyWindow present\n");
  760. return nil;
  761. };
  762. NSEvent* (^flags_changed_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
  763. {
  764. debug_key("-------------- flags changed -----------------\n");
  765. debug_key("%s\n", [[event description] UTF8String]);
  766. last_keydown_shortcut_event.virtual_key_code = 0xffff;
  767. // switching to the next input source is only confirmed when all modifier keys are released
  768. if (last_keydown_shortcut_event.input_source_switch_modifiers) {
  769. if (!([event modifierFlags] & last_keydown_shortcut_event.input_source_switch_modifiers))
  770. last_keydown_shortcut_event.input_source_switch_modifiers = 0;
  771. return event;
  772. }
  773. NSWindow *kw = [NSApp keyWindow];
  774. if (kw && kw.contentView) [kw.contentView flagsChanged:event];
  775. else debug_key("flagsChanged ignored as no keyWindow present\n");
  776. return nil;
  777. };
  778. _glfw.ns.keyUpMonitor =
  779. [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
  780. handler:keyup_block];
  781. _glfw.ns.keyDownMonitor =
  782. [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
  783. handler:keydown_block];
  784. _glfw.ns.flagsChangedMonitor =
  785. [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged
  786. handler:flags_changed_block];
  787. if (_glfw.hints.init.ns.chdir)
  788. changeToResourcesDirectory();
  789. NSDictionary* defaults = @{
  790. // Press and Hold prevents some keys from emitting repeated characters
  791. @"ApplePressAndHoldEnabled": @NO,
  792. // Dont generate openFile events from command line arguments
  793. @"NSTreatUnknownArgumentsAsOpen": @"NO",
  794. };
  795. [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
  796. NSUserDefaults *apple_settings = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"];
  797. [apple_settings addObserver:_glfw.ns.helper
  798. forKeyPath:@"AppleSymbolicHotKeys"
  799. options:NSKeyValueObservingOptionNew
  800. context:NULL];
  801. _glfw.ns.appleSettings = apple_settings;
  802. [[NSNotificationCenter defaultCenter]
  803. addObserver:_glfw.ns.helper
  804. selector:@selector(selectedKeyboardInputSourceChanged:)
  805. name:NSTextInputContextKeyboardSelectionDidChangeNotification
  806. object:nil];
  807. _glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
  808. if (!_glfw.ns.eventSource)
  809. return false;
  810. CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0);
  811. if (!initializeTIS())
  812. return false;
  813. _glfwPollMonitorsNS();
  814. return true;
  815. } // autoreleasepool
  816. }
  817. void _glfwPlatformTerminate(void)
  818. {
  819. @autoreleasepool {
  820. _glfwClearDisplayLinks();
  821. if (_glfw.ns.inputSource)
  822. {
  823. CFRelease(_glfw.ns.inputSource);
  824. _glfw.ns.inputSource = NULL;
  825. _glfw.ns.unicodeData = nil;
  826. }
  827. if (_glfw.ns.eventSource)
  828. {
  829. CFRelease(_glfw.ns.eventSource);
  830. _glfw.ns.eventSource = NULL;
  831. }
  832. if (_glfw.ns.delegate)
  833. {
  834. [NSApp setDelegate:nil];
  835. [_glfw.ns.delegate release];
  836. _glfw.ns.delegate = nil;
  837. }
  838. if (_glfw.ns.helper)
  839. {
  840. [[NSNotificationCenter defaultCenter]
  841. removeObserver:_glfw.ns.helper
  842. name:NSTextInputContextKeyboardSelectionDidChangeNotification
  843. object:nil];
  844. [[NSNotificationCenter defaultCenter]
  845. removeObserver:_glfw.ns.helper];
  846. if (_glfw.ns.appleSettings)
  847. [_glfw.ns.appleSettings removeObserver:_glfw.ns.helper forKeyPath:@"AppleSymbolicHotKeys"];
  848. [_glfw.ns.helper release];
  849. _glfw.ns.helper = nil;
  850. }
  851. if (_glfw.ns.keyUpMonitor)
  852. [NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
  853. if (_glfw.ns.keyDownMonitor)
  854. [NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
  855. if (_glfw.ns.flagsChangedMonitor)
  856. [NSEvent removeMonitor:_glfw.ns.flagsChangedMonitor];
  857. if (_glfw.ns.appleSettings != nil) {
  858. [_glfw.ns.appleSettings release];
  859. _glfw.ns.appleSettings = nil;
  860. }
  861. _glfwTerminateNSGL();
  862. if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }
  863. } // autoreleasepool
  864. }
  865. const char* _glfwPlatformGetVersionString(void)
  866. {
  867. return _GLFW_VERSION_NUMBER " Cocoa NSGL EGL OSMesa"
  868. #if defined(_GLFW_BUILD_DLL)
  869. " dynamic"
  870. #endif
  871. ;
  872. }
  873. static GLFWtickcallback tick_callback = NULL;
  874. static void* tick_callback_data = NULL;
  875. static bool tick_callback_requested = false;
  876. static pthread_t main_thread;
  877. static NSLock *tick_lock = NULL;
  878. void _glfwDispatchTickCallback(void) {
  879. if (tick_lock && tick_callback) {
  880. while(true) {
  881. bool do_call = false;
  882. [tick_lock lock];
  883. if (tick_callback_requested) { do_call = true; tick_callback_requested = false; }
  884. [tick_lock unlock];
  885. if (do_call) tick_callback(tick_callback_data);
  886. else break;
  887. }
  888. }
  889. }
  890. static void
  891. request_tick_callback(void) {
  892. if (!tick_callback_requested) {
  893. tick_callback_requested = true;
  894. [NSApp performSelectorOnMainThread:@selector(tick_callback) withObject:nil waitUntilDone:NO];
  895. }
  896. }
  897. void _glfwPlatformPostEmptyEvent(void)
  898. {
  899. if (pthread_equal(pthread_self(), main_thread)) {
  900. request_tick_callback();
  901. } else if (tick_lock) {
  902. [tick_lock lock];
  903. request_tick_callback();
  904. [tick_lock unlock];
  905. }
  906. }
  907. void _glfwPlatformStopMainLoop(void) {
  908. [NSApp stop:nil];
  909. _glfwCocoaPostEmptyEvent();
  910. }
  911. void _glfwPlatformRunMainLoop(GLFWtickcallback callback, void* data) {
  912. main_thread = pthread_self();
  913. tick_callback = callback;
  914. tick_callback_data = data;
  915. tick_lock = [NSLock new];
  916. [NSApp run];
  917. [tick_lock release];
  918. tick_lock = NULL;
  919. tick_callback = NULL;
  920. tick_callback_data = NULL;
  921. }
  922. typedef struct {
  923. NSTimer *os_timer;
  924. unsigned long long id;
  925. bool repeats;
  926. monotonic_t interval;
  927. GLFWuserdatafun callback;
  928. void *callback_data;
  929. GLFWuserdatafun free_callback_data;
  930. } Timer;
  931. static Timer timers[128] = {{0}};
  932. static size_t num_timers = 0;
  933. static void
  934. remove_timer_at(size_t idx) {
  935. if (idx < num_timers) {
  936. Timer *t = timers + idx;
  937. if (t->os_timer) { [t->os_timer invalidate]; t->os_timer = NULL; }
  938. if (t->callback_data && t->free_callback_data) { t->free_callback_data(t->id, t->callback_data); t->callback_data = NULL; }
  939. remove_i_from_array(timers, idx, num_timers);
  940. }
  941. }
  942. static void schedule_timer(Timer *t) {
  943. t->os_timer = [NSTimer scheduledTimerWithTimeInterval:monotonic_t_to_s_double(t->interval) repeats:(t->repeats ? YES: NO) block:^(NSTimer *os_timer) {
  944. for (size_t i = 0; i < num_timers; i++) {
  945. if (timers[i].os_timer == os_timer) {
  946. timers[i].callback(timers[i].id, timers[i].callback_data);
  947. if (!timers[i].repeats) remove_timer_at(i);
  948. break;
  949. }
  950. }
  951. }];
  952. }
  953. unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) {
  954. static unsigned long long timer_counter = 0;
  955. if (num_timers >= sizeof(timers)/sizeof(timers[0]) - 1) {
  956. _glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
  957. return 0;
  958. }
  959. Timer *t = timers + num_timers++;
  960. t->id = ++timer_counter;
  961. t->repeats = repeats;
  962. t->interval = interval;
  963. t->callback = callback;
  964. t->callback_data = callback_data;
  965. t->free_callback_data = free_callback;
  966. schedule_timer(t);
  967. return timer_counter;
  968. }
  969. void _glfwPlatformRemoveTimer(unsigned long long timer_id) {
  970. for (size_t i = 0; i < num_timers; i++) {
  971. if (timers[i].id == timer_id) {
  972. remove_timer_at(i);
  973. break;
  974. }
  975. }
  976. }
  977. void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) {
  978. for (size_t i = 0; i < num_timers; i++) {
  979. if (timers[i].id == timer_id) {
  980. Timer *t = timers + i;
  981. if (t->os_timer) { [t->os_timer invalidate]; t->os_timer = NULL; }
  982. t->interval = interval;
  983. if (enabled) schedule_timer(t);
  984. break;
  985. }
  986. }
  987. }
  988. void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { }