/** @file * VirtualBox X11 Additions graphics driver utility functions */ /* * Copyright (C) 2006-2007 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ #include #include #ifndef PCIACCESS # include # include #endif #include "xf86.h" #define NEED_XF86_TYPES #include #include "compiler.h" #include "vboxvideo.h" /************************************************************************** * Main functions * **************************************************************************/ /** * Inform VBox that we are aware of advanced graphics functions * (i.e. dynamic resizing, seamless). * * @returns TRUE for success, FALSE for failure */ Bool vboxEnableGraphicsCap(VBOXPtr pVBox) { TRACE_ENTRY(); if (!pVBox->useDevice) return FALSE; return RT_SUCCESS(VbglR3SetGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0)); } /** * Inform VBox that we are no longer aware of advanced graphics functions * (i.e. dynamic resizing, seamless). * * @returns TRUE for success, FALSE for failure */ Bool vboxDisableGraphicsCap(VBOXPtr pVBox) { TRACE_ENTRY(); if (!pVBox->useDevice) return FALSE; return RT_SUCCESS(VbglR3SetGuestCaps(0, VMMDEV_GUEST_SUPPORTS_GRAPHICS)); } /** * Query the last display change request. * * @returns boolean success indicator. * @param pScrn Pointer to the X screen info structure. * @param pcx Where to store the horizontal pixel resolution (0 = do not change). * @param pcy Where to store the vertical pixel resolution (0 = do not change). * @param pcBits Where to store the bits per pixel (0 = do not change). * @param iDisplay Where to store the display number the request was for - 0 for the * primary display, 1 for the first secondary, etc. */ Bool vboxGetDisplayChangeRequest(ScrnInfoPtr pScrn, uint32_t *pcx, uint32_t *pcy, uint32_t *pcBits, uint32_t *piDisplay) { VBOXPtr pVBox = pScrn->driverPrivate; TRACE_ENTRY(); if (!pVBox->useDevice) return FALSE; int rc = VbglR3GetDisplayChangeRequest(pcx, pcy, pcBits, piDisplay, false); if (RT_SUCCESS(rc)) return TRUE; xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Failed to obtain the last resolution requested by the guest, rc=%d.\n", rc); return FALSE; } /** * Query the host as to whether it likes a specific video mode. * * @returns the result of the query * @param cx the width of the mode being queried * @param cy the height of the mode being queried * @param cBits the bpp of the mode being queried */ Bool vboxHostLikesVideoMode(ScrnInfoPtr pScrn, uint32_t cx, uint32_t cy, uint32_t cBits) { VBOXPtr pVBox = pScrn->driverPrivate; TRACE_ENTRY(); if (!pVBox->useDevice) return TRUE; /* If we can't ask the host then we like everything. */ return VbglR3HostLikesVideoMode(cx, cy, cBits); } /** * Check if any seamless mode is enabled. * Seamless is only relevant for the newer Xorg modules. * * @returns the result of the query * (true = seamless enabled, false = seamless not enabled) * @param pScrn Screen info pointer. */ Bool vboxGuestIsSeamless(ScrnInfoPtr pScrn) { VMMDevSeamlessMode mode; VBOXPtr pVBox = pScrn->driverPrivate; TRACE_ENTRY(); if (!pVBox->useDevice) return FALSE; if (RT_FAILURE(VbglR3SeamlessGetLastEvent(&mode))) return FALSE; return (mode != VMMDev_Seamless_Disabled); } /** * Save video mode parameters to the registry. * * @returns iprt status value * @param pszName the name to save the mode parameters under * @param cx mode width * @param cy mode height * @param cBits bits per pixel for the mode */ Bool vboxSaveVideoMode(ScrnInfoPtr pScrn, uint32_t cx, uint32_t cy, uint32_t cBits) { VBOXPtr pVBox = pScrn->driverPrivate; TRACE_ENTRY(); if (!pVBox->useDevice) return FALSE; return RT_SUCCESS(VbglR3SaveVideoMode("SavedMode", cx, cy, cBits)); } /** * Retrieve video mode parameters from the registry. * * @returns iprt status value * @param pszName the name under which the mode parameters are saved * @param pcx where to store the mode width * @param pcy where to store the mode height * @param pcBits where to store the bits per pixel for the mode */ Bool vboxRetrieveVideoMode(ScrnInfoPtr pScrn, uint32_t *pcx, uint32_t *pcy, uint32_t *pcBits) { VBOXPtr pVBox = pScrn->driverPrivate; TRACE_ENTRY(); if (!pVBox->useDevice) return FALSE; int rc = VbglR3RetrieveVideoMode("SavedMode", pcx, pcy, pcBits); if (RT_SUCCESS(rc)) TRACE_LOG("Retrieved a video mode of %dx%dx%d\n", *pcx, *pcy, *pcBits); else TRACE_LOG("Failed to retrieve video mode, error %d\n", rc); return (RT_SUCCESS(rc)); } /** * Fills a display mode M with a built-in mode of name pszName and dimensions * cx and cy. */ static void vboxFillDisplayMode(ScrnInfoPtr pScrn, DisplayModePtr m, const char *pszName, unsigned cx, unsigned cy) { VBOXPtr pVBox = pScrn->driverPrivate; TRACE_LOG("pszName=%s, cx=%u, cy=%u\n", pszName, cx, cy); m->status = MODE_OK; m->type = M_T_BUILTIN; /* Older versions of VBox only support screen widths which are a multiple * of 8 */ if (pVBox->fAnyX) m->HDisplay = cx; else m->HDisplay = cx & ~7; m->HSyncStart = m->HDisplay + 2; m->HSyncEnd = m->HDisplay + 4; m->HTotal = m->HDisplay + 6; m->VDisplay = cy; m->VSyncStart = m->VDisplay + 2; m->VSyncEnd = m->VDisplay + 4; m->VTotal = m->VDisplay + 6; m->Clock = m->HTotal * m->VTotal * 60 / 1000; /* kHz */ if (pszName) { if (m->name) free(m->name); m->name = xnfstrdup(pszName); } } /** vboxvideo's list of standard video modes */ struct { /** mode width */ uint32_t cx; /** mode height */ uint32_t cy; } vboxStandardModes[] = { { 1600, 1200 }, { 1440, 1050 }, { 1280, 960 }, { 1024, 768 }, { 800, 600 }, { 640, 480 }, { 0, 0 } }; enum { vboxNumStdModes = sizeof(vboxStandardModes) / sizeof(vboxStandardModes[0]) }; /** * Returns a standard mode which the host likes. Can be called multiple * times with the index returned by the previous call to get a list of modes. * @returns the index of the mode in the list, or 0 if no more modes are * available * @param pScrn the screen information structure * @param pScrn->bitsPerPixel * if this is non-null, only modes with this BPP will be * returned * @param cIndex the index of the last mode queried, or 0 to query the * first mode available. Note: the first index is 1 * @param pcx where to store the mode's width * @param pcy where to store the mode's height * @param pcBits where to store the mode's BPP */ unsigned vboxNextStandardMode(ScrnInfoPtr pScrn, unsigned cIndex, uint32_t *pcx, uint32_t *pcy, uint32_t *pcBits) { XF86ASSERT(cIndex < vboxNumStdModes, ("cIndex = %d, vboxNumStdModes = %d\n", cIndex, vboxNumStdModes)); for (unsigned i = cIndex; i < vboxNumStdModes - 1; ++i) { uint32_t cBits = pScrn->bitsPerPixel; uint32_t cx = vboxStandardModes[i].cx; uint32_t cy = vboxStandardModes[i].cy; if (cBits != 0 && !vboxHostLikesVideoMode(pScrn, cx, cy, cBits)) continue; if (vboxHostLikesVideoMode(pScrn, cx, cy, 32)) cBits = 32; else if (vboxHostLikesVideoMode(pScrn, cx, cy, 16)) cBits = 16; else continue; if (pcx) *pcx = cx; if (pcy) *pcy = cy; if (pcBits) *pcBits = cBits; return i + 1; } return 0; } /** * Returns the preferred video mode. The current order of preference is * (from highest to least preferred): * - The mode corresponding to the last size hint from the host * - The video mode saved from the last session * - The largest standard mode which the host likes, falling back to * 640x480x32 as a worst case * - If the host can't be contacted at all, we return 1024x768x32 * * The return type is void as we guarantee we will return some mode. */ void vboxGetPreferredMode(ScrnInfoPtr pScrn, uint32_t iScreen, uint32_t *pcx, uint32_t *pcy, uint32_t *pcBits) { /* Query the host for the preferred resolution and colour depth */ uint32_t cx = 0, cy = 0, iScreenIn = iScreen, cBits = 32; VBOXPtr pVBox = pScrn->driverPrivate; TRACE_LOG("iScreen=%u\n", iScreen); bool found = false; if ( pVBox->aPreferredSize[iScreen].cx && pVBox->aPreferredSize[iScreen].cy) { cx = pVBox->aPreferredSize[iScreen].cx; cy = pVBox->aPreferredSize[iScreen].cy; found = true; } if (pVBox->useDevice) { if (!found) found = vboxGetDisplayChangeRequest(pScrn, &cx, &cy, &cBits, &iScreenIn); if ((cx == 0) || (cy == 0) || iScreenIn != iScreen) found = false; if (!found) found = vboxRetrieveVideoMode(pScrn, &cx, &cy, &cBits); if ((cx == 0) || (cy == 0)) found = false; if (!found) found = (vboxNextStandardMode(pScrn, 0, &cx, &cy, &cBits) != 0); if (!found) { /* Last resort */ cx = 640; cy = 480; cBits = 32; } } else { cx = 1024; cy = 768; } if (pcx) *pcx = cx; if (pcy) *pcy = cy; if (pcBits) *pcBits = cBits; TRACE_LOG("cx=%u, cy=%u, cBits=%u\n", cx, cy, cBits); } /* Move a screen mode found to the end of the list, so that RandR will give * it the highest priority when a mode switch is requested. Returns the mode * that was previously before the mode in the list in order to allow the * caller to continue walking the list. */ static DisplayModePtr vboxMoveModeToFront(ScrnInfoPtr pScrn, DisplayModePtr pMode) { DisplayModePtr pPrev = pMode->prev; if (pMode != pScrn->modes) { pMode->prev->next = pMode->next; pMode->next->prev = pMode->prev; pMode->next = pScrn->modes; pMode->prev = pScrn->modes->prev; pMode->next->prev = pMode; pMode->prev->next = pMode; pScrn->modes = pMode; } return pPrev; } /** * Rewrites the first dynamic mode found which is not the current screen mode * to contain the host's currently preferred screen size, then moves that * mode to the front of the screen information structure's mode list. * Additionally, if the current mode is not dynamic, the second dynamic mode * will be set to match the current mode and also added to the front. This * ensures that the user can always reset the current size to kick the driver * to update its mode list. */ void vboxWriteHostModes(ScrnInfoPtr pScrn, DisplayModePtr pCurrent) { uint32_t cx = 0, cy = 0, iDisplay = 0, cBits = 0; DisplayModePtr pMode; bool found = false; TRACE_ENTRY(); vboxGetPreferredMode(pScrn, 0, &cx, &cy, &cBits); #ifdef DEBUG /* Count the number of modes for sanity */ unsigned cModes = 1, cMode = 0; DisplayModePtr pCount; for (pCount = pScrn->modes; ; pCount = pCount->next, ++cModes) if (pCount->next == pScrn->modes) break; #endif for (pMode = pScrn->modes; ; pMode = pMode->next) { #ifdef DEBUG XF86ASSERT (cMode++ < cModes, (NULL)); #endif if ( pMode != pCurrent && !strcmp(pMode->name, "VBoxDynamicMode")) { if (!found) vboxFillDisplayMode(pScrn, pMode, NULL, cx, cy); else if (pCurrent) vboxFillDisplayMode(pScrn, pMode, NULL, pCurrent->HDisplay, pCurrent->VDisplay); found = true; pMode = vboxMoveModeToFront(pScrn, pMode); } if (pMode->next == pScrn->modes) break; } XF86ASSERT (found, ("vboxvideo: no free dynamic mode found. Exiting.\n")); XF86ASSERT ( (pScrn->modes->HDisplay == (long) cx) || ( (pScrn->modes->HDisplay == pCurrent->HDisplay) && (pScrn->modes->next->HDisplay == (long) cx)), ("pScrn->modes->HDisplay=%u, pScrn->modes->next->HDisplay=%u\n", pScrn->modes->HDisplay, pScrn->modes->next->HDisplay)); XF86ASSERT ( (pScrn->modes->VDisplay == (long) cy) || ( (pScrn->modes->VDisplay == pCurrent->VDisplay) && (pScrn->modes->next->VDisplay == (long) cy)), ("pScrn->modes->VDisplay=%u, pScrn->modes->next->VDisplay=%u\n", pScrn->modes->VDisplay, pScrn->modes->next->VDisplay)); } /** * Allocates an empty display mode and links it into the doubly linked list of * modes pointed to by pScrn->modes. Returns a pointer to the newly allocated * memory. */ static DisplayModePtr vboxAddEmptyScreenMode(ScrnInfoPtr pScrn) { DisplayModePtr pMode = xnfcalloc(sizeof(DisplayModeRec), 1); TRACE_ENTRY(); if (!pScrn->modes) { pScrn->modes = pMode; pMode->next = pMode; pMode->prev = pMode; } else { pMode->next = pScrn->modes; pMode->prev = pScrn->modes->prev; pMode->next->prev = pMode; pMode->prev->next = pMode; } return pMode; } /** * Create display mode entries in the screen information structure for each * of the initial graphics modes that we wish to support. This includes: * - An initial mode, of the size requested by the caller * - Two dynamic modes, one of which will be updated to match the last size * hint from the host on each mode switch, but initially also of the * requested size * - Several standard modes, if possible ones that the host likes * - Any modes that the user requested in xorg.conf/XFree86Config */ void vboxAddModes(ScrnInfoPtr pScrn, uint32_t cxInit, uint32_t cyInit) { unsigned cx = 0, cy = 0, cIndex = 0; /* For reasons related to the way RandR 1.1 is implemented, we need to * make sure that the initial mode (more precisely, a mode equal to the * initial virtual resolution) is always present in the mode list. RandR * has the assumption build in that there will either be a mode of that * size present at all times, or that the first mode in the list will * always be smaller than the initial virtual resolution. Since our * approach to dynamic resizing isn't quite the way RandR was intended to * be, and breaks the second assumption, we guarantee the first. */ DisplayModePtr pMode = vboxAddEmptyScreenMode(pScrn); vboxFillDisplayMode(pScrn, pMode, "VBoxInitialMode", cxInit, cyInit); /* Create our two dynamic modes. */ pMode = vboxAddEmptyScreenMode(pScrn); vboxFillDisplayMode(pScrn, pMode, "VBoxDynamicMode", cxInit, cyInit); pMode = vboxAddEmptyScreenMode(pScrn); vboxFillDisplayMode(pScrn, pMode, "VBoxDynamicMode", cxInit, cyInit); /* Add standard modes supported by the host */ for ( ; ; ) { char szName[256]; cIndex = vboxNextStandardMode(pScrn, cIndex, &cx, &cy, NULL); if (cIndex == 0) break; sprintf(szName, "VBox-%ux%u", cx, cy); pMode = vboxAddEmptyScreenMode(pScrn); vboxFillDisplayMode(pScrn, pMode, szName, cx, cy); } /* And finally any modes specified by the user. We assume here that * the mode names reflect the mode sizes. */ for (unsigned i = 0; pScrn->display->modes != NULL && pScrn->display->modes[i] != NULL; i++) { if (sscanf(pScrn->display->modes[i], "%ux%u", &cx, &cy) == 2) { pMode = vboxAddEmptyScreenMode(pScrn); vboxFillDisplayMode(pScrn, pMode, pScrn->display->modes[i], cx, cy); } } }