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