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