/* * Quartz-specific support for the XRandR extension * * Copyright (c) 2001-2004 Greg Parker and Torrey T. Lyons, * 2010 Jan Hauffa. * 2010 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 "quartzCommon.h" #include "quartzRandR.h" #include "quartz.h" #include "darwin.h" #include #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) typedef Bool (*QuartzModeCallback) (ScreenPtr, CGDirectDisplayID, QuartzModeInfoPtr, void *); #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 static long getDictLong (CFDictionaryRef dictRef, CFStringRef key) { long value; CFNumberRef numRef = (CFNumberRef) CFDictionaryGetValue(dictRef, key); if (!numRef) return 0; if (!CFNumberGetValue(numRef, kCFNumberLongType, &value)) return 0; return value; } static double getDictDouble (CFDictionaryRef dictRef, CFStringRef key) { double value; CFNumberRef numRef = (CFNumberRef) CFDictionaryGetValue(dictRef, key); if (!numRef) return 0.0; if (!CFNumberGetValue(numRef, kCFNumberDoubleType, &value)) return 0.0; return value; } static void QuartzRandRGetModeInfo (CFDictionaryRef modeRef, QuartzModeInfoPtr pMode) { pMode->width = (size_t) getDictLong(modeRef, kCGDisplayWidth); pMode->height = (size_t) getDictLong(modeRef, kCGDisplayHeight); pMode->refresh = (int)(getDictDouble(modeRef, kCGDisplayRefreshRate) + 0.5); if (pMode->refresh == 0) pMode->refresh = DEFAULT_REFRESH; pMode->ref = NULL; } static Bool QuartzRandRGetCurrentModeInfo (CGDirectDisplayID screenId, QuartzModeInfoPtr pMode) { CFDictionaryRef curModeRef = CGDisplayCurrentMode(screenId); if (!curModeRef) return FALSE; QuartzRandRGetModeInfo(curModeRef, pMode); return TRUE; } static Bool QuartzRandRSetMode (CGDirectDisplayID screenId, QuartzModeInfoPtr pMode) { CFDictionaryRef modeRef = (CFDictionaryRef) pMode->ref; return (CGDisplaySwitchToMode(screenId, modeRef) != kCGErrorSuccess); } static Bool QuartzRandREnumerateModes (ScreenPtr pScreen, CGDirectDisplayID screenId, QuartzModeCallback callback, void *data) { CFDictionaryRef curModeRef, modeRef; long curBpp; CFArrayRef modes; QuartzModeInfo modeInfo; int i; curModeRef = CGDisplayCurrentMode(screenId); if (!curModeRef) return FALSE; curBpp = getDictLong(curModeRef, kCGDisplayBitsPerPixel); modes = CGDisplayAvailableModes(screenId); if (!modes) return FALSE; for (i = 0; i < CFArrayGetCount(modes); i++) { modeRef = (CFDictionaryRef) CFArrayGetValueAtIndex(modes, i); /* Skip modes that are not usable on the current display or have a different pixel encoding than the current mode. */ if (((unsigned long) getDictLong(modeRef, kCGDisplayIOFlags) & kDisplayModeUsableFlags) != kDisplayModeUsableFlags) continue; if (getDictLong(modeRef, kCGDisplayBitsPerPixel) != curBpp) continue; QuartzRandRGetModeInfo(modeRef, &modeInfo); modeInfo.ref = modeRef; if (!callback(pScreen, screenId, &modeInfo, data)) break; } return TRUE; } #else /* we have the new CG APIs from Snow Leopard */ 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; } static Bool QuartzRandRGetCurrentModeInfo (CGDirectDisplayID screenId, QuartzModeInfoPtr pMode) { CGDisplayModeRef curModeRef = CGDisplayCopyDisplayMode(screenId); if (!curModeRef) return FALSE; QuartzRandRGetModeInfo(curModeRef, pMode); CGDisplayModeRelease(curModeRef); return TRUE; } static Bool QuartzRandRSetMode (CGDirectDisplayID screenId, QuartzModeInfoPtr pMode) { CGDisplayModeRef modeRef = (CGDisplayModeRef) pMode->ref; if (!modeRef) return FALSE; return (CGDisplaySetDisplayMode(screenId, modeRef, NULL) != kCGErrorSuccess); } static Bool QuartzRandREnumerateModes (ScreenPtr pScreen, CGDirectDisplayID screenId, QuartzModeCallback callback, void *data) { CGDisplayModeRef curModeRef, modeRef; CFStringRef curPixelEnc, pixelEnc; CFComparisonResult pixelEncEqual; CFArrayRef modes; QuartzModeInfo modeInfo; int i; 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++) { 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; if (!callback(pScreen, screenId, &modeInfo, data)) break; } CFRelease(modes); CFRelease(curPixelEnc); return TRUE; } #endif /* Snow Leopard CoreGraphics APIs */ 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); RRScreenSizePtr pSize = RRRegisterSize(pScreen, pMode->width, pMode->height, pScreen->mmWidth, pScreen->mmHeight); if (pSize) { RRRegisterRate(pScreen, pSize, pMode->refresh); if (isCurrentMode) RRSetCurrentConfig(pScreen, RR_Rotate_0, pMode->refresh, pSize); return TRUE; } return FALSE; } static Bool QuartzRandRRegisterModeCallback (ScreenPtr pScreen, CGDirectDisplayID screenId, QuartzModeInfoPtr pMode, void *data __unused) { return QuartzRandRRegisterMode(pScreen, pMode); } static Bool QuartzRandRSetModeCallback (ScreenPtr pScreen, CGDirectDisplayID screenId, QuartzModeInfoPtr pMode, void *data) { QuartzModeInfoPtr pReqMode = (QuartzModeInfoPtr) data; QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); if (!QuartzRandRModesEqual(pMode, pReqMode)) return TRUE; /* continue enumeration */ if (pReqMode->ref == pQuartzScreen->currentMode.ref) { DEBUG_LOG("Requested RandR resolution matches current CG mode\n"); return FALSE; /* We don't need to do anything in CG */ } if (QuartzRandRSetMode(screenId, pMode) == FALSE) { ignore_next_fake_mode_update = TRUE; return FALSE; } else { return TRUE; }; } static Bool QuartzRandRGetInfo (ScreenPtr pScreen, Rotation *rotations) { QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); CGDirectDisplayID screenId; *rotations = RR_Rotate_0; /* TODO: support rotation */ if (pQuartzScreen->displayCount == 0) return FALSE; QuartzRandRRegisterMode(pScreen, &pQuartzScreen->rootlessMode); QuartzRandRRegisterMode(pScreen, &pQuartzScreen->fullScreenMode); if (pQuartzScreen->displayCount > 1) { /* RandR operations are not well-defined for an X11 screen spanning multiple CG displays. Create two entries for the current virtual resolution including/excluding the menu bar. */ return TRUE; } screenId = pQuartzScreen->displayIDs[0]; return QuartzRandREnumerateModes(pScreen, screenId, QuartzRandRRegisterModeCallback, NULL); } static Bool QuartzRandRSetConfig (ScreenPtr pScreen, Rotation randr, int rate, RRScreenSizePtr pSize) { QuartzScreenPtr pQuartzScreen = QUARTZ_PRIV(pScreen); CGDirectDisplayID screenId; QuartzModeInfo reqMode; reqMode.width = pSize->width; reqMode.height = pSize->height; reqMode.refresh = rate; /* If the client requested the fake rootless mode, switch to rootless. * Otherwise, force fullscreen mode. */ QuartzSetFullscreen(reqMode.refresh != FAKE_REFRESH_ROOTLESS); QuartzSetRootless(reqMode.refresh == FAKE_REFRESH_ROOTLESS); if (pQuartzScreen->displayCount == 0) return FALSE; if (pQuartzScreen->displayCount > 1) { /* RandR operations are not well-defined for an X11 screen spanning multiple CG displays. Do not accept any configuations that differ from the current configuration. */ return TRUE; } /* Do not switch modes if requested mode is equal to current mode. */ if (QuartzRandRModesEqual(&reqMode, &pQuartzScreen->currentMode)) return TRUE; screenId = pQuartzScreen->displayIDs[0]; if (QuartzRandREnumerateModes(pScreen, screenId, QuartzRandRSetModeCallback, &reqMode)) { pQuartzScreen->currentMode = 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); if (pQuartzScreen->displayCount == 1) { if (!QuartzRandRGetCurrentModeInfo(pQuartzScreen->displayIDs[0], &pQuartzScreen->fullScreenMode)) return FALSE; } else { pQuartzScreen->fullScreenMode.width = pScreen->width; pQuartzScreen->fullScreenMode.height = pScreen->height; if(quartzEnableRootless) pQuartzScreen->fullScreenMode.height += aquaMenuBarHeight; } pQuartzScreen->fullScreenMode.refresh = FAKE_REFRESH_FULLSCREEN; pQuartzScreen->rootlessMode = pQuartzScreen->fullScreenMode; pQuartzScreen->rootlessMode.refresh = FAKE_REFRESH_ROOTLESS; pQuartzScreen->rootlessMode.height -= aquaMenuBarHeight; if(quartzEnableRootless) { pQuartzScreen->currentMode = pQuartzScreen->rootlessMode; } else { pQuartzScreen->currentMode = pQuartzScreen->fullScreenMode; } 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; }