cocoa_monitor.m (14569B)
1 //======================================================================== 2 // GLFW 3.3 macOS - www.glfw.org 3 //------------------------------------------------------------------------ 4 // Copyright (c) 2002-2006 Marcus Geelnard 5 // Copyright (c) 2006-2016 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 #include <stdlib.h> 31 #include <limits.h> 32 33 #include <IOKit/graphics/IOGraphicsLib.h> 34 #include <CoreVideo/CVBase.h> 35 #include <CoreVideo/CVDisplayLink.h> 36 #include <ApplicationServices/ApplicationServices.h> 37 38 39 // Get the name of the specified display, or NULL 40 // 41 static char* getDisplayName(CGDirectDisplayID displayID) 42 { 43 io_iterator_t it; 44 io_service_t service; 45 CFDictionaryRef info; 46 47 if (IOServiceGetMatchingServices(kIOMasterPortDefault, 48 IOServiceMatching("IODisplayConnect"), 49 &it) != 0) 50 { 51 // This may happen if a desktop Mac is running headless 52 return NULL; 53 } 54 55 while ((service = IOIteratorNext(it)) != 0) 56 { 57 info = IODisplayCreateInfoDictionary(service, kIODisplayOnlyPreferredName); 58 59 CFNumberRef vendorIDRef = 60 CFDictionaryGetValue(info, CFSTR(kDisplayVendorID)); 61 CFNumberRef productIDRef = 62 CFDictionaryGetValue(info, CFSTR(kDisplayProductID)); 63 if (!vendorIDRef || !productIDRef) 64 { 65 CFRelease(info); 66 continue; 67 } 68 69 unsigned int vendorID, productID; 70 CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID); 71 CFNumberGetValue(productIDRef, kCFNumberIntType, &productID); 72 73 if (CGDisplayVendorNumber(displayID) == vendorID && 74 CGDisplayModelNumber(displayID) == productID) 75 { 76 // Info dictionary is used and freed below 77 break; 78 } 79 80 CFRelease(info); 81 } 82 83 IOObjectRelease(it); 84 85 if (!service) 86 { 87 _glfwInputError(GLFW_PLATFORM_ERROR, 88 "Cocoa: Failed to find service port for display"); 89 return NULL; 90 } 91 92 CFDictionaryRef names = 93 CFDictionaryGetValue(info, CFSTR(kDisplayProductName)); 94 95 CFStringRef nameRef; 96 97 if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"), 98 (const void**) &nameRef)) 99 { 100 // This may happen if a desktop Mac is running headless 101 CFRelease(info); 102 return NULL; 103 } 104 105 const CFIndex size = 106 CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef), 107 kCFStringEncodingUTF8); 108 char* name = calloc(size + 1, 1); 109 CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8); 110 111 CFRelease(info); 112 return name; 113 } 114 115 // Check whether the display mode should be included in enumeration 116 // 117 static GLFWbool modeIsGood(CGDisplayModeRef mode) 118 { 119 uint32_t flags = CGDisplayModeGetIOFlags(mode); 120 121 if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag)) 122 return GLFW_FALSE; 123 if (flags & kDisplayModeInterlacedFlag) 124 return GLFW_FALSE; 125 if (flags & kDisplayModeStretchedFlag) 126 return GLFW_FALSE; 127 128 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 129 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode); 130 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) && 131 CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0)) 132 { 133 CFRelease(format); 134 return GLFW_FALSE; 135 } 136 137 CFRelease(format); 138 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ 139 return GLFW_TRUE; 140 } 141 142 // Convert Core Graphics display mode to GLFW video mode 143 // 144 static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode, 145 CVDisplayLinkRef link) 146 { 147 GLFWvidmode result; 148 result.width = (int) CGDisplayModeGetWidth(mode); 149 result.height = (int) CGDisplayModeGetHeight(mode); 150 result.refreshRate = (int) CGDisplayModeGetRefreshRate(mode); 151 152 if (result.refreshRate == 0) 153 { 154 const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); 155 if (!(time.flags & kCVTimeIsIndefinite)) 156 result.refreshRate = (int) (time.timeScale / (double) time.timeValue); 157 } 158 159 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 160 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode); 161 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0) 162 { 163 result.redBits = 5; 164 result.greenBits = 5; 165 result.blueBits = 5; 166 } 167 else 168 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ 169 { 170 result.redBits = 8; 171 result.greenBits = 8; 172 result.blueBits = 8; 173 } 174 175 #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 176 CFRelease(format); 177 #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ 178 return result; 179 } 180 181 // Starts reservation for display fading 182 // 183 static CGDisplayFadeReservationToken beginFadeReservation(void) 184 { 185 CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken; 186 187 if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess) 188 CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE); 189 190 return token; 191 } 192 193 // Ends reservation for display fading 194 // 195 static void endFadeReservation(CGDisplayFadeReservationToken token) 196 { 197 if (token != kCGDisplayFadeReservationInvalidToken) 198 { 199 CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE); 200 CGReleaseDisplayFadeReservation(token); 201 } 202 } 203 204 205 ////////////////////////////////////////////////////////////////////////// 206 ////// GLFW internal API ////// 207 ////////////////////////////////////////////////////////////////////////// 208 209 // Poll for changes in the set of connected monitors 210 // 211 void _glfwPollMonitorsNS(void) 212 { 213 uint32_t i, j, displayCount, disconnectedCount; 214 CGDirectDisplayID* displays; 215 _GLFWmonitor** disconnected = NULL; 216 217 CGGetOnlineDisplayList(0, NULL, &displayCount); 218 displays = calloc(displayCount, sizeof(CGDirectDisplayID)); 219 CGGetOnlineDisplayList(displayCount, displays, &displayCount); 220 221 disconnectedCount = _glfw.monitorCount; 222 if (disconnectedCount) 223 { 224 disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*)); 225 memcpy(disconnected, 226 _glfw.monitors, 227 _glfw.monitorCount * sizeof(_GLFWmonitor*)); 228 } 229 230 for (i = 0; i < displayCount; i++) 231 { 232 _GLFWmonitor* monitor; 233 const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]); 234 235 if (CGDisplayIsAsleep(displays[i])) 236 continue; 237 238 for (j = 0; j < disconnectedCount; j++) 239 { 240 // HACK: Compare unit numbers instead of display IDs to work around 241 // display replacement on machines with automatic graphics 242 // switching 243 if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber) 244 { 245 disconnected[j] = NULL; 246 break; 247 } 248 } 249 250 const CGSize size = CGDisplayScreenSize(displays[i]); 251 char* name = getDisplayName(displays[i]); 252 if (!name) 253 name = strdup("Unknown"); 254 255 monitor = _glfwAllocMonitor(name, size.width, size.height); 256 monitor->ns.displayID = displays[i]; 257 monitor->ns.unitNumber = unitNumber; 258 259 free(name); 260 261 _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST); 262 } 263 264 for (i = 0; i < disconnectedCount; i++) 265 { 266 if (disconnected[i]) 267 _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0); 268 } 269 270 free(disconnected); 271 free(displays); 272 } 273 274 // Change the current video mode 275 // 276 GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired) 277 { 278 CFArrayRef modes; 279 CFIndex count, i; 280 CVDisplayLinkRef link; 281 CGDisplayModeRef native = NULL; 282 GLFWvidmode current; 283 const GLFWvidmode* best; 284 285 best = _glfwChooseVideoMode(monitor, desired); 286 _glfwPlatformGetVideoMode(monitor, ¤t); 287 if (_glfwCompareVideoModes(¤t, best) == 0) 288 return GLFW_TRUE; 289 290 CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link); 291 292 modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL); 293 count = CFArrayGetCount(modes); 294 295 for (i = 0; i < count; i++) 296 { 297 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 298 if (!modeIsGood(dm)) 299 continue; 300 301 const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link); 302 if (_glfwCompareVideoModes(best, &mode) == 0) 303 { 304 native = dm; 305 break; 306 } 307 } 308 309 if (native) 310 { 311 if (monitor->ns.previousMode == NULL) 312 monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID); 313 314 CGDisplayFadeReservationToken token = beginFadeReservation(); 315 CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL); 316 endFadeReservation(token); 317 } 318 319 CFRelease(modes); 320 CVDisplayLinkRelease(link); 321 322 if (!native) 323 { 324 _glfwInputError(GLFW_PLATFORM_ERROR, 325 "Cocoa: Monitor mode list changed"); 326 return GLFW_FALSE; 327 } 328 329 return GLFW_TRUE; 330 } 331 332 // Restore the previously saved (original) video mode 333 // 334 void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor) 335 { 336 if (monitor->ns.previousMode) 337 { 338 CGDisplayFadeReservationToken token = beginFadeReservation(); 339 CGDisplaySetDisplayMode(monitor->ns.displayID, 340 monitor->ns.previousMode, NULL); 341 endFadeReservation(token); 342 343 CGDisplayModeRelease(monitor->ns.previousMode); 344 monitor->ns.previousMode = NULL; 345 } 346 } 347 348 349 ////////////////////////////////////////////////////////////////////////// 350 ////// GLFW platform API ////// 351 ////////////////////////////////////////////////////////////////////////// 352 353 void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) 354 { 355 const CGRect bounds = CGDisplayBounds(monitor->ns.displayID); 356 357 if (xpos) 358 *xpos = (int) bounds.origin.x; 359 if (ypos) 360 *ypos = (int) bounds.origin.y; 361 } 362 363 GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count) 364 { 365 CFArrayRef modes; 366 CFIndex found, i, j; 367 GLFWvidmode* result; 368 CVDisplayLinkRef link; 369 370 *count = 0; 371 372 CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link); 373 374 modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL); 375 found = CFArrayGetCount(modes); 376 result = calloc(found, sizeof(GLFWvidmode)); 377 378 for (i = 0; i < found; i++) 379 { 380 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 381 if (!modeIsGood(dm)) 382 continue; 383 384 const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link); 385 386 for (j = 0; j < *count; j++) 387 { 388 if (_glfwCompareVideoModes(result + j, &mode) == 0) 389 break; 390 } 391 392 // Skip duplicate modes 393 if (i < *count) 394 continue; 395 396 (*count)++; 397 result[*count - 1] = mode; 398 } 399 400 CFRelease(modes); 401 CVDisplayLinkRelease(link); 402 return result; 403 } 404 405 void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode) 406 { 407 CGDisplayModeRef displayMode; 408 CVDisplayLinkRef link; 409 410 CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link); 411 412 displayMode = CGDisplayCopyDisplayMode(monitor->ns.displayID); 413 *mode = vidmodeFromCGDisplayMode(displayMode, link); 414 CGDisplayModeRelease(displayMode); 415 416 CVDisplayLinkRelease(link); 417 } 418 419 void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) 420 { 421 uint32_t i, size = CGDisplayGammaTableCapacity(monitor->ns.displayID); 422 CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue)); 423 424 CGGetDisplayTransferByTable(monitor->ns.displayID, 425 size, 426 values, 427 values + size, 428 values + size * 2, 429 &size); 430 431 _glfwAllocGammaArrays(ramp, size); 432 433 for (i = 0; i < size; i++) 434 { 435 ramp->red[i] = (unsigned short) (values[i] * 65535); 436 ramp->green[i] = (unsigned short) (values[i + size] * 65535); 437 ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535); 438 } 439 440 free(values); 441 } 442 443 void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) 444 { 445 int i; 446 CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue)); 447 448 for (i = 0; i < ramp->size; i++) 449 { 450 values[i] = ramp->red[i] / 65535.f; 451 values[i + ramp->size] = ramp->green[i] / 65535.f; 452 values[i + ramp->size * 2] = ramp->blue[i] / 65535.f; 453 } 454 455 CGSetDisplayTransferByTable(monitor->ns.displayID, 456 ramp->size, 457 values, 458 values + ramp->size, 459 values + ramp->size * 2); 460 461 free(values); 462 } 463 464 465 ////////////////////////////////////////////////////////////////////////// 466 ////// GLFW native API ////// 467 ////////////////////////////////////////////////////////////////////////// 468 469 GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle) 470 { 471 _GLFWmonitor* monitor = (_GLFWmonitor*) handle; 472 _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay); 473 return monitor->ns.displayID; 474 } 475