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* 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 = 62 CFDictionaryGetValue(info, CFSTR(kDisplayVendorID)); 63 CFNumberRef productIDRef = 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 = 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 = 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// 119static 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// 146static 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// 181static 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// 199static 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// 214static 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// 239static 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 = 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 = 271 IORegistryEntryCreateCFProperty(service, 272 CFSTR("IOFBCurrentPixelClock"), 273 kCFAllocatorDefault, 274 kNilOptions); 275 const CFNumberRef countRef = 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// 306void _glfwPollMonitorsNS(void) 307{ 308 uint32_t displayCount; 309 CGGetOnlineDisplayList(0, NULL, &displayCount); 310 CGDirectDisplayID* displays = 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 = 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// 375void _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// 418void _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 437void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor) 438{ 439} 440 441void _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 455void _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 474void _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 497GLFWvidmode* _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 = 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 537void _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 548GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) 549{ 550 @autoreleasepool { 551 552 uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID); 553 CGGammaValue* values = 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 577void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) 578{ 579 @autoreleasepool { 580 581 CGGammaValue* values = 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 606GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle) 607{ 608 _GLFWmonitor* monitor = (_GLFWmonitor*) handle; 609 _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay); 610 return monitor->ns.displayID; 611} 612 613