summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp')
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp568
1 files changed, 568 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp
new file mode 100644
index 00000000..e4a88e7d
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp
@@ -0,0 +1,568 @@
+/* $Id: seamless-x11.cpp $ */
+/** @file
+ * X11 Seamless mode.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header files *
+*********************************************************************************************************************************/
+
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/vector.h>
+#include <iprt/thread.h>
+#include <VBox/log.h>
+
+#include "seamless-x11.h"
+#include "VBoxClient.h"
+
+#include <X11/Xatom.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <limits.h>
+
+#ifdef TESTCASE
+#undef DefaultRootWindow
+#define DefaultRootWindow XDefaultRootWindow
+#endif
+
+/*****************************************************************************
+* Static functions *
+*****************************************************************************/
+
+static unsigned char *XXGetProperty(Display *aDpy, Window aWnd, Atom aPropType,
+ const char *aPropName, unsigned long *nItems)
+{
+ LogRelFlowFuncEnter();
+ Atom propNameAtom = XInternAtom (aDpy, aPropName,
+ True /* only_if_exists */);
+ if (propNameAtom == None)
+ {
+ return NULL;
+ }
+
+ Atom actTypeAtom = None;
+ int actFmt = 0;
+ unsigned long nBytesAfter = 0;
+ unsigned char *propVal = 0;
+ int rc = XGetWindowProperty (aDpy, aWnd, propNameAtom,
+ 0, LONG_MAX, False /* delete */,
+ aPropType, &actTypeAtom, &actFmt,
+ nItems, &nBytesAfter, &propVal);
+ if (rc != Success)
+ return NULL;
+
+ LogRelFlowFuncLeave();
+ return propVal;
+}
+
+int SeamlessX11::init(PFNSENDREGIONUPDATE pHostCallback)
+{
+ int rc = VINF_SUCCESS;
+
+ LogRelFlowFuncEnter();
+ if (mHostCallback != NULL) /* Assertion */
+ {
+ VBClLogError("Attempting to initialise seamless guest object twice!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ if (!(mDisplay = XOpenDisplay(NULL)))
+ {
+ VBClLogError("Seamless guest object failed to acquire a connection to the display\n");
+ return VERR_ACCESS_DENIED;
+ }
+ mHostCallback = pHostCallback;
+ mEnabled = false;
+ unmonitorClientList();
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Shutdown seamless event monitoring.
+ */
+void SeamlessX11::uninit(void)
+{
+ if (mHostCallback)
+ stop();
+ mHostCallback = NULL;
+
+ /* Before closing a Display, make sure X11 is still running. The indicator
+ * that is when XOpenDisplay() returns non NULL. If it is not a
+ * case, XCloseDisplay() will hang on internal X11 mutex forever. */
+ Display *pDisplay = XOpenDisplay(NULL);
+ if (pDisplay)
+ {
+ XCloseDisplay(pDisplay);
+ if (mDisplay)
+ {
+ XCloseDisplay(mDisplay);
+ mDisplay = NULL;
+ }
+ }
+
+ if (mpRects)
+ {
+ RTMemFree(mpRects);
+ mpRects = NULL;
+ }
+}
+
+/**
+ * Read information about currently visible windows in the guest and subscribe to X11
+ * events about changes to this information.
+ *
+ * @note This class does not contain its own event thread, so an external thread must
+ * call nextConfigurationEvent() for as long as events are wished.
+ * @todo This function should switch the guest to fullscreen mode.
+ */
+int SeamlessX11::start(void)
+{
+ int rc = VINF_SUCCESS;
+ /** Dummy values for XShapeQueryExtension */
+ int error, event;
+
+ LogRelFlowFuncEnter();
+ if (mEnabled)
+ return VINF_SUCCESS;
+ mSupportsShape = XShapeQueryExtension(mDisplay, &event, &error);
+ mEnabled = true;
+ monitorClientList();
+ rebuildWindowTree();
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** Stop reporting seamless events to the host. Free information about guest windows
+ and stop requesting updates. */
+void SeamlessX11::stop(void)
+{
+ LogRelFlowFuncEnter();
+ if (!mEnabled)
+ return;
+ mEnabled = false;
+ unmonitorClientList();
+ freeWindowTree();
+ LogRelFlowFuncLeave();
+}
+
+void SeamlessX11::monitorClientList(void)
+{
+ LogRelFlowFuncEnter();
+ XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask | SubstructureNotifyMask);
+}
+
+void SeamlessX11::unmonitorClientList(void)
+{
+ LogRelFlowFuncEnter();
+ XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask);
+}
+
+/**
+ * Recreate the table of toplevel windows of clients on the default root window of the
+ * X server.
+ */
+void SeamlessX11::rebuildWindowTree(void)
+{
+ LogRelFlowFuncEnter();
+ freeWindowTree();
+ addClients(DefaultRootWindow(mDisplay));
+ mChanged = true;
+}
+
+
+/**
+ * Look at the list of children of a virtual root window and add them to the list of clients
+ * if they belong to a client which is not a virtual root.
+ *
+ * @param hRoot the virtual root window to be examined
+ */
+void SeamlessX11::addClients(const Window hRoot)
+{
+ /** Unused out parameters of XQueryTree */
+ Window hRealRoot, hParent;
+ /** The list of children of the root supplied, raw pointer */
+ Window *phChildrenRaw = NULL;
+ /** The list of children of the root supplied, auto-pointer */
+ Window *phChildren;
+ /** The number of children of the root supplied */
+ unsigned cChildren;
+
+ LogRelFlowFuncEnter();
+ if (!XQueryTree(mDisplay, hRoot, &hRealRoot, &hParent, &phChildrenRaw, &cChildren))
+ return;
+ phChildren = phChildrenRaw;
+ for (unsigned i = 0; i < cChildren; ++i)
+ addClientWindow(phChildren[i]);
+ XFree(phChildrenRaw);
+ LogRelFlowFuncLeave();
+}
+
+
+void SeamlessX11::addClientWindow(const Window hWin)
+{
+ LogRelFlowFuncEnter();
+ XWindowAttributes winAttrib;
+ bool fAddWin = true;
+ Window hClient = XmuClientWindow(mDisplay, hWin);
+
+ if (isVirtualRoot(hClient))
+ fAddWin = false;
+ if (fAddWin && !XGetWindowAttributes(mDisplay, hWin, &winAttrib))
+ {
+ VBClLogError("Failed to get the window attributes for window %d\n", hWin);
+ fAddWin = false;
+ }
+ if (fAddWin && (winAttrib.map_state == IsUnmapped))
+ fAddWin = false;
+ XSizeHints dummyHints;
+ long dummyLong;
+ /* Apparently (?) some old kwin versions had unwanted client windows
+ * without normal hints. */
+ if (fAddWin && (!XGetWMNormalHints(mDisplay, hClient, &dummyHints,
+ &dummyLong)))
+ {
+ LogRelFlowFunc(("window %lu, client window %lu has no size hints\n", hWin, hClient));
+ fAddWin = false;
+ }
+ if (fAddWin)
+ {
+ XRectangle *pRects = NULL;
+ int cRects = 0, iOrdering;
+ bool hasShape = false;
+
+ LogRelFlowFunc(("adding window %lu, client window %lu\n", hWin,
+ hClient));
+ if (mSupportsShape)
+ {
+ XShapeSelectInput(mDisplay, hWin, ShapeNotifyMask);
+ pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects, &iOrdering);
+ if (!pRects)
+ cRects = 0;
+ else
+ {
+ if ( (cRects > 1)
+ || (pRects[0].x != 0)
+ || (pRects[0].y != 0)
+ || (pRects[0].width != winAttrib.width)
+ || (pRects[0].height != winAttrib.height)
+ )
+ hasShape = true;
+ }
+ }
+ mGuestWindows.addWindow(hWin, hasShape, winAttrib.x, winAttrib.y,
+ winAttrib.width, winAttrib.height, cRects,
+ pRects);
+ }
+ LogRelFlowFuncLeave();
+}
+
+
+/**
+ * Checks whether a window is a virtual root.
+ * @returns true if it is, false otherwise
+ * @param hWin the window to be examined
+ */
+bool SeamlessX11::isVirtualRoot(Window hWin)
+{
+ unsigned char *windowTypeRaw = NULL;
+ Atom *windowType;
+ unsigned long ulCount;
+ bool rc = false;
+
+ LogRelFlowFuncEnter();
+ windowTypeRaw = XXGetProperty(mDisplay, hWin, XA_ATOM, WM_TYPE_PROP, &ulCount);
+ if (windowTypeRaw != NULL)
+ {
+ windowType = (Atom *)(windowTypeRaw);
+ if ( (ulCount != 0)
+ && (*windowType == XInternAtom(mDisplay, WM_TYPE_DESKTOP_PROP, True)))
+ rc = true;
+ }
+ if (windowTypeRaw)
+ XFree(windowTypeRaw);
+ LogRelFlowFunc(("returning %RTbool\n", rc));
+ return rc;
+}
+
+DECLCALLBACK(int) VBoxGuestWinFree(VBoxGuestWinInfo *pInfo, void *pvParam)
+{
+ Display *pDisplay = (Display *)pvParam;
+
+ XShapeSelectInput(pDisplay, pInfo->Core.Key, 0);
+ delete pInfo;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Free all information in the tree of visible windows
+ */
+void SeamlessX11::freeWindowTree(void)
+{
+ /* We use post-increment in the operation to prevent the iterator from being invalidated. */
+ LogRelFlowFuncEnter();
+ mGuestWindows.detachAll(VBoxGuestWinFree, mDisplay);
+ LogRelFlowFuncLeave();
+}
+
+
+/**
+ * Waits for a position or shape-related event from guest windows
+ *
+ * @note Called from the guest event thread.
+ */
+void SeamlessX11::nextConfigurationEvent(void)
+{
+ XEvent event;
+
+ LogRelFlowFuncEnter();
+ /* Start by sending information about the current window setup to the host. We do this
+ here because we want to send all such information from a single thread. */
+ if (mChanged && mEnabled)
+ {
+ updateRects();
+ mHostCallback(mpRects, mcRects);
+ }
+ mChanged = false;
+
+ if (XPending(mDisplay) > 0)
+ {
+ /* We execute this even when seamless is disabled, as it also waits for
+ * enable and disable notification. */
+ XNextEvent(mDisplay, &event);
+ } else
+ {
+ /* This function is called in a loop by upper layer. In order to
+ * prevent CPU spinning, sleep a bit before returning. */
+ RTThreadSleep(300 /* ms */);
+ return;
+ }
+
+ if (!mEnabled)
+ return;
+ switch (event.type)
+ {
+ case ConfigureNotify:
+ {
+ XConfigureEvent *pConf = &event.xconfigure;
+ LogRelFlowFunc(("configure event, window=%lu, x=%i, y=%i, w=%i, h=%i, send_event=%RTbool\n",
+ (unsigned long) pConf->window, (int) pConf->x,
+ (int) pConf->y, (int) pConf->width,
+ (int) pConf->height, pConf->send_event));
+ }
+ doConfigureEvent(event.xconfigure.window);
+ break;
+ case MapNotify:
+ LogRelFlowFunc(("map event, window=%lu, send_event=%RTbool\n",
+ (unsigned long) event.xmap.window,
+ event.xmap.send_event));
+ rebuildWindowTree();
+ break;
+ case PropertyNotify:
+ if ( event.xproperty.atom != XInternAtom(mDisplay, "_NET_CLIENT_LIST", True /* only_if_exists */)
+ || event.xproperty.window != DefaultRootWindow(mDisplay))
+ break;
+ LogRelFlowFunc(("_NET_CLIENT_LIST property event on root window\n"));
+ rebuildWindowTree();
+ break;
+ case VBoxShapeNotify: /* This is defined wrong in my X11 header files! */
+ LogRelFlowFunc(("shape event, window=%lu, send_event=%RTbool\n",
+ (unsigned long) event.xany.window,
+ event.xany.send_event));
+ /* the window member in xany is in the same place as in the shape event */
+ doShapeEvent(event.xany.window);
+ break;
+ case UnmapNotify:
+ LogRelFlowFunc(("unmap event, window=%lu, send_event=%RTbool\n",
+ (unsigned long) event.xunmap.window,
+ event.xunmap.send_event));
+ rebuildWindowTree();
+ break;
+ default:
+ break;
+ }
+ LogRelFlowFunc(("processed event\n"));
+}
+
+/**
+ * Handle a configuration event in the seamless event thread by setting the new position.
+ *
+ * @param hWin the window to be examined
+ */
+void SeamlessX11::doConfigureEvent(Window hWin)
+{
+ VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin);
+ if (pInfo)
+ {
+ XWindowAttributes winAttrib;
+
+ if (!XGetWindowAttributes(mDisplay, hWin, &winAttrib))
+ return;
+ pInfo->mX = winAttrib.x;
+ pInfo->mY = winAttrib.y;
+ pInfo->mWidth = winAttrib.width;
+ pInfo->mHeight = winAttrib.height;
+ mChanged = true;
+ }
+}
+
+/**
+ * Handle a window shape change event in the seamless event thread.
+ *
+ * @param hWin the window to be examined
+ */
+void SeamlessX11::doShapeEvent(Window hWin)
+{
+ LogRelFlowFuncEnter();
+ VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin);
+ if (pInfo)
+ {
+ XRectangle *pRects;
+ int cRects = 0, iOrdering;
+
+ pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects,
+ &iOrdering);
+ if (!pRects)
+ cRects = 0;
+ pInfo->mhasShape = true;
+ if (pInfo->mpRects)
+ XFree(pInfo->mpRects);
+ pInfo->mcRects = cRects;
+ pInfo->mpRects = pRects;
+ mChanged = true;
+ }
+ LogRelFlowFuncLeave();
+}
+
+/**
+ * Gets the list of visible rectangles
+ */
+RTRECT *SeamlessX11::getRects(void)
+{
+ return mpRects;
+}
+
+/**
+ * Gets the number of rectangles in the visible rectangle list
+ */
+size_t SeamlessX11::getRectCount(void)
+{
+ return mcRects;
+}
+
+RTVEC_DECL(RectList, RTRECT)
+
+static DECLCALLBACK(int) getRectsCallback(VBoxGuestWinInfo *pInfo, struct RectList *pRects)
+{
+ if (pInfo->mhasShape)
+ {
+ for (int i = 0; i < pInfo->mcRects; ++i)
+ {
+ RTRECT *pRect;
+
+ pRect = RectListPushBack(pRects);
+ if (!pRect)
+ return VERR_NO_MEMORY;
+ pRect->xLeft = pInfo->mX
+ + pInfo->mpRects[i].x;
+ pRect->yBottom = pInfo->mY
+ + pInfo->mpRects[i].y
+ + pInfo->mpRects[i].height;
+ pRect->xRight = pInfo->mX
+ + pInfo->mpRects[i].x
+ + pInfo->mpRects[i].width;
+ pRect->yTop = pInfo->mY
+ + pInfo->mpRects[i].y;
+ }
+ }
+ else
+ {
+ RTRECT *pRect;
+
+ pRect = RectListPushBack(pRects);
+ if (!pRect)
+ return VERR_NO_MEMORY;
+ pRect->xLeft = pInfo->mX;
+ pRect->yBottom = pInfo->mY
+ + pInfo->mHeight;
+ pRect->xRight = pInfo->mX
+ + pInfo->mWidth;
+ pRect->yTop = pInfo->mY;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Updates the list of seamless rectangles
+ */
+int SeamlessX11::updateRects(void)
+{
+ LogRelFlowFuncEnter();
+ struct RectList rects = RTVEC_INITIALIZER;
+
+ if (mcRects != 0)
+ {
+ int rc = RectListReserve(&rects, mcRects * 2);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ mGuestWindows.doWithAll((PFNVBOXGUESTWINCALLBACK)getRectsCallback, &rects);
+ if (mpRects)
+ RTMemFree(mpRects);
+ mcRects = RectListSize(&rects);
+ mpRects = RectListDetach(&rects);
+ LogRelFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Send a client event to wake up the X11 seamless event loop prior to stopping it.
+ *
+ * @note This function should only be called from the host event thread.
+ */
+bool SeamlessX11::interruptEventWait(void)
+{
+ bool rc = false;
+ Display *pDisplay = XOpenDisplay(NULL);
+
+ LogRelFlowFuncEnter();
+ if (pDisplay == NULL)
+ {
+ VBClLogError("Failed to open X11 display\n");
+ return false;
+ }
+
+ /* Message contents set to zero. */
+ XClientMessageEvent clientMessage =
+ { ClientMessage, 0, 0, 0, 0, XInternAtom(pDisplay, "VBOX_CLIENT_SEAMLESS_HEARTBEAT", false), 8 };
+
+ if (XSendEvent(pDisplay, DefaultRootWindow(mDisplay), false,
+ PropertyChangeMask, (XEvent *)&clientMessage))
+ rc = true;
+ XCloseDisplay(pDisplay);
+ LogRelFlowFunc(("returning %RTbool\n", rc));
+ return rc;
+}