quartzRandR.c revision 6747b715
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 <AvailabilityMacros.h> 44 45#include <X11/extensions/randr.h> 46#include <randrstr.h> 47#include <IOKit/graphics/IOGraphicsTypes.h> 48 49/* TODO: UGLY, find a better way! 50 * We want to ignore kXquartzDisplayChanged which are generated by us 51 */ 52static Bool ignore_next_fake_mode_update = FALSE; 53 54#define FAKE_REFRESH_ROOTLESS 1 55#define FAKE_REFRESH_FULLSCREEN 2 56 57#define DEFAULT_REFRESH 60 58#define kDisplayModeUsableFlags (kDisplayModeValidFlag | kDisplayModeSafeFlag) 59 60#define CALLBACK_SUCCESS 0 61#define CALLBACK_CONTINUE 1 62#define CALLBACK_ERROR -1 63 64typedef int (*QuartzModeCallback) 65 (ScreenPtr, QuartzModeInfoPtr, void *); 66 67#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 68 69static long getDictLong (CFDictionaryRef dictRef, CFStringRef key) { 70 long value; 71 72 CFNumberRef numRef = (CFNumberRef) CFDictionaryGetValue(dictRef, key); 73 if (!numRef) 74 return 0; 75 76 if (!CFNumberGetValue(numRef, kCFNumberLongType, &value)) 77 return 0; 78 return value; 79} 80 81static double getDictDouble (CFDictionaryRef dictRef, CFStringRef key) { 82 double value; 83 84 CFNumberRef numRef = (CFNumberRef) CFDictionaryGetValue(dictRef, key); 85 if (!numRef) 86 return 0.0; 87 88 if (!CFNumberGetValue(numRef, kCFNumberDoubleType, &value)) 89 return 0.0; 90 return value; 91} 92 93static void QuartzRandRGetModeInfo (CFDictionaryRef modeRef, 94 QuartzModeInfoPtr pMode) { 95 pMode->width = (size_t) getDictLong(modeRef, kCGDisplayWidth); 96 pMode->height = (size_t) getDictLong(modeRef, kCGDisplayHeight); 97 pMode->refresh = (int)(getDictDouble(modeRef, kCGDisplayRefreshRate) + 0.5); 98 if (pMode->refresh == 0) 99 pMode->refresh = DEFAULT_REFRESH; 100 pMode->ref = NULL; 101 pMode->pSize = NULL; 102} 103 104static Bool QuartzRandRCopyCurrentModeInfo (CGDirectDisplayID screenId, 105 QuartzModeInfoPtr pMode) { 106 CFDictionaryRef curModeRef = CGDisplayCurrentMode(screenId); 107 if (!curModeRef) 108 return FALSE; 109 110 QuartzRandRGetModeInfo(curModeRef, pMode); 111 pMode->ref = (void *)curModeRef; 112 CFRetain(pMode->ref); 113 return TRUE; 114} 115 116static Bool QuartzRandRSetCGMode (CGDirectDisplayID screenId, 117 QuartzModeInfoPtr pMode) { 118 CFDictionaryRef modeRef = (CFDictionaryRef) pMode->ref; 119 return (CGDisplaySwitchToMode(screenId, modeRef) == kCGErrorSuccess); 120} 121 122static Bool QuartzRandREnumerateModes (ScreenPtr pScreen, 123 QuartzModeCallback callback, 124 void *data) { 125 CFDictionaryRef curModeRef, modeRef; 126 long curBpp; 127 CFArrayRef modes; 128 QuartzModeInfo modeInfo; 129 int i; 130 BOOL retval = FALSE; 131 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 132 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; 133 134 curModeRef = CGDisplayCurrentMode(screenId); 135 if (!curModeRef) 136 return FALSE; 137 curBpp = getDictLong(curModeRef, kCGDisplayBitsPerPixel); 138 139 modes = CGDisplayAvailableModes(screenId); 140 if (!modes) 141 return FALSE; 142 for (i = 0; i < CFArrayGetCount(modes); i++) { 143 int cb; 144 modeRef = (CFDictionaryRef) CFArrayGetValueAtIndex(modes, i); 145 146 /* Skip modes that are not usable on the current display or have a 147 different pixel encoding than the current mode. */ 148 if (((unsigned long) getDictLong(modeRef, kCGDisplayIOFlags) & 149 kDisplayModeUsableFlags) != kDisplayModeUsableFlags) 150 continue; 151 if (getDictLong(modeRef, kCGDisplayBitsPerPixel) != curBpp) 152 continue; 153 154 QuartzRandRGetModeInfo(modeRef, &modeInfo); 155 modeInfo.ref = (void *)modeRef; 156 cb = callback(pScreen, &modeInfo, data); 157 if (cb == CALLBACK_CONTINUE) 158 retval = TRUE; 159 else if (cb == CALLBACK_SUCCESS) 160 return TRUE; 161 else if (cb == CALLBACK_ERROR) 162 return FALSE; 163 } 164 165 switch(callback(pScreen, &pQuartzScreen->rootlessMode, data)) { 166 case CALLBACK_SUCCESS: 167 return TRUE; 168 case CALLBACK_ERROR: 169 return FALSE; 170 case CALLBACK_CONTINUE: 171 retval = TRUE; 172 default: 173 break; 174 } 175 176 switch(callback(pScreen, &pQuartzScreen->fullscreenMode, data)) { 177 case CALLBACK_SUCCESS: 178 return TRUE; 179 case CALLBACK_ERROR: 180 return FALSE; 181 case CALLBACK_CONTINUE: 182 retval = TRUE; 183 default: 184 break; 185 } 186 187 return retval; 188} 189 190#else /* we have the new CG APIs from Snow Leopard */ 191 192static void QuartzRandRGetModeInfo (CGDisplayModeRef modeRef, 193 QuartzModeInfoPtr pMode) { 194 pMode->width = CGDisplayModeGetWidth(modeRef); 195 pMode->height = CGDisplayModeGetHeight(modeRef); 196 pMode->refresh = (int) (CGDisplayModeGetRefreshRate(modeRef) + 0.5); 197 if (pMode->refresh == 0) 198 pMode->refresh = DEFAULT_REFRESH; 199 pMode->ref = NULL; 200 pMode->pSize = NULL; 201} 202 203static Bool QuartzRandRCopyCurrentModeInfo (CGDirectDisplayID screenId, 204 QuartzModeInfoPtr pMode) { 205 CGDisplayModeRef curModeRef = CGDisplayCopyDisplayMode(screenId); 206 if (!curModeRef) 207 return FALSE; 208 209 QuartzRandRGetModeInfo(curModeRef, pMode); 210 pMode->ref = curModeRef; 211 return TRUE; 212} 213 214static Bool QuartzRandRSetCGMode (CGDirectDisplayID screenId, 215 QuartzModeInfoPtr pMode) { 216 CGDisplayModeRef modeRef = (CGDisplayModeRef) pMode->ref; 217 if (!modeRef) 218 return FALSE; 219 220 return (CGDisplaySetDisplayMode(screenId, modeRef, NULL) == kCGErrorSuccess); 221} 222 223static Bool QuartzRandREnumerateModes (ScreenPtr pScreen, 224 QuartzModeCallback callback, 225 void *data) { 226 CGDisplayModeRef curModeRef, modeRef; 227 CFStringRef curPixelEnc, pixelEnc; 228 CFComparisonResult pixelEncEqual; 229 CFArrayRef modes; 230 QuartzModeInfo modeInfo; 231 int i; 232 Bool retval = FALSE; 233 234 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 235 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; 236 237 curModeRef = CGDisplayCopyDisplayMode(screenId); 238 if (!curModeRef) 239 return FALSE; 240 curPixelEnc = CGDisplayModeCopyPixelEncoding(curModeRef); 241 CGDisplayModeRelease(curModeRef); 242 243 modes = CGDisplayCopyAllDisplayModes(screenId, NULL); 244 if (!modes) { 245 CFRelease(curPixelEnc); 246 return FALSE; 247 } 248 for (i = 0; i < CFArrayGetCount(modes); i++) { 249 int cb; 250 modeRef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 251 252 /* Skip modes that are not usable on the current display or have a 253 different pixel encoding than the current mode. */ 254 if ((CGDisplayModeGetIOFlags(modeRef) & kDisplayModeUsableFlags) != 255 kDisplayModeUsableFlags) 256 continue; 257 pixelEnc = CGDisplayModeCopyPixelEncoding(modeRef); 258 pixelEncEqual = CFStringCompare(pixelEnc, curPixelEnc, 0); 259 CFRelease(pixelEnc); 260 if (pixelEncEqual != kCFCompareEqualTo) 261 continue; 262 263 QuartzRandRGetModeInfo(modeRef, &modeInfo); 264 modeInfo.ref = modeRef; 265 cb = callback(pScreen, &modeInfo, data); 266 if (cb == CALLBACK_CONTINUE) { 267 retval = TRUE; 268 } else if (cb == CALLBACK_SUCCESS) { 269 CFRelease(modes); 270 CFRelease(curPixelEnc); 271 return TRUE; 272 } else if (cb == CALLBACK_ERROR) { 273 CFRelease(modes); 274 CFRelease(curPixelEnc); 275 return FALSE; 276 } 277 } 278 279 CFRelease(modes); 280 CFRelease(curPixelEnc); 281 282 switch(callback(pScreen, &pQuartzScreen->rootlessMode, data)) { 283 case CALLBACK_SUCCESS: 284 return TRUE; 285 case CALLBACK_ERROR: 286 return FALSE; 287 case CALLBACK_CONTINUE: 288 retval = TRUE; 289 default: 290 break; 291 } 292 293 switch(callback(pScreen, &pQuartzScreen->fullscreenMode, data)) { 294 case CALLBACK_SUCCESS: 295 return TRUE; 296 case CALLBACK_ERROR: 297 return FALSE; 298 case CALLBACK_CONTINUE: 299 retval = TRUE; 300 default: 301 break; 302 } 303 304 return retval; 305} 306 307#endif /* Snow Leopard CoreGraphics APIs */ 308 309 310static Bool QuartzRandRModesEqual (QuartzModeInfoPtr pMode1, 311 QuartzModeInfoPtr pMode2) { 312 return (pMode1->width == pMode2->width) && 313 (pMode1->height == pMode2->height) && 314 (pMode1->refresh == pMode2->refresh); 315} 316 317static Bool QuartzRandRRegisterMode (ScreenPtr pScreen, 318 QuartzModeInfoPtr pMode) { 319 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 320 Bool isCurrentMode = QuartzRandRModesEqual(&pQuartzScreen->currentMode, pMode); 321 322 /* TODO: DPI */ 323 pMode->pSize = RRRegisterSize(pScreen, pMode->width, pMode->height, pScreen->mmWidth, pScreen->mmHeight); 324 if (pMode->pSize) { 325 //DEBUG_LOG("registering: %d x %d @ %d %s\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh, isCurrentMode ? "*" : ""); 326 RRRegisterRate(pScreen, pMode->pSize, pMode->refresh); 327 328 if (isCurrentMode) 329 RRSetCurrentConfig(pScreen, RR_Rotate_0, pMode->refresh, pMode->pSize); 330 331 return TRUE; 332 } 333 return FALSE; 334} 335 336static int QuartzRandRRegisterModeCallback (ScreenPtr pScreen, 337 QuartzModeInfoPtr pMode, 338 void *data __unused) { 339 if(QuartzRandRRegisterMode(pScreen, pMode)) { 340 return CALLBACK_CONTINUE; 341 } else { 342 return CALLBACK_ERROR; 343 } 344} 345 346static Bool QuartzRandRSetMode(ScreenPtr pScreen, QuartzModeInfoPtr pMode, BOOL doRegister) { 347 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 348 CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; 349 350 if (pQuartzScreen->currentMode.ref && CFEqual(pMode->ref, pQuartzScreen->currentMode.ref)) { 351 DEBUG_LOG("Requested RandR resolution matches current CG mode\n"); 352 } if (QuartzRandRSetCGMode(screenId, pMode)) { 353 ignore_next_fake_mode_update = TRUE; 354 } else { 355 DEBUG_LOG("Error while requesting CG resolution change.\n"); 356 return FALSE; 357 } 358 359 /* If the client requested the fake rootless mode, switch to rootless. 360 * Otherwise, force fullscreen mode. 361 */ 362 QuartzSetRootless(pMode->refresh == FAKE_REFRESH_ROOTLESS); 363 if (pMode->refresh != FAKE_REFRESH_ROOTLESS) { 364 QuartzShowFullscreen(TRUE); 365 } 366 367 if(pQuartzScreen->currentMode.ref) 368 CFRelease(pQuartzScreen->currentMode.ref); 369 pQuartzScreen->currentMode = *pMode; 370 CFRetain(pQuartzScreen->currentMode.ref); 371 372 return TRUE; 373} 374 375static int QuartzRandRSetModeCallback (ScreenPtr pScreen, 376 QuartzModeInfoPtr pMode, 377 void *data) { 378 QuartzModeInfoPtr pReqMode = (QuartzModeInfoPtr) data; 379 380 if (!QuartzRandRModesEqual(pMode, pReqMode)) 381 return CALLBACK_CONTINUE; /* continue enumeration */ 382 383 DEBUG_LOG("Found a match for requested RandR resolution (%dx%d@%d).\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh); 384 385 if(QuartzRandRSetMode(pScreen, pMode, FALSE)) 386 return CALLBACK_SUCCESS; 387 else 388 return CALLBACK_ERROR; 389} 390 391static Bool QuartzRandRGetInfo (ScreenPtr pScreen, Rotation *rotations) { 392 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 393 394 *rotations = RR_Rotate_0; /* TODO: support rotation */ 395 396 if (pQuartzScreen->displayCount == 0) 397 return FALSE; 398 399 if (pQuartzScreen->displayCount > 1) { 400 /* RandR operations are not well-defined for an X11 screen spanning 401 multiple CG displays. Create two entries for the current virtual 402 resolution including/excluding the menu bar. */ 403 404 QuartzRandRRegisterMode(pScreen, &pQuartzScreen->rootlessMode); 405 QuartzRandRRegisterMode(pScreen, &pQuartzScreen->fullscreenMode); 406 return TRUE; 407 } 408 409 return QuartzRandREnumerateModes(pScreen, QuartzRandRRegisterModeCallback, NULL); 410} 411 412static Bool QuartzRandRSetConfig (ScreenPtr pScreen, 413 Rotation randr, 414 int rate, 415 RRScreenSizePtr pSize) { 416 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 417 QuartzModeInfo reqMode; 418 419 reqMode.width = pSize->width; 420 reqMode.height = pSize->height; 421 reqMode.refresh = rate; 422 423 if (pQuartzScreen->displayCount == 0) 424 return FALSE; 425 426 /* Do not switch modes if requested mode is equal to current mode. */ 427 if (QuartzRandRModesEqual(&reqMode, &pQuartzScreen->currentMode)) 428 return TRUE; 429 430 if (QuartzRandREnumerateModes(pScreen, QuartzRandRSetModeCallback, &reqMode)) { 431 return TRUE; 432 } 433 434 DEBUG_LOG("Unable to find a matching config: %d x %d @ %d\n", (int)reqMode.width, (int)reqMode.height, (int)reqMode.refresh); 435 return FALSE; 436} 437 438static Bool _QuartzRandRUpdateFakeModes (ScreenPtr pScreen) { 439 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 440 441 if (pQuartzScreen->displayCount == 1) { 442 if(pQuartzScreen->fullscreenMode.ref) 443 CFRelease(pQuartzScreen->fullscreenMode.ref); 444 if(pQuartzScreen->currentMode.ref) 445 CFRelease(pQuartzScreen->currentMode.ref); 446 447 if (!QuartzRandRCopyCurrentModeInfo(pQuartzScreen->displayIDs[0], 448 &pQuartzScreen->fullscreenMode)) 449 return FALSE; 450 451 CFRetain(pQuartzScreen->fullscreenMode.ref); /* This extra retain is for currentMode's copy */ 452 } else { 453 pQuartzScreen->fullscreenMode.width = pScreen->width; 454 pQuartzScreen->fullscreenMode.height = pScreen->height; 455 if(XQuartzIsRootless) 456 pQuartzScreen->fullscreenMode.height += aquaMenuBarHeight; 457 } 458 459 pQuartzScreen->fullscreenMode.refresh = FAKE_REFRESH_FULLSCREEN; 460 461 pQuartzScreen->rootlessMode = pQuartzScreen->fullscreenMode; 462 pQuartzScreen->rootlessMode.refresh = FAKE_REFRESH_ROOTLESS; 463 pQuartzScreen->rootlessMode.height -= aquaMenuBarHeight; 464 465 if(XQuartzIsRootless) { 466 pQuartzScreen->currentMode = pQuartzScreen->rootlessMode; 467 } else { 468 pQuartzScreen->currentMode = pQuartzScreen->fullscreenMode; 469 } 470 471 DEBUG_LOG("rootlessMode: %d x %d\n", (int)pQuartzScreen->rootlessMode.width, (int)pQuartzScreen->rootlessMode.height); 472 DEBUG_LOG("fullscreenMode: %d x %d\n", (int)pQuartzScreen->fullscreenMode.width, (int)pQuartzScreen->fullscreenMode.height); 473 DEBUG_LOG("currentMode: %d x %d\n", (int)pQuartzScreen->currentMode.width, (int)pQuartzScreen->currentMode.height); 474 475 return TRUE; 476} 477 478Bool QuartzRandRUpdateFakeModes (BOOL force_update) { 479 ScreenPtr pScreen = screenInfo.screens[0]; 480 481 if(ignore_next_fake_mode_update) { 482 DEBUG_LOG("Ignoring update request caused by RandR resolution change.\n"); 483 ignore_next_fake_mode_update = FALSE; 484 return TRUE; 485 } 486 487 if(!_QuartzRandRUpdateFakeModes(pScreen)) 488 return FALSE; 489 490 if(force_update) 491 RRGetInfo(pScreen, TRUE); 492 493 return TRUE; 494} 495 496Bool QuartzRandRInit (ScreenPtr pScreen) { 497 rrScrPrivPtr pScrPriv; 498 499 if (!RRScreenInit (pScreen)) return FALSE; 500 if (!_QuartzRandRUpdateFakeModes (pScreen)) return FALSE; 501 502 pScrPriv = rrGetScrPriv(pScreen); 503 pScrPriv->rrGetInfo = QuartzRandRGetInfo; 504 pScrPriv->rrSetConfig = QuartzRandRSetConfig; 505 return TRUE; 506} 507 508void QuartzRandRSetFakeRootless (void) { 509 int i; 510 511 DEBUG_LOG("QuartzRandRSetFakeRootless called.\n"); 512 513 for (i=0; i < screenInfo.numScreens; i++) { 514 ScreenPtr pScreen = screenInfo.screens[i]; 515 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 516 517 QuartzRandRSetMode(pScreen, &pQuartzScreen->rootlessMode, TRUE); 518 } 519} 520 521void QuartzRandRSetFakeFullscreen (BOOL state) { 522 int i; 523 524 DEBUG_LOG("QuartzRandRSetFakeFullscreen called.\n"); 525 526 for (i=0; i < screenInfo.numScreens; i++) { 527 ScreenPtr pScreen = screenInfo.screens[i]; 528 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 529 530 QuartzRandRSetMode(pScreen, &pQuartzScreen->fullscreenMode, TRUE); 531 } 532 533 QuartzShowFullscreen(state); 534} 535 536/* Toggle fullscreen mode. If "fake" fullscreen is the current mode, 537 * this will just show/hide the X11 windows. If we are in a RandR fullscreen 538 * mode, this will toggles us to the default fake mode and hide windows if 539 * it is fullscreen 540 */ 541void QuartzRandRToggleFullscreen (void) { 542 ScreenPtr pScreen = screenInfo.screens[0]; 543 QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); 544 545 if (pQuartzScreen->currentMode.ref == NULL) { 546 ErrorF("Ignoring QuartzRandRToggleFullscreen because don't have a current mode set.\n"); 547 } else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_ROOTLESS) { 548 ErrorF("Ignoring QuartzRandRToggleFullscreen because we are in rootless mode.\n"); 549 } else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_FULLSCREEN) { 550 /* Legacy fullscreen mode. Hide/Show */ 551 QuartzShowFullscreen(!XQuartzFullscreenVisible); 552 } else { 553 /* RandR fullscreen mode. Return to default mode and hide if it is fullscreen. */ 554 if(XQuartzRootlessDefault) { 555 QuartzRandRSetFakeRootless(); 556 } else { 557 QuartzRandRSetFakeFullscreen(FALSE); 558 } 559 } 560} 561