vboxutils.c revision b1ad3f99d0b864a8d2787ff732dd2c01f626f2d6
/** @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 <VBox/VMMDev.h>
#include <VBox/VBoxGuestLib.h>
#ifndef PCIACCESS
# include <xf86Pci.h>
# include <Pci.h>
#endif
#include "xf86.h"
#define NEED_XF86_TYPES
#include <iprt/string.h>
#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);
}
}
}