cocoa_monitor.m (19063B)
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 30 #include "internal.h" 31 32 #include <stdlib.h> 33 #include <limits.h> 34 #include <math.h> 35 36 #include <IOKit/graphics/IOGraphicsLib.h> 37 #include <ApplicationServices/ApplicationServices.h> 38 39 40 // Get the name of the specified display, or NULL 41 // 42 static char* getDisplayName(CGDirectDisplayID displayID) 43 { 44 io_iterator_t it; 45 io_service_t service; 46 CFDictionaryRef info; 47 48 if (IOServiceGetMatchingServices(kIOMasterPortDefault, 49 IOServiceMatching("IODisplayConnect"), 50 &it) != 0) 51 { 52 // This may happen if a desktop Mac is running headless 53 return NULL; 54 } 55 56 while ((service = IOIteratorNext(it)) != 0) 57 { 58 info = IODisplayCreateInfoDictionary(service, 59 kIODisplayOnlyPreferredName); 60 61 CFNumberRef vendorIDRef = (CFNumberRef) 62 CFDictionaryGetValue(info, CFSTR(kDisplayVendorID)); 63 CFNumberRef productIDRef = (CFNumberRef) 64 CFDictionaryGetValue(info, CFSTR(kDisplayProductID)); 65 if (!vendorIDRef || !productIDRef) 66 { 67 CFRelease(info); 68 continue; 69 } 70 71 unsigned int vendorID, productID; 72 CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID); 73 CFNumberGetValue(productIDRef, kCFNumberIntType, &productID); 74 75 if (CGDisplayVendorNumber(displayID) == vendorID && 76 CGDisplayModelNumber(displayID) == productID) 77 { 78 // Info dictionary is used and freed below 79 break; 80 } 81 82 CFRelease(info); 83 } 84 85 IOObjectRelease(it); 86 87 if (!service) 88 { 89 _glfwInputError(GLFW_PLATFORM_ERROR, 90 "Cocoa: Failed to find service port for display"); 91 return NULL; 92 } 93 94 CFDictionaryRef names = (CFDictionaryRef) 95 CFDictionaryGetValue(info, CFSTR(kDisplayProductName)); 96 97 CFStringRef nameRef; 98 99 if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"), 100 (const void**) &nameRef)) 101 { 102 // This may happen if a desktop Mac is running headless 103 CFRelease(info); 104 return NULL; 105 } 106 107 const CFIndex size = 108 CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef), 109 kCFStringEncodingUTF8); 110 char* name = (char*)calloc(size + 1, 1); 111 CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8); 112 113 CFRelease(info); 114 return name; 115 } 116 117 // Check whether the display mode should be included in enumeration 118 // 119 static GLFWbool modeIsGood(CGDisplayModeRef mode) 120 { 121 uint32_t flags = CGDisplayModeGetIOFlags(mode); 122 123 if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag)) 124 return GLFW_FALSE; 125 if (flags & kDisplayModeInterlacedFlag) 126 return GLFW_FALSE; 127 if (flags & kDisplayModeStretchedFlag) 128 return GLFW_FALSE; 129 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)) 134 { 135 CFRelease(format); 136 return GLFW_FALSE; 137 } 138 139 CFRelease(format); 140 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ 141 return GLFW_TRUE; 142 } 143 144 // Convert Core Graphics display mode to GLFW video mode 145 // 146 static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode, 147 double fallbackRefreshRate) 148 { 149 GLFWvidmode result; 150 result.width = (int) CGDisplayModeGetWidth(mode); 151 result.height = (int) CGDisplayModeGetHeight(mode); 152 result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode)); 153 154 if (result.refreshRate == 0) 155 result.refreshRate = (int) round(fallbackRefreshRate); 156 157 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 158 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode); 159 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0) 160 { 161 result.redBits = 5; 162 result.greenBits = 5; 163 result.blueBits = 5; 164 } 165 else 166 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ 167 { 168 result.redBits = 8; 169 result.greenBits = 8; 170 result.blueBits = 8; 171 } 172 173 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 174 CFRelease(format); 175 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ 176 return result; 177 } 178 179 // Starts reservation for display fading 180 // 181 static CGDisplayFadeReservationToken beginFadeReservation(void) 182 { 183 CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken; 184 185 if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess) 186 { 187 CGDisplayFade(token, 0.3, 188 kCGDisplayBlendNormal, 189 kCGDisplayBlendSolidColor, 190 0.0, 0.0, 0.0, 191 TRUE); 192 } 193 194 return token; 195 } 196 197 // Ends reservation for display fading 198 // 199 static void endFadeReservation(CGDisplayFadeReservationToken token) 200 { 201 if (token != kCGDisplayFadeReservationInvalidToken) 202 { 203 CGDisplayFade(token, 0.5, 204 kCGDisplayBlendSolidColor, 205 kCGDisplayBlendNormal, 206 0.0, 0.0, 0.0, 207 FALSE); 208 CGReleaseDisplayFadeReservation(token); 209 } 210 } 211 212 // Finds and caches the NSScreen corresponding to the specified monitor 213 // 214 static GLFWbool refreshMonitorScreen(_GLFWmonitor* monitor) 215 { 216 if (monitor->ns.screen) 217 return GLFW_TRUE; 218 219 for (NSScreen* screen in [NSScreen screens]) 220 { 221 NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"]; 222 223 // HACK: Compare unit numbers instead of display IDs to work around 224 // display replacement on machines with automatic graphics 225 // switching 226 if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue])) 227 { 228 monitor->ns.screen = screen; 229 return GLFW_TRUE; 230 } 231 } 232 233 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor"); 234 return GLFW_FALSE; 235 } 236 237 // Returns the display refresh rate queried from the I/O registry 238 // 239 static double getFallbackRefreshRate(CGDirectDisplayID displayID) 240 { 241 double refreshRate = 60.0; 242 243 io_iterator_t it; 244 io_service_t service; 245 246 if (IOServiceGetMatchingServices(kIOMasterPortDefault, 247 IOServiceMatching("IOFramebuffer"), 248 &it) != 0) 249 { 250 return refreshRate; 251 } 252 253 while ((service = IOIteratorNext(it)) != 0) 254 { 255 const CFNumberRef indexRef = (CFNumberRef) 256 IORegistryEntryCreateCFProperty(service, 257 CFSTR("IOFramebufferOpenGLIndex"), 258 kCFAllocatorDefault, 259 kNilOptions); 260 if (!indexRef) 261 continue; 262 263 uint32_t index = 0; 264 CFNumberGetValue(indexRef, kCFNumberIntType, &index); 265 CFRelease(indexRef); 266 267 if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID) 268 continue; 269 270 const CFNumberRef clockRef = (CFNumberRef) 271 IORegistryEntryCreateCFProperty(service, 272 CFSTR("IOFBCurrentPixelClock"), 273 kCFAllocatorDefault, 274 kNilOptions); 275 const CFNumberRef countRef = (CFNumberRef) 276 IORegistryEntryCreateCFProperty(service, 277 CFSTR("IOFBCurrentPixelCount"), 278 kCFAllocatorDefault, 279 kNilOptions); 280 if (!clockRef || !countRef) 281 break; 282 283 uint32_t clock = 0, count = 0; 284 CFNumberGetValue(clockRef, kCFNumberIntType, &clock); 285 CFNumberGetValue(countRef, kCFNumberIntType, &count); 286 CFRelease(clockRef); 287 CFRelease(countRef); 288 289 if (clock > 0 && count > 0) 290 refreshRate = clock / (double) count; 291 292 break; 293 } 294 295 IOObjectRelease(it); 296 return refreshRate; 297 } 298 299 300 ////////////////////////////////////////////////////////////////////////// 301 ////// GLFW internal API ////// 302 ////////////////////////////////////////////////////////////////////////// 303 304 // Poll for changes in the set of connected monitors 305 // 306 void _glfwPollMonitorsNS(void) 307 { 308 uint32_t displayCount; 309 CGGetOnlineDisplayList(0, NULL, &displayCount); 310 CGDirectDisplayID* displays = (CGDirectDisplayID*)calloc(displayCount, sizeof(CGDirectDisplayID)); 311 CGGetOnlineDisplayList(displayCount, displays, &displayCount); 312 313 for (int i = 0; i < _glfw.monitorCount; i++) 314 _glfw.monitors[i]->ns.screen = nil; 315 316 _GLFWmonitor** disconnected = NULL; 317 uint32_t disconnectedCount = _glfw.monitorCount; 318 if (disconnectedCount) 319 { 320 disconnected = (_GLFWmonitor**)calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*)); 321 memcpy(disconnected, 322 _glfw.monitors, 323 _glfw.monitorCount * sizeof(_GLFWmonitor*)); 324 } 325 326 for (uint32_t i = 0; i < displayCount; i++) 327 { 328 if (CGDisplayIsAsleep(displays[i])) 329 continue; 330 331 // HACK: Compare unit numbers instead of display IDs to work around 332 // display replacement on machines with automatic graphics 333 // switching 334 const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]); 335 for (uint32_t j = 0; j < disconnectedCount; j++) 336 { 337 if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber) 338 { 339 disconnected[j] = NULL; 340 break; 341 } 342 } 343 344 const CGSize size = CGDisplayScreenSize(displays[i]); 345 char* name = getDisplayName(displays[i]); 346 if (!name) 347 name = _glfw_strdup("Unknown"); 348 349 _GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height); 350 monitor->ns.displayID = displays[i]; 351 monitor->ns.unitNumber = unitNumber; 352 353 free(name); 354 355 CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]); 356 if (CGDisplayModeGetRefreshRate(mode) == 0.0) 357 monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]); 358 CGDisplayModeRelease(mode); 359 360 _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST); 361 } 362 363 for (uint32_t i = 0; i < disconnectedCount; i++) 364 { 365 if (disconnected[i]) 366 _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0); 367 } 368 369 free(disconnected); 370 free(displays); 371 } 372 373 // Change the current video mode 374 // 375 void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired) 376 { 377 GLFWvidmode current; 378 _glfwPlatformGetVideoMode(monitor, ¤t); 379 380 const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired); 381 if (_glfwCompareVideoModes(¤t, best) == 0) 382 return; 383 384 CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL); 385 const CFIndex count = CFArrayGetCount(modes); 386 CGDisplayModeRef native = NULL; 387 388 for (CFIndex i = 0; i < count; i++) 389 { 390 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 391 if (!modeIsGood(dm)) 392 continue; 393 394 const GLFWvidmode mode = 395 vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate); 396 if (_glfwCompareVideoModes(best, &mode) == 0) 397 { 398 native = dm; 399 break; 400 } 401 } 402 403 if (native) 404 { 405 if (monitor->ns.previousMode == NULL) 406 monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID); 407 408 CGDisplayFadeReservationToken token = beginFadeReservation(); 409 CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL); 410 endFadeReservation(token); 411 } 412 413 CFRelease(modes); 414 } 415 416 // Restore the previously saved (original) video mode 417 // 418 void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor) 419 { 420 if (monitor->ns.previousMode) 421 { 422 CGDisplayFadeReservationToken token = beginFadeReservation(); 423 CGDisplaySetDisplayMode(monitor->ns.displayID, 424 monitor->ns.previousMode, NULL); 425 endFadeReservation(token); 426 427 CGDisplayModeRelease(monitor->ns.previousMode); 428 monitor->ns.previousMode = NULL; 429 } 430 } 431 432 433 ////////////////////////////////////////////////////////////////////////// 434 ////// GLFW platform API ////// 435 ////////////////////////////////////////////////////////////////////////// 436 437 void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor) 438 { 439 } 440 441 void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) 442 { 443 @autoreleasepool { 444 445 const CGRect bounds = CGDisplayBounds(monitor->ns.displayID); 446 447 if (xpos) 448 *xpos = (int) bounds.origin.x; 449 if (ypos) 450 *ypos = (int) bounds.origin.y; 451 452 } // autoreleasepool 453 } 454 455 void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, 456 float* xscale, float* yscale) 457 { 458 @autoreleasepool { 459 460 if (!refreshMonitorScreen(monitor)) 461 return; 462 463 const NSRect points = [monitor->ns.screen frame]; 464 const NSRect pixels = [monitor->ns.screen convertRectToBacking:points]; 465 466 if (xscale) 467 *xscale = (float) (pixels.size.width / points.size.width); 468 if (yscale) 469 *yscale = (float) (pixels.size.height / points.size.height); 470 471 } // autoreleasepool 472 } 473 474 void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, 475 int* xpos, int* ypos, 476 int* width, int* height) 477 { 478 @autoreleasepool { 479 480 if (!refreshMonitorScreen(monitor)) 481 return; 482 483 const NSRect frameRect = [monitor->ns.screen visibleFrame]; 484 485 if (xpos) 486 *xpos = frameRect.origin.x; 487 if (ypos) 488 *ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1); 489 if (width) 490 *width = frameRect.size.width; 491 if (height) 492 *height = frameRect.size.height; 493 494 } // autoreleasepool 495 } 496 497 GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count) 498 { 499 @autoreleasepool { 500 501 *count = 0; 502 503 CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL); 504 const CFIndex found = CFArrayGetCount(modes); 505 GLFWvidmode* result = (GLFWvidmode*)calloc(found, sizeof(GLFWvidmode)); 506 507 for (CFIndex i = 0; i < found; i++) 508 { 509 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 510 if (!modeIsGood(dm)) 511 continue; 512 513 const GLFWvidmode mode = 514 vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate); 515 CFIndex j; 516 517 for (j = 0; j < *count; j++) 518 { 519 if (_glfwCompareVideoModes(result + j, &mode) == 0) 520 break; 521 } 522 523 // Skip duplicate modes 524 if (i < *count) 525 continue; 526 527 (*count)++; 528 result[*count - 1] = mode; 529 } 530 531 CFRelease(modes); 532 return result; 533 534 } // autoreleasepool 535 } 536 537 void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode) 538 { 539 @autoreleasepool { 540 541 CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID); 542 *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate); 543 CGDisplayModeRelease(native); 544 545 } // autoreleasepool 546 } 547 548 GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) 549 { 550 @autoreleasepool { 551 552 uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID); 553 CGGammaValue* values = (CGGammaValue*)calloc(size * 3, sizeof(CGGammaValue)); 554 555 CGGetDisplayTransferByTable(monitor->ns.displayID, 556 size, 557 values, 558 values + size, 559 values + size * 2, 560 &size); 561 562 _glfwAllocGammaArrays(ramp, size); 563 564 for (uint32_t i = 0; i < size; i++) 565 { 566 ramp->red[i] = (unsigned short) (values[i] * 65535); 567 ramp->green[i] = (unsigned short) (values[i + size] * 65535); 568 ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535); 569 } 570 571 free(values); 572 return GLFW_TRUE; 573 574 } // autoreleasepool 575 } 576 577 void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) 578 { 579 @autoreleasepool { 580 581 CGGammaValue* values = (CGGammaValue*)calloc(ramp->size * 3, sizeof(CGGammaValue)); 582 583 for (unsigned int i = 0; i < ramp->size; i++) 584 { 585 values[i] = ramp->red[i] / 65535.f; 586 values[i + ramp->size] = ramp->green[i] / 65535.f; 587 values[i + ramp->size * 2] = ramp->blue[i] / 65535.f; 588 } 589 590 CGSetDisplayTransferByTable(monitor->ns.displayID, 591 ramp->size, 592 values, 593 values + ramp->size, 594 values + ramp->size * 2); 595 596 free(values); 597 598 } // autoreleasepool 599 } 600 601 602 ////////////////////////////////////////////////////////////////////////// 603 ////// GLFW native API ////// 604 ////////////////////////////////////////////////////////////////////////// 605 606 GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle) 607 { 608 _GLFWmonitor* monitor = (_GLFWmonitor*) handle; 609 _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay); 610 return monitor->ns.displayID; 611 } 612