cocoa_monitor.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. //========================================================================
  2. // GLFW 3.4 macOS - www.glfw.org
  3. //------------------------------------------------------------------------
  4. // Copyright (c) 2002-2006 Marcus Geelnard
  5. // Copyright (c) 2006-2019 Camilla Löwy <elmindreda@glfw.org>
  6. //
  7. // This software is provided 'as-is', without any express or implied
  8. // warranty. In no event will the authors be held liable for any damages
  9. // arising from the use of this software.
  10. //
  11. // Permission is granted to anyone to use this software for any purpose,
  12. // including commercial applications, and to alter it and redistribute it
  13. // freely, subject to the following restrictions:
  14. //
  15. // 1. The origin of this software must not be misrepresented; you must not
  16. // claim that you wrote the original software. If you use this software
  17. // in a product, an acknowledgment in the product documentation would
  18. // be appreciated but is not required.
  19. //
  20. // 2. Altered source versions must be plainly marked as such, and must not
  21. // be misrepresented as being the original software.
  22. //
  23. // 3. This notice may not be removed or altered from any source
  24. // distribution.
  25. //
  26. //========================================================================
  27. // It is fine to use C99 in this file because it will not be built with VS
  28. //========================================================================
  29. #include "internal.h"
  30. #include <stdlib.h>
  31. #include <limits.h>
  32. #include <math.h>
  33. #include <IOKit/graphics/IOGraphicsLib.h>
  34. #include <CoreVideo/CVBase.h>
  35. #include <ApplicationServices/ApplicationServices.h>
  36. // Get the name of the specified display, or NULL
  37. //
  38. static char*
  39. getDisplayName(CGDirectDisplayID displayID, NSScreen* screen)
  40. {
  41. // IOKit doesn't work on Apple Silicon anymore
  42. // Luckily, 10.15 introduced -[NSScreen localizedName].
  43. // Use it if available, and fall back to IOKit otherwise.
  44. if (screen)
  45. {
  46. if ([screen respondsToSelector:@selector(localizedName)])
  47. {
  48. NSString* name = [screen valueForKey:@"localizedName"];
  49. if (name) {
  50. return _glfw_strdup([name UTF8String]);
  51. }
  52. }
  53. }
  54. io_iterator_t it;
  55. io_service_t service;
  56. CFDictionaryRef info;
  57. if (IOServiceGetMatchingServices(0,
  58. IOServiceMatching("IODisplayConnect"),
  59. &it) != 0)
  60. {
  61. // This may happen if a desktop Mac is running headless
  62. return NULL;
  63. }
  64. while ((service = IOIteratorNext(it)) != 0)
  65. {
  66. info = IODisplayCreateInfoDictionary(service,
  67. kIODisplayOnlyPreferredName);
  68. CFNumberRef vendorIDRef =
  69. CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
  70. CFNumberRef productIDRef =
  71. CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
  72. if (!vendorIDRef || !productIDRef)
  73. {
  74. CFRelease(info);
  75. continue;
  76. }
  77. unsigned int vendorID, productID;
  78. CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID);
  79. CFNumberGetValue(productIDRef, kCFNumberIntType, &productID);
  80. if (CGDisplayVendorNumber(displayID) == vendorID &&
  81. CGDisplayModelNumber(displayID) == productID)
  82. {
  83. // Info dictionary is used and freed below
  84. break;
  85. }
  86. CFRelease(info);
  87. }
  88. IOObjectRelease(it);
  89. if (!service)
  90. {
  91. _glfwInputError(GLFW_PLATFORM_ERROR,
  92. "Cocoa: Failed to find service port for display");
  93. return NULL;
  94. }
  95. CFDictionaryRef names =
  96. CFDictionaryGetValue(info, CFSTR(kDisplayProductName));
  97. CFStringRef nameRef;
  98. if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"),
  99. (const void**) &nameRef))
  100. {
  101. // This may happen if a desktop Mac is running headless
  102. CFRelease(info);
  103. return NULL;
  104. }
  105. const CFIndex size =
  106. CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
  107. kCFStringEncodingUTF8);
  108. char* name = calloc(size + 1, 1);
  109. CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8);
  110. CFRelease(info);
  111. return name;
  112. }
  113. // Check whether the display mode should be included in enumeration
  114. //
  115. static bool modeIsGood(CGDisplayModeRef mode)
  116. {
  117. uint32_t flags = CGDisplayModeGetIOFlags(mode);
  118. if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag))
  119. return false;
  120. if (flags & kDisplayModeInterlacedFlag)
  121. return false;
  122. if (flags & kDisplayModeStretchedFlag)
  123. return false;
  124. #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
  125. CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
  126. if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) &&
  127. CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0))
  128. {
  129. CFRelease(format);
  130. return false;
  131. }
  132. CFRelease(format);
  133. #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
  134. return true;
  135. }
  136. // Convert Core Graphics display mode to GLFW video mode
  137. //
  138. static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
  139. double fallbackRefreshRate)
  140. {
  141. GLFWvidmode result;
  142. result.width = (int) CGDisplayModeGetWidth(mode);
  143. result.height = (int) CGDisplayModeGetHeight(mode);
  144. result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
  145. if (result.refreshRate == 0)
  146. result.refreshRate = (int) round(fallbackRefreshRate);
  147. #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
  148. CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
  149. if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0)
  150. {
  151. result.redBits = 5;
  152. result.greenBits = 5;
  153. result.blueBits = 5;
  154. }
  155. else
  156. #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
  157. {
  158. result.redBits = 8;
  159. result.greenBits = 8;
  160. result.blueBits = 8;
  161. }
  162. #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
  163. CFRelease(format);
  164. #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
  165. return result;
  166. }
  167. // Starts reservation for display fading
  168. //
  169. static CGDisplayFadeReservationToken beginFadeReservation(void)
  170. {
  171. CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken;
  172. if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess)
  173. {
  174. CGDisplayFade(token, 0.3,
  175. kCGDisplayBlendNormal,
  176. kCGDisplayBlendSolidColor,
  177. 0.0, 0.0, 0.0,
  178. TRUE);
  179. }
  180. return token;
  181. }
  182. // Ends reservation for display fading
  183. //
  184. static void endFadeReservation(CGDisplayFadeReservationToken token)
  185. {
  186. if (token != kCGDisplayFadeReservationInvalidToken)
  187. {
  188. CGDisplayFade(token, 0.5,
  189. kCGDisplayBlendSolidColor,
  190. kCGDisplayBlendNormal,
  191. 0.0, 0.0, 0.0,
  192. FALSE);
  193. CGReleaseDisplayFadeReservation(token);
  194. }
  195. }
  196. // Finds and caches the NSScreen corresponding to the specified monitor
  197. //
  198. static bool refreshMonitorScreen(_GLFWmonitor* monitor)
  199. {
  200. if (monitor->ns.screen)
  201. return true;
  202. for (NSScreen* screen in [NSScreen screens])
  203. {
  204. NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"];
  205. // HACK: Compare unit numbers instead of display IDs to work around
  206. // display replacement on machines with automatic graphics
  207. // switching
  208. if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue]))
  209. {
  210. monitor->ns.screen = screen;
  211. return true;
  212. }
  213. }
  214. _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor");
  215. return false;
  216. }
  217. // Returns the display refresh rate queried from the I/O registry
  218. //
  219. static double getFallbackRefreshRate(CGDirectDisplayID displayID)
  220. {
  221. double refreshRate = 60.0;
  222. io_iterator_t it;
  223. io_service_t service;
  224. if (IOServiceGetMatchingServices(0,
  225. IOServiceMatching("IOFramebuffer"),
  226. &it) != 0)
  227. {
  228. return refreshRate;
  229. }
  230. while ((service = IOIteratorNext(it)) != 0)
  231. {
  232. const CFNumberRef indexRef =
  233. IORegistryEntryCreateCFProperty(service,
  234. CFSTR("IOFramebufferOpenGLIndex"),
  235. kCFAllocatorDefault,
  236. kNilOptions);
  237. if (!indexRef)
  238. continue;
  239. uint32_t index = 0;
  240. CFNumberGetValue(indexRef, kCFNumberIntType, &index);
  241. CFRelease(indexRef);
  242. if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
  243. continue;
  244. const CFNumberRef clockRef =
  245. IORegistryEntryCreateCFProperty(service,
  246. CFSTR("IOFBCurrentPixelClock"),
  247. kCFAllocatorDefault,
  248. kNilOptions);
  249. const CFNumberRef countRef =
  250. IORegistryEntryCreateCFProperty(service,
  251. CFSTR("IOFBCurrentPixelCount"),
  252. kCFAllocatorDefault,
  253. kNilOptions);
  254. uint32_t clock = 0, count = 0;
  255. if (clockRef)
  256. {
  257. CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
  258. CFRelease(clockRef);
  259. }
  260. if (countRef)
  261. {
  262. CFNumberGetValue(countRef, kCFNumberIntType, &count);
  263. CFRelease(countRef);
  264. }
  265. if (clock > 0 && count > 0)
  266. refreshRate = clock / (double) count;
  267. break;
  268. }
  269. IOObjectRelease(it);
  270. return refreshRate;
  271. }
  272. //////////////////////////////////////////////////////////////////////////
  273. ////// GLFW internal API //////
  274. //////////////////////////////////////////////////////////////////////////
  275. // Poll for changes in the set of connected monitors
  276. void _glfwPollMonitorsNS(void)
  277. {
  278. uint32_t displayCount;
  279. CGGetOnlineDisplayList(0, NULL, &displayCount);
  280. CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
  281. CGGetOnlineDisplayList(displayCount, displays, &displayCount);
  282. _glfwClearDisplayLinks();
  283. if (_glfw.hints.init.debugRendering) {
  284. fprintf(stderr, "Polling for monitors: %u found\n", displayCount);
  285. }
  286. for (int i = 0; i < _glfw.monitorCount; i++)
  287. _glfw.monitors[i]->ns.screen = nil;
  288. _GLFWmonitor** disconnected = NULL;
  289. uint32_t disconnectedCount = _glfw.monitorCount;
  290. if (disconnectedCount)
  291. {
  292. disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
  293. memcpy(disconnected,
  294. _glfw.monitors,
  295. _glfw.monitorCount * sizeof(_GLFWmonitor*));
  296. }
  297. for (uint32_t i = 0; i < displayCount; i++)
  298. {
  299. if (CGDisplayIsAsleep(displays[i])) {
  300. if (_glfw.hints.init.debugRendering) fprintf(stderr, "Ignoring sleeping display: %u", displays[i]);
  301. continue;
  302. }
  303. const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
  304. NSScreen* screen = nil;
  305. for (screen in [NSScreen screens])
  306. {
  307. NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
  308. // HACK: Compare unit numbers instead of display IDs to work around
  309. // display replacement on machines with automatic graphics
  310. // switching
  311. if (CGDisplayUnitNumber([screenNumber unsignedIntValue]) == unitNumber)
  312. break;
  313. }
  314. // HACK: Compare unit numbers instead of display IDs to work around
  315. // display replacement on machines with automatic graphics
  316. // switching
  317. uint32_t j;
  318. for (j = 0; j < disconnectedCount; j++)
  319. {
  320. if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
  321. {
  322. disconnected[j]->ns.displayID = displays[i];
  323. disconnected[j]->ns.screen = screen;
  324. _glfwCreateDisplayLink(displays[i]);
  325. disconnected[j] = NULL;
  326. break;
  327. }
  328. }
  329. if (j < disconnectedCount)
  330. continue;
  331. const CGSize size = CGDisplayScreenSize(displays[i]);
  332. char* name = getDisplayName(displays[i], screen);
  333. if (!name) {
  334. _glfwInputError(GLFW_PLATFORM_ERROR,
  335. "Failed to get name for display, using generic name");
  336. name = _glfw_strdup("Display with no name");
  337. }
  338. _GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height);
  339. monitor->ns.displayID = displays[i];
  340. monitor->ns.unitNumber = unitNumber;
  341. monitor->ns.screen = screen;
  342. _glfwCreateDisplayLink(monitor->ns.displayID);
  343. free(name);
  344. CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
  345. if (CGDisplayModeGetRefreshRate(mode) == 0.0)
  346. monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
  347. CGDisplayModeRelease(mode);
  348. _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
  349. }
  350. for (uint32_t i = 0; i < disconnectedCount; i++)
  351. {
  352. if (disconnected[i])
  353. _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
  354. }
  355. free(disconnected);
  356. free(displays);
  357. _glfwRestartDisplayLinks();
  358. }
  359. // Change the current video mode
  360. //
  361. void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
  362. {
  363. GLFWvidmode current;
  364. _glfwPlatformGetVideoMode(monitor, &current);
  365. const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
  366. if (_glfwCompareVideoModes(&current, best) == 0)
  367. return;
  368. CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
  369. const CFIndex count = CFArrayGetCount(modes);
  370. CGDisplayModeRef native = NULL;
  371. for (CFIndex i = 0; i < count; i++)
  372. {
  373. CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
  374. if (!modeIsGood(dm))
  375. continue;
  376. const GLFWvidmode mode =
  377. vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
  378. if (_glfwCompareVideoModes(best, &mode) == 0)
  379. {
  380. native = dm;
  381. break;
  382. }
  383. }
  384. if (native)
  385. {
  386. if (monitor->ns.previousMode == NULL)
  387. monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
  388. CGDisplayFadeReservationToken token = beginFadeReservation();
  389. CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL);
  390. endFadeReservation(token);
  391. }
  392. CFRelease(modes);
  393. }
  394. // Restore the previously saved (original) video mode
  395. //
  396. void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor)
  397. {
  398. if (monitor->ns.previousMode)
  399. {
  400. CGDisplayFadeReservationToken token = beginFadeReservation();
  401. CGDisplaySetDisplayMode(monitor->ns.displayID,
  402. monitor->ns.previousMode, NULL);
  403. endFadeReservation(token);
  404. CGDisplayModeRelease(monitor->ns.previousMode);
  405. monitor->ns.previousMode = NULL;
  406. }
  407. }
  408. //////////////////////////////////////////////////////////////////////////
  409. ////// GLFW platform API //////
  410. //////////////////////////////////////////////////////////////////////////
  411. void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor UNUSED)
  412. {
  413. }
  414. void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
  415. {
  416. const CGRect bounds = CGDisplayBounds(monitor->ns.displayID);
  417. if (xpos)
  418. *xpos = (int) bounds.origin.x;
  419. if (ypos)
  420. *ypos = (int) bounds.origin.y;
  421. }
  422. void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
  423. float* xscale, float* yscale)
  424. {
  425. if (!refreshMonitorScreen(monitor))
  426. return;
  427. const NSRect points = [monitor->ns.screen frame];
  428. const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];
  429. if (xscale)
  430. *xscale = (float) (pixels.size.width / points.size.width);
  431. if (yscale)
  432. *yscale = (float) (pixels.size.height / points.size.height);
  433. }
  434. void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
  435. int* xpos, int* ypos,
  436. int* width, int* height)
  437. {
  438. if (!refreshMonitorScreen(monitor))
  439. return;
  440. const NSRect frameRect = [monitor->ns.screen visibleFrame];
  441. if (xpos)
  442. *xpos = (int)frameRect.origin.x;
  443. if (ypos)
  444. *ypos = (int)_glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
  445. if (width)
  446. *width = (int)frameRect.size.width;
  447. if (height)
  448. *height = (int)frameRect.size.height;
  449. }
  450. GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
  451. {
  452. *count = 0;
  453. CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
  454. const CFIndex found = CFArrayGetCount(modes);
  455. GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
  456. for (CFIndex i = 0; i < found; i++)
  457. {
  458. CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
  459. if (!modeIsGood(dm))
  460. continue;
  461. const GLFWvidmode mode =
  462. vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
  463. CFIndex j;
  464. for (j = 0; j < *count; j++)
  465. {
  466. if (_glfwCompareVideoModes(result + j, &mode) == 0)
  467. break;
  468. }
  469. // Skip duplicate modes
  470. if (j < *count)
  471. continue;
  472. (*count)++;
  473. result[*count - 1] = mode;
  474. }
  475. CFRelease(modes);
  476. return result;
  477. }
  478. bool _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
  479. {
  480. CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
  481. if (!native) {
  482. _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to query display mode");
  483. return false;
  484. }
  485. *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
  486. CGDisplayModeRelease(native);
  487. return true;
  488. }
  489. bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
  490. {
  491. uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
  492. CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
  493. CGGetDisplayTransferByTable(monitor->ns.displayID,
  494. size,
  495. values,
  496. values + size,
  497. values + size * 2,
  498. &size);
  499. _glfwAllocGammaArrays(ramp, size);
  500. for (uint32_t i = 0; i < size; i++)
  501. {
  502. ramp->red[i] = (unsigned short) (values[i] * 65535);
  503. ramp->green[i] = (unsigned short) (values[i + size] * 65535);
  504. ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535);
  505. }
  506. free(values);
  507. return true;
  508. }
  509. void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
  510. {
  511. CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue));
  512. for (unsigned int i = 0; i < ramp->size; i++)
  513. {
  514. values[i] = ramp->red[i] / 65535.f;
  515. values[i + ramp->size] = ramp->green[i] / 65535.f;
  516. values[i + ramp->size * 2] = ramp->blue[i] / 65535.f;
  517. }
  518. CGSetDisplayTransferByTable(monitor->ns.displayID,
  519. ramp->size,
  520. values,
  521. values + ramp->size,
  522. values + ramp->size * 2);
  523. free(values);
  524. }
  525. //////////////////////////////////////////////////////////////////////////
  526. ////// GLFW native API //////
  527. //////////////////////////////////////////////////////////////////////////
  528. GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
  529. {
  530. _GLFWmonitor* monitor = (_GLFWmonitor*) handle;
  531. _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
  532. return monitor->ns.displayID;
  533. }