/* * Quartz-specific support for the XRandR extension * * Copyright (c) 2001-2004 Greg Parker and Torrey T. Lyons, * 2010 Jan Hauffa. * 2010-2012 Apple Inc. * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name(s) of the above copyright * holders shall not be used in advertising or otherwise to promote the sale, * use or other dealings in this Software without prior written authorization. */ #include "sanitizedCarbon.h" #ifdef HAVE_DIX_CONFIG_H #include #endif #include "quartzRandR.h" #include "quartz.h" #include "darwin.h" #include "X11Application.h" #include #include #include /* TODO: UGLY, find a better way! * We want to ignore kXquartzDisplayChanged which are generated by us */ static Bool ignore_next_fake_mode_update = FALSE; #define FAKE_REFRESH_ROOTLESS 1 #define FAKE_REFRESH_FULLSCREEN 2 #define DEFAULT_REFRESH 60 #define kDisplayModeUsableFlags (kDisplayModeValidFlag | kDisplayModeSafeFlag) #define CALLBACK_SUCCESS 0 #define CALLBACK_CONTINUE 1 #define CALLBACK_ERROR -1 typedef int (*QuartzModeCallback) (ScreenPtr, QuartzModeInfoPtr, void *); static void QuartzRandRGetModeInfo(CGDisplayModeRef modeRef, QuartzModeInfoPtr pMode) { pMode->width = CGDisplayModeGetWidth(modeRef); pMode->height = CGDisplayModeGetHeight(modeRef); pMode->refresh = (int)(CGDisplayModeGetRefreshRate(modeRef) + 0.5); if (pMode->refresh == 0) pMode->refresh = DEFAULT_REFRESH; pMode->ref = NULL; pMode->pSize = NULL; } static Bool QuartzRandRCopyCurrentModeInfo(CGDirectDisplayID screenId, QuartzModeInfoPtr pMode) { CGDisplayModeRef curModeRef = CGDisplayCopyDisplayMode(screenId); if (!curModeRef) return FALSE; QuartzRandRGetModeInfo(curModeRef, pMode); pMode->ref = curModeRef; return TRUE; } static Bool QuartzRandRSetCGMode(CGDirectDisplayID screenId, QuartzModeInfoPtr pMode) { CGDisplayModeRef modeRef = (CGDisplayModeRef)pMode->ref; if (!modeRef) return FALSE; return (CGDisplaySetDisplayMode(screenId, modeRef, NULL) == kCGErrorSuccess); } static Bool QuartzRandREnumerateModes(ScreenPtr pScreen, QuartzModeCallback callback, void *data) { Bool retval = FALSE; QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); /* Just an 800x600 fallback if we have no attached heads */ if (pQuartzScreen->displayIDs) { CGDisplayModeRef curModeRef, modeRef; CFStringRef curPixelEnc, pixelEnc; CFComparisonResult pixelEncEqual; CFArrayRef modes; QuartzModeInfo modeInfo; int i; CGDirectDisplayID screenId = pQuartzScreen->displayIDs[0]; curModeRef = CGDisplayCopyDisplayMode(screenId); if (!curModeRef) return FALSE; curPixelEnc = CGDisplayModeCopyPixelEncoding(curModeRef); CGDisplayModeRelease(curModeRef); modes = CGDisplayCopyAllDisplayModes(screenId, NULL); if (!modes) { CFRelease(curPixelEnc); return FALSE; } for (i = 0; i < CFArrayGetCount(modes); i++) { int cb; modeRef = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i); /* Skip modes that are not usable on the current display or have a different pixel encoding than the current mode. */ if ((CGDisplayModeGetIOFlags(modeRef) & kDisplayModeUsableFlags) != kDisplayModeUsableFlags) continue; pixelEnc = CGDisplayModeCopyPixelEncoding(modeRef); pixelEncEqual = CFStringCompare(pixelEnc, curPixelEnc, 0); CFRelease(pixelEnc); if (pixelEncEqual != kCFCompareEqualTo) continue; QuartzRandRGetModeInfo(modeRef, &modeInfo); modeInfo.ref = modeRef; cb = callback(pScreen, &modeInfo, data); if (cb == CALLBACK_CONTINUE) { retval = TRUE; } else if (cb == CALLBACK_SUCCESS) { CFRelease(modes); CFRelease(curPixelEnc); return TRUE; } else if (cb == CALLBACK_ERROR) { CFRelease(modes); CFRelease(curPixelEnc); return FALSE; } } CFRelease(modes); CFRelease(curPixelEnc); } switch (callback(pScreen, &pQuartzScreen->rootlessMode, data)) { case CALLBACK_SUCCESS: return TRUE; case CALLBACK_ERROR: return FALSE; case CALLBACK_CONTINUE: retval = TRUE; default: break; } switch (callback(pScreen, &pQuartzScreen->fullscreenMode, data)) { case CALLBACK_SUCCESS: return TRUE; case CALLBACK_ERROR: return FALSE; case CALLBACK_CONTINUE: retval = TRUE; default: break; } return retval; } static Bool QuartzRandRModesEqual(QuartzModeInfoPtr pMode1, QuartzModeInfoPtr pMode2) { return (pMode1->width == pMode2->width) && (pMode1->height == pMode2->height) && (pMode1->refresh == pMode2->refresh); } static Bool QuartzRandRRegisterMode(ScreenPtr pScreen, QuartzModeInfoPtr pMode) { QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); Bool isCurrentMode = QuartzRandRModesEqual(&pQuartzScreen->currentMode, pMode); /* TODO: DPI */ pMode->pSize = RRRegisterSize(pScreen, pMode->width, pMode->height, pScreen->mmWidth, pScreen->mmHeight); if (pMode->pSize) { //DEBUG_LOG("registering: %d x %d @ %d %s\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh, isCurrentMode ? "*" : ""); RRRegisterRate(pScreen, pMode->pSize, pMode->refresh); if (isCurrentMode) RRSetCurrentConfig(pScreen, RR_Rotate_0, pMode->refresh, pMode->pSize); return TRUE; } return FALSE; } static int QuartzRandRRegisterModeCallback(ScreenPtr pScreen, QuartzModeInfoPtr pMode, void *data __unused) { if (QuartzRandRRegisterMode(pScreen, pMode)) { return CALLBACK_CONTINUE; } else { return CALLBACK_ERROR; } } static Bool QuartzRandRSetMode(ScreenPtr pScreen, QuartzModeInfoPtr pMode, BOOL doRegister) { QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); Bool captureDisplay = (pMode->refresh != FAKE_REFRESH_FULLSCREEN && pMode->refresh != FAKE_REFRESH_ROOTLESS); CGDirectDisplayID screenId; if (pQuartzScreen->displayIDs == NULL) return FALSE; screenId = pQuartzScreen->displayIDs[0]; if (XQuartzShieldingWindowLevel == 0 && captureDisplay) { if (!X11ApplicationCanEnterRandR()) return FALSE; CGCaptureAllDisplays(); XQuartzShieldingWindowLevel = CGShieldingWindowLevel(); // 2147483630 DEBUG_LOG("Display captured. ShieldWindowID: %u, Shield level: %d\n", CGShieldingWindowID(screenId), XQuartzShieldingWindowLevel); } if (pQuartzScreen->currentMode.ref && CFEqual(pMode->ref, pQuartzScreen->currentMode.ref)) { DEBUG_LOG("Requested RandR resolution matches current CG mode\n"); } if (QuartzRandRSetCGMode(screenId, pMode)) { ignore_next_fake_mode_update = TRUE; } else { DEBUG_LOG("Error while requesting CG resolution change.\n"); return FALSE; } /* If the client requested the fake rootless mode, switch to rootless. * Otherwise, force fullscreen mode. */ QuartzSetRootless(pMode->refresh == FAKE_REFRESH_ROOTLESS); if (pMode->refresh != FAKE_REFRESH_ROOTLESS) { QuartzShowFullscreen(TRUE); } if (pQuartzScreen->currentMode.ref) CFRelease(pQuartzScreen->currentMode.ref); pQuartzScreen->currentMode = *pMode; if (pQuartzScreen->currentMode.ref) CFRetain(pQuartzScreen->currentMode.ref); if (XQuartzShieldingWindowLevel != 0 && !captureDisplay) { CGReleaseAllDisplays(); XQuartzShieldingWindowLevel = 0; } return TRUE; } static int QuartzRandRSetModeCallback(ScreenPtr pScreen, QuartzModeInfoPtr pMode, void *data) { QuartzModeInfoPtr pReqMode = (QuartzModeInfoPtr)data; if (!QuartzRandRModesEqual(pMode, pReqMode)) return CALLBACK_CONTINUE; /* continue enumeration */ DEBUG_LOG("Found a match for requested RandR resolution (%dx%d@%d).\n", (int)pMode->width, (int)pMode->height, (int)pMode->refresh); if (QuartzRandRSetMode(pScreen, pMode, FALSE)) return CALLBACK_SUCCESS; else return CALLBACK_ERROR; } static Bool QuartzRandRGetInfo(ScreenPtr pScreen, Rotation *rotations) { *rotations = RR_Rotate_0; /* TODO: support rotation */ return QuartzRandREnumerateModes(pScreen, QuartzRandRRegisterModeCallback, NULL); } static Bool QuartzRandRSetConfig(ScreenPtr pScreen, Rotation randr, int rate, RRScreenSizePtr pSize) { QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); QuartzModeInfo reqMode; reqMode.width = pSize->width; reqMode.height = pSize->height; reqMode.refresh = rate; /* Do not switch modes if requested mode is equal to current mode. */ if (QuartzRandRModesEqual(&reqMode, &pQuartzScreen->currentMode)) return TRUE; if (QuartzRandREnumerateModes(pScreen, QuartzRandRSetModeCallback, &reqMode)) { return TRUE; } DEBUG_LOG("Unable to find a matching config: %d x %d @ %d\n", (int)reqMode.width, (int)reqMode.height, (int)reqMode.refresh); return FALSE; } static Bool _QuartzRandRUpdateFakeModes(ScreenPtr pScreen) { QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); QuartzModeInfo activeMode; if (pQuartzScreen->displayCount > 0) { if (!QuartzRandRCopyCurrentModeInfo(pQuartzScreen->displayIDs[0], &activeMode)) { ErrorF("Unable to determine current display mode.\n"); return FALSE; } } else { memset(&activeMode, 0, sizeof(activeMode)); activeMode.width = 800; activeMode.height = 600; activeMode.refresh = 60; } if (pQuartzScreen->fullscreenMode.ref) CFRelease(pQuartzScreen->fullscreenMode.ref); if (pQuartzScreen->currentMode.ref) CFRelease(pQuartzScreen->currentMode.ref); if (pQuartzScreen->displayCount > 1) { activeMode.width = pScreen->width; activeMode.height = pScreen->height; if (XQuartzIsRootless) activeMode.height += aquaMenuBarHeight; } pQuartzScreen->fullscreenMode = activeMode; pQuartzScreen->fullscreenMode.refresh = FAKE_REFRESH_FULLSCREEN; pQuartzScreen->rootlessMode = activeMode; pQuartzScreen->rootlessMode.refresh = FAKE_REFRESH_ROOTLESS; pQuartzScreen->rootlessMode.height -= aquaMenuBarHeight; if (XQuartzIsRootless) { pQuartzScreen->currentMode = pQuartzScreen->rootlessMode; } else { pQuartzScreen->currentMode = pQuartzScreen->fullscreenMode; } /* This extra retain is for currentMode's copy. * fullscreen and rootless share a retain. */ if (pQuartzScreen->currentMode.ref) CFRetain(pQuartzScreen->currentMode.ref); DEBUG_LOG("rootlessMode: %d x %d\n", (int)pQuartzScreen->rootlessMode.width, (int)pQuartzScreen->rootlessMode.height); DEBUG_LOG("fullscreenMode: %d x %d\n", (int)pQuartzScreen->fullscreenMode.width, (int)pQuartzScreen->fullscreenMode.height); DEBUG_LOG("currentMode: %d x %d\n", (int)pQuartzScreen->currentMode.width, (int)pQuartzScreen->currentMode.height); return TRUE; } Bool QuartzRandRUpdateFakeModes(BOOL force_update) { ScreenPtr pScreen = screenInfo.screens[0]; if (ignore_next_fake_mode_update) { DEBUG_LOG( "Ignoring update request caused by RandR resolution change.\n"); ignore_next_fake_mode_update = FALSE; return TRUE; } if (!_QuartzRandRUpdateFakeModes(pScreen)) return FALSE; if (force_update) RRGetInfo(pScreen, TRUE); return TRUE; } Bool QuartzRandRInit(ScreenPtr pScreen) { rrScrPrivPtr pScrPriv; if (!RRScreenInit(pScreen)) return FALSE; if (!_QuartzRandRUpdateFakeModes(pScreen)) return FALSE; pScrPriv = rrGetScrPriv(pScreen); pScrPriv->rrGetInfo = QuartzRandRGetInfo; pScrPriv->rrSetConfig = QuartzRandRSetConfig; return TRUE; } void QuartzRandRSetFakeRootless(void) { int i; DEBUG_LOG("QuartzRandRSetFakeRootless called.\n"); for (i = 0; i < screenInfo.numScreens; i++) { ScreenPtr pScreen = screenInfo.screens[i]; QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); QuartzRandRSetMode(pScreen, &pQuartzScreen->rootlessMode, TRUE); } } void QuartzRandRSetFakeFullscreen(BOOL state) { int i; DEBUG_LOG("QuartzRandRSetFakeFullscreen called.\n"); for (i = 0; i < screenInfo.numScreens; i++) { ScreenPtr pScreen = screenInfo.screens[i]; QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); QuartzRandRSetMode(pScreen, &pQuartzScreen->fullscreenMode, TRUE); } QuartzShowFullscreen(state); } /* Toggle fullscreen mode. If "fake" fullscreen is the current mode, * this will just show/hide the X11 windows. If we are in a RandR fullscreen * mode, this will toggles us to the default fake mode and hide windows if * it is fullscreen */ void QuartzRandRToggleFullscreen(void) { ScreenPtr pScreen = screenInfo.screens[0]; QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); if (pQuartzScreen->currentMode.ref == NULL) { ErrorF( "Ignoring QuartzRandRToggleFullscreen because don't have a current mode set.\n"); } else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_ROOTLESS) { ErrorF( "Ignoring QuartzRandRToggleFullscreen because we are in rootless mode.\n"); } else if (pQuartzScreen->currentMode.refresh == FAKE_REFRESH_FULLSCREEN) { /* Legacy fullscreen mode. Hide/Show */ QuartzShowFullscreen(!XQuartzFullscreenVisible); } else { /* RandR fullscreen mode. Return to default mode and hide if it is fullscreen. */ if (XQuartzRootlessDefault) { QuartzRandRSetFakeRootless(); } else { QuartzRandRSetFakeFullscreen(FALSE); } } }