2 * Quartz-specific support for the XRandR extension
4 * Copyright (c) 2001-2004 Greg Parker and Torrey T. Lyons,
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:
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
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.
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.
32 #include "sanitizedCarbon.h"
34 #ifdef HAVE_DIX_CONFIG_H
35 #include <dix-config.h>
38 #include "quartzCommon.h"
39 #include "quartzRandR.h"
43 #include "X11Application.h"
45 #include <AvailabilityMacros.h>
47 #include <X11/extensions/randr.h>
49 #include <IOKit/graphics/IOGraphicsTypes.h>
51 /* TODO: UGLY, find a better way!
52 * We want to ignore kXquartzDisplayChanged which are generated by us
54 static Bool ignore_next_fake_mode_update
= FALSE
;
56 #define FAKE_REFRESH_ROOTLESS 1
57 #define FAKE_REFRESH_FULLSCREEN 2
59 #define DEFAULT_REFRESH 60
60 #define kDisplayModeUsableFlags (kDisplayModeValidFlag | kDisplayModeSafeFlag)
62 #define CALLBACK_SUCCESS 0
63 #define CALLBACK_CONTINUE 1
64 #define CALLBACK_ERROR -1
66 typedef int (*QuartzModeCallback
)
67 (ScreenPtr
, QuartzModeInfoPtr
, void *);
69 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
72 getDictLong(CFDictionaryRef dictRef
, CFStringRef key
)
76 CFNumberRef numRef
= (CFNumberRef
)CFDictionaryGetValue(dictRef
, key
);
80 if (!CFNumberGetValue(numRef
, kCFNumberLongType
, &value
))
86 getDictDouble(CFDictionaryRef dictRef
, CFStringRef key
)
90 CFNumberRef numRef
= (CFNumberRef
)CFDictionaryGetValue(dictRef
, key
);
94 if (!CFNumberGetValue(numRef
, kCFNumberDoubleType
, &value
))
100 QuartzRandRGetModeInfo(CFDictionaryRef modeRef
,
101 QuartzModeInfoPtr pMode
)
103 pMode
->width
= (size_t)getDictLong(modeRef
, kCGDisplayWidth
);
104 pMode
->height
= (size_t)getDictLong(modeRef
, kCGDisplayHeight
);
106 (int)(getDictDouble(modeRef
, kCGDisplayRefreshRate
) + 0.5);
107 if (pMode
->refresh
== 0)
108 pMode
->refresh
= DEFAULT_REFRESH
;
114 QuartzRandRCopyCurrentModeInfo(CGDirectDisplayID screenId
,
115 QuartzModeInfoPtr pMode
)
117 CFDictionaryRef curModeRef
= CGDisplayCurrentMode(screenId
);
121 QuartzRandRGetModeInfo(curModeRef
, pMode
);
122 pMode
->ref
= (void *)curModeRef
;
123 CFRetain(pMode
->ref
);
128 QuartzRandRSetCGMode(CGDirectDisplayID screenId
,
129 QuartzModeInfoPtr pMode
)
131 CFDictionaryRef modeRef
= (CFDictionaryRef
)pMode
->ref
;
132 return (CGDisplaySwitchToMode(screenId
, modeRef
) == kCGErrorSuccess
);
136 QuartzRandREnumerateModes(ScreenPtr pScreen
,
137 QuartzModeCallback callback
,
141 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
143 /* Just an 800x600 fallback if we have no attached heads */
144 if (pQuartzScreen
->displayIDs
) {
145 CFDictionaryRef curModeRef
, modeRef
;
148 QuartzModeInfo modeInfo
;
150 CGDirectDisplayID screenId
= pQuartzScreen
->displayIDs
[0];
152 curModeRef
= CGDisplayCurrentMode(screenId
);
155 curBpp
= getDictLong(curModeRef
, kCGDisplayBitsPerPixel
);
157 modes
= CGDisplayAvailableModes(screenId
);
160 for (i
= 0; i
< CFArrayGetCount(modes
); i
++) {
162 modeRef
= (CFDictionaryRef
)CFArrayGetValueAtIndex(modes
, i
);
164 /* Skip modes that are not usable on the current display or have a
165 different pixel encoding than the current mode. */
166 if (((unsigned long)getDictLong(modeRef
, kCGDisplayIOFlags
) &
167 kDisplayModeUsableFlags
) != kDisplayModeUsableFlags
)
169 if (getDictLong(modeRef
, kCGDisplayBitsPerPixel
) != curBpp
)
172 QuartzRandRGetModeInfo(modeRef
, &modeInfo
);
173 modeInfo
.ref
= (void *)modeRef
;
174 cb
= callback(pScreen
, &modeInfo
, data
);
175 if (cb
== CALLBACK_CONTINUE
)
177 else if (cb
== CALLBACK_SUCCESS
)
179 else if (cb
== CALLBACK_ERROR
)
184 switch (callback(pScreen
, &pQuartzScreen
->rootlessMode
, data
)) {
185 case CALLBACK_SUCCESS
:
191 case CALLBACK_CONTINUE
:
198 switch (callback(pScreen
, &pQuartzScreen
->fullscreenMode
, data
)) {
199 case CALLBACK_SUCCESS
:
205 case CALLBACK_CONTINUE
:
215 #else /* we have the new CG APIs from Snow Leopard */
218 QuartzRandRGetModeInfo(CGDisplayModeRef modeRef
,
219 QuartzModeInfoPtr pMode
)
221 pMode
->width
= CGDisplayModeGetWidth(modeRef
);
222 pMode
->height
= CGDisplayModeGetHeight(modeRef
);
223 pMode
->refresh
= (int)(CGDisplayModeGetRefreshRate(modeRef
) + 0.5);
224 if (pMode
->refresh
== 0)
225 pMode
->refresh
= DEFAULT_REFRESH
;
231 QuartzRandRCopyCurrentModeInfo(CGDirectDisplayID screenId
,
232 QuartzModeInfoPtr pMode
)
234 CGDisplayModeRef curModeRef
= CGDisplayCopyDisplayMode(screenId
);
238 QuartzRandRGetModeInfo(curModeRef
, pMode
);
239 pMode
->ref
= curModeRef
;
244 QuartzRandRSetCGMode(CGDirectDisplayID screenId
,
245 QuartzModeInfoPtr pMode
)
247 CGDisplayModeRef modeRef
= (CGDisplayModeRef
)pMode
->ref
;
251 return (CGDisplaySetDisplayMode(screenId
, modeRef
,
252 NULL
) == kCGErrorSuccess
);
256 QuartzRandREnumerateModes(ScreenPtr pScreen
,
257 QuartzModeCallback callback
,
261 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
263 /* Just an 800x600 fallback if we have no attached heads */
264 if (pQuartzScreen
->displayIDs
) {
265 CGDisplayModeRef curModeRef
, modeRef
;
266 CFStringRef curPixelEnc
, pixelEnc
;
267 CFComparisonResult pixelEncEqual
;
269 QuartzModeInfo modeInfo
;
271 CGDirectDisplayID screenId
= pQuartzScreen
->displayIDs
[0];
273 curModeRef
= CGDisplayCopyDisplayMode(screenId
);
276 curPixelEnc
= CGDisplayModeCopyPixelEncoding(curModeRef
);
277 CGDisplayModeRelease(curModeRef
);
279 modes
= CGDisplayCopyAllDisplayModes(screenId
, NULL
);
281 CFRelease(curPixelEnc
);
284 for (i
= 0; i
< CFArrayGetCount(modes
); i
++) {
286 modeRef
= (CGDisplayModeRef
)CFArrayGetValueAtIndex(modes
, i
);
288 /* Skip modes that are not usable on the current display or have a
289 different pixel encoding than the current mode. */
290 if ((CGDisplayModeGetIOFlags(modeRef
) &
291 kDisplayModeUsableFlags
) !=
292 kDisplayModeUsableFlags
)
294 pixelEnc
= CGDisplayModeCopyPixelEncoding(modeRef
);
295 pixelEncEqual
= CFStringCompare(pixelEnc
, curPixelEnc
, 0);
297 if (pixelEncEqual
!= kCFCompareEqualTo
)
300 QuartzRandRGetModeInfo(modeRef
, &modeInfo
);
301 modeInfo
.ref
= modeRef
;
302 cb
= callback(pScreen
, &modeInfo
, data
);
303 if (cb
== CALLBACK_CONTINUE
) {
306 else if (cb
== CALLBACK_SUCCESS
) {
308 CFRelease(curPixelEnc
);
311 else if (cb
== CALLBACK_ERROR
) {
313 CFRelease(curPixelEnc
);
319 CFRelease(curPixelEnc
);
322 switch (callback(pScreen
, &pQuartzScreen
->rootlessMode
, data
)) {
323 case CALLBACK_SUCCESS
:
329 case CALLBACK_CONTINUE
:
336 switch (callback(pScreen
, &pQuartzScreen
->fullscreenMode
, data
)) {
337 case CALLBACK_SUCCESS
:
343 case CALLBACK_CONTINUE
:
353 #endif /* Snow Leopard CoreGraphics APIs */
356 QuartzRandRModesEqual(QuartzModeInfoPtr pMode1
,
357 QuartzModeInfoPtr pMode2
)
359 return (pMode1
->width
== pMode2
->width
) &&
360 (pMode1
->height
== pMode2
->height
) &&
361 (pMode1
->refresh
== pMode2
->refresh
);
365 QuartzRandRRegisterMode(ScreenPtr pScreen
,
366 QuartzModeInfoPtr pMode
)
368 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
369 Bool isCurrentMode
= QuartzRandRModesEqual(&pQuartzScreen
->currentMode
,
374 RRRegisterSize(pScreen
, pMode
->width
, pMode
->height
, pScreen
->mmWidth
,
377 //DEBUG_LOG("registering: %d x %d @ %d %s\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh, isCurrentMode ? "*" : "");
378 RRRegisterRate(pScreen
, pMode
->pSize
, pMode
->refresh
);
381 RRSetCurrentConfig(pScreen
, RR_Rotate_0
, pMode
->refresh
,
390 QuartzRandRRegisterModeCallback(ScreenPtr pScreen
,
391 QuartzModeInfoPtr pMode
,
394 if (QuartzRandRRegisterMode(pScreen
, pMode
)) {
395 return CALLBACK_CONTINUE
;
398 return CALLBACK_ERROR
;
403 QuartzRandRSetMode(ScreenPtr pScreen
, QuartzModeInfoPtr pMode
,
406 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
407 Bool captureDisplay
=
408 (pMode
->refresh
!= FAKE_REFRESH_FULLSCREEN
&& pMode
->refresh
!=
409 FAKE_REFRESH_ROOTLESS
);
410 CGDirectDisplayID screenId
;
412 if (pQuartzScreen
->displayIDs
== NULL
)
415 screenId
= pQuartzScreen
->displayIDs
[0];
416 if (XQuartzShieldingWindowLevel
== 0 && captureDisplay
) {
417 if (!X11ApplicationCanEnterRandR())
419 CGCaptureAllDisplays();
420 XQuartzShieldingWindowLevel
= CGShieldingWindowLevel(); // 2147483630
421 DEBUG_LOG("Display captured. ShieldWindowID: %u, Shield level: %d\n",
422 CGShieldingWindowID(screenId
), XQuartzShieldingWindowLevel
);
425 if (pQuartzScreen
->currentMode
.ref
&&
426 CFEqual(pMode
->ref
, pQuartzScreen
->currentMode
.ref
)) {
427 DEBUG_LOG("Requested RandR resolution matches current CG mode\n");
429 if (QuartzRandRSetCGMode(screenId
, pMode
)) {
430 ignore_next_fake_mode_update
= TRUE
;
433 DEBUG_LOG("Error while requesting CG resolution change.\n");
437 /* If the client requested the fake rootless mode, switch to rootless.
438 * Otherwise, force fullscreen mode.
440 QuartzSetRootless(pMode
->refresh
== FAKE_REFRESH_ROOTLESS
);
441 if (pMode
->refresh
!= FAKE_REFRESH_ROOTLESS
) {
442 QuartzShowFullscreen(TRUE
);
445 if (pQuartzScreen
->currentMode
.ref
)
446 CFRelease(pQuartzScreen
->currentMode
.ref
);
447 pQuartzScreen
->currentMode
= *pMode
;
448 if (pQuartzScreen
->currentMode
.ref
)
449 CFRetain(pQuartzScreen
->currentMode
.ref
);
451 if (XQuartzShieldingWindowLevel
!= 0 && !captureDisplay
) {
452 CGReleaseAllDisplays();
453 XQuartzShieldingWindowLevel
= 0;
460 QuartzRandRSetModeCallback(ScreenPtr pScreen
,
461 QuartzModeInfoPtr pMode
,
464 QuartzModeInfoPtr pReqMode
= (QuartzModeInfoPtr
)data
;
466 if (!QuartzRandRModesEqual(pMode
, pReqMode
))
467 return CALLBACK_CONTINUE
; /* continue enumeration */
469 DEBUG_LOG("Found a match for requested RandR resolution (%dx%d@%d).\n",
470 (int)pMode
->width
, (int)pMode
->height
, (int)pMode
->refresh
);
472 if (QuartzRandRSetMode(pScreen
, pMode
, FALSE
))
473 return CALLBACK_SUCCESS
;
475 return CALLBACK_ERROR
;
479 QuartzRandRGetInfo(ScreenPtr pScreen
, Rotation
*rotations
)
481 *rotations
= RR_Rotate_0
; /* TODO: support rotation */
483 return QuartzRandREnumerateModes(pScreen
, QuartzRandRRegisterModeCallback
,
488 QuartzRandRSetConfig(ScreenPtr pScreen
,
491 RRScreenSizePtr pSize
)
493 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
494 QuartzModeInfo reqMode
;
496 reqMode
.width
= pSize
->width
;
497 reqMode
.height
= pSize
->height
;
498 reqMode
.refresh
= rate
;
500 /* Do not switch modes if requested mode is equal to current mode. */
501 if (QuartzRandRModesEqual(&reqMode
, &pQuartzScreen
->currentMode
))
504 if (QuartzRandREnumerateModes(pScreen
, QuartzRandRSetModeCallback
,
509 DEBUG_LOG("Unable to find a matching config: %d x %d @ %d\n",
510 (int)reqMode
.width
, (int)reqMode
.height
,
511 (int)reqMode
.refresh
);
516 _QuartzRandRUpdateFakeModes(ScreenPtr pScreen
)
518 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
519 QuartzModeInfo activeMode
;
521 if (pQuartzScreen
->displayCount
> 0) {
522 if (!QuartzRandRCopyCurrentModeInfo(pQuartzScreen
->displayIDs
[0],
524 ErrorF("Unable to determine current display mode.\n");
529 memset(&activeMode
, 0, sizeof(activeMode
));
530 activeMode
.width
= 800;
531 activeMode
.height
= 600;
532 activeMode
.refresh
= 60;
535 if (pQuartzScreen
->fullscreenMode
.ref
)
536 CFRelease(pQuartzScreen
->fullscreenMode
.ref
);
537 if (pQuartzScreen
->currentMode
.ref
)
538 CFRelease(pQuartzScreen
->currentMode
.ref
);
540 if (pQuartzScreen
->displayCount
> 1) {
541 activeMode
.width
= pScreen
->width
;
542 activeMode
.height
= pScreen
->height
;
543 if (XQuartzIsRootless
)
544 activeMode
.height
+= aquaMenuBarHeight
;
547 pQuartzScreen
->fullscreenMode
= activeMode
;
548 pQuartzScreen
->fullscreenMode
.refresh
= FAKE_REFRESH_FULLSCREEN
;
550 pQuartzScreen
->rootlessMode
= activeMode
;
551 pQuartzScreen
->rootlessMode
.refresh
= FAKE_REFRESH_ROOTLESS
;
552 pQuartzScreen
->rootlessMode
.height
-= aquaMenuBarHeight
;
554 if (XQuartzIsRootless
) {
555 pQuartzScreen
->currentMode
= pQuartzScreen
->rootlessMode
;
558 pQuartzScreen
->currentMode
= pQuartzScreen
->fullscreenMode
;
561 /* This extra retain is for currentMode's copy.
562 * fullscreen and rootless share a retain.
564 if (pQuartzScreen
->currentMode
.ref
)
565 CFRetain(pQuartzScreen
->currentMode
.ref
);
567 DEBUG_LOG("rootlessMode: %d x %d\n",
568 (int)pQuartzScreen
->rootlessMode
.width
,
569 (int)pQuartzScreen
->rootlessMode
.height
);
570 DEBUG_LOG("fullscreenMode: %d x %d\n",
571 (int)pQuartzScreen
->fullscreenMode
.width
,
572 (int)pQuartzScreen
->fullscreenMode
.height
);
573 DEBUG_LOG("currentMode: %d x %d\n", (int)pQuartzScreen
->currentMode
.width
,
574 (int)pQuartzScreen
->currentMode
.height
);
580 QuartzRandRUpdateFakeModes(BOOL force_update
)
582 ScreenPtr pScreen
= screenInfo
.screens
[0];
584 if (ignore_next_fake_mode_update
) {
586 "Ignoring update request caused by RandR resolution change.\n");
587 ignore_next_fake_mode_update
= FALSE
;
591 if (!_QuartzRandRUpdateFakeModes(pScreen
))
595 RRGetInfo(pScreen
, TRUE
);
601 QuartzRandRInit(ScreenPtr pScreen
)
603 rrScrPrivPtr pScrPriv
;
605 if (!RRScreenInit(pScreen
)) return FALSE
;
606 if (!_QuartzRandRUpdateFakeModes(pScreen
)) return FALSE
;
608 pScrPriv
= rrGetScrPriv(pScreen
);
609 pScrPriv
->rrGetInfo
= QuartzRandRGetInfo
;
610 pScrPriv
->rrSetConfig
= QuartzRandRSetConfig
;
615 QuartzRandRSetFakeRootless(void)
619 DEBUG_LOG("QuartzRandRSetFakeRootless called.\n");
621 for (i
= 0; i
< screenInfo
.numScreens
; i
++) {
622 ScreenPtr pScreen
= screenInfo
.screens
[i
];
623 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
625 QuartzRandRSetMode(pScreen
, &pQuartzScreen
->rootlessMode
, TRUE
);
630 QuartzRandRSetFakeFullscreen(BOOL state
)
634 DEBUG_LOG("QuartzRandRSetFakeFullscreen called.\n");
636 for (i
= 0; i
< screenInfo
.numScreens
; i
++) {
637 ScreenPtr pScreen
= screenInfo
.screens
[i
];
638 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
640 QuartzRandRSetMode(pScreen
, &pQuartzScreen
->fullscreenMode
, TRUE
);
643 QuartzShowFullscreen(state
);
646 /* Toggle fullscreen mode. If "fake" fullscreen is the current mode,
647 * this will just show/hide the X11 windows. If we are in a RandR fullscreen
648 * mode, this will toggles us to the default fake mode and hide windows if
652 QuartzRandRToggleFullscreen(void)
654 ScreenPtr pScreen
= screenInfo
.screens
[0];
655 QuartzScreenPtr pQuartzScreen
= QUARTZ_PRIV(pScreen
);
657 if (pQuartzScreen
->currentMode
.ref
== NULL
) {
659 "Ignoring QuartzRandRToggleFullscreen because don't have a current mode set.\n");
661 else if (pQuartzScreen
->currentMode
.refresh
== FAKE_REFRESH_ROOTLESS
) {
663 "Ignoring QuartzRandRToggleFullscreen because we are in rootless mode.\n");
665 else if (pQuartzScreen
->currentMode
.refresh
== FAKE_REFRESH_FULLSCREEN
) {
666 /* Legacy fullscreen mode. Hide/Show */
667 QuartzShowFullscreen(!XQuartzFullscreenVisible
);
670 /* RandR fullscreen mode. Return to default mode and hide if it is fullscreen. */
671 if (XQuartzRootlessDefault
) {
672 QuartzRandRSetFakeRootless();
675 QuartzRandRSetFakeFullscreen(FALSE
);