quartzRandR.c revision 8223e2f2
1/* 2 * Quartz-specific support for the XRandR extension 3 * 4 * Copyright (c) 2001-2004 Greg Parker and Torrey T. Lyons, 5 * 2010 Jan Hauffa. 6 * 2010 Apple Inc. 7 * All Rights Reserved. 8 * 9 * Permission is hereby granted, free of charge, to any person obtaining a 10 * copy of this software and associated documentation files (the "Software"), 11 * to deal in the Software without restriction, including without limitation 12 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 * and/or sell copies of the Software, and to permit persons to whom the 14 * Software is furnished to do so, subject to the following conditions: 15 * 16 * The above copyright notice and this permission notice shall be included in 17 * all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 * DEALINGS IN THE SOFTWARE. 26 * 27 * Except as contained in this notice, the name(s) of the above copyright 28 * holders shall not be used in advertising or otherwise to promote the sale, 29 * use or other dealings in this Software without prior written authorization. 30 */ 31 32#include "sanitizedCarbon.h" 33 34#ifdef HAVE_DIX_CONFIG_H 35#include <dix-config.h> 36#endif 37 38#include "quartzCommon.h" 39#include "quartzRandR.h" 40#include "quartz.h" 41#include "darwin.h" 42 43#include "X11Application.h" 44 45#include <AvailabilityMacros.h> 46 47#include <X11/extensions/randr.h> 48#include <randrstr.h> 49#include <IOKit/graphics/IOGraphicsTypes.h> 50 51/* TODO: UGLY, find a better way! 52 * We want to ignore kXquartzDisplayChanged which are generated by us 53 */ 54static Bool ignore_next_fake_mode_update = FALSE; 55 56#define FAKE_REFRESH_ROOTLESS 1 57#define FAKE_REFRESH_FULLSCREEN 2 58 59#define DEFAULT_REFRESH 60 60#define kDisplayModeUsableFlags (kDisplayModeValidFlag | kDisplayModeSafeFlag) 61 62#define CALLBACK_SUCCESS 0 63#define CALLBACK_CONTINUE 1 64#define CALLBACK_ERROR -1 65 66typedef int (*QuartzModeCallback) 67 (ScreenPtr, QuartzModeInfoPtr, void *); 68 69#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 70 71static long getDictLong (CFDictionaryRef dictRef, CFStringRef key) { 72 long value; 73 74 CFNumberRef numRef = (CFNumberRef) CFDictionaryGetValue(dictRef, key); 75 if (!numRef) 76 return 0; 77 78 if (!CFNumberGetValue(numRef, kCFNumberLongType, &value)) 79 return 0; 80 return value; 81} 82 83static double getDictDouble (CFDictionaryRef dictRef, CFStringRef key) { 84 double value; 85 86 CFNumberRef numRef = (CFNumberRef) CFDictionaryGetValue(dictRef, key); 87 if (!numRef) 88 return 0.0; 89 90 if (!CFNumberGetValue(numRef, kCFNumberDoubleType, &value)) 91 return 0.0; 92 return value; 93} 94 95static void QuartzRandRGetModeInfo (CFDictionaryRef modeRef, 96 QuartzModeInfoPtr pMode) { 97 pMode->width = (size_t) getDictLong(modeRef, kCGDisplayWidth); 98 pMode->height = (size_t) getDictLong(modeRef, kCGDisplayHeight); 99 pMode->refresh = (int)(getDictDouble(modeRef, kCGDisplayRefreshRate) + 0.5); 100 if (pMode->refresh == 0) 101 pMode->refresh = DEFAULT_REFRESH; 102 pMode->ref = NULL; 103 pMode->pSize = NULL; 104} 105 106static Bool QuartzRandRCopyCurrentModeInfo (CGDirectDisplayID screenId, 107 QuartzModeInfoPtr pMode) { 108 CFDictionaryRef curModeRef = CGDisplayCurrentMode(screenId); 109 if (!curModeRef) 110 return FALSE; 111 112 QuartzRandRGetModeInfo(curModeRef, pMode); 113 pMode->ref = (void *)curModeRef; 114 CFRetain(pMode->ref); 115 return TRUE; 116} 117 118static Bool QuartzRandRSetCGMode (CGDirectDisplayID screenId, 119 QuartzModeInfoPtr pMode) { 120 CFDictionaryRef modeRef = (CFDictionaryRef) pMode->ref; 121 return (CGDisplaySwitchToMode(screenId, modeRef) == kCGErrorSuccess); 122} 123 124static Bool QuartzRandREnumerateModes (ScreenPtr pScreen, 125 QuartzModeCallback callback, 126 void *data) { 127 CFDictionaryRef curModeRef, modeRef; 128 long curBpp; 129 CFArrayRef modes; 130 QuartzModeInfo modeInfo; 131 int i; 132 BOOL retval = FALSE; 133 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 134 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; 135 136 curModeRef = CGDisplayCurrentMode(screenId); 137 if (!curModeRef) 138 return FALSE; 139 curBpp = getDictLong(curModeRef, kCGDisplayBitsPerPixel); 140 141 modes = CGDisplayAvailableModes(screenId); 142 if (!modes) 143 return FALSE; 144 for (i = 0; i < CFArrayGetCount(modes); i++) { 145 int cb; 146 modeRef = (CFDictionaryRef) CFArrayGetValueAtIndex(modes, i); 147 148 /* Skip modes that are not usable on the current display or have a 149 different pixel encoding than the current mode. */ 150 if (((unsigned long) getDictLong(modeRef, kCGDisplayIOFlags) & 151 kDisplayModeUsableFlags) != kDisplayModeUsableFlags) 152 continue; 153 if (getDictLong(modeRef, kCGDisplayBitsPerPixel) != curBpp) 154 continue; 155 156 QuartzRandRGetModeInfo(modeRef, &modeInfo); 157 modeInfo.ref = (void *)modeRef; 158 cb = callback(pScreen, &modeInfo, data); 159 if (cb == CALLBACK_CONTINUE) 160 retval = TRUE; 161 else if (cb == CALLBACK_SUCCESS) 162 return TRUE; 163 else if (cb == CALLBACK_ERROR) 164 return FALSE; 165 } 166 167 switch(callback(pScreen, &pQuartzScreen->rootlessMode, data)) { 168 case CALLBACK_SUCCESS: 169 return TRUE; 170 case CALLBACK_ERROR: 171 return FALSE; 172 case CALLBACK_CONTINUE: 173 retval = TRUE; 174 default: 175 break; 176 } 177 178 switch(callback(pScreen, &pQuartzScreen->fullscreenMode, data)) { 179 case CALLBACK_SUCCESS: 180 return TRUE; 181 case CALLBACK_ERROR: 182 return FALSE; 183 case CALLBACK_CONTINUE: 184 retval = TRUE; 185 default: 186 break; 187 } 188 189 return retval; 190} 191 192#else /* we have the new CG APIs from Snow Leopard */ 193 194static void QuartzRandRGetModeInfo (CGDisplayModeRef modeRef, 195 QuartzModeInfoPtr pMode) { 196 pMode->width = CGDisplayModeGetWidth(modeRef); 197 pMode->height = CGDisplayModeGetHeight(modeRef); 198 pMode->refresh = (int) (CGDisplayModeGetRefreshRate(modeRef) + 0.5); 199 if (pMode->refresh == 0) 200 pMode->refresh = DEFAULT_REFRESH; 201 pMode->ref = NULL; 202 pMode->pSize = NULL; 203} 204 205static Bool QuartzRandRCopyCurrentModeInfo (CGDirectDisplayID screenId, 206 QuartzModeInfoPtr pMode) { 207 CGDisplayModeRef curModeRef = CGDisplayCopyDisplayMode(screenId); 208 if (!curModeRef) 209 return FALSE; 210 211 QuartzRandRGetModeInfo(curModeRef, pMode); 212 pMode->ref = curModeRef; 213 return TRUE; 214} 215 216static Bool QuartzRandRSetCGMode (CGDirectDisplayID screenId, 217 QuartzModeInfoPtr pMode) { 218 CGDisplayModeRef modeRef = (CGDisplayModeRef) pMode->ref; 219 if (!modeRef) 220 return FALSE; 221 222 return (CGDisplaySetDisplayMode(screenId, modeRef, NULL) == kCGErrorSuccess); 223} 224 225static Bool QuartzRandREnumerateModes (ScreenPtr pScreen, 226 QuartzModeCallback callback, 227 void *data) { 228 CGDisplayModeRef curModeRef, modeRef; 229 CFStringRef curPixelEnc, pixelEnc; 230 CFComparisonResult pixelEncEqual; 231 CFArrayRef modes; 232 QuartzModeInfo modeInfo; 233 int i; 234 Bool retval = FALSE; 235 236 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 237 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; 238 239 curModeRef = CGDisplayCopyDisplayMode(screenId); 240 if (!curModeRef) 241 return FALSE; 242 curPixelEnc = CGDisplayModeCopyPixelEncoding(curModeRef); 243 CGDisplayModeRelease(curModeRef); 244 245 modes = CGDisplayCopyAllDisplayModes(screenId, NULL); 246 if (!modes) { 247 CFRelease(curPixelEnc); 248 return FALSE; 249 } 250 for (i = 0; i < CFArrayGetCount(modes); i++) { 251 int cb; 252 modeRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 253 254 /* Skip modes that are not usable on the current display or have a 255 different pixel encoding than the current mode. */ 256 if ((CGDisplayModeGetIOFlags(modeRef) & kDisplayModeUsableFlags) != 257 kDisplayModeUsableFlags) 258 continue; 259 pixelEnc = CGDisplayModeCopyPixelEncoding(modeRef); 260 pixelEncEqual = CFStringCompare(pixelEnc, curPixelEnc, 0); 261 CFRelease(pixelEnc); 262 if (pixelEncEqual != kCFCompareEqualTo) 263 continue; 264 265 QuartzRandRGetModeInfo(modeRef, &modeInfo); 266 modeInfo.ref = modeRef; 267 cb = callback(pScreen, &modeInfo, data); 268 if (cb == CALLBACK_CONTINUE) { 269 retval = TRUE; 270 } else if (cb == CALLBACK_SUCCESS) { 271 CFRelease(modes); 272 CFRelease(curPixelEnc); 273 return TRUE; 274 } else if (cb == CALLBACK_ERROR) { 275 CFRelease(modes); 276 CFRelease(curPixelEnc); 277 return FALSE; 278 } 279 } 280 281 CFRelease(modes); 282 CFRelease(curPixelEnc); 283 284 switch(callback(pScreen, &pQuartzScreen->rootlessMode, data)) { 285 case CALLBACK_SUCCESS: 286 return TRUE; 287 case CALLBACK_ERROR: 288 return FALSE; 289 case CALLBACK_CONTINUE: 290 retval = TRUE; 291 default: 292 break; 293 } 294 295 switch(callback(pScreen, &pQuartzScreen->fullscreenMode, data)) { 296 case CALLBACK_SUCCESS: 297 return TRUE; 298 case CALLBACK_ERROR: 299 return FALSE; 300 case CALLBACK_CONTINUE: 301 retval = TRUE; 302 default: 303 break; 304 } 305 306 return retval; 307} 308 309#endif /* Snow Leopard CoreGraphics APIs */ 310 311 312static Bool QuartzRandRModesEqual (QuartzModeInfoPtr pMode1, 313 QuartzModeInfoPtr pMode2) { 314 return (pMode1->width == pMode2->width) && 315 (pMode1->height == pMode2->height) && 316 (pMode1->refresh == pMode2->refresh); 317} 318 319static Bool QuartzRandRRegisterMode (ScreenPtr pScreen, 320 QuartzModeInfoPtr pMode) { 321 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 322 Bool isCurrentMode = QuartzRandRModesEqual(&pQuartzScreen->currentMode, pMode); 323 324 /* TODO: DPI */ 325 pMode->pSize = RRRegisterSize(pScreen, pMode->width, pMode->height, pScreen->mmWidth, pScreen->mmHeight); 326 if (pMode->pSize) { 327 //DEBUG_LOG("registering: %d x %d @ %d %s\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh, isCurrentMode ? "*" : ""); 328 RRRegisterRate(pScreen, pMode->pSize, pMode->refresh); 329 330 if (isCurrentMode) 331 RRSetCurrentConfig(pScreen, RR_Rotate_0, pMode->refresh, pMode->pSize); 332 333 return TRUE; 334 } 335 return FALSE; 336} 337 338static int QuartzRandRRegisterModeCallback (ScreenPtr pScreen, 339 QuartzModeInfoPtr pMode, 340 void *data __unused) { 341 if(QuartzRandRRegisterMode(pScreen, pMode)) { 342 return CALLBACK_CONTINUE; 343 } else { 344 return CALLBACK_ERROR; 345 } 346} 347 348static Bool QuartzRandRSetMode(ScreenPtr pScreen, QuartzModeInfoPtr pMode, BOOL doRegister) { 349 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 350 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; 351 Bool captureDisplay = (pMode->refresh != FAKE_REFRESH_FULLSCREEN && pMode->refresh != FAKE_REFRESH_ROOTLESS); 352 353 if(XQuartzShieldingWindowLevel == 0 && captureDisplay) { 354 if(!X11ApplicationCanEnterRandR()) 355 return FALSE; 356 CGCaptureAllDisplays(); 357 XQuartzShieldingWindowLevel = CGShieldingWindowLevel(); // 2147483630 358 DEBUG_LOG("Display captured. ShieldWindowID: %u, Shield level: %d\n", 359 CGShieldingWindowID(screenId), XQuartzShieldingWindowLevel); 360 } 361 362 if (pQuartzScreen->currentMode.ref && CFEqual(pMode->ref, pQuartzScreen->currentMode.ref)) { 363 DEBUG_LOG("Requested RandR resolution matches current CG mode\n"); 364 } if (QuartzRandRSetCGMode(screenId, pMode)) { 365 ignore_next_fake_mode_update = TRUE; 366 } else { 367 DEBUG_LOG("Error while requesting CG resolution change.\n"); 368 return FALSE; 369 } 370 371 /* If the client requested the fake rootless mode, switch to rootless. 372 * Otherwise, force fullscreen mode. 373 */ 374 QuartzSetRootless(pMode->refresh == FAKE_REFRESH_ROOTLESS); 375 if (pMode->refresh != FAKE_REFRESH_ROOTLESS) { 376 QuartzShowFullscreen(TRUE); 377 } 378 379 if(pQuartzScreen->currentMode.ref) 380 CFRelease(pQuartzScreen->currentMode.ref); 381 pQuartzScreen->currentMode = *pMode; 382 CFRetain(pQuartzScreen->currentMode.ref); 383 384 if(XQuartzShieldingWindowLevel != 0 && !captureDisplay) { 385 CGReleaseAllDisplays(); 386 XQuartzShieldingWindowLevel = 0; 387 } 388 389 return TRUE; 390} 391 392static int QuartzRandRSetModeCallback (ScreenPtr pScreen, 393 QuartzModeInfoPtr pMode, 394 void *data) { 395 QuartzModeInfoPtr pReqMode = (QuartzModeInfoPtr) data; 396 397 if (!QuartzRandRModesEqual(pMode, pReqMode)) 398 return CALLBACK_CONTINUE; /* continue enumeration */ 399 400 DEBUG_LOG("Found a match for requested RandR resolution (%dx%d@%d).\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh); 401 402 if(QuartzRandRSetMode(pScreen, pMode, FALSE)) 403 return CALLBACK_SUCCESS; 404 else 405 return CALLBACK_ERROR; 406} 407 408static Bool QuartzRandRGetInfo (ScreenPtr pScreen, Rotation *rotations) { 409 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 410 411 *rotations = RR_Rotate_0; /* TODO: support rotation */ 412 413 if (pQuartzScreen->displayCount == 0) 414 return FALSE; 415 416 return QuartzRandREnumerateModes(pScreen, QuartzRandRRegisterModeCallback, NULL); 417} 418 419static Bool QuartzRandRSetConfig (ScreenPtr pScreen, 420 Rotation randr, 421 int rate, 422 RRScreenSizePtr pSize) { 423 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 424 QuartzModeInfo reqMode; 425 426 reqMode.width = pSize->width; 427 reqMode.height = pSize->height; 428 reqMode.refresh = rate; 429 430 if (pQuartzScreen->displayCount == 0) 431 return FALSE; 432 433 /* Do not switch modes if requested mode is equal to current mode. */ 434 if (QuartzRandRModesEqual(&reqMode, &pQuartzScreen->currentMode)) 435 return TRUE; 436 437 if (QuartzRandREnumerateModes(pScreen, QuartzRandRSetModeCallback, &reqMode)) { 438 return TRUE; 439 } 440 441 DEBUG_LOG("Unable to find a matching config: %d x %d @ %d\n", (int)reqMode.width, (int)reqMode.height, (int)reqMode.refresh); 442 return FALSE; 443} 444 445static Bool _QuartzRandRUpdateFakeModes (ScreenPtr pScreen) { 446 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 447 448 if(pQuartzScreen->fullscreenMode.ref) 449 CFRelease(pQuartzScreen->fullscreenMode.ref); 450 if(pQuartzScreen->currentMode.ref) 451 CFRelease(pQuartzScreen->currentMode.ref); 452 453 if (!QuartzRandRCopyCurrentModeInfo(pQuartzScreen->displayIDs[0], 454 &pQuartzScreen->fullscreenMode)) 455 return FALSE; 456 457 if (pQuartzScreen->displayCount > 1) { 458 pQuartzScreen->fullscreenMode.width = pScreen->width; 459 pQuartzScreen->fullscreenMode.height = pScreen->height; 460 if(XQuartzIsRootless) 461 pQuartzScreen->fullscreenMode.height += aquaMenuBarHeight; 462 } 463 464 pQuartzScreen->fullscreenMode.refresh = FAKE_REFRESH_FULLSCREEN; 465 466 pQuartzScreen->rootlessMode = pQuartzScreen->fullscreenMode; 467 pQuartzScreen->rootlessMode.refresh = FAKE_REFRESH_ROOTLESS; 468 pQuartzScreen->rootlessMode.height -= aquaMenuBarHeight; 469 470 if(XQuartzIsRootless) { 471 pQuartzScreen->currentMode = pQuartzScreen->rootlessMode; 472 } else { 473 pQuartzScreen->currentMode = pQuartzScreen->fullscreenMode; 474 } 475 476 /* This extra retain is for currentMode's copy. 477 * fullscreen and rootless share a retain. 478 */ 479 CFRetain(pQuartzScreen->currentMode.ref); 480 481 DEBUG_LOG("rootlessMode: %d x %d\n", (int)pQuartzScreen->rootlessMode.width, (int)pQuartzScreen->rootlessMode.height); 482 DEBUG_LOG("fullscreenMode: %d x %d\n", (int)pQuartzScreen->fullscreenMode.width, (int)pQuartzScreen->fullscreenMode.height); 483 DEBUG_LOG("currentMode: %d x %d\n", (int)pQuartzScreen->currentMode.width, (int)pQuartzScreen->currentMode.height); 484 485 return TRUE; 486} 487 488Bool QuartzRandRUpdateFakeModes (BOOL force_update) { 489 ScreenPtr pScreen = screenInfo.screens[0]; 490 491 if(ignore_next_fake_mode_update) { 492 DEBUG_LOG("Ignoring update request caused by RandR resolution change.\n"); 493 ignore_next_fake_mode_update = FALSE; 494 return TRUE; 495 } 496 497 if(!_QuartzRandRUpdateFakeModes(pScreen)) 498 return FALSE; 499 500 if(force_update) 501 RRGetInfo(pScreen, TRUE); 502 503 return TRUE; 504} 505 506Bool QuartzRandRInit (ScreenPtr pScreen) { 507 rrScrPrivPtr pScrPriv; 508 509 if (!RRScreenInit (pScreen)) return FALSE; 510 if (!_QuartzRandRUpdateFakeModes (pScreen)) return FALSE; 511 512 pScrPriv = rrGetScrPriv(pScreen); 513 pScrPriv->rrGetInfo = QuartzRandRGetInfo; 514 pScrPriv->rrSetConfig = QuartzRandRSetConfig; 515 return TRUE; 516} 517 518void QuartzRandRSetFakeRootless (void) { 519 int i; 520 521 DEBUG_LOG("QuartzRandRSetFakeRootless called.\n"); 522 523 for (i=0; i < screenInfo.numScreens; i++) { 524 ScreenPtr pScreen = screenInfo.screens[i]; 525 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 526 527 QuartzRandRSetMode(pScreen, &pQuartzScreen->rootlessMode, TRUE); 528 } 529} 530 531void QuartzRandRSetFakeFullscreen (BOOL state) { 532 int i; 533 534 DEBUG_LOG("QuartzRandRSetFakeFullscreen called.\n"); 535 536 for (i=0; i < screenInfo.numScreens; i++) { 537 ScreenPtr pScreen = screenInfo.screens[i]; 538 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 539 540 QuartzRandRSetMode(pScreen, &pQuartzScreen->fullscreenMode, TRUE); 541 } 542 543 QuartzShowFullscreen(state); 544} 545 546/* Toggle fullscreen mode. If "fake" fullscreen is the current mode, 547 * this will just show/hide the X11 windows. If we are in a RandR fullscreen 548 * mode, this will toggles us to the default fake mode and hide windows if 549 * it is fullscreen 550 */ 551void QuartzRandRToggleFullscreen (void) { 552 ScreenPtr pScreen = screenInfo.screens[0]; 553 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 554 555 if (pQuartzScreen->currentMode.ref == NULL) { 556 ErrorF("Ignoring QuartzRandRToggleFullscreen because don't have a current mode set.\n"); 557 } else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_ROOTLESS) { 558 ErrorF("Ignoring QuartzRandRToggleFullscreen because we are in rootless mode.\n"); 559 } else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_FULLSCREEN) { 560 /* Legacy fullscreen mode. Hide/Show */ 561 QuartzShowFullscreen(!XQuartzFullscreenVisible); 562 } else { 563 /* RandR fullscreen mode. Return to default mode and hide if it is fullscreen. */ 564 if(XQuartzRootlessDefault) { 565 QuartzRandRSetFakeRootless(); 566 } else { 567 QuartzRandRSetFakeFullscreen(FALSE); 568 } 569 } 570} 571