summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp')
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp1402
1 files changed, 1402 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp
new file mode 100644
index 00000000..90005ab1
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp
@@ -0,0 +1,1402 @@
+/* $Id: display-svga-x11.cpp $ */
+/** @file
+ * X11 guest client - VMSVGA emulation resize event pass-through to X.Org
+ * guest driver.
+ */
+
+/*
+ * Copyright (C) 2017-2023 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
+ */
+
+/*
+ * Known things to test when changing this code. All assume a guest with VMSVGA
+ * active and controlled by X11 or Wayland, and Guest Additions installed and
+ * running, unless otherwise stated.
+ * - On Linux 4.6 and later guests, VBoxClient --vmsvga should be running as
+ * root and not as the logged-in user. Dynamic resizing should work for all
+ * screens in any environment which handles kernel resize notifications,
+ * including at log-in screens. Test GNOME Shell Wayland and GNOME Shell
+ * under X.Org or Unity or KDE at the log-in screen and after log-in.
+ * - Linux 4.10 changed the user-kernel-ABI introduced in 4.6: test both.
+ * - On other guests (than Linux 4.6 or later) running X.Org Server 1.3 or
+ * later, VBoxClient --vmsvga should never be running as root, and should run
+ * (and dynamic resizing and screen enable/disable should work for all
+ * screens) whenever a user is logged in to a supported desktop environment.
+ * - On guests running X.Org Server 1.2 or older, VBoxClient --vmsvga should
+ * never run as root and should run whenever a user is logged in to a
+ * supported desktop environment. Dynamic resizing should work for the first
+ * screen, and enabling others should not be possible.
+ * - When VMSVGA is not enabled, VBoxClient --vmsvga should never stay running.
+ * - The following assumptions are done and should be taken into account when reading/chaning the code:
+ * # The order of the outputs (monitors) is assumed to be the same in RANDROUTPUT array and
+ * XRRScreenResources.outputs array.
+ * - This code does 2 related but separate things: 1- It resizes and enables/disables monitors upon host's
+ * requests (see the infinite loop in run()). 2- it listens to RandR events (caused by this or any other X11 client)
+ * on a different thread and notifies host about the new monitor positions. See sendMonitorPositions(...). This is
+ * mainly a work around since we have realized that vmsvga does not convey correct monitor positions thru FIFO.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <dlfcn.h>
+/** For sleep(..) */
+#include <unistd.h>
+#include "VBoxClient.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/env.h>
+
+#include <X11/Xlibint.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/panoramiXproto.h>
+
+#include "display-svga-xf86cvt.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MILLIS_PER_INCH (25.4)
+#define DEFAULT_DPI (96.0)
+
+/* Time in milliseconds to relax if no X11 events available. */
+#define VBOX_SVGA_X11_RELAX_TIME_MS (500)
+/* Time in milliseconds to wait for host events. */
+#define VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS (500)
+
+/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
+/** @todo if this ever changes, dynamically allocate resizeable arrays in the
+ * context structure. */
+#define VMW_MAX_HEADS 32
+
+#define checkFunctionPtrReturn(pFunction) \
+ do { \
+ if (pFunction) { } \
+ else \
+ { \
+ VBClLogFatalError("Could not find symbol address (%s)\n", #pFunction); \
+ dlclose(x11Context.pRandLibraryHandle); \
+ x11Context.pRandLibraryHandle = NULL; \
+ return VERR_NOT_FOUND; \
+ } \
+ } while (0)
+
+#define checkFunctionPtr(pFunction) \
+ do { \
+ if (pFunction) {} \
+ else VBClLogError("Could not find symbol address (%s)\n", #pFunction);\
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#define X_VMwareCtrlSetRes 1
+
+typedef struct
+{
+ CARD8 reqType;
+ CARD8 VMwareCtrlReqType;
+ CARD16 length B16;
+ CARD32 screen B32;
+ CARD32 x B32;
+ CARD32 y B32;
+} xVMwareCtrlSetResReq;
+#define sz_xVMwareCtrlSetResReq 16
+
+typedef struct
+{
+ BYTE type;
+ BYTE pad1;
+ CARD16 sequenceNumber B16;
+ CARD32 length B32;
+ CARD32 screen B32;
+ CARD32 x B32;
+ CARD32 y B32;
+ CARD32 pad2 B32;
+ CARD32 pad3 B32;
+ CARD32 pad4 B32;
+} xVMwareCtrlSetResReply;
+#define sz_xVMwareCtrlSetResReply 32
+
+typedef struct {
+ CARD8 reqType; /* always X_VMwareCtrlReqCode */
+ CARD8 VMwareCtrlReqType; /* always X_VMwareCtrlSetTopology */
+ CARD16 length B16;
+ CARD32 screen B32;
+ CARD32 number B32;
+ CARD32 pad1 B32;
+} xVMwareCtrlSetTopologyReq;
+#define sz_xVMwareCtrlSetTopologyReq 16
+
+#define X_VMwareCtrlSetTopology 2
+
+typedef struct {
+ BYTE type; /* X_Reply */
+ BYTE pad1;
+ CARD16 sequenceNumber B16;
+ CARD32 length B32;
+ CARD32 screen B32;
+ CARD32 pad2 B32;
+ CARD32 pad3 B32;
+ CARD32 pad4 B32;
+ CARD32 pad5 B32;
+ CARD32 pad6 B32;
+} xVMwareCtrlSetTopologyReply;
+#define sz_xVMwareCtrlSetTopologyReply 32
+
+struct X11VMWRECT
+{
+ int16_t x;
+ int16_t y;
+ uint16_t w;
+ uint16_t h;
+};
+AssertCompileSize(struct X11VMWRECT, 8);
+
+struct X11CONTEXT
+{
+ Display *pDisplay;
+ /* We use a separate connection for randr event listening since sharing a
+ single display object with resizing (main) and event listening threads ends up having a deadlock.*/
+ Display *pDisplayRandRMonitoring;
+ Window rootWindow;
+ int iDefaultScreen;
+ XRRScreenResources *pScreenResources;
+ int hRandRMajor;
+ int hRandRMinor;
+ int hRandREventBase;
+ int hRandRErrorBase;
+ int hEventMask;
+ bool fMonitorInfoAvailable;
+ /** The number of outputs (monitors, including disconnect ones) xrandr reports. */
+ int hOutputCount;
+ void *pRandLibraryHandle;
+ bool fWmwareCtrlExtention;
+ int hVMWCtrlMajorOpCode;
+ /** Function pointers we used if we dlopen libXrandr instead of linking. */
+ void (*pXRRSelectInput) (Display *, Window, int);
+ Bool (*pXRRQueryExtension) (Display *, int *, int *);
+ Status (*pXRRQueryVersion) (Display *, int *, int*);
+ XRRMonitorInfo* (*pXRRGetMonitors)(Display *, Window, Bool, int *);
+ XRRScreenResources* (*pXRRGetScreenResources)(Display *, Window);
+ Status (*pXRRSetCrtcConfig)(Display *, XRRScreenResources *, RRCrtc,
+ Time, int, int, RRMode, Rotation, RROutput *, int);
+ void (*pXRRFreeMonitors)(XRRMonitorInfo *);
+ void (*pXRRFreeScreenResources)(XRRScreenResources *);
+ void (*pXRRFreeModeInfo)(XRRModeInfo *);
+ void (*pXRRFreeOutputInfo)(XRROutputInfo *);
+ void (*pXRRSetScreenSize)(Display *, Window, int, int, int, int);
+ int (*pXRRUpdateConfiguration)(XEvent *event);
+ XRRModeInfo* (*pXRRAllocModeInfo)(_Xconst char *, int);
+ RRMode (*pXRRCreateMode) (Display *, Window, XRRModeInfo *);
+ XRROutputInfo* (*pXRRGetOutputInfo) (Display *, XRRScreenResources *, RROutput);
+ XRRCrtcInfo* (*pXRRGetCrtcInfo) (Display *, XRRScreenResources *, RRCrtc crtc);
+ void (*pXRRFreeCrtcInfo)(XRRCrtcInfo *);
+ void (*pXRRAddOutputMode)(Display *, RROutput, RRMode);
+ void (*pXRRDeleteOutputMode)(Display *, RROutput, RRMode);
+ void (*pXRRDestroyMode)(Display *, RRMode);
+ void (*pXRRSetOutputPrimary)(Display *, Window, RROutput);
+};
+
+static X11CONTEXT x11Context;
+
+struct RANDROUTPUT
+{
+ int32_t x;
+ int32_t y;
+ uint32_t width;
+ uint32_t height;
+ bool fEnabled;
+ bool fPrimary;
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void x11Connect();
+static int determineOutputCount();
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Monitor positions array. Allocated here and deallocated in the class descructor. */
+RTPOINT *mpMonitorPositions;
+/** Thread to listen to some of the X server events. */
+RTTHREAD mX11MonitorThread = NIL_RTTHREAD;
+/** Shutdown indicator for the monitor thread. */
+static bool g_fMonitorThreadShutdown = false;
+
+
+
+#ifdef RT_OS_SOLARIS
+static bool VMwareCtrlSetRes(
+ Display *dpy, int hExtensionMajorOpcode, int screen, int x, int y)
+{
+ xVMwareCtrlSetResReply rep;
+ xVMwareCtrlSetResReq *pReq;
+ bool fResult = false;
+
+ LockDisplay(dpy);
+
+ GetReq(VMwareCtrlSetRes, pReq);
+ AssertPtrReturn(pReq, false);
+
+ pReq->reqType = hExtensionMajorOpcode;
+ pReq->VMwareCtrlReqType = X_VMwareCtrlSetRes;
+ pReq->screen = screen;
+ pReq->x = x;
+ pReq->y = y;
+
+ fResult = !!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVMwareCtrlSetResReply) - SIZEOF(xReply)) >> 2, xFalse);
+
+ UnlockDisplay(dpy);
+
+ return fResult;
+}
+#endif /* RT_OS_SOLARIS */
+
+/** Makes a call to vmwarectrl extension. This updates the
+ * connection information and possible resolutions (modes)
+ * of each monitor on the driver. Also sets the preferred mode
+ * of each output (monitor) to currently selected one. */
+bool VMwareCtrlSetTopology(Display *dpy, int hExtensionMajorOpcode,
+ int screen, xXineramaScreenInfo extents[], int number)
+{
+ xVMwareCtrlSetTopologyReply rep;
+ xVMwareCtrlSetTopologyReq *req;
+
+ long len;
+
+ LockDisplay(dpy);
+
+ GetReq(VMwareCtrlSetTopology, req);
+ req->reqType = hExtensionMajorOpcode;
+ req->VMwareCtrlReqType = X_VMwareCtrlSetTopology;
+ req->screen = screen;
+ req->number = number;
+
+ len = ((long) number) << 1;
+ SetReqLen(req, len, len);
+ len <<= 2;
+ _XSend(dpy, (char *)extents, len);
+
+ if (!_XReply(dpy, (xReply *)&rep,
+ (SIZEOF(xVMwareCtrlSetTopologyReply) - SIZEOF(xReply)) >> 2,
+ xFalse))
+ {
+ UnlockDisplay(dpy);
+ SyncHandle();
+ return false;
+ }
+ UnlockDisplay(dpy);
+ SyncHandle();
+ return true;
+}
+
+/** This function assumes monitors are named as from Virtual1 to VirtualX. */
+static int getMonitorIdFromName(const char *sMonitorName)
+{
+ if (!sMonitorName)
+ return -1;
+#ifdef RT_OS_SOLARIS
+ if (!strcmp(sMonitorName, "default"))
+ return 1;
+#endif
+ int iLen = strlen(sMonitorName);
+ if (iLen <= 0)
+ return -1;
+ int iBase = 10;
+ int iResult = 0;
+ for (int i = iLen - 1; i >= 0; --i)
+ {
+ /* Stop upon seeing the first non-numeric char. */
+ if (sMonitorName[i] < 48 || sMonitorName[i] > 57)
+ break;
+ iResult += (sMonitorName[i] - 48) * iBase / 10;
+ iBase *= 10;
+ }
+ return iResult;
+}
+
+static void sendMonitorPositions(RTPOINT *pPositions, size_t cPositions)
+{
+ if (cPositions && !pPositions)
+ {
+ VBClLogError(("Monitor position update called with NULL pointer!\n"));
+ return;
+ }
+ int rc = VbglR3SeamlessSendMonitorPositions(cPositions, pPositions);
+ if (RT_SUCCESS(rc))
+ VBClLogInfo("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc);
+ else
+ VBClLogError("Error during sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc);
+}
+
+static void queryMonitorPositions()
+{
+ static const int iSentinelPosition = -1;
+ if (mpMonitorPositions)
+ {
+ free(mpMonitorPositions);
+ mpMonitorPositions = NULL;
+ }
+
+ int iMonitorCount = 0;
+ XRRMonitorInfo *pMonitorInfo = NULL;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pMonitorInfo = XRRGetMonitors(x11Context.pDisplayRandRMonitoring,
+ DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount);
+#else
+ if (x11Context.pXRRGetMonitors)
+ pMonitorInfo = x11Context.pXRRGetMonitors(x11Context.pDisplayRandRMonitoring,
+ DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount);
+#endif
+ if (!pMonitorInfo)
+ return;
+ if (iMonitorCount == -1)
+ VBClLogError("Could not get monitor info\n");
+ else
+ {
+ mpMonitorPositions = (RTPOINT*)malloc(x11Context.hOutputCount * sizeof(RTPOINT));
+ /** @todo memset? */
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ mpMonitorPositions[i].x = iSentinelPosition;
+ mpMonitorPositions[i].y = iSentinelPosition;
+ }
+ for (int i = 0; i < iMonitorCount; ++i)
+ {
+ char *pszMonitorName = XGetAtomName(x11Context.pDisplayRandRMonitoring, pMonitorInfo[i].name);
+ if (!pszMonitorName)
+ {
+ VBClLogError("queryMonitorPositions: skip monitor with unknown name %d\n", i);
+ continue;
+ }
+
+ int iMonitorID = getMonitorIdFromName(pszMonitorName) - 1;
+ XFree((void *)pszMonitorName);
+ pszMonitorName = NULL;
+
+ if (iMonitorID >= x11Context.hOutputCount || iMonitorID == -1)
+ {
+ VBClLogInfo("queryMonitorPositions: skip monitor %d (id %d) (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
+ i, iMonitorID,
+ pMonitorInfo[i].width, pMonitorInfo[i].height,
+ pMonitorInfo[i].x, pMonitorInfo[i].y);
+ continue;
+ }
+ VBClLogInfo("Monitor %d (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
+ i,
+ pMonitorInfo[i].width, pMonitorInfo[i].height,
+ pMonitorInfo[i].x, pMonitorInfo[i].y);
+ mpMonitorPositions[iMonitorID].x = pMonitorInfo[i].x;
+ mpMonitorPositions[iMonitorID].y = pMonitorInfo[i].y;
+ }
+ if (iMonitorCount > 0)
+ sendMonitorPositions(mpMonitorPositions, x11Context.hOutputCount);
+ }
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeMonitors(pMonitorInfo);
+#else
+ if (x11Context.pXRRFreeMonitors)
+ x11Context.pXRRFreeMonitors(pMonitorInfo);
+#endif
+}
+
+static void monitorRandREvents()
+{
+ XEvent event;
+
+ if (XPending(x11Context.pDisplayRandRMonitoring) > 0)
+ {
+ XNextEvent(x11Context.pDisplayRandRMonitoring, &event);
+ int eventTypeOffset = event.type - x11Context.hRandREventBase;
+ VBClLogInfo("received X11 event (%d)\n", event.type);
+ switch (eventTypeOffset)
+ {
+ case RRScreenChangeNotify:
+ VBClLogInfo("RRScreenChangeNotify event received\n");
+ queryMonitorPositions();
+ break;
+ default:
+ break;
+ }
+ } else
+ {
+ RTThreadSleep(VBOX_SVGA_X11_RELAX_TIME_MS);
+ }
+}
+
+/**
+ * @callback_method_impl{FNRTTHREAD}
+ */
+static DECLCALLBACK(int) x11MonitorThreadFunction(RTTHREAD ThreadSelf, void *pvUser)
+{
+ RT_NOREF(ThreadSelf, pvUser);
+ while (!ASMAtomicReadBool(&g_fMonitorThreadShutdown))
+ {
+ monitorRandREvents();
+ }
+
+ VBClLogInfo("X11 thread gracefully terminated\n");
+
+ return 0;
+}
+
+static int startX11MonitorThread()
+{
+ int rc;
+ Assert(g_fMonitorThreadShutdown == false);
+ if (mX11MonitorThread == NIL_RTTHREAD)
+ {
+ rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThreadFunction, NULL /*pvUser*/, 0 /*cbStack*/,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "X11 events");
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Warning: failed to start X11 monitor thread (VBoxClient) rc=%Rrc!\n", rc);
+ }
+ else
+ rc = VINF_ALREADY_INITIALIZED;
+ return rc;
+}
+
+static int stopX11MonitorThread(void)
+{
+ int rc = VINF_SUCCESS;
+ if (mX11MonitorThread != NIL_RTTHREAD)
+ {
+ ASMAtomicWriteBool(&g_fMonitorThreadShutdown, true);
+ /** @todo Send event to thread to get it out of XNextEvent. */
+ //????????
+ //mX11Monitor.interruptEventWait();
+ rc = RTThreadWait(mX11MonitorThread, RT_MS_1SEC, NULL /*prc*/);
+ if (RT_SUCCESS(rc))
+ {
+ mX11MonitorThread = NIL_RTTHREAD;
+ g_fMonitorThreadShutdown = false;
+ }
+ else
+ VBClLogError("Failed to stop X11 monitor thread, rc=%Rrc!\n", rc);
+ }
+ return rc;
+}
+
+static bool callVMWCTRL(struct RANDROUTPUT *paOutputs)
+{
+ int hHeight = 600;
+ int hWidth = 800;
+ bool fResult = false;
+ int idxDefaultScreen = DefaultScreen(x11Context.pDisplay);
+
+ AssertReturn(idxDefaultScreen >= 0, false);
+ AssertReturn(idxDefaultScreen < x11Context.hOutputCount, false);
+
+ xXineramaScreenInfo *extents = (xXineramaScreenInfo *)malloc(x11Context.hOutputCount * sizeof(xXineramaScreenInfo));
+ if (!extents)
+ return false;
+ int hRunningOffset = 0;
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ if (paOutputs[i].fEnabled)
+ {
+ hHeight = paOutputs[i].height;
+ hWidth = paOutputs[i].width;
+ }
+ else
+ {
+ hHeight = 0;
+ hWidth = 0;
+ }
+ extents[i].x_org = hRunningOffset;
+ extents[i].y_org = 0;
+ extents[i].width = hWidth;
+ extents[i].height = hHeight;
+ hRunningOffset += hWidth;
+ }
+#ifdef RT_OS_SOLARIS
+ fResult = VMwareCtrlSetRes(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
+ idxDefaultScreen, extents[idxDefaultScreen].width,
+ extents[idxDefaultScreen].height);
+#else
+ fResult = VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
+ idxDefaultScreen, extents, x11Context.hOutputCount);
+#endif
+ free(extents);
+ return fResult;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSVGAInit(void)
+{
+ int rc;
+
+ /* In 32-bit guests GAs build on our release machines causes an xserver hang.
+ * So for 32-bit GAs we use our DRM client. */
+#if ARCH_BITS == 32
+ rc = VbglR3DrmClientStart();
+ if (RT_FAILURE(rc))
+ VBClLogError("Starting DRM resizing client (32-bit) failed with %Rrc\n", rc);
+ return VERR_NOT_AVAILABLE; /** @todo r=andy Why ignoring rc here? */
+#endif
+
+ /* If DRM client is already running don't start this service. */
+ if (VbglR3DrmClientIsRunning())
+ {
+ VBClLogInfo("DRM resizing is already running. Exiting this service\n");
+ return VERR_NOT_AVAILABLE;
+ }
+
+ if (VBClHasWayland())
+ {
+ rc = VbglR3DrmClientStart();
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogInfo("VBoxDrmClient has been successfully started, exitting parent process\n");
+ exit(0);
+ }
+ else
+ {
+ VBClLogError("Starting DRM resizing client failed with %Rrc\n", rc);
+ }
+ return rc;
+ }
+
+ x11Connect();
+
+ if (x11Context.pDisplay == NULL)
+ return VERR_NOT_AVAILABLE;
+
+ /* don't start the monitoring thread if related randr functionality is not available. */
+ if (x11Context.fMonitorInfoAvailable)
+ {
+ if (RT_FAILURE(startX11MonitorThread()))
+ return VERR_NOT_AVAILABLE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSVGAStop(void)
+{
+ int rc;
+
+ rc = stopX11MonitorThread();
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("cannot stop X11 monitor thread (%Rrc)\n", rc);
+ return;
+ }
+
+ if (mpMonitorPositions)
+ {
+ free(mpMonitorPositions);
+ mpMonitorPositions = NULL;
+ }
+
+ if (x11Context.pDisplayRandRMonitoring)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0);
+#endif
+ }
+
+ if (x11Context.pDisplay)
+ {
+ XCloseDisplay(x11Context.pDisplay);
+ x11Context.pDisplay = NULL;
+ }
+
+ if (x11Context.pDisplayRandRMonitoring)
+ {
+ XCloseDisplay(x11Context.pDisplayRandRMonitoring);
+ x11Context.pDisplayRandRMonitoring = NULL;
+ }
+
+ if (x11Context.pRandLibraryHandle)
+ {
+ dlclose(x11Context.pRandLibraryHandle);
+ x11Context.pRandLibraryHandle = NULL;
+ }
+}
+
+#ifndef WITH_DISTRO_XRAND_XINERAMA
+static int openLibRandR()
+{
+ x11Context.pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */);
+ if (!x11Context.pRandLibraryHandle)
+ x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */);
+ if (!x11Context.pRandLibraryHandle)
+ x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */);
+
+ if (!x11Context.pRandLibraryHandle)
+ {
+ VBClLogFatalError("Could not locate libXrandr for dlopen\n");
+ return VERR_NOT_FOUND;
+ }
+
+ *(void **)(&x11Context.pXRRSelectInput) = dlsym(x11Context.pRandLibraryHandle, "XRRSelectInput");
+ checkFunctionPtrReturn(x11Context.pXRRSelectInput);
+
+ *(void **)(&x11Context.pXRRQueryExtension) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryExtension");
+ checkFunctionPtrReturn(x11Context.pXRRQueryExtension);
+
+ *(void **)(&x11Context.pXRRQueryVersion) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryVersion");
+ checkFunctionPtrReturn(x11Context.pXRRQueryVersion);
+
+ /* Don't bail out when XRRGetMonitors XRRFreeMonitors are missing as in Oracle Solaris 10. It is not crucial esp. for single monitor. */
+ *(void **)(&x11Context.pXRRGetMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRGetMonitors");
+ checkFunctionPtr(x11Context.pXRRGetMonitors);
+
+ *(void **)(&x11Context.pXRRFreeMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeMonitors");
+ checkFunctionPtr(x11Context.pXRRFreeMonitors);
+
+ x11Context.fMonitorInfoAvailable = x11Context.pXRRGetMonitors && x11Context.pXRRFreeMonitors;
+
+ *(void **)(&x11Context.pXRRGetScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRGetScreenResources");
+ checkFunctionPtr(x11Context.pXRRGetScreenResources);
+
+ *(void **)(&x11Context.pXRRSetCrtcConfig) = dlsym(x11Context.pRandLibraryHandle, "XRRSetCrtcConfig");
+ checkFunctionPtr(x11Context.pXRRSetCrtcConfig);
+
+ *(void **)(&x11Context.pXRRFreeScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeScreenResources");
+ checkFunctionPtr(x11Context.pXRRFreeScreenResources);
+
+ *(void **)(&x11Context.pXRRFreeModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeModeInfo");
+ checkFunctionPtr(x11Context.pXRRFreeModeInfo);
+
+ *(void **)(&x11Context.pXRRFreeOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeOutputInfo");
+ checkFunctionPtr(x11Context.pXRRFreeOutputInfo);
+
+ *(void **)(&x11Context.pXRRSetScreenSize) = dlsym(x11Context.pRandLibraryHandle, "XRRSetScreenSize");
+ checkFunctionPtr(x11Context.pXRRSetScreenSize);
+
+ *(void **)(&x11Context.pXRRUpdateConfiguration) = dlsym(x11Context.pRandLibraryHandle, "XRRUpdateConfiguration");
+ checkFunctionPtr(x11Context.pXRRUpdateConfiguration);
+
+ *(void **)(&x11Context.pXRRAllocModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRAllocModeInfo");
+ checkFunctionPtr(x11Context.pXRRAllocModeInfo);
+
+ *(void **)(&x11Context.pXRRCreateMode) = dlsym(x11Context.pRandLibraryHandle, "XRRCreateMode");
+ checkFunctionPtr(x11Context.pXRRCreateMode);
+
+ *(void **)(&x11Context.pXRRGetOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetOutputInfo");
+ checkFunctionPtr(x11Context.pXRRGetOutputInfo);
+
+ *(void **)(&x11Context.pXRRGetCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetCrtcInfo");
+ checkFunctionPtr(x11Context.pXRRGetCrtcInfo);
+
+ *(void **)(&x11Context.pXRRFreeCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeCrtcInfo");
+ checkFunctionPtr(x11Context.pXRRFreeCrtcInfo);
+
+ *(void **)(&x11Context.pXRRAddOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRAddOutputMode");
+ checkFunctionPtr(x11Context.pXRRAddOutputMode);
+
+ *(void **)(&x11Context.pXRRDeleteOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDeleteOutputMode");
+ checkFunctionPtr(x11Context.pXRRDeleteOutputMode);
+
+ *(void **)(&x11Context.pXRRDestroyMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDestroyMode");
+ checkFunctionPtr(x11Context.pXRRDestroyMode);
+
+ *(void **)(&x11Context.pXRRSetOutputPrimary) = dlsym(x11Context.pRandLibraryHandle, "XRRSetOutputPrimary");
+ checkFunctionPtr(x11Context.pXRRSetOutputPrimary);
+
+ return VINF_SUCCESS;
+}
+#endif
+
+static void x11Connect()
+{
+ x11Context.pScreenResources = NULL;
+ x11Context.pXRRSelectInput = NULL;
+ x11Context.pRandLibraryHandle = NULL;
+ x11Context.pXRRQueryExtension = NULL;
+ x11Context.pXRRQueryVersion = NULL;
+ x11Context.pXRRGetMonitors = NULL;
+ x11Context.pXRRGetScreenResources = NULL;
+ x11Context.pXRRSetCrtcConfig = NULL;
+ x11Context.pXRRFreeMonitors = NULL;
+ x11Context.pXRRFreeScreenResources = NULL;
+ x11Context.pXRRFreeOutputInfo = NULL;
+ x11Context.pXRRFreeModeInfo = NULL;
+ x11Context.pXRRSetScreenSize = NULL;
+ x11Context.pXRRUpdateConfiguration = NULL;
+ x11Context.pXRRAllocModeInfo = NULL;
+ x11Context.pXRRCreateMode = NULL;
+ x11Context.pXRRGetOutputInfo = NULL;
+ x11Context.pXRRGetCrtcInfo = NULL;
+ x11Context.pXRRFreeCrtcInfo = NULL;
+ x11Context.pXRRAddOutputMode = NULL;
+ x11Context.pXRRDeleteOutputMode = NULL;
+ x11Context.pXRRDestroyMode = NULL;
+ x11Context.pXRRSetOutputPrimary = NULL;
+ x11Context.fWmwareCtrlExtention = false;
+ x11Context.fMonitorInfoAvailable = false;
+ x11Context.hRandRMajor = 0;
+ x11Context.hRandRMinor = 0;
+
+ int dummy;
+ if (x11Context.pDisplay != NULL)
+ VBClLogFatalError("%s called with bad argument\n", __func__);
+ x11Context.pDisplay = XOpenDisplay(NULL);
+ x11Context.pDisplayRandRMonitoring = XOpenDisplay(NULL);
+ if (x11Context.pDisplay == NULL)
+ return;
+#ifndef WITH_DISTRO_XRAND_XINERAMA
+ if (openLibRandR() != VINF_SUCCESS)
+ {
+ XCloseDisplay(x11Context.pDisplay);
+ XCloseDisplay(x11Context.pDisplayRandRMonitoring);
+ x11Context.pDisplay = NULL;
+ x11Context.pDisplayRandRMonitoring = NULL;
+ return;
+ }
+#endif
+
+ x11Context.fWmwareCtrlExtention = XQueryExtension(x11Context.pDisplay, "VMWARE_CTRL",
+ &x11Context.hVMWCtrlMajorOpCode, &dummy, &dummy);
+ if (!x11Context.fWmwareCtrlExtention)
+ VBClLogError("VMWARE's ctrl extension is not available! Multi monitor management is not possible\n");
+ else
+ VBClLogInfo("VMWARE's ctrl extension is available. Major Opcode is %d.\n", x11Context.hVMWCtrlMajorOpCode);
+
+ /* Check Xrandr stuff. */
+ bool fSuccess = false;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ fSuccess = XRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
+#else
+ if (x11Context.pXRRQueryExtension)
+ fSuccess = x11Context.pXRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
+#endif
+ if (fSuccess)
+ {
+ fSuccess = false;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ fSuccess = XRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
+#else
+ if (x11Context.pXRRQueryVersion)
+ fSuccess = x11Context.pXRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
+#endif
+ if (!fSuccess)
+ {
+ XCloseDisplay(x11Context.pDisplay);
+ x11Context.pDisplay = NULL;
+ return;
+ }
+ if (x11Context.hRandRMajor < 1 || x11Context.hRandRMinor <= 3)
+ {
+ VBClLogError("Resizing service requires libXrandr Version >= 1.4. Detected version is %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor);
+ XCloseDisplay(x11Context.pDisplay);
+ x11Context.pDisplay = NULL;
+
+ int rc = VbglR3DrmLegacyX11AgentStart();
+ VBClLogInfo("Attempt to start legacy X11 resize agent, rc=%Rrc\n", rc);
+
+ return;
+ }
+ }
+ x11Context.rootWindow = DefaultRootWindow(x11Context.pDisplay);
+ x11Context.hEventMask = RRScreenChangeNotifyMask;
+
+ /* Select the XEvent types we want to listen to. */
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask);
+#endif
+ x11Context.iDefaultScreen = DefaultScreen(x11Context.pDisplay);
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#else
+ if (x11Context.pXRRGetScreenResources)
+ x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#endif
+ x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+}
+
+static int determineOutputCount()
+{
+ if (!x11Context.pScreenResources)
+ return 0;
+ return x11Context.pScreenResources->noutput;
+}
+
+static int findExistingModeIndex(unsigned iXRes, unsigned iYRes)
+{
+ if (!x11Context.pScreenResources)
+ return -1;
+ for (int i = 0; i < x11Context.pScreenResources->nmode; ++i)
+ {
+ if (x11Context.pScreenResources->modes[i].width == iXRes && x11Context.pScreenResources->modes[i].height == iYRes)
+ return i;
+ }
+ return -1;
+}
+
+static bool disableCRTC(RRCrtc crtcID)
+{
+ XRRCrtcInfo *pCrctInfo = NULL;
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pCrctInfo = XRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID);
+#else
+ if (x11Context.pXRRGetCrtcInfo)
+ pCrctInfo = x11Context.pXRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID);
+#endif
+
+ if (!pCrctInfo)
+ return false;
+
+ Status ret = Success;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID,
+ CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0);
+#else
+ if (x11Context.pXRRSetCrtcConfig)
+ ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID,
+ CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0);
+#endif
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeCrtcInfo(pCrctInfo);
+#else
+ if (x11Context.pXRRFreeCrtcInfo)
+ x11Context.pXRRFreeCrtcInfo(pCrctInfo);
+#endif
+
+ /** @todo In case of unsuccesful crtc config set we have to revert frame buffer size and crtc sizes. */
+ if (ret == Success)
+ return true;
+ else
+ return false;
+}
+
+static XRRScreenSize currentSize()
+{
+ XRRScreenSize cSize;
+ cSize.width = DisplayWidth(x11Context.pDisplay, x11Context.iDefaultScreen);
+ cSize.mwidth = DisplayWidthMM(x11Context.pDisplay, x11Context.iDefaultScreen);
+ cSize.height = DisplayHeight(x11Context.pDisplay, x11Context.iDefaultScreen);
+ cSize.mheight = DisplayHeightMM(x11Context.pDisplay, x11Context.iDefaultScreen);
+ return cSize;
+}
+
+static unsigned int computeDpi(unsigned int pixels, unsigned int mm)
+{
+ unsigned int dpi = 0;
+ if (mm > 0)
+ dpi = (unsigned int)((double)pixels * MILLIS_PER_INCH / (double)mm + 0.5);
+ return dpi > 0 ? dpi : (unsigned int)DEFAULT_DPI;
+}
+
+static bool resizeFrameBuffer(struct RANDROUTPUT *paOutputs)
+{
+ unsigned int iXRes = 0;
+ unsigned int iYRes = 0;
+ /* Don't care about the output positions for now. */
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ if (!paOutputs[i].fEnabled)
+ continue;
+ iXRes += paOutputs[i].width;
+ iYRes = iYRes < paOutputs[i].height ? paOutputs[i].height : iYRes;
+ }
+ XRRScreenSize cSize= currentSize();
+ unsigned int xdpi = computeDpi(cSize.width, cSize.mwidth);
+ unsigned int ydpi = computeDpi(cSize.height, cSize.mheight);
+ unsigned int xmm;
+ unsigned int ymm;
+ xmm = (int)(MILLIS_PER_INCH * iXRes / ((double)xdpi) + 0.5);
+ ymm = (int)(MILLIS_PER_INCH * iYRes / ((double)ydpi) + 0.5);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask);
+ XRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask);
+ if (x11Context.pXRRSetScreenSize)
+ x11Context.pXRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm);
+#endif
+ XSync(x11Context.pDisplay, False);
+ XEvent configEvent;
+ bool event = false;
+ while (XCheckTypedEvent(x11Context.pDisplay, RRScreenChangeNotify + x11Context.hRandREventBase, &configEvent))
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRUpdateConfiguration(&configEvent);
+#else
+ if (x11Context.pXRRUpdateConfiguration)
+ x11Context.pXRRUpdateConfiguration(&configEvent);
+#endif
+ event = true;
+ }
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
+#endif
+ XRRScreenSize newSize = currentSize();
+
+ /* On Solaris guest, new screen size is not reported properly despite
+ * RRScreenChangeNotify event arrives. Hense, only check for event here.
+ * Linux guests do report new size correctly. */
+ if ( !event
+#ifndef RT_OS_SOLARIS
+ || newSize.width != (int)iXRes || newSize.height != (int)iYRes
+#endif
+ )
+ {
+ VBClLogError("Resizing frame buffer to %d %d has failed, current mode %d %d\n",
+ iXRes, iYRes, newSize.width, newSize.height);
+ return false;
+ }
+ return true;
+}
+
+static XRRModeInfo *createMode(int iXRes, int iYRes)
+{
+ XRRModeInfo *pModeInfo = NULL;
+ char sModeName[126];
+ sprintf(sModeName, "%dx%d_vbox", iXRes, iYRes);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pModeInfo = XRRAllocModeInfo(sModeName, strlen(sModeName));
+#else
+ if (x11Context.pXRRAllocModeInfo)
+ pModeInfo = x11Context.pXRRAllocModeInfo(sModeName, strlen(sModeName));
+#endif
+ pModeInfo->width = iXRes;
+ pModeInfo->height = iYRes;
+
+ DisplayModeR const mode = VBoxClient_xf86CVTMode(iXRes, iYRes, 60 /*VRefresh */, true /*Reduced */, false /* Interlaced */);
+
+ /* Convert kHz to Hz: f86CVTMode returns clock value in units of kHz,
+ * XRRCreateMode will expect it in units of Hz. */
+ pModeInfo->dotClock = mode.Clock * 1000;
+
+ pModeInfo->hSyncStart = mode.HSyncStart;
+ pModeInfo->hSyncEnd = mode.HSyncEnd;
+ pModeInfo->hTotal = mode.HTotal;
+ pModeInfo->hSkew = mode.HSkew;
+ pModeInfo->vSyncStart = mode.VSyncStart;
+ pModeInfo->vSyncEnd = mode.VSyncEnd;
+ pModeInfo->vTotal = mode.VTotal;
+
+ RRMode newMode = None;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ newMode = XRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo);
+#else
+ if (x11Context.pXRRCreateMode)
+ newMode = x11Context.pXRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo);
+#endif
+ if (newMode == None)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeModeInfo(pModeInfo);
+#else
+ if (x11Context.pXRRFreeModeInfo)
+ x11Context.pXRRFreeModeInfo(pModeInfo);
+#endif
+ return NULL;
+ }
+ pModeInfo->id = newMode;
+ return pModeInfo;
+}
+
+static bool configureOutput(int iOutputIndex, struct RANDROUTPUT *paOutputs)
+{
+ if (iOutputIndex >= x11Context.hOutputCount)
+ {
+ VBClLogError("Output index %d is greater than # of oputputs %d\n", iOutputIndex, x11Context.hOutputCount);
+ return false;
+ }
+
+ AssertReturn(iOutputIndex >= 0, false);
+ AssertReturn(iOutputIndex < VMW_MAX_HEADS, false);
+
+ /* Remember the last instantiated display mode ID here. This mode will be replaced with the
+ * new one on the next guest screen resize event. */
+ static RRMode aPrevMode[VMW_MAX_HEADS];
+
+ RROutput outputId = x11Context.pScreenResources->outputs[iOutputIndex];
+ XRROutputInfo *pOutputInfo = NULL;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId);
+#else
+ if (x11Context.pXRRGetOutputInfo)
+ pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId);
+#endif
+ if (!pOutputInfo)
+ return false;
+ XRRModeInfo *pModeInfo = NULL;
+ bool fNewMode = false;
+ /* Index of the mode within the XRRScreenResources.modes array. -1 if such a mode with required resolution does not exists*/
+ int iModeIndex = findExistingModeIndex(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
+ if (iModeIndex != -1 && iModeIndex < x11Context.pScreenResources->nmode)
+ pModeInfo = &(x11Context.pScreenResources->modes[iModeIndex]);
+ else
+ {
+ /* A mode with required size was not found. Create a new one. */
+ pModeInfo = createMode(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
+ VBClLogInfo("create mode %s (%u) on output %d\n", pModeInfo->name, pModeInfo->id, iOutputIndex);
+ fNewMode = true;
+ }
+ if (!pModeInfo)
+ {
+ VBClLogError("Could not create mode for the resolution (%d, %d)\n",
+ paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
+ return false;
+ }
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id);
+#else
+ if (x11Context.pXRRAddOutputMode)
+ x11Context.pXRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id);
+#endif
+
+ /* If mode has been newly created, destroy and forget mode created on previous guest screen resize event. */
+ if ( aPrevMode[iOutputIndex] > 0
+ && pModeInfo->id != aPrevMode[iOutputIndex]
+ && fNewMode)
+ {
+ VBClLogInfo("removing unused mode %u from output %d\n", aPrevMode[iOutputIndex], iOutputIndex);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]);
+ XRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]);
+#else
+ if (x11Context.pXRRDeleteOutputMode)
+ x11Context.pXRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]);
+ if (x11Context.pXRRDestroyMode)
+ x11Context.pXRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]);
+#endif
+ /* Forget destroyed mode. */
+ aPrevMode[iOutputIndex] = 0;
+ }
+
+ /* Only cache modes created "by us". XRRDestroyMode will complain if provided mode
+ * was not created by XRRCreateMode call. */
+ if (fNewMode)
+ aPrevMode[iOutputIndex] = pModeInfo->id;
+
+ if (paOutputs[iOutputIndex].fPrimary)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId);
+#else
+ if (x11Context.pXRRSetOutputPrimary)
+ x11Context.pXRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId);
+#endif
+ }
+
+ /* Make sure outputs crtc is set. */
+ pOutputInfo->crtc = pOutputInfo->crtcs[0];
+
+ RRCrtc crtcId = pOutputInfo->crtcs[0];
+ Status ret = Success;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime,
+ paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y,
+ pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/);
+#else
+ if (x11Context.pXRRSetCrtcConfig)
+ ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime,
+ paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y,
+ pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/);
+#endif
+ if (ret != Success)
+ VBClLogError("crtc set config failed for output %d\n", iOutputIndex);
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeOutputInfo(pOutputInfo);
+#else
+ if (x11Context.pXRRFreeOutputInfo)
+ x11Context.pXRRFreeOutputInfo(pOutputInfo);
+#endif
+
+ if (fNewMode)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeModeInfo(pModeInfo);
+#else
+ if (x11Context.pXRRFreeModeInfo)
+ x11Context.pXRRFreeModeInfo(pModeInfo);
+#endif
+ }
+ return true;
+}
+
+/** Construct the xrandr command which sets the whole monitor topology each time. */
+static void setXrandrTopology(struct RANDROUTPUT *paOutputs)
+{
+ if (!x11Context.pDisplay)
+ {
+ VBClLogInfo("not connected to X11\n");
+ return;
+ }
+
+ XGrabServer(x11Context.pDisplay);
+ if (x11Context.fWmwareCtrlExtention)
+ callVMWCTRL(paOutputs);
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#else
+ if (x11Context.pXRRGetScreenResources)
+ x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#endif
+
+ x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0;
+ if (!x11Context.pScreenResources)
+ {
+ XUngrabServer(x11Context.pDisplay);
+ XFlush(x11Context.pDisplay);
+ return;
+ }
+
+ /* Disable crtcs. */
+ for (int i = 0; i < x11Context.pScreenResources->noutput; ++i)
+ {
+ XRROutputInfo *pOutputInfo = NULL;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]);
+#else
+ if (x11Context.pXRRGetOutputInfo)
+ pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]);
+#endif
+ if (!pOutputInfo)
+ continue;
+ if (pOutputInfo->crtc == None)
+ continue;
+
+ if (!disableCRTC(pOutputInfo->crtc))
+ {
+ VBClLogFatalError("Crtc disable failed %lu\n", pOutputInfo->crtc);
+ XUngrabServer(x11Context.pDisplay);
+ XSync(x11Context.pDisplay, False);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+ XFlush(x11Context.pDisplay);
+ return;
+ }
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeOutputInfo(pOutputInfo);
+#else
+ if (x11Context.pXRRFreeOutputInfo)
+ x11Context.pXRRFreeOutputInfo(pOutputInfo);
+#endif
+ }
+ /* Resize the frame buffer. */
+ if (!resizeFrameBuffer(paOutputs))
+ {
+ XUngrabServer(x11Context.pDisplay);
+ XSync(x11Context.pDisplay, False);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+ XFlush(x11Context.pDisplay);
+ return;
+ }
+
+ /* Configure the outputs. */
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ /* be paranoid. */
+ if (i >= x11Context.pScreenResources->noutput)
+ break;
+ if (!paOutputs[i].fEnabled)
+ continue;
+ if (configureOutput(i, paOutputs))
+ VBClLogInfo("output[%d] successfully configured\n", i);
+ else
+ VBClLogError("failed to configure output[%d]\n", i);
+ }
+ XSync(x11Context.pDisplay, False);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+ XUngrabServer(x11Context.pDisplay);
+ XFlush(x11Context.pDisplay);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSVGAWorker(bool volatile *pfShutdown)
+{
+ /* Do not acknowledge the first event we query for to pick up old events,
+ * e.g. from before a guest reboot. */
+ bool fAck = false;
+ bool fFirstRun = true;
+ static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS];
+
+ int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
+ rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
+ if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
+ return VERR_RESOURCE_BUSY;
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ for (;;)
+ {
+ struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
+ uint32_t cDisplaysOut;
+ /* Query the first size without waiting. This lets us e.g. pick up
+ * the last event before a guest reboot when we start again after. */
+ rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck);
+ fAck = true;
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
+ if (cDisplaysOut > VMW_MAX_HEADS)
+ VBClLogError("Display change request contained, rc=%Rrc\n", rc);
+ if (cDisplaysOut > 0)
+ {
+ for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
+ {
+ uint32_t idDisplay = aDisplays[i].idDisplay;
+ if (idDisplay >= VMW_MAX_HEADS)
+ continue;
+ aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags;
+ if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
+ {
+ if (idDisplay == 0 || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
+ {
+ aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin;
+ aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin;
+ } else {
+ aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx;
+ aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin;
+ }
+ aMonitors[idDisplay].cx = aDisplays[i].cx;
+ aMonitors[idDisplay].cy = aDisplays[i].cy;
+ }
+ }
+ /* Create a whole topology and send it to xrandr. */
+ struct RANDROUTPUT aOutputs[VMW_MAX_HEADS];
+ int iRunningX = 0;
+ for (int j = 0; j < x11Context.hOutputCount; ++j)
+ {
+ aOutputs[j].x = iRunningX;
+ aOutputs[j].y = aMonitors[j].yOrigin;
+ aOutputs[j].width = aMonitors[j].cx;
+ aOutputs[j].height = aMonitors[j].cy;
+ aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED);
+ aOutputs[j].fPrimary = (aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY);
+ if (aOutputs[j].fEnabled)
+ iRunningX += aOutputs[j].width;
+ }
+ /* In 32-bit guests GAs build on our release machines causes an xserver lock during vmware_ctrl extention
+ if we do the call withing XGrab. We make the call the said extension only once (to connect the outputs)
+ rather than at each resize iteration. */
+#if ARCH_BITS == 32
+ if (fFirstRun)
+ callVMWCTRL(aOutputs);
+#endif
+ setXrandrTopology(aOutputs);
+ /* Wait for some seconds and set toplogy again after the boot. In some desktop environments (cinnamon) where
+ DE get into our resizing our first resize is reverted by the DE. Sleeping for some secs. helps. Setting
+ topology a 2nd time resolves the black screen I get after resizing.*/
+ if (fFirstRun)
+ {
+ sleep(4);
+ setXrandrTopology(aOutputs);
+ fFirstRun = false;
+ }
+ }
+ uint32_t events;
+ do
+ {
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS, &events);
+ } while (rc == VERR_TIMEOUT && !ASMAtomicReadBool(pfShutdown));
+
+ if (ASMAtomicReadBool(pfShutdown))
+ {
+ /* Shutdown requested. */
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc);
+ }
+
+ };
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcDisplaySVGA =
+{
+ "dp-svga-x11", /* szName */
+ "SVGA X11 display", /* pszDescription */
+ ".vboxclient-display-svga-x11", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSVGAInit, /* pfnInit */
+ vbclSVGAWorker, /* pfnWorker */
+ vbclSVGAStop, /* pfnStop*/
+ NULL /* pfnTerm */
+};
+