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