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