cocoa_monitor.m (19725B)
1 //======================================================================== 2 // GLFW 3.4 macOS (modified for raylib) - www.glfw.org; www.raylib.com 3 //------------------------------------------------------------------------ 4 // Copyright (c) 2002-2006 Marcus Geelnard 5 // Copyright (c) 2006-2019 Camilla Löwy <elmindreda@glfw.org> 6 // Copyright (c) 2024 M374LX <wilsalx@gmail.com> 7 // 8 // This software is provided 'as-is', without any express or implied 9 // warranty. In no event will the authors be held liable for any damages 10 // arising from the use of this software. 11 // 12 // Permission is granted to anyone to use this software for any purpose, 13 // including commercial applications, and to alter it and redistribute it 14 // freely, subject to the following restrictions: 15 // 16 // 1. The origin of this software must not be misrepresented; you must not 17 // claim that you wrote the original software. If you use this software 18 // in a product, an acknowledgment in the product documentation would 19 // be appreciated but is not required. 20 // 21 // 2. Altered source versions must be plainly marked as such, and must not 22 // be misrepresented as being the original software. 23 // 24 // 3. This notice may not be removed or altered from any source 25 // distribution. 26 // 27 //======================================================================== 28 29 #include "internal.h" 30 31 #if defined(_GLFW_COCOA) 32 33 #include <stdlib.h> 34 #include <limits.h> 35 #include <math.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 // 43 static 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 // 129 static 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 // 156 static 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 // 191 static 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 // 209 static 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 // 224 static 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 // 297 void _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 // 385 void _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 // 428 void _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 447 void _glfwFreeMonitorCocoa(_GLFWmonitor* monitor) 448 { 449 } 450 451 void _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 465 void _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 487 void _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 513 GLFWvidmode* _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 553 GLFWbool _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 571 GLFWbool _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 600 void _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 629 GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle) 630 { 631 _GLFWmonitor* monitor = (_GLFWmonitor*) handle; 632 _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay); 633 634 if (_glfw.platform.platformID != GLFW_PLATFORM_COCOA) 635 { 636 _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "Cocoa: Platform not initialized"); 637 return kCGNullDirectDisplay; 638 } 639 640 return monitor->ns.displayID; 641 } 642 643 #endif // _GLFW_COCOA 644