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>
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.
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:
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.
20 // 2. Altered source versions must be plainly marked as such, and must not
21 // be misrepresented as being the original software.
23 // 3. This notice may not be removed or altered from any source
26 //========================================================================
27 // It is fine to use C99 in this file because it will not be built with VS
28 //========================================================================
36 #include <IOKit/graphics/IOGraphicsLib.h>
37 #include <ApplicationServices/ApplicationServices.h>
40 // Get the name of the specified display, or NULL
42 static char* getDisplayName(CGDirectDisplayID displayID)
48 if (IOServiceGetMatchingServices(kIOMasterPortDefault,
49 IOServiceMatching("IODisplayConnect"),
52 // This may happen if a desktop Mac is running headless
56 while ((service = IOIteratorNext(it)) != 0)
58 info = IODisplayCreateInfoDictionary(service,
59 kIODisplayOnlyPreferredName);
61 CFNumberRef vendorIDRef =
62 CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
63 CFNumberRef productIDRef =
64 CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
65 if (!vendorIDRef || !productIDRef)
71 unsigned int vendorID, productID;
72 CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID);
73 CFNumberGetValue(productIDRef, kCFNumberIntType, &productID);
75 if (CGDisplayVendorNumber(displayID) == vendorID &&
76 CGDisplayModelNumber(displayID) == productID)
78 // Info dictionary is used and freed below
89 _glfwInputError(GLFW_PLATFORM_ERROR,
90 "Cocoa: Failed to find service port for display");
94 CFDictionaryRef names =
95 CFDictionaryGetValue(info, CFSTR(kDisplayProductName));
99 if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"),
100 (const void**) &nameRef))
102 // This may happen if a desktop Mac is running headless
108 CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
109 kCFStringEncodingUTF8);
110 char* name = calloc(size + 1, 1);
111 CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8);
117 // Check whether the display mode should be included in enumeration
119 static GLFWbool modeIsGood(CGDisplayModeRef mode)
121 uint32_t flags = CGDisplayModeGetIOFlags(mode);
123 if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag))
125 if (flags & kDisplayModeInterlacedFlag)
127 if (flags & kDisplayModeStretchedFlag)
130 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
131 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
132 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) &&
133 CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0))
140 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
144 // Convert Core Graphics display mode to GLFW video mode
146 static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
147 double fallbackRefreshRate)
150 result.width = (int) CGDisplayModeGetWidth(mode);
151 result.height = (int) CGDisplayModeGetHeight(mode);
152 result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
154 if (result.refreshRate == 0)
155 result.refreshRate = (int) round(fallbackRefreshRate);
157 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
158 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
159 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0)
162 result.greenBits = 5;
166 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
169 result.greenBits = 8;
173 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
175 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
179 // Starts reservation for display fading
181 static CGDisplayFadeReservationToken beginFadeReservation(void)
183 CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken;
185 if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess)
187 CGDisplayFade(token, 0.3,
188 kCGDisplayBlendNormal,
189 kCGDisplayBlendSolidColor,
197 // Ends reservation for display fading
199 static void endFadeReservation(CGDisplayFadeReservationToken token)
201 if (token != kCGDisplayFadeReservationInvalidToken)
203 CGDisplayFade(token, 0.5,
204 kCGDisplayBlendSolidColor,
205 kCGDisplayBlendNormal,
208 CGReleaseDisplayFadeReservation(token);
212 // Finds and caches the NSScreen corresponding to the specified monitor
214 static GLFWbool refreshMonitorScreen(_GLFWmonitor* monitor)
216 if (monitor->ns.screen)
219 for (NSScreen* screen in [NSScreen screens])
221 NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"];
223 // HACK: Compare unit numbers instead of display IDs to work around
224 // display replacement on machines with automatic graphics
226 if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue]))
228 monitor->ns.screen = screen;
233 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor");
237 // Returns the display refresh rate queried from the I/O registry
239 static double getFallbackRefreshRate(CGDirectDisplayID displayID)
241 double refreshRate = 60.0;
244 io_service_t service;
246 if (IOServiceGetMatchingServices(kIOMasterPortDefault,
247 IOServiceMatching("IOFramebuffer"),
253 while ((service = IOIteratorNext(it)) != 0)
255 const CFNumberRef indexRef =
256 IORegistryEntryCreateCFProperty(service,
257 CFSTR("IOFramebufferOpenGLIndex"),
264 CFNumberGetValue(indexRef, kCFNumberIntType, &index);
267 if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
270 const CFNumberRef clockRef =
271 IORegistryEntryCreateCFProperty(service,
272 CFSTR("IOFBCurrentPixelClock"),
275 const CFNumberRef countRef =
276 IORegistryEntryCreateCFProperty(service,
277 CFSTR("IOFBCurrentPixelCount"),
281 uint32_t clock = 0, count = 0;
285 CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
291 CFNumberGetValue(countRef, kCFNumberIntType, &count);
295 if (clock > 0 && count > 0)
296 refreshRate = clock / (double) count;
306 //////////////////////////////////////////////////////////////////////////
307 ////// GLFW internal API //////
308 //////////////////////////////////////////////////////////////////////////
310 // Poll for changes in the set of connected monitors
312 void _glfwPollMonitorsNS(void)
314 uint32_t displayCount;
315 CGGetOnlineDisplayList(0, NULL, &displayCount);
316 CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
317 CGGetOnlineDisplayList(displayCount, displays, &displayCount);
319 for (int i = 0; i < _glfw.monitorCount; i++)
320 _glfw.monitors[i]->ns.screen = nil;
322 _GLFWmonitor** disconnected = NULL;
323 uint32_t disconnectedCount = _glfw.monitorCount;
324 if (disconnectedCount)
326 disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
329 _glfw.monitorCount * sizeof(_GLFWmonitor*));
332 for (uint32_t i = 0; i < displayCount; i++)
334 if (CGDisplayIsAsleep(displays[i]))
337 // HACK: Compare unit numbers instead of display IDs to work around
338 // display replacement on machines with automatic graphics
340 const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
341 for (uint32_t j = 0; j < disconnectedCount; j++)
343 if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
345 disconnected[j] = NULL;
350 const CGSize size = CGDisplayScreenSize(displays[i]);
351 char* name = getDisplayName(displays[i]);
353 name = _glfw_strdup("Unknown");
355 _GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height);
356 monitor->ns.displayID = displays[i];
357 monitor->ns.unitNumber = unitNumber;
361 CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
362 if (CGDisplayModeGetRefreshRate(mode) == 0.0)
363 monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
364 CGDisplayModeRelease(mode);
366 _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
369 for (uint32_t i = 0; i < disconnectedCount; i++)
372 _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
379 // Change the current video mode
381 void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
384 _glfwPlatformGetVideoMode(monitor, ¤t);
386 const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
387 if (_glfwCompareVideoModes(¤t, best) == 0)
390 CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
391 const CFIndex count = CFArrayGetCount(modes);
392 CGDisplayModeRef native = NULL;
394 for (CFIndex i = 0; i < count; i++)
396 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
400 const GLFWvidmode mode =
401 vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
402 if (_glfwCompareVideoModes(best, &mode) == 0)
411 if (monitor->ns.previousMode == NULL)
412 monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
414 CGDisplayFadeReservationToken token = beginFadeReservation();
415 CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL);
416 endFadeReservation(token);
422 // Restore the previously saved (original) video mode
424 void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor)
426 if (monitor->ns.previousMode)
428 CGDisplayFadeReservationToken token = beginFadeReservation();
429 CGDisplaySetDisplayMode(monitor->ns.displayID,
430 monitor->ns.previousMode, NULL);
431 endFadeReservation(token);
433 CGDisplayModeRelease(monitor->ns.previousMode);
434 monitor->ns.previousMode = NULL;
439 //////////////////////////////////////////////////////////////////////////
440 ////// GLFW platform API //////
441 //////////////////////////////////////////////////////////////////////////
443 void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
447 void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
451 const CGRect bounds = CGDisplayBounds(monitor->ns.displayID);
454 *xpos = (int) bounds.origin.x;
456 *ypos = (int) bounds.origin.y;
461 void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
462 float* xscale, float* yscale)
466 if (!refreshMonitorScreen(monitor))
469 const NSRect points = [monitor->ns.screen frame];
470 const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];
473 *xscale = (float) (pixels.size.width / points.size.width);
475 *yscale = (float) (pixels.size.height / points.size.height);
480 void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
481 int* xpos, int* ypos,
482 int* width, int* height)
486 if (!refreshMonitorScreen(monitor))
489 const NSRect frameRect = [monitor->ns.screen visibleFrame];
492 *xpos = frameRect.origin.x;
494 *ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
496 *width = frameRect.size.width;
498 *height = frameRect.size.height;
503 GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
509 CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
510 const CFIndex found = CFArrayGetCount(modes);
511 GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
513 for (CFIndex i = 0; i < found; i++)
515 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
519 const GLFWvidmode mode =
520 vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
523 for (j = 0; j < *count; j++)
525 if (_glfwCompareVideoModes(result + j, &mode) == 0)
529 // Skip duplicate modes
534 result[*count - 1] = mode;
543 void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
547 CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
548 *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
549 CGDisplayModeRelease(native);
554 GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
558 uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
559 CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
561 CGGetDisplayTransferByTable(monitor->ns.displayID,
568 _glfwAllocGammaArrays(ramp, size);
570 for (uint32_t i = 0; i < size; i++)
572 ramp->red[i] = (unsigned short) (values[i] * 65535);
573 ramp->green[i] = (unsigned short) (values[i + size] * 65535);
574 ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535);
583 void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
587 CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue));
589 for (unsigned int i = 0; i < ramp->size; i++)
591 values[i] = ramp->red[i] / 65535.f;
592 values[i + ramp->size] = ramp->green[i] / 65535.f;
593 values[i + ramp->size * 2] = ramp->blue[i] / 65535.f;
596 CGSetDisplayTransferByTable(monitor->ns.displayID,
600 values + ramp->size * 2);
608 //////////////////////////////////////////////////////////////////////////
609 ////// GLFW native API //////
610 //////////////////////////////////////////////////////////////////////////
612 GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
614 _GLFWmonitor* monitor = (_GLFWmonitor*) handle;
615 _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
616 return monitor->ns.displayID;