diff options
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/display.cpp')
-rw-r--r-- | src/VBox/Additions/x11/VBoxClient/display.cpp | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/display.cpp b/src/VBox/Additions/x11/VBoxClient/display.cpp new file mode 100644 index 00000000..cea3fdbf --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display.cpp @@ -0,0 +1,304 @@ +/* $Id: display.cpp $ */ +/** @file + * X11 guest client - display management. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "VBoxClient.h" + +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xrandr.h> + +/** @todo this should probably be replaced by something IPRT */ +/* For system() and WEXITSTATUS() */ +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <time.h> +#include <dlfcn.h> + +/* TESTING: Dynamic resizing and mouse integration toggling should work + * correctly with a range of X servers (pre-1.3, 1.3 and later under Linux, 1.3 + * and later under Solaris) with Guest Additions installed. Switching to a + * virtual terminal while a user session is in place should disable dynamic + * resizing and cursor integration, switching back should re-enable them. */ + +/** State information needed for the service. The main VBoxClient code provides + * the daemon logic needed by all services. */ +struct DISPLAYSTATE +{ + /** Are we initialised yet? */ + bool mfInit; + /** The connection to the server. */ + Display *pDisplay; + /** The RandR extension base event number. */ + int cRREventBase; + /** Can we use version 1.2 or later of the RandR protocol here? */ + bool fHaveRandR12; + /** The command argument to use for the xrandr binary. Currently only + * used to support the non-standard location on some Solaris systems - + * would it make sense to use absolute paths on all systems? */ + const char *pcszXrandr; + /** Was there a recent mode hint with no following root window resize, and + * if so, have we waited for a reasonable time? */ + time_t timeLastModeHint; + /** Handle to libXrandr. */ + void *pRandLibraryHandle; + /** Handle to pXRRSelectInput. */ + void (*pXRRSelectInput) (Display *, Window, int); + /** Handle to pXRRQueryExtension. */ + Bool (*pXRRQueryExtension) (Display *, int *, int *); +}; + +static struct DISPLAYSTATE g_DisplayState; + +static unsigned char *getRootProperty(struct DISPLAYSTATE *pState, const char *pszName, + long cItems, Atom type) +{ + Atom actualType = None; + int iFormat = 0; + unsigned long cReturned = 0; + unsigned long cAfter = 0; + unsigned char *pData = 0; + + if (XGetWindowProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), + XInternAtom(pState->pDisplay, pszName, 0), 0, cItems, + False /* delete */, type, &actualType, &iFormat, + &cReturned, &cAfter, &pData)) + return NULL; + return pData; +} + +static void doResize(struct DISPLAYSTATE *pState) +{ + /** @note The xrandr command can fail if something else accesses RandR at + * the same time. We just ignore failure for now as we do not know what + * someone else is doing. */ + if (!pState->fHaveRandR12) + { + char szCommand[256]; + unsigned char *pData; + + pData = getRootProperty(pState, "VBOXVIDEO_PREFERRED_MODE", 1, XA_INTEGER); + if (pData != NULL) + { + RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u", + pState->pcszXrandr, ((unsigned long *)pData)[0] >> 16, ((unsigned long *)pData)[0] & 0xFFFF); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + XFree(pData); + } + } + else + { + const char szCommandBase[] = + "%s --output VGA-0 --auto --output VGA-1 --auto --right-of VGA-0 " + "--output VGA-2 --auto --right-of VGA-1 --output VGA-3 --auto --right-of VGA-2 " + "--output VGA-4 --auto --right-of VGA-3 --output VGA-5 --auto --right-of VGA-4 " + "--output VGA-6 --auto --right-of VGA-5 --output VGA-7 --auto --right-of VGA-6 " + "--output VGA-8 --auto --right-of VGA-7 --output VGA-9 --auto --right-of VGA-8 " + "--output VGA-10 --auto --right-of VGA-9 --output VGA-11 --auto --right-of VGA-10 " + "--output VGA-12 --auto --right-of VGA-11 --output VGA-13 --auto --right-of VGA-12 " + "--output VGA-14 --auto --right-of VGA-13 --output VGA-15 --auto --right-of VGA-14 " + "--output VGA-16 --auto --right-of VGA-15 --output VGA-17 --auto --right-of VGA-16 " + "--output VGA-18 --auto --right-of VGA-17 --output VGA-19 --auto --right-of VGA-18 " + "--output VGA-20 --auto --right-of VGA-19 --output VGA-21 --auto --right-of VGA-20 " + "--output VGA-22 --auto --right-of VGA-21 --output VGA-23 --auto --right-of VGA-22 " + "--output VGA-24 --auto --right-of VGA-23 --output VGA-25 --auto --right-of VGA-24 " + "--output VGA-26 --auto --right-of VGA-25 --output VGA-27 --auto --right-of VGA-26 " + "--output VGA-28 --auto --right-of VGA-27 --output VGA-29 --auto --right-of VGA-28 " + "--output VGA-30 --auto --right-of VGA-29 --output VGA-31 --auto --right-of VGA-30"; + char szCommand[sizeof(szCommandBase) + 256]; + RTStrPrintf(szCommand, sizeof(szCommand), szCommandBase, pState->pcszXrandr); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + } +} + +/** Main loop: handle display hot-plug events, property updates (which can + * signal VT switches hot-plug in old X servers). */ +static void runDisplay(struct DISPLAYSTATE *pState) +{ + Display *pDisplay = pState->pDisplay; + long cValue = 1; + + /* One way or another we want the preferred mode at server start-up. */ + doResize(pState); + XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask | StructureNotifyMask); + if (pState->fHaveRandR12) + pState->pXRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask); + /* Semantics: when VBOXCLIENT_STARTED is set, pre-1.3 X.Org Server driver + * assumes that a client capable of handling mode hints will be present for the + * rest of the X session. If we crash things will not work as they should. + * I thought that preferable to implementing complex crash-handling logic. + */ + XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOXCLIENT_STARTED", 0), + XA_INTEGER, 32, PropModeReplace, (unsigned char *)&cValue, 1); + /* Interrupting this cleanly will be more work than making it robust + * against spontaneous termination, especially as it will never get + * properly tested, so I will go for the second. */ + while (true) + { + XEvent event; + struct pollfd PollFd; + int pollTimeOut = -1; + int cFds; + + /* Do not handle overflow. */ + if (pState->timeLastModeHint > 0 && pState->timeLastModeHint < INT_MAX - 2) + pollTimeOut = 2 - (time(0) - pState->timeLastModeHint); + PollFd.fd = ConnectionNumber(pDisplay); + PollFd.events = POLLIN; /* Hang-up is always reported. */ + XFlush(pDisplay); + cFds = poll(&PollFd, 1, pollTimeOut >= 0 ? pollTimeOut * 1000 : -1); + while (XPending(pDisplay)) + { + XNextEvent(pDisplay, &event); + /* This property is deleted when the server regains the virtual + * terminal. Force the main thread to call xrandr again, as old X + * servers could not handle it while switched out. */ + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyDelete + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False)) + doResize(pState); + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyNewValue + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_PREFERRED_MODE", False)) + doResize(pState); + if ( pState->fHaveRandR12 + && event.type == pState->cRREventBase + RRScreenChangeNotify) + pState->timeLastModeHint = time(0); + if ( event.type == ConfigureNotify + && event.xproperty.window == DefaultRootWindow(pDisplay)) + pState->timeLastModeHint = 0; + } + if (cFds == 0 && pState->timeLastModeHint > 0) + doResize(pState); + } +} + +static int initDisplay(struct DISPLAYSTATE *pState) +{ + char szCommand[256]; + int status; + + pState->pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */); + if (!pState->pRandLibraryHandle) + pState->pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */); + if (!pState->pRandLibraryHandle) + pState->pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */); + + if (!RT_VALID_PTR(pState->pRandLibraryHandle)) + { + VBClLogFatalError("Could not locate libXrandr for dlopen\n"); + return VERR_NOT_FOUND; + } + + *(void **)(&pState->pXRRSelectInput) = dlsym(pState->pRandLibraryHandle, "XRRSelectInput"); + *(void **)(&pState->pXRRQueryExtension) = dlsym(pState->pRandLibraryHandle, "XRRQueryExtension"); + + if ( !RT_VALID_PTR(pState->pXRRSelectInput) + || !RT_VALID_PTR(pState->pXRRQueryExtension)) + { + VBClLogFatalError("Could not load required libXrandr symbols\n"); + dlclose(pState->pRandLibraryHandle); + pState->pRandLibraryHandle = NULL; + return VERR_NOT_FOUND; + } + + pState->pDisplay = XOpenDisplay(NULL); + if (!pState->pDisplay) + return VERR_NOT_FOUND; + if (!pState->pXRRQueryExtension(pState->pDisplay, &pState->cRREventBase, &status)) + return VERR_NOT_FOUND; + pState->fHaveRandR12 = false; + pState->pcszXrandr = "xrandr"; + if (RTFileExists("/usr/X11/bin/xrandr")) + pState->pcszXrandr = "/usr/X11/bin/xrandr"; + status = system(pState->pcszXrandr); + if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */ + VBClLogFatalError("Failed to execute the xrandr utility\n"); + RTStrPrintf(szCommand, sizeof(szCommand), "%s --q12", pState->pcszXrandr); + status = system(szCommand); + if (WEXITSTATUS(status) == 0) + pState->fHaveRandR12 = true; + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) init(void) +{ + struct DISPLAYSTATE *pSelf = &g_DisplayState; + int rc; + + if (pSelf->mfInit) + return VERR_WRONG_ORDER; + rc = initDisplay(pSelf); + if (RT_FAILURE(rc)) + return rc; + if (RT_SUCCESS(rc)) + pSelf->mfInit = true; + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) run(bool volatile *pfShutdown) +{ + RT_NOREF(pfShutdown); /** @todo Probably very wrong not to check pfShutdown... Especially given no pfnStop implementation. */ + struct DISPLAYSTATE *pSelf = &g_DisplayState; + + if (!pSelf->mfInit) + return VERR_WRONG_ORDER; + runDisplay(pSelf); + return VERR_INTERNAL_ERROR; /* "Should never reach here." */ +} + +VBCLSERVICE g_SvcDisplayLegacy = +{ + "dp-legacy-x11", /* szName */ + "Legacy display assistant", /* pszDescription */ + ".vboxclient-display.pid", /* pszPidFilePath (no pid file lock) */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + init, /* pfnInit */ + run, /* pfnWorker */ + NULL, /* pfnStop */ + NULL, /* pfnTerm */ +}; |