summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/x11/VBoxClient
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Additions/x11/VBoxClient
parentInitial commit. (diff)
downloadvirtualbox-upstream/7.0.14-dfsg.tar.xz
virtualbox-upstream/7.0.14-dfsg.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient')
-rw-r--r--src/VBox/Additions/x11/VBoxClient/Makefile.kmk236
-rw-r--r--src/VBox/Additions/x11/VBoxClient/VBoxClient.h148
-rw-r--r--src/VBox/Additions/x11/VBoxClient/chk_stubs.c69
-rw-r--r--src/VBox/Additions/x11/VBoxClient/clipboard.cpp440
-rw-r--r--src/VBox/Additions/x11/VBoxClient/clipboard.h49
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-drm.cpp1369
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp421
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp1019
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-helper.h130
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-ipc.cpp451
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-ipc.h242
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp460
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp1402
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp310
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h56
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display.cpp315
-rw-r--r--src/VBox/Additions/x11/VBoxClient/draganddrop.cpp3877
-rw-r--r--src/VBox/Additions/x11/VBoxClient/hostversion.cpp130
-rw-r--r--src/VBox/Additions/x11/VBoxClient/logging.cpp458
-rw-r--r--src/VBox/Additions/x11/VBoxClient/main.cpp885
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp568
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless-x11.h275
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless.cpp360
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless.h109
-rw-r--r--src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup0
-rw-r--r--src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp791
-rw-r--r--src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp185
27 files changed, 14755 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/Makefile.kmk b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk
new file mode 100644
index 00000000..1687b59b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk
@@ -0,0 +1,236 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Guest Addition X11 Client.
+#
+
+#
+# Copyright (C) 2006-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
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Use header files from our tree for randr and xinerama. and don't link but rather dlopen libXrand
+# This is mostly because the GA build boxes can have a very old xrandr lib, so instead of linking we dlopen.
+VBOX_WITH_DISTRO_XRAND_XINERAMA =
+
+# We don't yet have a seamless mode compilation flag, so define it here unconditionally.
+VBOX_WITH_SEAMLESS := 1
+
+#
+# VBoxClient - clipboard and seamless.
+#
+PROGRAMS += VBoxClient
+#
+# Please make sure that you grep the source tree and modify all occurences accordingly
+# if you rename the following program name.
+#
+PROGRAMS.linux += VBoxDRMClient
+
+VBoxClient_TEMPLATE = VBoxGuestR3Exe
+VBoxClient_DEFS += VBOX_X11_CLIPBOARD VBOX_WITH_HGCM
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxClient_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxClient_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+ifdef VBOX_WITH_DBUS
+ VBoxClient_DEFS += VBOX_WITH_DBUS
+endif
+
+VBoxClient_DEFS.linux += _GNU_SOURCE
+VBoxClient_INCS = $(VBOX_GRAPHICS_INCS)
+VBoxClient_INCS += ../x11include/panoramiXproto-1.1
+VBoxClient_INCS += ../x11include/libXrandr-1.5.2
+VBoxClient_INCS += ../x11include/randrproto-1.5.0
+VBoxClient_SOURCES = \
+ main.cpp \
+ logging.cpp
+
+VBoxDRMClient_TEMPLATE = VBoxGuestR3Exe
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxDRMClient_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxDRMClient_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+VBoxDRMClient_SOURCES = \
+ display-drm.cpp \
+ display-ipc.cpp \
+ logging.cpp
+
+
+VBoxClient_SOURCES.linux = \
+ chk_stubs.c
+VBoxClient_LIBPATH = \
+ $(VBOX_LIBPATH32_X11)
+VBoxClient_LIBS.freebsd = \
+ iconv
+VBoxClient_LIBS.linux = \
+ dl
+VBoxClient_LIBS.netbsd = \
+ crypt
+VBoxClient_LIBS.solaris = \
+ dl
+VBoxClient_LIBS = \
+ X11 Xt Xext Xmu
+ifdef VBOX_WITH_DISTRO_XRAND_XINERAMA
+ VBoxClient_DEFS += WITH_DISTRO_XRAND_XINERAMA
+ VBoxClient_LIBS += Xrandr
+endif
+
+# XXX: -L comes from the template, but not rpath
+VBoxClient_LDFLAGS.netbsd = \
+ -Wl,-rpath /usr/X11R7/lib
+
+ifdef VBOX_WITH_DRAG_AND_DROP
+ ifdef VBOX_DND_WITH_XTEST
+ VBoxClient_DEFS += VBOX_DND_WITH_XTEST
+ VBoxClient_LIBS += \
+ Xtst
+ endif
+endif
+
+# This forces the memcpy references in the static libraries to go to
+# __wrap_memcpy, which we can wrap around memcpy@GLIBC_2.2.5. I do not know
+# how else to do that without recompiling or implementing our own memcpy.
+ifeq ($(KBUILD_TARGET),linux)
+ VBoxClient_LDFLAGS.amd64 += \
+ -Wl,--wrap=memcpy
+endif
+
+ifdef VBOX_WITH_GUEST_PROPS
+ VBoxClient_DEFS += VBOX_WITH_GUEST_PROPS
+ VBoxClient_SOURCES += \
+ hostversion.cpp
+ VBoxDRMClient_DEFS += VBOX_WITH_GUEST_PROPS
+endif
+
+ifdef VBOX_WITH_DRAG_AND_DROP
+ VBoxClient_DEFS += \
+ VBOX_WITH_DRAG_AND_DROP \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,)
+ VBoxClient_SOURCES += \
+ draganddrop.cpp
+ VBoxClient_LIBS += \
+ $(VBOX_LIB_VBGL_R3) \
+ $(PATH_STAGE_LIB)/additions/VBoxDnDGuestR3Lib$(VBOX_SUFF_LIB)
+endif
+
+ifdef VBOX_WITH_SEAMLESS
+ VBoxClient_DEFS += VBOX_WITH_SEAMLESS
+ VBoxClient_SOURCES += \
+ seamless.cpp \
+ seamless-x11.cpp
+endif
+
+ifdef VBOX_WITH_VMSVGA
+ VBoxClient_DEFS += VBOX_WITH_VMSVGA
+ VBoxClient_SOURCES += \
+ display.cpp \
+ display-svga-x11.cpp \
+ display-svga-xf86cvt.cpp
+ VBoxClient_SOURCES.linux += \
+ display-svga-session.cpp \
+ display-ipc.cpp \
+ display-helper-gnome3.cpp \
+ display-helper-generic.cpp
+
+ ### include $(PATH_SUB_CURRENT)/helpers/Makefile.kmk
+endif
+
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \
+ clipboard.cpp
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBOX_WITH_SHARED_CLIPBOARD_GUEST
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp
+ endif
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ # @todo Make this dynamic loading more generic.
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/ImageMounter/vboximg-mount/fuse.cpp \
+ clipboard-fuse.cpp
+ # @todo Ditto.
+ VBoxClient_INCS += \
+ $(PATH_ROOT)/src/VBox/ImageMounter/vboximg-mount
+ endif
+ endif
+endif
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+
+ # Set this in LocalConfig.kmk if you are working on the X11 clipboard service
+ # to automatically run the unit test at build time.
+ # OTHERS += $(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto.run
+
+ PROGRAMS += tstSeamlessX11-auto
+ tstSeamlessX11-auto_TEMPLATE = VBoxR3TstExe
+ tstSeamlessX11-auto_SOURCES = \
+ testcase/tstSeamlessX11-auto.cpp \
+ seamless-x11.cpp
+ tstSeamlessX11-auto_DEFS = TESTCASE
+ tstSeamlessX11-auto_LIBS = \
+ $(LIB_RUNTIME)
+
+ TESTING += $(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto
+ $$(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto.run: \
+ $$(tstSeamlessX11-auto_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstSeamlessX11-auto_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ #
+ # Additional testcase designed to be run manually, which initiates and starts the Linux
+ # guest client part of the seamless additions in the host, faking seamless events from
+ # the host and writing information about guest events to standard output.
+ #
+ PROGRAMS += tstSeamlessX11
+ tstSeamlessX11_TEMPLATE = VBoxR3TstExe
+ tstSeamlessX11_SOURCES = \
+ testcase/tstSeamlessX11.cpp \
+ seamless.cpp \
+ seamless-x11.cpp
+ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ tstSeamlessX11_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+ else
+ tstSeamlessX11_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+ endif
+ tstSeamlessX11_LIBPATH = \
+ $(VBOX_LIBPATH_X11)
+ tstSeamlessX11_LIBS = \
+ $(LIB_RUNTIME) \
+ Xext \
+ Xmu \
+ X11
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/x11/VBoxClient/VBoxClient.h b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h
new file mode 100644
index 00000000..2bbf88bf
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h
@@ -0,0 +1,148 @@
+/* $Id: VBoxClient.h $ */
+/** @file
+ *
+ * VirtualBox additions user session daemon.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/log.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/string.h>
+
+/** Environment variable which is exported when in Wayland Desktop Environment. */
+#define VBCL_ENV_WAYLAND_DISPLAY "WAYLAND_DISPLAY"
+/** Environment variable which contains information about currently running Desktop Environment. */
+#define VBCL_ENV_XDG_CURRENT_DESKTOP "XDG_CURRENT_DESKTOP"
+/** Environment variable which contains information about currently running session (X11, Wayland, etc). */
+#define VBCL_ENV_XDG_SESSION_TYPE "XDG_SESSION_TYPE"
+
+int VBClShowNotify(const char *pszHeader, const char *pszBody);
+
+void VBClLogInfo(const char *pszFormat, ...);
+void VBClLogError(const char *pszFormat, ...);
+void VBClLogFatalError(const char *pszFormat, ...);
+void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...);
+
+int VBClLogCreate(const char *pszLogFile);
+void VBClLogSetLogPrefix(const char *pszPrefix);
+void VBClLogDestroy(void);
+
+/**
+ * Detect if user is running on Wayland by checking corresponding environment variable.
+ *
+ * @returns True if Wayland has been detected, False otherwise.
+ */
+extern bool VBClHasWayland(void);
+
+/** Call clean-up for the current service and exit. */
+extern void VBClShutdown(bool fExit = true);
+
+/**
+ * A service descriptor.
+ */
+typedef struct
+{
+ /** The short service name. 16 chars maximum (RTTHREAD_NAME_LEN). */
+ const char *pszName;
+ /** The longer service name. */
+ const char *pszDesc;
+ /** Get the services default path to pidfile, relative to $HOME */
+ /** @todo Should this also have a component relative to the X server number?
+ */
+ const char *pszPidFilePathTemplate;
+ /** The usage options stuff for the --help screen. */
+ const char *pszUsage;
+ /** The option descriptions for the --help screen. */
+ const char *pszOptions;
+
+ /**
+ * Tries to parse the given command line option.
+ *
+ * @returns 0 if we parsed, -1 if it didn't and anything else means exit.
+ * @param ppszShort If not NULL it points to the short option iterator. a short argument.
+ * If NULL examine argv[*pi].
+ * @param argc The argument count.
+ * @param argv The argument vector.
+ * @param pi The argument vector index. Update if any value(s) are eaten.
+ */
+ DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi));
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code, or
+ * VERR_NOT_AVAILABLE if service is supported on this platform in general but not available at the moment.
+ * VERR_NOT_SUPPORTED if service is not supported on this platform. */
+ DECLCALLBACKMEMBER(int, pfnInit,(void));
+
+ /** Called from the worker thread.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS if exitting because *pfShutdown was set.
+ * @param pfShutdown Pointer to a per service termination flag to check
+ * before and after blocking.
+ */
+ DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown));
+
+ /**
+ * Asks the service to stop.
+ *
+ * @remarks Will be called from the signal handler.
+ */
+ DECLCALLBACKMEMBER(void, pfnStop,(void));
+
+ /**
+ * Does termination cleanups.
+ *
+ * @remarks This will be called even if pfnInit hasn't been called or pfnStop failed!
+ */
+ DECLCALLBACKMEMBER(int, pfnTerm,(void));
+} VBCLSERVICE;
+/** Pointer to a VBCLSERVICE. */
+typedef VBCLSERVICE *PVBCLSERVICE;
+/** Pointer to a const VBCLSERVICE. */
+typedef VBCLSERVICE const *PCVBCLSERVICE;
+
+RT_C_DECLS_BEGIN
+extern VBCLSERVICE g_SvcClipboard;
+extern VBCLSERVICE g_SvcDisplayDRM;
+extern VBCLSERVICE g_SvcDisplaySVGA;
+extern VBCLSERVICE g_SvcDisplayLegacy;
+# ifdef RT_OS_LINUX
+extern VBCLSERVICE g_SvcDisplaySVGASession;
+# endif
+extern VBCLSERVICE g_SvcDragAndDrop;
+extern VBCLSERVICE g_SvcHostVersion;
+extern VBCLSERVICE g_SvcSeamless;
+
+extern unsigned g_cVerbosity;
+extern bool g_fDaemonized;
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/chk_stubs.c b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c
new file mode 100644
index 00000000..9b5093eb
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c
@@ -0,0 +1,69 @@
+/* $Id: chk_stubs.c $ */
+/** @file
+ * glibc stubs for the VirtualBox Guest Addition X11 Client.
+ */
+
+/*
+ * Copyright (C) 2018-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
+ */
+
+/* If we want the binary to be usable with glibc 2.3, we have to prevent
+ VBoxClient from containing later symbols. This includes resolution of
+ symbols from supc++ and gcc_eh. */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+extern int __sprintf_chk(char *psz, int fFlags, size_t cb, const char *pszFormat, ...);
+int __sprintf_chk(char *psz, int fFlags, size_t cb, const char *pszFormat, ...)
+{
+ int rc;
+ va_list va;
+
+ (void)fFlags;
+ va_start(va, pszFormat);
+ rc = vsnprintf(psz, cb, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+extern void __stack_chk_fail(void);
+void __stack_chk_fail(void)
+{
+ fprintf(stderr, "Stack check failed!\n");
+ _exit(1);
+}
+
+#ifdef __x86_64
+/* Furthermore, wrap references to memcpy to force them to go to the right
+ * version. We are forced to do it this way because the shared libraries
+ * supc++ and gcc_eh contain references which we cannot change. */
+
+extern void *__wrap_memcpy(void *dest, const void *src, size_t n);
+
+asm (".symver memcpy, memcpy@GLIBC_2.2.5");
+void *__wrap_memcpy(void *dest, const void *src, size_t n)
+{
+ return memcpy(dest, src, n);
+}
+#endif
diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
new file mode 100644
index 00000000..13b1fbc2
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
@@ -0,0 +1,440 @@
+/** $Id: clipboard.cpp $ */
+/** @file
+ * Guest Additions - X11 Shared Clipboard.
+ */
+
+/*
+ * Copyright (C) 2007-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/alloc.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+# include <iprt/dir.h>
+#endif
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+
+#include "VBoxClient.h"
+
+#include "clipboard.h"
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+# include "clipboard-fuse.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Only one context is supported at a time for now. */
+SHCLCONTEXT g_Ctx;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+SHCLFUSECTX g_FuseCtx;
+#endif
+
+
+static DECLCALLBACK(int) vbclOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
+ SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
+
+ int rc = VINF_SUCCESS;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ if (uFmt == VBOX_SHCL_FMT_URI_LIST)
+ {
+ //rc = VbglR3ClipboardRootListRead()
+ rc = VERR_NO_DATA;
+ }
+ else
+#endif
+ {
+ uint32_t cbRead = 0;
+
+ uint32_t cbData = _4K; /** @todo Make this dynamic. */
+ void *pvData = RTMemAlloc(cbData);
+ if (pvData)
+ {
+ rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
+ * larger buffer. The size of the buffer needed is placed in *pcb.
+ * So we start all over again.
+ */
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ /* cbRead contains the size required. */
+
+ cbData = cbRead;
+ pvData = RTMemRealloc(pvData, cbRead);
+ if (pvData)
+ {
+ rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
+ if (rc == VINF_BUFFER_OVERFLOW)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (!cbRead)
+ rc = VERR_NO_DATA;
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcb = cbRead; /* Actual bytes read. */
+ *ppv = pvData;
+ }
+ else
+ {
+ /*
+ * Catch other errors. This also catches the case in which the buffer was
+ * too small a second time, possibly because the clipboard contents
+ * changed half-way through the operation. Since we can't say whether or
+ * not this is actually an error, we just return size 0.
+ */
+ RTMemFree(pvData);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opaque data structure describing a request from the host for clipboard
+ * data, passed in when the request is forwarded to the X11 backend so that
+ * it can be completed correctly.
+ */
+struct CLIPREADCBREQ
+{
+ /** The data format that was requested. */
+ SHCLFORMAT uFmt;
+};
+
+static DECLCALLBACK(int) vbclReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ LogFlowFunc(("fFormats=%#x\n", fFormats));
+
+ int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
+ LogFlowFuncLeaveRC(rc);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vbclOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser;
+ AssertPtrReturn(pData, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", pData->rcCompletion, pData->pReq->uFmt, pv, cb));
+
+ Assert((cb == 0 && pv == NULL) || (cb != 0 && pv != NULL));
+ pData->rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pData->pReq->uFmt, pv, cb);
+
+ RTMemFree(pData->pReq);
+
+ LogFlowFuncLeaveRC(pData->rcCompletion);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Connect the guest clipboard to the host.
+ *
+ * @returns VBox status code.
+ */
+static int vboxClipboardConnect(void)
+{
+ LogFlowFuncEnter();
+
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = vbclReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = vbclOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = vbclOnSendDataToDestCallback;
+
+ int rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
+ if (RT_FAILURE(rc))
+ ShClX11ThreadStop(&g_Ctx.X11);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error connecting to host service, rc=%Rrc\n", rc);
+
+ VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
+ ShClX11Destroy(&g_Ctx.X11);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * The main loop of our clipboard reader.
+ */
+int vboxClipboardMain(void)
+{
+ int rc;
+
+ PSHCLCONTEXT pCtx = &g_Ctx;
+
+ bool fShutdown = false;
+
+ /* The thread waits for incoming messages from the host. */
+ for (;;)
+ {
+ PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
+ AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
+
+ LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
+ pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
+
+ uint32_t idMsg = 0;
+ uint32_t cParms = 0;
+ rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
+#else
+ rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
+
+ VbglR3ClipboardEventFree(pEvent);
+ pEvent = NULL;
+
+ if (fShutdown)
+ break;
+
+ /* Wait a bit before retrying. */
+ RTThreadSleep(1000);
+ continue;
+ }
+ else
+ {
+ AssertPtr(pEvent);
+ LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
+
+ switch (pEvent->enmType)
+ {
+ case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
+ {
+ ShClX11ReportFormatsToX11(&g_Ctx.X11, pEvent->u.fReportedFormats);
+ break;
+ }
+
+ case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
+ {
+ /* The host needs data in the specified format. */
+ CLIPREADCBREQ *pReq;
+ pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ));
+ if (pReq)
+ {
+ pReq->uFmt = pEvent->u.fReadData;
+ ShClX11ReadDataFromX11(&g_Ctx.X11, pReq->uFmt, pReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
+ {
+ VBClLogVerbose(2, "Host requested termination\n");
+ fShutdown = true;
+ break;
+ }
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
+ {
+ /* Nothing to do here. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+#endif
+ case VBGLR3CLIPBOARDEVENTTYPE_NONE:
+ {
+ /* Nothing to do here. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
+ }
+ }
+
+ if (pEvent)
+ {
+ VbglR3ClipboardEventFree(pEvent);
+ pEvent = NULL;
+ }
+ }
+
+ if (fShutdown)
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclShClInit(void)
+{
+ int rc;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ rc = ShClTransferCtxInit(&g_Ctx.TransferCtx);
+#else
+ rc = VINF_SUCCESS;
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown)
+{
+ RT_NOREF(pfShutdown);
+
+ /* Initialise the guest library. */
+ int rc = vboxClipboardConnect();
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ rc = VbClShClFUSEInit(&g_FuseCtx, &g_Ctx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbClShClFUSEStart(&g_FuseCtx);
+ if (RT_SUCCESS(rc))
+ {
+#endif
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ rc = vboxClipboardMain();
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ int rc2 = VbClShClFUSEStop(&g_FuseCtx);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Service terminated abnormally with %Rrc\n", rc);
+
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
+ rc = VINF_SUCCESS; /* Prevent automatic restart by daemon script if host service not available. */
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclShClStop(void)
+{
+ /* Disconnect from the host service.
+ * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
+ VbglR3ClipboardDisconnect(g_Ctx.CmdCtx.idClient);
+ g_Ctx.CmdCtx.idClient = 0;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclShClTerm(void)
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ ShClTransferCtxDestroy(&g_Ctx.TransferCtx);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcClipboard =
+{
+ "shcl", /* szName */
+ "Shared Clipboard", /* pszDescription */
+ ".vboxclient-clipboard", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclShClInit, /* pfnInit */
+ vbclShClWorker, /* pfnWorker */
+ vbclShClStop, /* pfnStop*/
+ vbclShClTerm /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.h b/src/VBox/Additions/x11/VBoxClient/clipboard.h
new file mode 100644
index 00000000..bc98487b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/clipboard.h
@@ -0,0 +1,49 @@
+/** $Id: clipboard.h $ */
+/** @file
+ * Guest Additions - X11 Shared Clipboard - Main header.
+ */
+
+/*
+ * Copyright (C) 2020-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/**
+ * Struct keeping a Shared Clipboard context.
+ */
+struct SHCLCONTEXT
+{
+ /** Client command context */
+ VBGLR3SHCLCMDCTX CmdCtx;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /** Associated transfer data. */
+ SHCLTRANSFERCTX TransferCtx;
+#endif
+ /** X11 clipboard context. */
+ SHCLX11CTX X11;
+};
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/display-drm.cpp b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp
new file mode 100644
index 00000000..fa5d56d2
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp
@@ -0,0 +1,1369 @@
+/* $Id: display-drm.cpp $ */
+/** @file
+ * Guest Additions - VMSVGA guest screen resize service.
+ *
+ * A user space daemon which communicates with VirtualBox host interface
+ * and performs VMSVGA-specific guest screen resize and communicates with
+ * Desktop Environment helper daemon over IPC.
+ */
+
+/*
+ * Copyright (C) 2016-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
+ */
+
+/** @page pg_vboxdrmcliet VBoxDRMClient - The VMSVGA Guest Screen Resize Service
+ *
+ * The VMSVGA Guest Screen Resize Service is a service which communicates with a
+ * guest VMSVGA driver and triggers it to perform screen resize on a guest side.
+ *
+ * This service supposed to be started on early boot. On start it will try to find
+ * compatible VMSVGA graphics card and terminate immediately if not found.
+ * VMSVGA functionality implemented here is only supported starting from vmgfx
+ * driver version 2.10 which was introduced in Linux kernel 4.6. When compatible
+ * graphics card is found, service will start a worker loop in order to receive screen
+ * update data from host and apply it to local DRM stack.
+ *
+ * In addition, it will start a local IPC server in order to communicate with Desktop
+ * Environment specific service(s). Currently, it will propagate to IPC client information regarding to
+ * which display should be set as primary on Desktop Environment level. As well as
+ * receive screen layout change events obtained on Desktop Environment level and send it
+ * back to host, so host and guest will have the same screen layout representation.
+ *
+ * By default, access to IPC server socket is granted to all users. It can be restricted to
+ * only root and users from group 'vboxdrmipc' if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest
+ * property is set and READ-ONLY for guest. User group 'vboxdrmipc' is created during Guest
+ * Additions installation. If this group is removed (or not found due to any reason) prior to
+ * service start, access to IPC server socket will be granted to root only regardless
+ * if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property is set or not. If guest property
+ * is set, but is not READ-ONLY for guest, property is ignored and IPC socket access is granted
+ * to all users.
+ *
+ * Logging is implemented in a way that errors are always printed out, VBClLogVerbose(1) and
+ * VBClLogVerbose(2) are used for debugging purposes. Verbosity level 1 is for messages related
+ * to service itself (excluding IPC), level 2 is for IPC communication debugging. In order to see
+ * logging on a host side it is enough to do:
+ *
+ * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host.
+ *
+ *
+ * Service is running the following threads:
+ *
+ * DrmResizeThread - this thread listens for display layout update events from host.
+ * Once event is received, it either injects new screen layout data into DRM stack,
+ * and/or asks IPC client(s) to set primary display. This thread is accessing IPC
+ * client connection list when it needs to sent new primary display data to all the
+ * connected clients.
+ *
+ * DrmIpcSRV - this thread is a main loop for IPC server. It accepts new connection(s),
+ * authenticates it and starts new client thread IpcCLT-XXX for processing client
+ * requests. This thread is accessing IPC client connection list by adding a new
+ * connection data into it.
+ *
+ * IpcCLT-%u - this thread processes all the client data. Suffix '-%u' in thread name is PID
+ * of a remote client process. Typical name for client thread would be IpcCLT-1234. This
+ * thread is accessing IPC client connection list when it removes connection data from it
+ * when actual IPC connection is closed. Due to IPRT thread name limitation, actual thread
+ * name will be cropped by 15 characters.
+ *
+ *
+ * The following locks are utilized:
+ *
+ * #g_ipcClientConnectionsListCritSect - protects access to list of IPC client connections.
+ * It is used by each thread - DrmResizeThread, DrmIpcSRV and IpcCLT-XXX.
+ *
+ * #g_monitorPositionsCritSect - protects access to display layout data cache and vmwgfx driver
+ * handle, serializes access to host interface and vmwgfx driver handle between
+ * DrmResizeThread and IpcCLT-%u.
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestPropertySvc.h>
+
+#include <iprt/getopt.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/thread.h>
+#include <iprt/asm.h>
+#include <iprt/localipc.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <limits.h>
+#include <signal.h>
+#include <grp.h>
+#include <errno.h>
+
+#ifdef RT_OS_LINUX
+# include <sys/ioctl.h>
+#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */
+# include <sys/ioccom.h>
+#endif
+
+/** Ioctl command to query vmwgfx version information. */
+#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION)
+/** Ioctl command to set new screen layout. */
+#define DRM_IOCTL_VMW_UPDATE_LAYOUT _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT)
+/** A driver name which identifies VMWare driver. */
+#define DRM_DRIVER_NAME "vmwgfx"
+/** VMWare driver compatible version number. On previous versions resizing does not seem work. */
+#define DRM_DRIVER_VERSION_MAJOR_MIN (2)
+#define DRM_DRIVER_VERSION_MINOR_MIN (10)
+
+/** VMWare char device driver minor numbers range. */
+#define VMW_CONTROL_DEVICE_MINOR_START (64)
+#define VMW_RENDER_DEVICE_MINOR_START (128)
+#define VMW_RENDER_DEVICE_MINOR_END (192)
+
+/** Name of DRM resize thread. */
+#define DRM_RESIZE_THREAD_NAME "DrmResizeThread"
+
+/** Name of DRM IPC server thread. */
+#define DRM_IPC_SERVER_THREAD_NAME "DrmIpcSRV"
+/** Maximum length of thread name. */
+#define DRM_IPC_THREAD_NAME_MAX (16)
+/** Name pattern of DRM IPC client thread. */
+#define DRM_IPC_CLIENT_THREAD_NAME_PTR "IpcCLT-%u"
+/** Maximum number of simultaneous IPC client connections. */
+#define DRM_IPC_SERVER_CONNECTIONS_MAX (16)
+
+/** IPC client connections counter. */
+static volatile uint32_t g_cDrmIpcConnections = 0;
+/* A flag which indicates whether access to IPC socket should be restricted.
+ * This flag caches '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property
+ * in order to prevent its retrieving from the host side each time a new IPC
+ * client connects to server. This flag is updated each time when property is
+ * changed on the host side. */
+static volatile bool g_fDrmIpcRestricted;
+
+/** Global handle to vmwgfx file descriptor (protected by #g_monitorPositionsCritSect). */
+static RTFILE g_hDevice = NIL_RTFILE;
+
+/** DRM version structure. */
+struct DRMVERSION
+{
+ int cMajor;
+ int cMinor;
+ int cPatchLevel;
+ size_t cbName;
+ char *pszName;
+ size_t cbDate;
+ char *pszDate;
+ size_t cbDescription;
+ char *pszDescription;
+};
+AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *));
+
+/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The
+ * rects argument is a cast pointer to an array of drm_vmw_rect. */
+struct DRMVMWUPDATELAYOUT
+{
+ uint32_t cOutputs;
+ uint32_t u32Pad;
+ uint64_t ptrRects;
+};
+AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16);
+
+/** A node of IPC client connections list. */
+typedef struct VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE
+{
+ /** The list node. */
+ RTLISTNODE Node;
+ /** List node payload. */
+ PVBOX_DRMIPC_CLIENT pClient;
+} VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE;
+
+/* Pointer to VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE. */
+typedef VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE *PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE;
+
+/** IPC client connections list. */
+static VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE g_ipcClientConnectionsList;
+
+/** IPC client connections list critical section. */
+static RTCRITSECT g_ipcClientConnectionsListCritSect;
+
+/** Critical section used for reporting monitors position back to host. */
+static RTCRITSECT g_monitorPositionsCritSect;
+
+/** Counter of how often our daemon has been re-spawned. */
+unsigned g_cRespawn = 0;
+/** Logging verbosity level. */
+unsigned g_cVerbosity = 0;
+
+/** Path to the PID file. */
+static const char *g_pszPidFile = "/var/run/VBoxDRMClient";
+
+/** Global flag which is triggered when service requested to shutdown. */
+static bool volatile g_fShutdown;
+
+/**
+ * Go over all existing IPC client connection and put set-primary-screen request
+ * data into TX queue of each of them .
+ *
+ * @return IPRT status code.
+ * @param u32PrimaryDisplay Primary display ID.
+ */
+static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay);
+
+/**
+ * Attempts to open DRM device by given path and check if it is
+ * capable for screen resize.
+ *
+ * @return DRM device handle on success, NIL_RTFILE otherwise.
+ * @param szPathPattern Path name pattern to the DRM device.
+ * @param uInstance Driver / device instance.
+ */
+static RTFILE vbDrmTryDevice(const char *szPathPattern, uint8_t uInstance)
+{
+ int rc = VERR_NOT_FOUND;
+ char szPath[PATH_MAX];
+ struct DRMVERSION vmwgfxVersion;
+ RTFILE hDevice = NIL_RTFILE;
+
+ RT_ZERO(szPath);
+ RT_ZERO(vmwgfxVersion);
+
+ rc = RTStrPrintf(szPath, sizeof(szPath), szPathPattern, uInstance);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ char szVmwgfxDriverName[sizeof(DRM_DRIVER_NAME)];
+ RT_ZERO(szVmwgfxDriverName);
+
+ vmwgfxVersion.cbName = sizeof(szVmwgfxDriverName);
+ vmwgfxVersion.pszName = szVmwgfxDriverName;
+
+ /* Query driver version information and check if it can be used for screen resizing. */
+ rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &vmwgfxVersion, sizeof(vmwgfxVersion), NULL);
+ if ( RT_SUCCESS(rc)
+ && strncmp(szVmwgfxDriverName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) == 0
+ && ( vmwgfxVersion.cMajor > DRM_DRIVER_VERSION_MAJOR_MIN
+ || ( vmwgfxVersion.cMajor == DRM_DRIVER_VERSION_MAJOR_MIN
+ && vmwgfxVersion.cMinor >= DRM_DRIVER_VERSION_MINOR_MIN)))
+ {
+ VBClLogInfo("found compatible device: %s\n", szPath);
+ }
+ else
+ {
+ RTFileClose(hDevice);
+ hDevice = NIL_RTFILE;
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ }
+ else
+ {
+ VBClLogError("unable to construct path to DRM device: %Rrc\n", rc);
+ }
+
+ return RT_SUCCESS(rc) ? hDevice : NIL_RTFILE;
+}
+
+/**
+ * Attempts to find and open DRM device to be used for screen resize.
+ *
+ * @return DRM device handle on success, NIL_RTFILE otherwise.
+ */
+static RTFILE vbDrmOpenVmwgfx(void)
+{
+ /* Control devices for drm graphics driver control devices go from
+ * controlD64 to controlD127. Render node devices go from renderD128
+ * to renderD192. The driver takes resize hints via the control device
+ * on pre-4.10 (???) kernels and on the render device on newer ones.
+ * At first, try to find control device and render one if not found.
+ */
+ uint8_t i;
+ RTFILE hDevice = NIL_RTFILE;
+
+ /* Lookup control device. */
+ for (i = VMW_CONTROL_DEVICE_MINOR_START; i < VMW_RENDER_DEVICE_MINOR_START; i++)
+ {
+ hDevice = vbDrmTryDevice("/dev/dri/controlD%u", i);
+ if (hDevice != NIL_RTFILE)
+ return hDevice;
+ }
+
+ /* Lookup render device. */
+ for (i = VMW_RENDER_DEVICE_MINOR_START; i <= VMW_RENDER_DEVICE_MINOR_END; i++)
+ {
+ hDevice = vbDrmTryDevice("/dev/dri/renderD%u", i);
+ if (hDevice != NIL_RTFILE)
+ return hDevice;
+ }
+
+ VBClLogError("unable to find DRM device\n");
+
+ return hDevice;
+}
+
+/**
+ * This function converts input monitors layout array passed from DevVMM
+ * into monitors layout array to be passed to DRM stack. Last validation
+ * request is cached.
+ *
+ * @return VINF_SUCCESS on success, VERR_DUPLICATE if monitors layout was not changed, IPRT error code otherwise.
+ * @param aDisplaysIn Input displays array.
+ * @param cDisplaysIn Number of elements in input displays array.
+ * @param aDisplaysOut Output displays array.
+ * @param cDisplaysOutMax Number of elements in output displays array.
+ * @param pu32PrimaryDisplay ID of a display which marked as primary.
+ * @param pcActualDisplays Number of displays to report to DRM stack (number of enabled displays).
+ * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not.
+ * When layout is reported by Desktop Environment helper, aDisplaysIn does not have
+ * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them).
+ */
+static int vbDrmValidateLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn,
+ struct VBOX_DRMIPC_VMWRECT *aDisplaysOut, uint32_t *pu32PrimaryDisplay,
+ uint32_t cDisplaysOutMax, uint32_t *pcActualDisplays, bool fPartialLayout)
+{
+ /* This array is a cache of what was received from DevVMM so far.
+ * DevVMM may send to us partial information bout scree layout. This
+ * cache remembers entire picture. */
+ static struct VMMDevDisplayDef aVmMonitorsCache[VBOX_DRMIPC_MONITORS_MAX];
+ /* Number of valid (enabled) displays in output array. */
+ uint32_t cDisplaysOut = 0;
+ /* Flag indicates that current layout cache is consistent and can be passed to DRM stack. */
+ bool fValid = true;
+
+ /* Make sure input array fits cache size. */
+ if (cDisplaysIn > VBOX_DRMIPC_MONITORS_MAX)
+ {
+ VBClLogError("unable to validate screen layout: input (%u) array does not fit to cache size (%u)\n",
+ cDisplaysIn, VBOX_DRMIPC_MONITORS_MAX);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Make sure there is enough space in output array. */
+ if (cDisplaysIn > cDisplaysOutMax)
+ {
+ VBClLogError("unable to validate screen layout: input array (%u) is bigger than output one (%u)\n",
+ cDisplaysIn, cDisplaysOut);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Make sure input and output arrays are of non-zero size. */
+ if (!(cDisplaysIn > 0 && cDisplaysOutMax > 0))
+ {
+ VBClLogError("unable to validate screen layout: invalid size of either input (%u) or output display array\n",
+ cDisplaysIn, cDisplaysOutMax);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Update cache. */
+ for (uint32_t i = 0; i < cDisplaysIn; i++)
+ {
+ uint32_t idDisplay = !fPartialLayout ? aDisplaysIn[i].idDisplay : i;
+ if (idDisplay < VBOX_DRMIPC_MONITORS_MAX)
+ {
+ if (!fPartialLayout)
+ {
+ aVmMonitorsCache[idDisplay].idDisplay = idDisplay;
+ aVmMonitorsCache[idDisplay].fDisplayFlags = aDisplaysIn[i].fDisplayFlags;
+ aVmMonitorsCache[idDisplay].cBitsPerPixel = aDisplaysIn[i].cBitsPerPixel;
+ }
+
+ aVmMonitorsCache[idDisplay].cx = aDisplaysIn[i].cx;
+ aVmMonitorsCache[idDisplay].cy = aDisplaysIn[i].cy;
+ aVmMonitorsCache[idDisplay].xOrigin = aDisplaysIn[i].xOrigin;
+ aVmMonitorsCache[idDisplay].yOrigin = aDisplaysIn[i].yOrigin;
+ }
+ else
+ {
+ VBClLogError("received display ID (0x%x, position %u) is invalid\n", idDisplay, i);
+ /* If monitor configuration cannot be placed into cache, consider entire cache is invalid. */
+ fValid = false;
+ }
+ }
+
+ /* Now, go though complete cache and check if it is valid. */
+ for (uint32_t i = 0; i < VBOX_DRMIPC_MONITORS_MAX; i++)
+ {
+ if (i == 0)
+ {
+ if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
+ {
+ VBClLogError("unable to validate screen layout: first monitor is not allowed to be disabled\n");
+ fValid = false;
+ }
+ else
+ cDisplaysOut++;
+ }
+ else
+ {
+ /* Check if there is no hole in between monitors (i.e., if current monitor is enabled, but previous one does not). */
+ if ( !(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
+ && aVmMonitorsCache[i - 1].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
+ {
+ VBClLogError("unable to validate screen layout: there is a hole in displays layout config, "
+ "monitor (%u) is ENABLED while (%u) does not\n", i, i - 1);
+ fValid = false;
+ }
+ else
+ {
+ /* Always align screens since unaligned layout will result in disaster. */
+ aVmMonitorsCache[i].xOrigin = aVmMonitorsCache[i - 1].xOrigin + aVmMonitorsCache[i - 1].cx;
+ aVmMonitorsCache[i].yOrigin = aVmMonitorsCache[i - 1].yOrigin;
+
+ /* Only count enabled monitors. */
+ if (!(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
+ cDisplaysOut++;
+ }
+ }
+ }
+
+ /* Copy out layout data. */
+ if (fValid)
+ {
+ /* Start with invalid display ID. */
+ uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX;
+
+ for (uint32_t i = 0; i < cDisplaysOut; i++)
+ {
+ aDisplaysOut[i].x = aVmMonitorsCache[i].xOrigin;
+ aDisplaysOut[i].y = aVmMonitorsCache[i].yOrigin;
+ aDisplaysOut[i].w = aVmMonitorsCache[i].cx;
+ aDisplaysOut[i].h = aVmMonitorsCache[i].cy;
+
+ if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY)
+ {
+ /* Make sure display layout has only one primary display
+ * set (for display 0, host side sets primary flag, so exclude it). */
+ Assert(u32PrimaryDisplay == 0 || u32PrimaryDisplay == VBOX_DRMIPC_MONITORS_MAX);
+ u32PrimaryDisplay = i;
+ }
+
+ VBClLogVerbose(1, "update monitor %u parameters: %dx%d, (%d, %d)\n",
+ i, aDisplaysOut[i].w, aDisplaysOut[i].h, aDisplaysOut[i].x, aDisplaysOut[i].y);
+ }
+
+ *pu32PrimaryDisplay = u32PrimaryDisplay;
+ *pcActualDisplays = cDisplaysOut;
+ }
+
+ return (fValid && cDisplaysOut > 0) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+}
+
+/**
+ * This function sends screen layout data to DRM stack.
+ *
+ * Helper function for vbDrmPushScreenLayout(). Should be called
+ * under g_monitorPositionsCritSect lock.
+ *
+ * @return VINF_SUCCESS on success, IPRT error code otherwise.
+ * @param hDevice Handle to opened DRM device.
+ * @param paRects Array of screen configuration data.
+ * @param cRects Number of elements in screen configuration array.
+ */
+static int vbDrmSendHints(RTFILE hDevice, struct VBOX_DRMIPC_VMWRECT *paRects, uint32_t cRects)
+{
+ int rc = 0;
+ uid_t curuid;
+
+ /* Store real user id. */
+ curuid = getuid();
+
+ /* Change effective user id. */
+ if (setreuid(0, 0) == 0)
+ {
+ struct DRMVMWUPDATELAYOUT ioctlLayout;
+
+ RT_ZERO(ioctlLayout);
+ ioctlLayout.cOutputs = cRects;
+ ioctlLayout.ptrRects = (uint64_t)paRects;
+
+ rc = RTFileIoCtl(hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT,
+ &ioctlLayout, sizeof(ioctlLayout), NULL);
+
+ if (setreuid(curuid, 0) != 0)
+ {
+ VBClLogError("reset of setreuid failed after drm ioctl");
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ VBClLogError("setreuid failed during drm ioctl\n");
+ rc = VERR_ACCESS_DENIED;
+ }
+
+ return rc;
+}
+
+/**
+ * This function converts vmwgfx monitors layout data into an array of monitor offsets
+ * and sends it back to the host in order to ensure that host and guest have the same
+ * monitors layout representation.
+ *
+ * @return IPRT status code.
+ * @param cDisplays Number of displays (elements in pDisplays).
+ * @param pDisplays Displays parameters as it was sent to vmwgfx driver.
+ */
+static int drmSendMonitorPositions(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pDisplays)
+{
+ static RTPOINT aPositions[VBOX_DRMIPC_MONITORS_MAX];
+
+ if (!pDisplays || !cDisplays || cDisplays > VBOX_DRMIPC_MONITORS_MAX)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Prepare monitor offsets list to be sent to the host. */
+ for (uint32_t i = 0; i < cDisplays; i++)
+ {
+ aPositions[i].x = pDisplays[i].x;
+ aPositions[i].y = pDisplays[i].y;
+ }
+
+ return VbglR3SeamlessSendMonitorPositions(cDisplays, aPositions);
+}
+
+/**
+ * Validate and apply screen layout data.
+ *
+ * @return IPRT status code.
+ * @param aDisplaysIn An array with screen layout data.
+ * @param cDisplaysIn Number of elements in aDisplaysIn.
+ * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not.
+ * When layout is reported by Desktop Environment helper, aDisplaysIn does not have
+ * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them).
+ * @param fApply Whether to apply provided display layout data to the DRM stack or send display offsets only.
+ */
+static int vbDrmPushScreenLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, bool fPartialLayout, bool fApply)
+{
+ int rc;
+
+ struct VBOX_DRMIPC_VMWRECT aDisplaysOut[VBOX_DRMIPC_MONITORS_MAX];
+ uint32_t cDisplaysOut = 0;
+
+ uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX;
+
+ rc = RTCritSectEnter(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to lock monitor data cache, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ static uint32_t u32PrimaryDisplayLast = VBOX_DRMIPC_MONITORS_MAX;
+
+ RT_ZERO(aDisplaysOut);
+
+ /* Validate displays layout and push it to DRM stack if valid. */
+ rc = vbDrmValidateLayout(aDisplaysIn, cDisplaysIn, aDisplaysOut, &u32PrimaryDisplay,
+ sizeof(aDisplaysOut), &cDisplaysOut, fPartialLayout);
+ if (RT_SUCCESS(rc))
+ {
+ if (fApply)
+ {
+ rc = vbDrmSendHints(g_hDevice, aDisplaysOut, cDisplaysOut);
+ VBClLogInfo("push screen layout data of %u display(s) to DRM stack, fPartialLayout=%RTbool, rc=%Rrc\n",
+ cDisplaysOut, fPartialLayout, rc);
+ }
+
+ /* In addition, notify host that configuration was successfully applied to the guest vmwgfx driver. */
+ if (RT_SUCCESS(rc))
+ {
+ rc = drmSendMonitorPositions(cDisplaysOut, aDisplaysOut);
+ if (RT_FAILURE(rc))
+ VBClLogError("cannot send host notification: %Rrc\n", rc);
+
+ /* If information about primary display is present in display layout, send it to DE over IPC. */
+ if (u32PrimaryDisplay != VBOX_DRMIPC_MONITORS_MAX
+ && u32PrimaryDisplayLast != u32PrimaryDisplay)
+ {
+ rc = vbDrmIpcBroadcastPrimaryDisplay(u32PrimaryDisplay);
+
+ /* Cache last value in order to avoid sending duplicate data over IPC. */
+ u32PrimaryDisplayLast = u32PrimaryDisplay;
+
+ VBClLogVerbose(2, "DE was notified that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc);
+ }
+ else
+ VBClLogVerbose(2, "do not notify DE second time that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc);
+ }
+ }
+ else if (rc == VERR_DUPLICATE)
+ VBClLogVerbose(2, "do not notify DRM stack about monitors layout change twice, rc=%Rrc\n", rc);
+ else
+ VBClLogError("displays layout is invalid, will not notify guest driver, rc=%Rrc\n", rc);
+
+ int rc2 = RTCritSectLeave(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to unlock monitor data cache, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/** Worker thread for resize task. */
+static DECLCALLBACK(int) vbDrmResizeWorker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ RT_NOREF(ThreadSelf);
+ RT_NOREF(pvUser);
+
+ for (;;)
+ {
+ /* Do not acknowledge the first event we query for to pick up old events,
+ * e.g. from before a guest reboot. */
+ bool fAck = false;
+
+ uint32_t events;
+
+ VMMDevDisplayDef aDisplaysIn[VBOX_DRMIPC_MONITORS_MAX];
+ uint32_t cDisplaysIn = 0;
+
+ RT_ZERO(aDisplaysIn);
+
+ /* 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(VBOX_DRMIPC_MONITORS_MAX, &cDisplaysIn, aDisplaysIn, fAck);
+ fAck = true;
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmPushScreenLayout(aDisplaysIn, cDisplaysIn, false, true);
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to push display change as requested by host, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
+
+ do
+ {
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_DRMIPC_RX_TIMEOUT_MS, &events);
+ } while ((rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) && !ASMAtomicReadBool(&g_fShutdown));
+
+ if (ASMAtomicReadBool(&g_fShutdown))
+ {
+ VBClLogInfo("exiting resize thread: shutdown requested\n");
+ /* This is a case when we should return positive status. */
+ rc = (rc == VERR_TIMEOUT) ? VINF_SUCCESS : rc;
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: resize thread: failure waiting for event, rc=%Rrc\n", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Go over all existing IPC client connection and put set-primary-screen request
+ * data into TX queue of each of them .
+ *
+ * @return IPRT status code.
+ * @param u32PrimaryDisplay Primary display ID.
+ */
+static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay)
+{
+ int rc;
+
+ rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node))
+ {
+ PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry;
+ RTListForEach(&g_ipcClientConnectionsList.Node, pEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node)
+ {
+ AssertReturn(pEntry, VERR_INVALID_PARAMETER);
+ AssertReturn(pEntry->pClient, VERR_INVALID_PARAMETER);
+ AssertReturn(pEntry->pClient->hThread, VERR_INVALID_PARAMETER);
+
+ rc = vbDrmIpcSetPrimaryDisplay(pEntry->pClient, u32PrimaryDisplay);
+
+ VBClLogInfo("thread %s notified IPC Client that display %u is now primary, rc=%Rrc\n",
+ RTThreadGetName(pEntry->pClient->hThread), u32PrimaryDisplay, rc);
+ }
+ }
+
+ int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("notify DE: unable to leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("notify DE: unable to enter critical section, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Main loop for IPC client connection handling.
+ *
+ * @return IPRT status code.
+ * @param pClient Pointer to IPC client data.
+ */
+static int vbDrmIpcConnectionProc(PVBOX_DRMIPC_CLIENT pClient)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+
+ /* This loop handles incoming messages. */
+ for (;;)
+ {
+ rc = vbDrmIpcConnectionHandler(pClient);
+
+ /* Try to detect if we should shutdown as early as we can. */
+ if (ASMAtomicReadBool(&g_fShutdown))
+ break;
+
+ /* Normal case. No data received within short interval. */
+ if (rc == VERR_TIMEOUT)
+ {
+ continue;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ /* Terminate connection handling in case of error. */
+ VBClLogError("unable to handle IPC session, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Add IPC client connection data into list of connections.
+ *
+ * List size is limited indirectly by DRM_IPC_SERVER_CONNECTIONS_MAX value.
+ * This function should only be invoked from client thread context
+ * (from vbDrmIpcClientWorker() in particular).
+ *
+ * @return IPRT status code.
+ * @param pClientNode Client connection information to add to the list.
+ */
+static int vbDrmIpcClientsListAdd(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode)
+{
+ int rc;
+
+ AssertReturn(pClientNode, VERR_INVALID_PARAMETER);
+
+ rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&g_ipcClientConnectionsList.Node, &pClientNode->Node);
+
+ int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("add client connection: unable to leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("add client connection: unable to enter critical section, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Remove IPC client connection data from list of connections.
+ *
+ * This function should only be invoked from client thread context
+ * (from vbDrmIpcClientWorker() in particular).
+ *
+ * @return IPRT status code.
+ * @param pClientNode Client connection information to remove from the list.
+ */
+static int vbDrmIpcClientsListRemove(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode)
+{
+ int rc;
+ PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry, pNextEntry, pFound = NULL;
+
+ AssertReturn(pClientNode, VERR_INVALID_PARAMETER);
+
+ rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
+ if (RT_SUCCESS(rc))
+ {
+
+ if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node))
+ {
+ RTListForEachSafe(&g_ipcClientConnectionsList.Node, pEntry, pNextEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node)
+ {
+ if (pEntry == pClientNode)
+ pFound = (PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE)RTListNodeRemoveRet(&pEntry->Node);
+ }
+ }
+ else
+ VBClLogError("remove client connection: connections list empty, node %p not there\n", pClientNode);
+
+ int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("remove client connection: unable to leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("remove client connection: unable to enter critical section, rc=%Rrc\n", rc);
+
+ if (!pFound)
+ VBClLogError("remove client connection: node not found\n");
+
+ return !rc && pFound ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+}
+
+/**
+ * Convert VBOX_DRMIPC_VMWRECT into VMMDevDisplayDef and check layout correctness.
+ *
+ * VBOX_DRMIPC_VMWRECT does not represent enough information needed for
+ * VMMDevDisplayDef. Missing fields (fDisplayFlags, idDisplay, cBitsPerPixel)
+ * are initialized with default (invalid) values due to this.
+ *
+ * @return True if given screen layout is correct (i.e., has no displays which overlap), False
+ * if it needs to be adjusted before injecting into DRM stack.
+ * @param cDisplays Number of displays in configuration data.
+ * @param pIn A pointer to display configuration data array in form of VBOX_DRMIPC_VMWRECT (input).
+ * @param pOut A pointer to display configuration data array in form of VMMDevDisplayDef (output).
+ */
+static bool vbDrmVmwRectToDisplayDef(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pIn, VMMDevDisplayDef *pOut)
+{
+ bool fCorrect = true;
+
+ for (uint32_t i = 0; i < cDisplays; i++)
+ {
+ /* VBOX_DRMIPC_VMWRECT has no information about this fields. */
+ pOut[i].fDisplayFlags = 0;
+ pOut[i].idDisplay = VBOX_DRMIPC_MONITORS_MAX;
+ pOut[i].cBitsPerPixel = 0;
+
+ pOut[i].xOrigin = pIn[i].x;
+ pOut[i].yOrigin = pIn[i].y;
+ pOut[i].cx = pIn[i].w;
+ pOut[i].cy = pIn[i].h;
+
+ /* Make sure that displays do not overlap within reported screen layout. Ask IPC server to fix layout otherwise. */
+ fCorrect = i > 0
+ && pIn[i].x != (int32_t)pIn[i - 1].w + pIn[i - 1].x
+ ? false
+ : fCorrect;
+ }
+
+ return fCorrect;
+}
+
+/**
+ * @interface_method_impl{VBOX_DRMIPC_CLIENT,pfnRxCb}
+ */
+static DECLCALLBACK(int) vbDrmIpcClientRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
+{
+ int rc = VERR_INVALID_PARAMETER;
+
+ AssertReturn(pvData, VERR_INVALID_PARAMETER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ switch (idCmd)
+ {
+ case VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS:
+ {
+ VMMDevDisplayDef aDisplays[VBOX_DRMIPC_MONITORS_MAX];
+ bool fCorrect;
+
+ PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)pvData;
+ AssertReturn(cbData == sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS), VERR_INVALID_PARAMETER);
+ AssertReturn(pCmd->cDisplays < VBOX_DRMIPC_MONITORS_MAX, VERR_INVALID_PARAMETER);
+
+ /* Convert input display config into VMMDevDisplayDef representation. */
+ RT_ZERO(aDisplays);
+ fCorrect = vbDrmVmwRectToDisplayDef(pCmd->cDisplays, pCmd->aDisplays, aDisplays);
+
+ rc = vbDrmPushScreenLayout(aDisplays, pCmd->cDisplays, true, !fCorrect);
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to push display change as requested by Desktop Environment helper, rc=%Rrc\n", rc);
+
+ break;
+ }
+
+ default:
+ {
+ VBClLogError("received unknown IPC command 0x%x\n", idCmd);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/** Worker thread for IPC client task. */
+static DECLCALLBACK(int) vbDrmIpcClientWorker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ VBOX_DRMIPC_CLIENT hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
+ RTLOCALIPCSESSION hSession = (RTLOCALIPCSESSION)pvUser;
+ int rc;
+
+ AssertReturn(RT_VALID_PTR(hSession), VERR_INVALID_PARAMETER);
+
+ /* Initialize client session resources. */
+ rc = vbDrmIpcClientInit(&hClient, ThreadSelf, hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbDrmIpcClientRxCallBack);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add IPC client connection data into clients list. */
+ VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE hClientNode = { { 0, 0 } , &hClient };
+
+ rc = vbDrmIpcClientsListAdd(&hClientNode);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserSignal(ThreadSelf);
+ if (RT_SUCCESS(rc))
+ {
+ /* Start spinning the connection. */
+ VBClLogInfo("IPC client connection started\n", rc);
+ rc = vbDrmIpcConnectionProc(&hClient);
+ VBClLogInfo("IPC client connection ended, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to report IPC client connection handler start, rc=%Rrc\n", rc);
+
+ /* Remove IPC client connection data from clients list. */
+ rc = vbDrmIpcClientsListRemove(&hClientNode);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to remove IPC client session from list of connections, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to add IPC client connection to the list, rc=%Rrc\n");
+
+ /* Disconnect remote peer if still connected. */
+ if (RT_VALID_PTR(hSession))
+ {
+ rc = RTLocalIpcSessionClose(hSession);
+ VBClLogInfo("IPC session closed, rc=%Rrc\n", rc);
+ }
+
+ /* Connection handler loop has ended, release session resources. */
+ rc = vbDrmIpcClientReleaseResources(&hClient);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC client session, rc=%Rrc\n", rc);
+
+ ASMAtomicDecU32(&g_cDrmIpcConnections);
+ }
+ else
+ VBClLogError("unable to initialize IPC client session, rc=%Rrc\n", rc);
+
+ VBClLogInfo("closing IPC client session, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Start processing thread for IPC client requests handling.
+ *
+ * @returns IPRT status code.
+ * @param hSession IPC client connection handle.
+ */
+static int vbDrmIpcClientStart(RTLOCALIPCSESSION hSession)
+{
+ int rc;
+ RTTHREAD hThread = 0;
+ RTPROCESS hProcess = 0;
+
+ rc = RTLocalIpcSessionQueryProcess(hSession, &hProcess);
+ if (RT_SUCCESS(rc))
+ {
+ char pszThreadName[DRM_IPC_THREAD_NAME_MAX];
+ RT_ZERO(pszThreadName);
+
+ RTStrPrintf2(pszThreadName, DRM_IPC_THREAD_NAME_MAX, DRM_IPC_CLIENT_THREAD_NAME_PTR, hProcess);
+
+ /* Attempt to start IPC client connection handler task. */
+ rc = RTThreadCreate(&hThread, vbDrmIpcClientWorker, (void *)hSession, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, pszThreadName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(hThread, RT_MS_5SEC);
+ }
+ }
+
+ return rc;
+}
+
+/** Worker thread for IPC server task. */
+static DECLCALLBACK(int) vbDrmIpcServerWorker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+
+ RT_NOREF1(ThreadSelf);
+
+ AssertReturn(hIpcServer, VERR_INVALID_PARAMETER);
+
+ /* This loop accepts incoming connections. */
+ for (;;)
+ {
+ RTLOCALIPCSESSION hClientSession;
+
+ /* Wait for incoming connection. */
+ rc = RTLocalIpcServerListen(hIpcServer, &hClientSession);
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogVerbose(2, "new IPC session\n");
+
+ if (ASMAtomicIncU32(&g_cDrmIpcConnections) <= DRM_IPC_SERVER_CONNECTIONS_MAX)
+ {
+ /* Authenticate remote peer. */
+ if (ASMAtomicReadBool(&g_fDrmIpcRestricted))
+ rc = vbDrmIpcAuth(hClientSession);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Start incoming connection handler thread. */
+ rc = vbDrmIpcClientStart(hClientSession);
+ VBClLogVerbose(2, "connection processing ended, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("IPC authentication failed, rc=%Rrc\n", rc);
+ }
+ else
+ rc = VERR_RESOURCE_BUSY;
+
+ /* Release resources in case of error. */
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("maximum amount of IPC client connections reached, dropping connection\n");
+
+ int rc2 = RTLocalIpcSessionClose(hClientSession);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to close IPC session, rc=%Rrc\n", rc2);
+
+ ASMAtomicDecU32(&g_cDrmIpcConnections);
+ }
+ }
+ else
+ VBClLogError("IPC authentication failed, rc=%Rrc\n", rc);
+
+ /* Check shutdown was requested. */
+ if (ASMAtomicReadBool(&g_fShutdown))
+ {
+ VBClLogInfo("exiting IPC thread: shutdown requested\n");
+ break;
+ }
+
+ /* Wait a bit before spinning a loop if something went wrong. */
+ if (RT_FAILURE(rc))
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ }
+
+ return rc;
+}
+
+/** A signal handler. */
+static void vbDrmRequestShutdown(int sig)
+{
+ RT_NOREF(sig);
+ ASMAtomicWriteBool(&g_fShutdown, true);
+}
+
+/**
+ * Grant access to DRM IPC server socket depending on VM configuration.
+ *
+ * If VM has '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property set
+ * and this property is READ-ONLY for the guest side, access will be
+ * granted to root and users from 'vboxdrmipc' group only. If group does
+ * not exists, only root will have access to the socket. When property is
+ * not set or not READ-ONLY, all users will have access to the socket.
+ *
+ * @param hIpcServer IPC server handle.
+ * @param fRestrict Whether to restrict access to socket or not.
+ */
+static void vbDrmSetIpcServerAccessPermissions(RTLOCALIPCSERVER hIpcServer, bool fRestrict)
+{
+ int rc;
+
+ if (fRestrict)
+ {
+ struct group *pGrp;
+ pGrp = getgrnam(VBOX_DRMIPC_USER_GROUP);
+ if (pGrp)
+ {
+ rc = RTLocalIpcServerGrantGroupAccess(hIpcServer, pGrp->gr_gid);
+ if (RT_SUCCESS(rc))
+ VBClLogInfo("IPC server socket access granted to '" VBOX_DRMIPC_USER_GROUP "' users\n");
+ else
+ VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "' users, rc=%Rrc\n", rc);
+
+ }
+ else
+ VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "', group does not exist\n");
+ }
+ else
+ {
+ rc = RTLocalIpcServerSetAccessMode(hIpcServer,
+ RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR |
+ RTFS_UNIX_IRGRP | RTFS_UNIX_IWGRP |
+ RTFS_UNIX_IROTH | RTFS_UNIX_IWOTH);
+ if (RT_SUCCESS(rc))
+ VBClLogInfo("IPC server socket access granted to all users\n");
+ else
+ VBClLogError("unable to grant IPC server socket access to all users, rc=%Rrc\n", rc);
+ }
+
+ /* Set flag for the thread which serves incomming IPC connections. */
+ ASMAtomicWriteBool(&g_fDrmIpcRestricted, fRestrict);
+}
+
+/**
+ * Wait and handle '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property change.
+ *
+ * This function is executed in context of main().
+ *
+ * @param hIpcServer IPC server handle.
+ */
+static void vbDrmPollIpcServerAccessMode(RTLOCALIPCSERVER hIpcServer)
+{
+ HGCMCLIENTID idClient;
+ int rc;
+
+ rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ /* Buffer should be big enough to fit guest property data layout: Name\0Value\0Flags\0fWasDeleted\0. */
+ static char achBuf[GUEST_PROP_MAX_NAME_LEN];
+ char *pszName = NULL;
+ char *pszValue = NULL;
+ char *pszFlags = NULL;
+ bool fWasDeleted = false;
+ uint64_t u64Timestamp = 0;
+
+ rc = VbglR3GuestPropWait(idClient, VBGLR3DRMPROPPTR, achBuf, sizeof(achBuf), u64Timestamp,
+ VBOX_DRMIPC_RX_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp,
+ &pszFlags, NULL, &fWasDeleted);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFlags = 0;
+
+ VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n",
+ pszName, pszValue, pszFlags, fWasDeleted);
+
+ if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags)))
+ {
+ if (RTStrNCmp(pszName, VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_MAX_NAME_LEN) == 0)
+ {
+ /* Enforce restricted socket access until guest property exist and READ-ONLY for the guest. */
+ vbDrmSetIpcServerAccessPermissions(hIpcServer, !fWasDeleted && fFlags & GUEST_PROP_F_RDONLYGUEST);
+ }
+
+ } else
+ VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n",
+ pszName, pszValue, pszFlags, fWasDeleted);
+
+ } else if ( rc != VERR_TIMEOUT
+ && rc != VERR_INTERRUPTED)
+ {
+ VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc);
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ }
+
+ } while (!ASMAtomicReadBool(&g_fShutdown));
+
+ VbglR3GuestPropDisconnect(idClient);
+ }
+ else
+ VBClLogError("cannot connect to VM guest properties service, rc=%Rrc\n", rc);
+}
+
+int main(int argc, char *argv[])
+{
+ /** Custom log prefix to be used for logger instance of this process. */
+ static const char *pszLogPrefix = "VBoxDRMClient:";
+
+ static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, };
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int ch;
+
+ RTFILE hPidFile;
+
+ RTLOCALIPCSERVER hIpcServer;
+ RTTHREAD vbDrmIpcThread;
+ int rcDrmIpcThread = 0;
+
+ RTTHREAD drmResizeThread;
+ int rcDrmResizeThread = 0;
+ int rc, rc2 = 0;
+
+ rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: VbglR3InitUser failed: %Rrc", rc);
+
+ /* Process command line options. */
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: unable to process command line options, rc=%Rrc\n", rc);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case 'v':
+ {
+ g_cVerbosity++;
+ break;
+ }
+
+ case VERR_GETOPT_UNKNOWN_OPTION:
+ {
+ VBClLogFatalError("unknown command line option '%s'\n", ValueUnion.psz);
+ return RTEXITCODE_SYNTAX;
+
+ }
+
+ default:
+ break;
+ }
+ }
+
+ rc = VBClLogCreate("");
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: failed to setup logging, rc=%Rrc\n", rc);
+ VBClLogSetLogPrefix(pszLogPrefix);
+
+ /* Check PID file before attempting to initialize anything. */
+ rc = VbglR3PidFile(g_pszPidFile, &hPidFile);
+ if (rc == VERR_FILE_LOCK_VIOLATION)
+ {
+ VBClLogInfo("already running, exiting\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to lock PID file (%Rrc), exiting\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ g_hDevice = vbDrmOpenVmwgfx();
+ if (g_hDevice == NIL_RTFILE)
+ return RTEXITCODE_FAILURE;
+
+ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+ rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Setup signals: gracefully terminate on SIGINT, SIGTERM. */
+ if ( signal(SIGINT, vbDrmRequestShutdown) == SIG_ERR
+ || signal(SIGTERM, vbDrmRequestShutdown) == SIG_ERR)
+ {
+ VBClLogError("unable to setup signals\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Init IPC client connection list. */
+ RTListInit(&g_ipcClientConnectionsList.Node);
+ rc = RTCritSectInit(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to initialize IPC client connection list critical section\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Init critical section which is used for reporting monitors offset back to host. */
+ rc = RTCritSectInit(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to initialize monitors position critical section\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Instantiate IPC server for VBoxClient service communication. */
+ rc = RTLocalIpcServerCreate(&hIpcServer, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to setup IPC server, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Set IPC server socket access permissions according to VM configuration. */
+ vbDrmSetIpcServerAccessPermissions(hIpcServer, VbglR3DrmRestrictedIpcAccessIsNeeded());
+
+ /* Attempt to start DRM resize task. */
+ rc = RTThreadCreate(&drmResizeThread, vbDrmResizeWorker, NULL, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_RESIZE_THREAD_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ /* Attempt to start IPC task. */
+ rc = RTThreadCreate(&vbDrmIpcThread, vbDrmIpcServerWorker, (void *)hIpcServer, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_IPC_SERVER_THREAD_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ /* Poll for host notification about IPC server socket access mode change. */
+ vbDrmPollIpcServerAccessMode(hIpcServer);
+
+ /* HACK ALERT!
+ * The sequence of RTThreadWait(drmResizeThread) -> RTLocalIpcServerDestroy() -> RTThreadWait(vbDrmIpcThread)
+ * is intentional! Once process received a signal, it will pull g_fShutdown flag, which in turn will cause
+ * drmResizeThread to quit. The vbDrmIpcThread might hang on accept() call, so we terminate IPC server to
+ * release it and then wait for its termination. */
+
+ rc = RTThreadWait(drmResizeThread, RT_INDEFINITE_WAIT, &rcDrmResizeThread);
+ VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_RESIZE_THREAD_NAME, rcDrmResizeThread);
+
+ rc = RTLocalIpcServerCancel(hIpcServer);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify IPC server about shutdown, rc=%Rrc\n", rc);
+
+ /* Wait for threads to terminate gracefully. */
+ rc = RTThreadWait(vbDrmIpcThread, RT_INDEFINITE_WAIT, &rcDrmIpcThread);
+ VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_IPC_SERVER_THREAD_NAME, rcDrmResizeThread);
+
+ }
+ else
+ VBClLogError("unable to start IPC thread, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to start resize thread, rc=%Rrc\n", rc);
+
+ rc = RTLocalIpcServerDestroy(hIpcServer);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to stop IPC server, rc=%Rrc\n", rc);
+
+ rc2 = RTCritSectDelete(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to destroy g_monitorPositionsCritSect critsect, rc=%Rrc\n", rc2);
+
+ rc2 = RTCritSectDelete(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to destroy g_ipcClientConnectionsListCritSect critsect, rc=%Rrc\n", rc2);
+
+ RTFileClose(g_hDevice);
+
+ VBClLogInfo("releasing PID file lock\n");
+ VbglR3ClosePidFile(g_pszPidFile, hPidFile);
+
+ VBClLogDestroy();
+
+ return rc == 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp b/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp
new file mode 100644
index 00000000..8e88e669
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp
@@ -0,0 +1,421 @@
+/* $Id: display-helper-generic.cpp $ */
+/** @file
+ * Guest Additions - Generic Desktop Environment helper.
+ *
+ * A generic helper for X11 Client which performs Desktop Environment
+ * specific actions utilizing libXrandr.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#include "VBoxClient.h"
+#include "display-helper.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <VBox/log.h>
+#include <VBox/xrandr.h>
+
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/thread.h>
+#include <iprt/mem.h>
+#include <iprt/list.h>
+
+/** Load libxrandr symbols needed for us. */
+#include <VBox/xrandr.h>
+/* Declarations of the functions that we need from libXrandr. */
+#define VBOX_XRANDR_GENERATE_BODY
+#include <VBox/xrandr-calls.h>
+
+#include <X11/Xlibint.h>
+
+/** Name of Display Change Monitor thread. */
+#define VBCL_HLP_DCM_THREAD_NAME "dcm-task"
+
+/** Display Change Monitor thread. */
+static RTTHREAD g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
+
+/** Global flag which is triggered when service requested to shutdown. */
+static bool volatile g_fShutdown;
+
+/** Node of monitors info list. */
+typedef struct vbcl_hlp_generic_monitor_list_t
+{
+ /** List node. */
+ RTLISTNODE Node;
+ /** Pointer to xRandr monitor info. */
+ XRRMonitorInfo *pMonitorInfo;
+} vbcl_hlp_generic_monitor_list_t;
+
+/** Pointer to display change event notification callback (set by external function call). */
+static FNDISPLAYOFFSETCHANGE *g_pfnDisplayOffsetChangeCb;
+
+/**
+ * Determine monitor name strings order in a list of monitors which is sorted in ascending way.
+ *
+ * @return TRUE if first name should go first in a list, FALSE otherwise.
+ * @param pszName1 First monitor name.
+ * @param pszName2 Second monitor name.
+ */
+static bool vbcl_hlp_generic_order_names(char *pszName1, char *pszName2)
+{
+ AssertReturn(pszName1, false);
+ AssertReturn(pszName2, false);
+
+ char *pszFirst = pszName1;
+ char *pszSecond = pszName2;
+
+ while (*pszFirst && *pszSecond)
+ {
+ if (*pszFirst < *pszSecond)
+ return true;
+
+ pszFirst++;
+ pszSecond++;
+ }
+
+ return false;
+}
+
+/**
+ * Insert monitor info into the list sorted ascending.
+ *
+ * @return IPRT status code.
+ * @param pDisplay X11 display handle to fetch monitor name string from.
+ * @param pListHead Head of monitors info list.
+ * @param pMonitorInfo Monitor info ti be inserted into the list.
+ */
+static int vbcl_hlp_generic_monitor_list_insert_sorted(
+ Display *pDisplay, vbcl_hlp_generic_monitor_list_t *pListHead, XRRMonitorInfo *pMonitorInfo)
+{
+ vbcl_hlp_generic_monitor_list_t *pNode = (vbcl_hlp_generic_monitor_list_t *)RTMemAllocZ(sizeof(vbcl_hlp_generic_monitor_list_t));
+ vbcl_hlp_generic_monitor_list_t *pNodeIter;
+ char *pszMonitorName;
+
+ AssertReturn(pNode, VERR_NO_MEMORY);
+
+ pNode->pMonitorInfo = pMonitorInfo;
+
+ if (RTListIsEmpty(&pListHead->Node))
+ {
+ RTListNodeInsertAfter(&pListHead->Node, &pNode->Node);
+ return VINF_SUCCESS;
+ }
+
+ pszMonitorName = XGetAtomName(pDisplay, pMonitorInfo->name);
+ AssertReturn(pszMonitorName, VERR_NO_MEMORY);
+
+ RTListForEach(&pListHead->Node, pNodeIter, vbcl_hlp_generic_monitor_list_t, Node)
+ {
+ char *pszIterMonitorName = XGetAtomName(pDisplay, pNodeIter->pMonitorInfo->name);
+
+ if (vbcl_hlp_generic_order_names(pszMonitorName, pszIterMonitorName))
+ {
+ RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
+ XFree((void *)pszIterMonitorName);
+ XFree((void *)pszMonitorName);
+ return VINF_SUCCESS;
+ }
+
+ XFree((void *)pszIterMonitorName);
+ }
+
+ XFree((void *)pszMonitorName);
+
+ /* If we reached the end of the list, it means that monitor
+ * should be placed in the end (according to alphabetical sorting). */
+ RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Release monitors info list resources.
+ *
+ * @param pListHead List head.
+ */
+static void vbcl_hlp_generic_free_monitor_list(vbcl_hlp_generic_monitor_list_t *pListHead)
+{
+ vbcl_hlp_generic_monitor_list_t *pEntry, *pNextEntry;
+
+ RTListForEachSafe(&pListHead->Node, pEntry, pNextEntry, vbcl_hlp_generic_monitor_list_t, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+ RTMemFree(pEntry);
+ }
+}
+
+/**
+ * Handle received RRScreenChangeNotify event.
+ *
+ * @param pDisplay X11 display handle.
+ */
+static void vbcl_hlp_generic_process_display_change_event(Display *pDisplay)
+{
+ int iCount;
+ uint32_t idxDisplay = 0;
+ XRRMonitorInfo *pMonitorsInfo = XRRGetMonitors(pDisplay, DefaultRootWindow(pDisplay), true, &iCount);
+ if (pMonitorsInfo && iCount > 0 && iCount < VBOX_DRMIPC_MONITORS_MAX)
+ {
+ int rc;
+ vbcl_hlp_generic_monitor_list_t pMonitorsInfoList, *pIter;
+ struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX];
+
+ RTListInit(&pMonitorsInfoList.Node);
+
+ /* Put monitors info into sorted (by monitor name) list. */
+ for (int i = 0; i < iCount; i++)
+ {
+ rc = vbcl_hlp_generic_monitor_list_insert_sorted(pDisplay, &pMonitorsInfoList, &pMonitorsInfo[i]);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to fill monitors info list, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+
+ /* Now iterate over sorted list of monitor configurations. */
+ RTListForEach(&pMonitorsInfoList.Node, pIter, vbcl_hlp_generic_monitor_list_t, Node)
+ {
+ char *pszMonitorName = XGetAtomName(pDisplay, pIter->pMonitorInfo->name);
+
+ VBClLogVerbose(1, "reporting monitor %s offset: (%d, %d)\n",
+ pszMonitorName, pIter->pMonitorInfo->x, pIter->pMonitorInfo->y);
+
+ XFree((void *)pszMonitorName);
+
+ aDisplays[idxDisplay].x = pIter->pMonitorInfo->x;
+ aDisplays[idxDisplay].y = pIter->pMonitorInfo->y;
+ aDisplays[idxDisplay].w = pIter->pMonitorInfo->width;
+ aDisplays[idxDisplay].h = pIter->pMonitorInfo->height;
+
+ idxDisplay++;
+ }
+
+ vbcl_hlp_generic_free_monitor_list(&pMonitorsInfoList);
+
+ XRRFreeMonitors(pMonitorsInfo);
+
+ if (g_pfnDisplayOffsetChangeCb)
+ {
+ rc = g_pfnDisplayOffsetChangeCb(idxDisplay, aDisplays);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify subscriber about monitors info change, rc=%Rrc\n", rc);
+ }
+ }
+ else
+ VBClLogError("cannot get monitors info\n");
+}
+
+/** Worker thread for display change events monitoring. */
+static DECLCALLBACK(int) vbcl_hlp_generic_display_change_event_monitor_worker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ RT_NOREF(pvUser);
+
+ VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker started\n");
+
+ Display *pDisplay = XOpenDisplay(NULL);
+ if (pDisplay)
+ {
+ bool fSuccess;
+ int iEventBase, iErrorBase /* unused */, iMajor, iMinor;
+
+ fSuccess = XRRQueryExtension(pDisplay, &iEventBase, &iErrorBase);
+ fSuccess &= XRRQueryVersion(pDisplay, &iMajor, &iMinor);
+
+ if (fSuccess && iMajor >= 1 && iMinor > 3)
+ {
+ /* All required checks are now passed. Notify parent thread that we started. */
+ RTThreadUserSignal(ThreadSelf);
+
+ /* Only receive events we need. */
+ XRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask);
+
+ /* Monitor main loop. */
+ while (!ASMAtomicReadBool(&g_fShutdown))
+ {
+ XEvent Event;
+
+ if (XPending(pDisplay) > 0)
+ {
+ XNextEvent(pDisplay, &Event);
+ switch (Event.type - iEventBase)
+ {
+ case RRScreenChangeNotify:
+ {
+ vbcl_hlp_generic_process_display_change_event(pDisplay);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ else
+ RTThreadSleep(RT_MS_1SEC / 2);
+ }
+ }
+ else
+ {
+ VBClLogError("dcm monitor cannot find XRandr 1.3+ extension\n");
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ XCloseDisplay(pDisplay);
+ }
+ else
+ {
+ VBClLogError("dcm monitor cannot open X Display\n");
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker ended\n");
+
+ return rc;
+}
+
+static void vbcl_hlp_generic_start_display_change_monitor()
+{
+ int rc;
+
+ rc = RTXrandrLoadLib();
+ if (RT_SUCCESS(rc))
+ {
+ /* Start thread which will monitor display change events. */
+ rc = RTThreadCreate(&g_vbclHlpGenericDcmThread, vbcl_hlp_generic_display_change_event_monitor_worker, (void *)NULL, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, VBCL_HLP_DCM_THREAD_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC);
+ }
+ else
+ g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
+
+ VBClLogInfo("attempt to start display change monitor thread, rc=%Rrc\n", rc);
+
+ }
+ else
+ VBClLogInfo("libXrandr not available, will not monitor display change events, rc=%Rrc\n", rc);
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnSetPrimaryDisplay}
+ */
+static DECLCALLBACK(int) vbcl_hlp_generic_set_primary_display(uint32_t idDisplay)
+{
+ XRRScreenResources *pScreenResources;
+ Display *pDisplay;
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ pDisplay = XOpenDisplay(NULL);
+ if (pDisplay)
+ {
+ pScreenResources = XRRGetScreenResources(pDisplay, DefaultRootWindow(pDisplay));
+ if (pScreenResources)
+ {
+ if ((int)idDisplay < pScreenResources->noutput)
+ {
+ XRRSetOutputPrimary(pDisplay, DefaultRootWindow(pDisplay), pScreenResources->outputs[idDisplay]);
+ VBClLogInfo("display %u has been set as primary\n", idDisplay);
+ rc = VINF_SUCCESS;
+ }
+ else
+ VBClLogError("cannot set display %u as primary: index out of range\n", idDisplay);
+
+ XRRFreeScreenResources(pScreenResources);
+ }
+ else
+ VBClLogError("cannot set display %u as primary: libXrandr can not get screen resources\n", idDisplay);
+
+ XCloseDisplay(pDisplay);
+ }
+ else
+ VBClLogError("cannot set display %u as primary: cannot connect to X11\n", idDisplay);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
+ */
+static DECLCALLBACK(int) vbcl_hlp_generic_probe(void)
+{
+ /* Generic helper always supposed to return positive status on probe(). This
+ * helper is a fallback one in case all the other helpers were failed to detect
+ * their environments. */
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) vbcl_hlp_generic_init(void)
+{
+ ASMAtomicWriteBool(&g_fShutdown, false);
+
+ /* Attempt to start display change events monitor. */
+ vbcl_hlp_generic_start_display_change_monitor();
+
+ /* Always return positive status for generic (fallback, last resort) helper. */
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) vbcl_hlp_generic_term(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (g_vbclHlpGenericDcmThread != NIL_RTTHREAD)
+ {
+ /* Signal thread we are going to shutdown. */
+ ASMAtomicWriteBool(&g_fShutdown, true);
+
+ /* Wait for thread to terminate gracefully. */
+ rc = RTThreadWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC, NULL);
+ }
+
+ return rc;
+}
+
+RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb)
+{
+ g_pfnDisplayOffsetChangeCb = pfnCb;
+}
+
+RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void)
+{
+ g_pfnDisplayOffsetChangeCb = NULL;
+}
+
+/* Helper callbacks. */
+const VBCLDISPLAYHELPER g_DisplayHelperGeneric =
+{
+ "GENERIC", /* .pszName */
+ vbcl_hlp_generic_probe, /* .pfnProbe */
+ vbcl_hlp_generic_init, /* .pfnInit */
+ vbcl_hlp_generic_term, /* .pfnTerm */
+ vbcl_hlp_generic_set_primary_display, /* .pfnSetPrimaryDisplay */
+ vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
+ vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp b/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp
new file mode 100644
index 00000000..d85f6f46
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp
@@ -0,0 +1,1019 @@
+/* $Id: display-helper-gnome3.cpp $ */
+/** @file
+ * Guest Additions - Gnome3 Desktop Environment helper.
+ *
+ * A helper for X11/Wayland Client which performs Gnome Desktop
+ * Environment specific actions.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+/**
+ * This helper implements communication protocol between gnome-settings-daemon
+ * and itself using interface defined in (revision e88467f9):
+ *
+ * https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/org.gnome.Mutter.DisplayConfig.xml
+ */
+
+#include "VBoxClient.h"
+#include "display-helper.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/env.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+/** Load libDbus symbols needed for us. */
+#include <VBox/dbus.h>
+/* Declarations of the functions that we need from libXrandr. */
+#define VBOX_DBUS_GENERATE_BODY
+#include <VBox/dbus-calls.h>
+
+/** D-bus parameters for connecting to Gnome display service. */
+#define VBOXCLIENT_HELPER_DBUS_DESTINATION "org.gnome.Mutter.DisplayConfig"
+#define VBOXCLIENT_HELPER_DBUS_PATH "/org/gnome/Mutter/DisplayConfig"
+#define VBOXCLIENT_HELPER_DBUS_IFACE "org.gnome.Mutter.DisplayConfig"
+#define VBOXCLIENT_HELPER_DBUS_GET_METHOD "GetCurrentState"
+#define VBOXCLIENT_HELPER_DBUS_APPLY_METHOD "ApplyMonitorsConfig"
+
+/** D-bus communication timeout value, milliseconds.*/
+#define VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS (1 * 1000)
+
+/** gnome-settings-daemon ApplyMonitorsConfig method:
+ * 0: verify - test if configuration can be applied and do not change anything,
+ * 1: temporary - apply configuration temporary, all will be reverted after re-login,
+ * 2: persistent - apply configuration permanently (asks for user confirmation).
+ */
+#define VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD (1)
+
+/**
+ * Helper macro which is used in order to simplify code when batch of
+ * values needed to be parsed out of D-bus. Macro prevents execution
+ * of the 'next' command if 'previous' one was failed (tracked via
+ * local variable _ret). It is required that '_ret' should be initialized
+ * to TRUE before batch started.
+ *
+ * @param _ret Local variable which is used in order to track execution flow.
+ * @param _call A function (with full arguments) which returns 'dbus_bool_t'.
+ */
+#define VBCL_HLP_GNOME3_NEXT(_ret, _call) \
+ { _ret &= _ret ? _call : _ret; if (!ret) VBClLogError(__FILE__ ":%d: check fail here!\n", __LINE__); }
+
+/**
+ * This structure describes sub-part of physical monitor state
+ * required to compose a payload for calling ApplyMonitorsConfig method. */
+struct vbcl_hlp_gnome3_physical_display_state
+{
+ /** Physical display connector name string. */
+ char *connector;
+ /** Current mode name string for physical display. */
+ char *mode;
+};
+
+/**
+ * Verify if data represented by D-bus message iteration corresponds to given data type.
+ *
+ * @return True if D-bus message iteration corresponds to given data type, False otherwise.
+ * @param iter D-bus message iteration.
+ * @param type D-bus data type.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_verify_data_type(DBusMessageIter *iter, int type)
+{
+ if (!iter)
+ return false;
+
+ if (dbus_message_iter_get_arg_type(iter) != type)
+ return false;
+
+ return true;
+}
+
+/**
+ * Verifies D-bus iterator signature.
+ *
+ * @return True if iterator signature matches to given one.
+ * @param iter D-bus iterator to check.
+ * @param signature Expected iterator signature.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_check_iter_signature(DBusMessageIter *iter, const char *signature)
+{
+ char *iter_signature;
+ dbus_bool_t match;
+
+ if ( !iter
+ || !signature)
+ {
+ return false;
+ }
+
+ /* In case of dbus_message_iter_get_signature() returned memory should be freed by us. */
+ iter_signature = dbus_message_iter_get_signature(iter);
+ match = (strcmp(iter_signature, signature) == 0);
+
+ if (!match)
+ VBClLogError("iter signature mismatch: '%s' vs. '%s'\n", signature, iter_signature);
+
+ if (iter_signature)
+ dbus_free(iter_signature);
+
+ return match;
+}
+
+/**
+ * Verifies D-bus message signature.
+ *
+ * @return True if message signature matches to given one.
+ * @param message D-bus message to check.
+ * @param signature Expected message signature.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_check_message_signature(DBusMessage *message, const char *signature)
+{
+ char *message_signature;
+ dbus_bool_t match;
+
+ if ( !message
+ || !signature)
+ {
+ return false;
+ }
+
+ /* In case of dbus_message_get_signature() returned memory need NOT be freed by us. */
+ message_signature = dbus_message_get_signature(message);
+ match = (strcmp(message_signature, signature) == 0);
+
+ if (!match)
+ VBClLogError("message signature mismatch: '%s' vs. '%s'\n", signature, message_signature);
+
+ return match;
+}
+
+/**
+ * Jump into DBUS_TYPE_ARRAY iter container and initialize sub-iterator
+ * aimed to traverse container child nodes.
+ *
+ * @return True if operation was successful, False otherwise.
+ * @param iter D-bus iter of type DBUS_TYPE_ARRAY.
+ * @param array Returned sub-iterator.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_iter_get_array(DBusMessageIter *iter, DBusMessageIter *array)
+{
+ if (!iter || !array)
+ return false;
+
+ if (vbcl_hlp_gnome3_verify_data_type(iter, DBUS_TYPE_ARRAY))
+ {
+ dbus_message_iter_recurse(iter, array);
+ /* Move to the next iter, returned value not important. */
+ dbus_message_iter_next(iter);
+ return true;
+ }
+ else
+ {
+ VBClLogError(
+ "cannot get array: argument signature '%s' does not match to type of array\n",
+ dbus_message_iter_get_signature(iter));
+ }
+
+ return false;
+}
+
+/**
+ * Get value of D-bus iter of specified simple type (numerals, strings).
+ *
+ * @return True if operation was successful, False otherwise.
+ * @param iter D-bus iter of type simple type.
+ * @param type D-bus data type.
+ * @param value Returned value.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_iter_get_basic(DBusMessageIter *iter, int type, void *value)
+{
+ if (!iter || !value)
+ return false;
+
+ if (vbcl_hlp_gnome3_verify_data_type(iter, type))
+ {
+ dbus_message_iter_get_basic(iter, value);
+ /* Move to the next iter, returned value not important. */
+ dbus_message_iter_next(iter);
+ return true;
+ }
+ else
+ {
+ VBClLogError(
+ "cannot get value: argument signature '%s' does not match to specified type\n",
+ dbus_message_iter_get_signature(iter));
+ }
+
+ return false;
+}
+
+/**
+ * Lookup simple value (numeral, string, bool etc) in D-bus dictionary
+ * by given key and type.
+ *
+ * @return True value is found, False otherwise.
+ * @param dict D-bus iterator which represents dictionary.
+ * @param key_match Dictionary key.
+ * @param type Type of value.
+ * @param value Returning value.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_lookup_dict(DBusMessageIter *dict, const char *key_match, int type, void *value)
+{
+ dbus_bool_t found = false;
+
+ if (!dict || !key_match)
+ return false;
+
+ if (!vbcl_hlp_gnome3_check_iter_signature(dict, "{sv}"))
+ return false;
+
+ do
+ {
+ dbus_bool_t ret = true;
+ DBusMessageIter iter;
+ char *key = NULL;
+
+ /* Proceed to part a{ > sv < } of a{sv}. */
+ dbus_message_iter_recurse(dict, &iter);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ /* Proceed to part a{ > s < v} of a{sv}. */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_STRING, &key));
+
+ /* Check if key matches. */
+ if (strcmp(key_match, key) == 0)
+ {
+ DBusMessageIter value_iter;
+
+ /* Proceed to part a{s > v < } of a{sv}. */
+ dbus_message_iter_recurse(&iter, &value_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&value_iter, type, value));
+
+ /* Make sure there are no more arguments. */
+ VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&value_iter));
+
+ if (ret)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ while (dbus_message_iter_next(dict));
+
+ return found;
+}
+
+/**
+ * Go through available modes and pick up the one which has property 'is-current' set.
+ * See GetCurrentState interface documentation for more details. Returned string memory
+ * must be freed by calling function.
+ *
+ * @return Mode name as a string if found, NULL otherwise.
+ * @param modes List of monitor modes.
+ */
+static char *vbcl_hlp_gnome3_lookup_monitor_current_mode(DBusMessageIter *modes)
+{
+ char *szCurrentMode = NULL;
+ DBusMessageIter modes_iter;
+
+ /* De-serialization parameters for 'modes': (siiddada{sv}). */
+ char *id = NULL;
+ int32_t width = 0;
+ int32_t height = 0;
+ double refresh_rate = 0;
+ double preferred_scale = 0;
+ DBusMessageIter supported_scales;
+ DBusMessageIter properties;
+
+ if (!modes)
+ return NULL;
+
+ if(!vbcl_hlp_gnome3_check_iter_signature(modes, "(siiddada{sv})"))
+ return NULL;
+
+ do
+ {
+ static const char *key_match = "is-current";
+ dbus_bool_t default_mode_found = false;
+ dbus_bool_t ret = true;
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, NULL);
+
+ /* Proceed to part a( > siiddada{sv} < ) of a(siiddada{sv}). */
+ dbus_message_iter_recurse(modes, &modes_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_STRING, &id));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &width));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &height));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &refresh_rate));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &preferred_scale));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &supported_scales));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &properties));
+
+ ret = vbcl_hlp_gnome3_lookup_dict(&properties, key_match, DBUS_TYPE_BOOLEAN, &default_mode_found);
+ if (ret && default_mode_found)
+ {
+ szCurrentMode = strdup(id);
+ break;
+ }
+ }
+ while (dbus_message_iter_next(modes));
+
+ return szCurrentMode;
+}
+
+/**
+ * Parse physical monitors list entry. See GetCurrentState interface documentation for more details.
+ *
+ * @return True if monitors list entry has been successfully parsed, False otherwise.
+ * @param physical_monitors_in D-bus iterator representing list of physical monitors.
+ * @param connector Connector name (out).
+ * @param vendor Vendor name (out).
+ * @param product Product name (out).
+ * @param physical_monitor_serial Serial number (out).
+ * @param modes List of monitor modes (out).
+ * @param physical_monitor_properties A D-bus dictionary containing monitor properties (out).
+ */
+static dbus_bool_t vbcl_hlp_gnome3_parse_physical_monitor_record(
+ DBusMessageIter *physical_monitors_in,
+ char **connector,
+ char **vendor,
+ char **product,
+ char **physical_monitor_serial,
+ DBusMessageIter *modes,
+ DBusMessageIter *physical_monitor_properties)
+{
+ dbus_bool_t ret = true;
+
+ DBusMessageIter physical_monitors_in_iter;
+ DBusMessageIter physical_monitors_in_description_iter;
+
+ if ( !physical_monitors_in
+ || !connector
+ || !vendor
+ || !product
+ || !physical_monitor_serial
+ || !modes
+ || !physical_monitor_properties)
+ {
+ return false;
+ }
+
+ /* Validate signature. */
+ if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})"))
+ return false;
+
+ /* Proceed to part ( > (ssss)a(siiddada{sv})a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */
+ dbus_message_iter_recurse(physical_monitors_in, &physical_monitors_in_iter);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ /* Proceed to part ( > (ssss) < a(siiddada{sv})a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */
+ dbus_message_iter_recurse(&physical_monitors_in_iter, &physical_monitors_in_description_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, connector));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, vendor));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, product));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, physical_monitor_serial));
+
+ /* Proceed to part ((ssss) > a(siiddada{sv}) < a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */
+ if (ret)
+ dbus_message_iter_next(&physical_monitors_in_iter);
+
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, modes));
+
+ /* Proceed to part ((ssss)a(siiddada{sv}) > a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, physical_monitor_properties));
+
+ /* Make sure there are no more arguments. */
+ VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&physical_monitors_in_iter));
+
+ return ret;
+}
+
+/**
+ * Parse logical monitors list entry. See GetCurrentState interface documentation for more details.
+ *
+ * @return True if monitors list entry has been successfully parsed, False otherwise.
+ * @param logical_monitors_in D-bus iterator representing list of logical monitors.
+ * @param x Monitor X position (out).
+ * @param y Monitor Y position (out).
+ * @param scale Monitor scale factor (out).
+ * @param transform Current monitor transform (rotation) (out).
+ * @param primary A flag which indicates if monitor is set as primary (out).
+ * @param monitors List of physical monitors which are displaying this logical monitor (out).
+ * @param properties List of monitor properties (out).
+ */
+static dbus_bool_t vbcl_hlp_gnome3_parse_logical_monitor_record(
+ DBusMessageIter *logical_monitors_in,
+ int32_t *x,
+ int32_t *y,
+ double *scale,
+ uint32_t *transform,
+ dbus_bool_t *primary,
+ DBusMessageIter *monitors,
+ DBusMessageIter *properties)
+{
+ dbus_bool_t ret = true;
+
+ /* Iter used to traverse logical monitor parameters: @a(iiduba(ssss)a{sv}). */
+ DBusMessageIter logical_monitors_in_iter;
+
+ if ( !logical_monitors_in
+ || !x
+ || !y
+ || !scale
+ || !transform
+ || !primary
+ || !monitors
+ || !properties)
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ /* Proceed to part @a( > iiduba(ssss)a{sv} < ) of @a(iiduba(ssss)a{sv}). */
+ dbus_message_iter_recurse(logical_monitors_in, &logical_monitors_in_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, x));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, y));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_DOUBLE, scale));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_UINT32, transform));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_BOOLEAN, primary));
+ /* Proceed to part @a(iidub > a(ssss) < a{sv}) of @a(iiduba(ssss)a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, monitors));
+ /* Proceed to part @a(iiduba(ssss) > a{sv} < ) of @a(iiduba(ssss)a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, properties));
+
+ /* Make sure there are no more arguments. */
+ VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&logical_monitors_in_iter));
+
+ return ret;
+}
+
+/**
+ * Get list of physical monitors parameters from D-bus iterator.
+ *
+ * Once this function was traversed 'physical_monitors_in' iterator, we are in the
+ * end of the list of physical monitors parameters. So, it is important to do it once.
+ *
+ * @return True if monitors parameters were successfully discovered, False otherwise.
+ * @param physical_monitors_in D-bus iterator representing list of physical monitors.
+ * @param state Storage to put monitors state to.
+ * @param state_records_max Size of state storage.
+ * @param cPhysicalMonitors Actual number of physical displays parsed.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_get_physical_monitors_state(
+ DBusMessageIter *physical_monitors_in,
+ vbcl_hlp_gnome3_physical_display_state *state,
+ uint32_t state_records_max,
+ uint32_t *cPhysicalMonitors)
+{
+ dbus_bool_t ret = true;
+ uint32_t iMonitor = 0;
+
+ if ( !physical_monitors_in
+ || !state
+ || !cPhysicalMonitors)
+ {
+ return false;
+ }
+
+ /* Validate signature. */
+ if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})"))
+ return false;
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ do
+ {
+ char *connector = NULL;
+ char *vendor = NULL;
+ char *product = NULL;
+ char *physical_monitor_serial = NULL;
+ DBusMessageIter modes;
+ DBusMessageIter physical_monitor_properties;
+
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_physical_monitor_record(
+ physical_monitors_in, &connector, &vendor, &product, &physical_monitor_serial,
+ &modes, &physical_monitor_properties));
+
+ if (iMonitor < state_records_max)
+ {
+ state[iMonitor].connector = connector;
+ state[iMonitor].mode = vbcl_hlp_gnome3_lookup_monitor_current_mode(&modes);
+
+ /* Check if both parameters were discovered successfully. */
+ VBCL_HLP_GNOME3_NEXT(ret, state[iMonitor].connector && state[iMonitor].mode);
+ }
+
+ iMonitor++;
+
+ }
+ while (ret && dbus_message_iter_next(physical_monitors_in));
+
+ if (iMonitor >= state_records_max)
+ {
+ VBClLogError("physical monitors list is too big (%u)\n", iMonitor);
+ ret = false;
+ }
+
+ *cPhysicalMonitors = iMonitor;
+
+ return ret;
+}
+
+/**
+ * Release monitors state resources.
+ *
+ * @param state Array of monitor states.
+ * @param cPhysicalMonitors Number of elements in array.
+ */
+static void vbcl_hlp_gnome3_free_physical_monitors_state(
+ vbcl_hlp_gnome3_physical_display_state *state,
+ uint32_t cPhysicalMonitors)
+{
+ if (!state || !cPhysicalMonitors)
+ return;
+
+ for (uint32_t i = 0; i < cPhysicalMonitors; i++)
+ {
+ /* Only free() what we allocated ourselves. */
+ if (state[i].mode)
+ free(state[i].mode);
+ }
+}
+
+/**
+ * Add dictionary element with boolean value into an array.
+ *
+ * @return True on success, False otherwise.
+ * @param parent_iter Array to add dictionary element into.
+ * @param key Dictionary key.
+ * @param value Boolean value for given key.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_add_dict_bool_entry(
+ DBusMessageIter *parent_iter, const char *key, const dbus_bool_t value)
+{
+ dbus_bool_t ret = true;
+
+ DBusMessageIter sub_iter_key;
+ DBusMessageIter sub_iter_value;
+
+ RT_ZERO(sub_iter_key);
+ RT_ZERO(sub_iter_value);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(parent_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_iter_key));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_key, DBUS_TYPE_STRING, &key));
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter_key, ((int) 'v'), "b", &sub_iter_value));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_value, DBUS_TYPE_BOOLEAN, &value));
+ }
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter_key, &sub_iter_value));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(parent_iter, &sub_iter_key));
+
+ return ret;
+}
+
+/**
+ * This function is responsible for gathering current display
+ * information (via its helper functions), compose a payload
+ * for ApplyMonitorsConfig method and finally send configuration
+ * change to gnome-settings-daemon over D-bus.
+ *
+ * @return IPRT status code.
+ * @param connection Handle to D-bus connection.
+ * @param serial Serial number obtained from GetCurrentState interface,
+ * needs to be passed to ApplyMonitorsConfig.
+ * @param physical_monitors_in List of physical monitors (see GetCurrentState).
+ * @param logical_monitors_in List of logical monitors (see GetCurrentState).
+ * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary.
+ */
+static int vbcl_hlp_gnome3_convert_and_apply_display_settings(
+ DBusConnection *connection,
+ uint32_t serial,
+ DBusMessageIter *physical_monitors_in,
+ DBusMessageIter *logical_monitors_in,
+ uint32_t idPrimaryDisplay)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ uint32_t iLogicalMonitor = 0;
+ uint32_t cPhysicalMonitors = 0;
+ int32_t method = VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD;
+
+ dbus_bool_t ret = true;
+ DBusError error;
+ DBusMessage *reply = NULL;;
+ DBusMessage *message = NULL;
+ DBusMessageIter message_iter;
+ DBusMessageIter logical_monitors_out_iter;
+ DBusMessageIter properties_out_iter;
+
+ struct vbcl_hlp_gnome3_physical_display_state
+ physical_monitors_state[VBOX_DRMIPC_MONITORS_MAX];
+
+ if ( !connection
+ || !physical_monitors_in
+ || !logical_monitors_in)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */
+ RT_ZERO(message_iter);
+ RT_ZERO(logical_monitors_out_iter);
+ RT_ZERO(properties_out_iter);
+
+ message = dbus_message_new_method_call(
+ VBOXCLIENT_HELPER_DBUS_DESTINATION,
+ VBOXCLIENT_HELPER_DBUS_PATH,
+ VBOXCLIENT_HELPER_DBUS_IFACE,
+ VBOXCLIENT_HELPER_DBUS_APPLY_METHOD);
+ if (!message)
+ {
+ VBClLogError("unable to apply monitors config: no memory\n");
+ return VERR_NO_MEMORY;
+ }
+
+ /* Start composing payload for ApplyMonitorsConfig method: (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ dbus_message_iter_init_append(message, &message_iter);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, VERR_INVALID_PARAMETER);
+
+ /* Get list of physical monitors parameters. */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_get_physical_monitors_state(
+ physical_monitors_in, physical_monitors_state, VBOX_DRMIPC_MONITORS_MAX, &cPhysicalMonitors));
+
+ /* ( >u< u@a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &serial));
+ /* (u >u< @a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &method));
+
+ /* Parameter "monitors" of method ApplyMonitorsConfig.
+ * Part (uu >@a(iiduba(ssa{sv}))< @a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "(iiduba(ssa{sv}))", &logical_monitors_out_iter));
+
+ /* Iterate over current configuration monitors (@logical_monitors
+ * parameter of GetCurrentState interface) and compose the rest part of message. */
+ do
+ {
+ /* De-serialization parameters for @logical_monitors data (see GetCurrentState interface documentation). */
+ int32_t x = 0;
+ int32_t y = 0;
+ double scale = 0;
+ uint32_t transform = 0;
+ dbus_bool_t primary = false;
+ dbus_bool_t isPrimary = false;
+ DBusMessageIter monitors;
+ DBusMessageIter properties;
+
+ /* These iterators are used in order to compose sub-containers of the message. */
+ DBusMessageIter sub_iter0;
+ DBusMessageIter sub_iter1;
+ DBusMessageIter sub_iter2;
+ DBusMessageIter sub_iter3;
+ /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */
+ RT_ZERO(sub_iter0);
+ RT_ZERO(sub_iter1);
+ RT_ZERO(sub_iter2);
+ RT_ZERO(sub_iter3);
+
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_logical_monitor_record(
+ logical_monitors_in, &x, &y, &scale, &transform, &primary, &monitors, &properties));
+
+ if (ret)
+ {
+ /* Whether current display supposed to be set as primary. */
+ isPrimary = (iLogicalMonitor == idPrimaryDisplay);
+
+ /* Compose part (uu@a( > iiduba(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&logical_monitors_out_iter, DBUS_TYPE_STRUCT, NULL, &sub_iter0));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &x));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &y));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_DOUBLE, &scale));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_UINT32, &transform));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_BOOLEAN, &isPrimary));
+
+ /* Compose part (uu@a(iidub > a(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter0, DBUS_TYPE_ARRAY, "(ssa{sv})", &sub_iter1));
+ {
+ /* Compose part (uu@a(iiduba > (ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter1, DBUS_TYPE_STRUCT, NULL, &sub_iter2));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].connector));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].mode));
+
+ /* Compose part (uu@a(iiduba(ss > a{sv} < ))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter2, DBUS_TYPE_ARRAY, "{sv}", &sub_iter3));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-current", true));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-preferred", true));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter2, &sub_iter3));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter1, &sub_iter2));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter0, &sub_iter1));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&logical_monitors_out_iter, &sub_iter0));
+
+ iLogicalMonitor++;
+
+ if (!ret)
+ {
+ dbus_message_iter_abandon_container_if_open(&sub_iter2, &sub_iter3);
+ dbus_message_iter_abandon_container_if_open(&sub_iter1, &sub_iter2);
+ dbus_message_iter_abandon_container_if_open(&sub_iter0, &sub_iter1);
+ dbus_message_iter_abandon_container_if_open(&logical_monitors_out_iter, &sub_iter0);
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ }
+ while (ret && dbus_message_iter_next(logical_monitors_in));
+
+ /* Finish with parameter "monitors" of method ApplyMonitorsConfig. */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &logical_monitors_out_iter));
+
+ /* Parameter "properties" of method ApplyMonitorsConfig (empty dict).
+ * Part (uu@a(iiduba(ssa{sv})) >@a{sv}< ) of (uu@a(iiduba(ssa{sv}))@a{sv}).*/
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "{sv}", &properties_out_iter));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &properties_out_iter));
+
+ if (ret)
+ {
+ dbus_error_init(&error);
+
+ reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error);
+ if (reply)
+ {
+ VBClLogInfo("display %d has been set as primary\n", idPrimaryDisplay);
+ dbus_message_unref(reply);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VBClLogError("unable to apply monitors config: %s\n",
+ dbus_error_is_set(&error) ? error.message : "unknown error");
+ dbus_error_free(&error);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ VBClLogError("unable to apply monitors config: cannot compose monitors config\n");
+
+ dbus_message_iter_abandon_container_if_open(&message_iter, &logical_monitors_out_iter);
+ dbus_message_iter_abandon_container_if_open(&message_iter, &properties_out_iter);
+
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ /* Clean physical monitors state. */
+ vbcl_hlp_gnome3_free_physical_monitors_state(physical_monitors_state, cPhysicalMonitors);
+
+ dbus_message_unref(message);
+
+ return rc;
+}
+
+/**
+ * This function parses GetCurrentState interface call reply and passes it for further processing.
+ *
+ * @return IPRT status code.
+ * @param connection Handle to D-bus connection.
+ * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary.
+ * @param reply Reply message of GetCurrentState call.
+ */
+static int vbcl_hlp_gnome3_process_current_display_layout(
+ DBusConnection *connection, uint32_t idPrimaryDisplay, DBusMessage *reply)
+{
+ static const char *expected_signature = "ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv}";
+
+ dbus_bool_t ret = true;
+ DBusMessageIter iter;
+ int rc = VERR_GENERAL_FAILURE;
+
+ uint32_t serial = 0;
+ DBusMessageIter monitors;
+ DBusMessageIter logical_monitors_in;
+ DBusMessageIter properties;
+
+ if (!reply)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Parse VBOXCLIENT_HELPER_DBUS_GET_METHOD reply payload:
+ *
+ * (u@a((ssss)a(siiddada{sv})a{sv})@a(iiduba(ssss)a{sv})@a{sv}).
+ *
+ * Method return the following arguments: monitors, logical_monitors, properties.
+ */
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, VERR_INVALID_PARAMETER);
+
+ /* Important: in order to avoid libdbus asserts during parsing, its signature should be verified at first. */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_check_message_signature(reply, expected_signature));
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_init(reply, &iter));
+ if (ret)
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_UINT32, &serial));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &monitors));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &logical_monitors_in));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &properties));
+
+ /* Make sure there are no more arguments. */
+ if (ret && !dbus_message_iter_has_next(&iter))
+ {
+ rc = vbcl_hlp_gnome3_convert_and_apply_display_settings(
+ connection, serial, &monitors, &logical_monitors_in, idPrimaryDisplay);
+ }
+ else
+ {
+ VBClLogError("cannot fetch current displays configuration: incorrect number of arguments\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ VBClLogError("cannot fetch current displays configuration: no data\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+/**
+ * This function establishes D-bus connection, requests gnome-settings-daemon
+ * to provide current display configuration via GetCurrentState interface call
+ * and passes this information further to helper functions in order to set
+ * requested display as primary.
+ *
+ * @return IPRT status code.
+ * @param idPrimaryDisplay A display ID which is requested to be set as primary.
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_set_primary_display(uint32_t idPrimaryDisplay)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ DBusConnection *connection = NULL;
+ DBusMessage *message = NULL;
+ DBusError error;
+
+ rc = RTDBusLoadLib();
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to load D-bus library\n");
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+
+ dbus_error_init(&error);
+ connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
+ if (!dbus_error_is_set(&error))
+ {
+ message = dbus_message_new_method_call(
+ VBOXCLIENT_HELPER_DBUS_DESTINATION,
+ VBOXCLIENT_HELPER_DBUS_PATH,
+ VBOXCLIENT_HELPER_DBUS_IFACE,
+ VBOXCLIENT_HELPER_DBUS_GET_METHOD);
+
+ if (message)
+ {
+ DBusMessage *reply;
+
+ reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error);
+ if (!dbus_error_is_set(&error))
+ {
+ rc = vbcl_hlp_gnome3_process_current_display_layout(connection, idPrimaryDisplay, reply);
+ dbus_message_unref(reply);
+ }
+ else
+ {
+ VBClLogError("unable to get current display configuration: %s\n", error.message);
+ dbus_error_free(&error);
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ dbus_message_unref(message);
+ }
+ else
+ {
+ VBClLogError("unable to get current display configuration: no memory\n");
+ rc = VERR_NO_MEMORY;
+ }
+
+ dbus_connection_flush(connection);
+ }
+ else
+ {
+ VBClLogError("unable to establish dbus connection: %s\n", error.message);
+ dbus_error_free(&error);
+ rc = VERR_INVALID_HANDLE;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_probe(void)
+{
+ const char *pszCurrentDesktop = RTEnvGet(VBCL_ENV_XDG_CURRENT_DESKTOP);
+
+ /* GNOME3 identifies itself by XDG_CURRENT_DESKTOP environment variable.
+ * It can slightly vary for different distributions, but we assume that this
+ * variable should at least contain sub-string 'GNOME' in its value. */
+ if (pszCurrentDesktop && RTStrStr(pszCurrentDesktop, "GNOME"))
+ return VINF_SUCCESS;
+
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit}
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_init(void)
+{
+ int rc;
+
+ if (!VBClHasWayland())
+ {
+ rc = vbcl_hlp_generic_init();
+ VBClLogInfo("attempt to start generic helper routines, rc=%Rrc\n", rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnTerm}
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_term(void)
+{
+ int rc;
+
+ if (!VBClHasWayland())
+ {
+ rc = vbcl_hlp_generic_term();
+ VBClLogInfo("attempt to stop generic helper routines, rc=%Rrc\n", rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* Helper callbacks. */
+const VBCLDISPLAYHELPER g_DisplayHelperGnome3 =
+{
+ "GNOME3", /* .pszName */
+ vbcl_hlp_gnome3_probe, /* .pfnProbe */
+ vbcl_hlp_gnome3_init, /* .pfnInit */
+ vbcl_hlp_gnome3_term, /* .pfnTerm */
+ vbcl_hlp_gnome3_set_primary_display, /* .pfnSetPrimaryDisplay */
+ vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
+ vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper.h b/src/VBox/Additions/x11/VBoxClient/display-helper.h
new file mode 100644
index 00000000..81fae51c
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-helper.h
@@ -0,0 +1,130 @@
+/* $Id: display-helper.h $ */
+/** @file
+ * Guest Additions - Definitions for Desktop Environment helpers.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "display-ipc.h"
+
+/**
+ * Display offsets change notification callback.
+ *
+ * @returns IPRT status code.
+ * @param cDisplays Number of displays which have changed their offset.
+ * @param aDisplays Displays offset data.
+ */
+typedef DECLCALLBACKTYPE(int, FNDISPLAYOFFSETCHANGE, (uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays));
+
+/**
+ * Desktop Environment helper definition structure.
+ */
+typedef struct
+{
+ /** A short helper name. 16 chars maximum (RTTHREAD_NAME_LEN). */
+ const char *pszName;
+
+ /**
+ * Probing callback.
+ *
+ * Called in attempt to detect if user is currently running Desktop Environment
+ * which is compatible with the helper.
+ *
+ * @returns IPRT status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnProbe, (void));
+
+ /**
+ * Initialization callback.
+ *
+ * @returns IPRT status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnInit, (void));
+
+ /**
+ * Termination callback.
+ *
+ * @returns IPRT status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnTerm, (void));
+
+ /**
+ * Set primary display in Desktop Environment specific way.
+ *
+ * @returns IPRT status code.
+ * @param idDisplay Display ID which should be set as primary.
+ */
+ DECLCALLBACKMEMBER(int, pfnSetPrimaryDisplay, (uint32_t idDisplay));
+
+ /**
+ * Register notification callback for display offsets change event.
+ *
+ * @param pfnCb Notification callback.
+ */
+ DECLCALLBACKMEMBER(void, pfnSubscribeDisplayOffsetChangeNotification, (FNDISPLAYOFFSETCHANGE *pfnCb));
+
+ /**
+ * Unregister notification callback for display offsets change event.
+ */
+ DECLCALLBACKMEMBER(void, pfnUnsubscribeDisplayOffsetChangeNotification, (void));
+
+} VBCLDISPLAYHELPER;
+
+/**
+ * Initialization callback for generic Desktop Environment helper.
+ *
+ * @returns IPRT status code.
+ */
+RTDECL(int) vbcl_hlp_generic_init(void);
+
+/**
+ * Termination callback for generic Desktop Environment helper.
+ *
+ * @returns IPRT status code.
+ */
+RTDECL(int) vbcl_hlp_generic_term(void);
+
+/**
+ * Subscribe to display offset change notifications emitted by Generic Desktop Environment helper.
+ *
+ * @param pfnCb A pointer to callback function which will be triggered when event arrives.
+ */
+RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb);
+
+/**
+ * Unsubscribe from display offset change notifications emitted by Generic Desktop Environment helper.
+ */
+RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void);
+
+/** GNOME3 helper private data. */
+extern const VBCLDISPLAYHELPER g_DisplayHelperGnome3;
+/** Generic helper private data. */
+extern const VBCLDISPLAYHELPER g_DisplayHelperGeneric;
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp b/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp
new file mode 100644
index 00000000..5f5d5c8b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp
@@ -0,0 +1,451 @@
+/* $Id: display-ipc.cpp $ */
+/** @file
+ * Guest Additions - DRM IPC communication core functions.
+ */
+
+/*
+ * 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
+ */
+
+/*
+ * This module implements connection handling routine which is common for
+ * both IPC server and client (see vbDrmIpcConnectionHandler()). This function
+ * at first tries to read incoming command from IPC socket and if no data has
+ * arrived within VBOX_DRMIPC_RX_TIMEOUT_MS, it checks is there is some data in
+ * TX queue and sends it. TX queue and IPC connection handle is unique per IPC
+ * client and handled in a separate thread of either server or client process.
+ *
+ * Logging is implemented in a way that errors are always printed out,
+ * VBClLogVerbose(2) is used for debugging purposes and reflects what is related to
+ * IPC communication. In order to see logging on a host side it is enough to do:
+ *
+ * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host.
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/localipc.h>
+#include <iprt/err.h>
+#include <iprt/crc.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/assert.h>
+
+#include <grp.h>
+#include <pwd.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+
+/**
+ * Calculate size of TX list entry.
+ *
+ * TX list entry consists of RTLISTNODE, DRM IPC message header and message payload.
+ * Given IpcCmd already includes message header and payload. So, TX list entry size
+ * equals to size of IpcCmd plus size of RTLISTNODE.
+ *
+ * @param IpcCmd A structure which represents DRM IPC command.
+ */
+#define DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(IpcCmd) (sizeof(IpcCmd) + RT_UOFFSETOF(VBOX_DRMIPC_TX_LIST_ENTRY, Hdr))
+
+/**
+ * Initialize IPC client private data.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC client private data to be initialized.
+ * @param hThread A thread which server IPC client connection.
+ * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX().
+ * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session.
+ * @param pfnRxCb IPC RX callback function pointer.
+ */
+RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession,
+ uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb)
+{
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+ AssertReturn(hThread, VERR_INVALID_PARAMETER);
+ AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
+ AssertReturn(cTxListCapacity, VERR_INVALID_PARAMETER);
+ AssertReturn(pfnRxCb, VERR_INVALID_PARAMETER);
+
+ pClient->hThread = hThread;
+ pClient->hClientSession = hClientSession;
+
+ RT_ZERO(pClient->TxList);
+ RTListInit(&pClient->TxList.Node);
+
+ pClient->cTxListCapacity = cTxListCapacity;
+ ASMAtomicWriteU32(&pClient->cTxListSize, 0);
+
+ pClient->pfnRxCb = pfnRxCb;
+
+ return RTCritSectInit(&pClient->CritSect);
+}
+
+/**
+ * Releases IPC client private data resources.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data to be initialized.
+ */
+RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient)
+{
+ PVBOX_DRMIPC_TX_LIST_ENTRY pEntry, pNextEntry;
+ int rc;
+
+ pClient->hClientSession = 0;
+
+ rc = RTCritSectEnter(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTListIsEmpty(&pClient->TxList.Node))
+ {
+ RTListForEachSafe(&pClient->TxList.Node, pEntry, pNextEntry, VBOX_DRMIPC_TX_LIST_ENTRY, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+ RTMemFree(pEntry);
+ ASMAtomicDecU32(&pClient->cTxListSize);
+ }
+ }
+
+ rc = RTCritSectLeave(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectDelete(&pClient->CritSect);
+ if (RT_FAILURE(rc))
+ VBClLogError("vbDrmIpcClientReleaseResources: unable to delete critical section, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("vbDrmIpcClientReleaseResources: unable to leave critical section, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("vbDrmIpcClientReleaseResources: unable to enter critical section, rc=%Rrc\n", rc);
+
+ Assert(ASMAtomicReadU32(&pClient->cTxListSize) == 0);
+
+ RT_ZERO(*pClient);
+
+ return rc;
+}
+
+/**
+ * Add message to IPC session TX queue.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param pEntry Pointer to the message.
+ */
+static int vbDrmIpcSessionScheduleTx(PVBOX_DRMIPC_CLIENT pClient, PVBOX_DRMIPC_TX_LIST_ENTRY pEntry)
+{
+ int rc;
+
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+ AssertReturn(pEntry, VERR_INVALID_PARAMETER);
+
+ rc = RTCritSectEnter(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pClient->cTxListSize < pClient->cTxListCapacity)
+ {
+ RTListAppend(&pClient->TxList.Node, &pEntry->Node);
+ pClient->cTxListSize++;
+ }
+ else
+ VBClLogError("vbDrmIpcSessionScheduleTx: TX queue is full\n");
+
+ int rc2 = RTCritSectLeave(&pClient->CritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbDrmIpcSessionScheduleTx: cannot leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbDrmIpcSessionScheduleTx: cannot enter critical section, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Pick up message from TX queue if available.
+ *
+ * @return Pointer to list entry or NULL if queue is empty.
+ */
+static PVBOX_DRMIPC_TX_LIST_ENTRY vbDrmIpcSessionPickupTxMessage(PVBOX_DRMIPC_CLIENT pClient)
+{
+ PVBOX_DRMIPC_TX_LIST_ENTRY pEntry = NULL;
+ int rc;
+
+ AssertReturn(pClient, NULL);
+
+ rc = RTCritSectEnter(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTListIsEmpty(&pClient->TxList.Node))
+ {
+ pEntry = (PVBOX_DRMIPC_TX_LIST_ENTRY)RTListRemoveFirst(&pClient->TxList.Node, VBOX_DRMIPC_TX_LIST_ENTRY, Node);
+ pClient->cTxListSize--;
+ Assert(pEntry);
+ }
+
+ int rc2 = RTCritSectLeave(&pClient->CritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot enter critical section, rc=%Rrc\n", rc);
+
+ return pEntry;
+}
+
+RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession)
+{
+ int rc = VERR_ACCESS_DENIED;
+ RTUID uUid;
+ struct group *pAllowedGroup;
+
+ AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
+
+ /* Get DRM IPC user group entry from system database. */
+ pAllowedGroup = getgrnam(VBOX_DRMIPC_USER_GROUP);
+ if (!pAllowedGroup)
+ return RTErrConvertFromErrno(errno);
+
+ /* Get remote user ID and check if it is in allowed user group. */
+ rc = RTLocalIpcSessionQueryUserId(hClientSession, &uUid);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get user record from system database and look for it in group's members list. */
+ struct passwd *UserRecord = getpwuid(uUid);
+
+ if (UserRecord && UserRecord->pw_name)
+ {
+ while (*pAllowedGroup->gr_mem)
+ {
+ if (RTStrNCmp(*pAllowedGroup->gr_mem, UserRecord->pw_name, LOGIN_NAME_MAX) == 0)
+ return VINF_SUCCESS;
+
+ pAllowedGroup->gr_mem++;
+ }
+ }
+ }
+
+ return rc;
+}
+
+RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
+ (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY));
+
+ if (pTxListEntry)
+ {
+ PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)(&pTxListEntry->Hdr);
+
+ pCmd->Hdr.idCmd = VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY;
+ pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY);
+ pCmd->idDisplay = idDisplay;
+ pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
+ Assert(pCmd->Hdr.u64Crc);
+
+ /* Put command into queue and trigger TX. */
+ rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogVerbose(2, "vbDrmIpcSetPrimaryDisplay: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
+ }
+ else
+ {
+ RTMemFree(pTxListEntry);
+ VBClLogError("vbDrmIpcSetPrimaryDisplay: unable to schedule TX, rc=%Rrc\n", rc);
+ }
+ }
+ else
+ {
+ VBClLogInfo("cannot allocate SET_PRIMARY_DISPLAY command\n");
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Report to IPC server that display layout offsets have been changed (called by IPC client).
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param cDisplays Number of monitors which have offsets changed.
+ * @param aDisplays Offsets data.
+ */
+RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
+ (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(
+ DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS));
+
+ if (pTxListEntry)
+ {
+ PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)(&pTxListEntry->Hdr);
+
+ pCmd->Hdr.idCmd = VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS;
+ pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS);
+ pCmd->cDisplays = cDisplays;
+ memcpy(pCmd->aDisplays, aDisplays, cDisplays * sizeof(struct VBOX_DRMIPC_VMWRECT));
+ pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
+ Assert(pCmd->Hdr.u64Crc);
+
+ /* Put command into queue and trigger TX. */
+ rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogVerbose(2, "vbDrmIpcReportDisplayOffsets: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
+ }
+ else
+ {
+ RTMemFree(pTxListEntry);
+ VBClLogError("vbDrmIpcReportDisplayOffsets: unable to schedule TX, rc=%Rrc\n", rc);
+ }
+ }
+ else
+ {
+ VBClLogInfo("cannot allocate REPORT_DISPLAY_OFFSETS command\n");
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Common function for both IPC server and client which is responsible
+ * for handling IPC communication flow.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC connection private data.
+ */
+RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient)
+{
+ int rc;
+ static uint8_t aInputBuf[VBOX_DRMIPC_RX_BUFFER_SIZE];
+ size_t cbRead = 0;
+ PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry;
+
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+
+ /* Make sure we are still connected to IPC server. */
+ if (!pClient->hClientSession)
+ {
+ VBClLogVerbose(2, "connection to IPC server lost\n");
+ return VERR_NET_CONNECTION_RESET_BY_PEER;
+ }
+
+ AssertReturn(pClient->pfnRxCb, VERR_INVALID_PARAMETER);
+
+ /* Make sure we have valid connection handle. By reporting VERR_BROKEN_PIPE,
+ * we trigger reconnect to IPC server. */
+ if (!RT_VALID_PTR(pClient->hClientSession))
+ return VERR_BROKEN_PIPE;
+
+ rc = RTLocalIpcSessionWaitForData(pClient->hClientSession, VBOX_DRMIPC_RX_TIMEOUT_MS);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read IPC message header. */
+ rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf, sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbRead == sizeof(VBOX_DRMIPC_COMMAND_HEADER))
+ {
+ PVBOX_DRMIPC_COMMAND_HEADER pHdr = (PVBOX_DRMIPC_COMMAND_HEADER)aInputBuf;
+ if (pHdr)
+ {
+ AssertReturn(pHdr->cbData <= sizeof(aInputBuf) - sizeof(VBOX_DRMIPC_COMMAND_HEADER), VERR_INVALID_PARAMETER);
+
+ /* Read the rest of a message. */
+ rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf + sizeof(VBOX_DRMIPC_COMMAND_HEADER), pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
+ AssertRCReturn(rc, rc);
+ AssertReturn(cbRead == (pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER)), VERR_INVALID_PARAMETER);
+
+ uint64_t u64Crc = pHdr->u64Crc;
+
+ /* Verify checksum. */
+ pHdr->u64Crc = 0;
+ if (u64Crc != 0 && RTCrc64(pHdr, pHdr->cbData) == u64Crc)
+ {
+ /* Restore original CRC. */
+ pHdr->u64Crc = u64Crc;
+
+ /* Trigger RX callback. */
+ rc = pClient->pfnRxCb(pHdr->idCmd, (void *)pHdr, pHdr->cbData);
+ VBClLogVerbose(2, "command 0x%X executed, rc=%Rrc\n", pHdr->idCmd, rc);
+ }
+ else
+ {
+ VBClLogError("unable to read from IPC: CRC mismatch, provided crc=0x%X, cmd=0x%X\n", u64Crc, pHdr->idCmd);
+ rc = VERR_NOT_EQUAL;
+ }
+ }
+ else
+ {
+ VBClLogError("unable to read from IPC: zero data received\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ VBClLogError("received partial IPC message header (%u bytes)\n", cbRead);
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ VBClLogVerbose(2, "received %u bytes from IPC\n", cbRead);
+ }
+ else
+ {
+ VBClLogError("unable to read from IPC, rc=%Rrc\n", rc);
+ }
+ }
+
+ /* Check if TX queue has some messages to transfer. */
+ while ((pTxListEntry = vbDrmIpcSessionPickupTxMessage(pClient)) != NULL)
+ {
+ PVBOX_DRMIPC_COMMAND_HEADER pMessageHdr = (PVBOX_DRMIPC_COMMAND_HEADER)(&pTxListEntry->Hdr);
+ Assert(pMessageHdr);
+
+ rc = RTLocalIpcSessionWrite(
+ pClient->hClientSession, (void *)(&pTxListEntry->Hdr), pMessageHdr->cbData);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLocalIpcSessionFlush(pClient->hClientSession);
+ if (RT_SUCCESS(rc))
+ VBClLogVerbose(2, "vbDrmIpcConnectionHandler: transferred %u bytes\n", pMessageHdr->cbData);
+ else
+ VBClLogError("vbDrmIpcConnectionHandler: cannot flush IPC connection, transfer of %u bytes failed\n", pMessageHdr->cbData);
+ }
+ else
+ VBClLogError("vbDrmIpcConnectionHandler: cannot TX, rc=%Rrc\n", rc);
+
+ RTMemFree(pTxListEntry);
+ }
+
+ return rc;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/display-ipc.h b/src/VBox/Additions/x11/VBoxClient/display-ipc.h
new file mode 100644
index 00000000..39e9760e
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-ipc.h
@@ -0,0 +1,242 @@
+/* $Id: display-ipc.h $ */
+/** @file
+ * Guest Additions - DRM IPC communication core function definitions.
+ *
+ * Definitions for IPC communication in between VBoxDRMClient and VBoxClient.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+# include <iprt/assert.h>
+# include <iprt/localipc.h>
+# include <iprt/critsect.h>
+# include <iprt/list.h>
+
+/** Name of DRM IPC server.*/
+# define VBOX_DRMIPC_SERVER_NAME "DRMIpcServer"
+/** A user group which is allowed to connect to IPC server. */
+#define VBOX_DRMIPC_USER_GROUP "vboxdrmipc"
+/** Time in milliseconds to wait for host events. */
+#define VBOX_DRMIPC_RX_TIMEOUT_MS (500)
+/** Time in milliseconds to relax in between unsuccessful connect attempts. */
+#define VBOX_DRMIPC_RX_RELAX_MS (500)
+/** Size of RX buffer for IPC communication. */
+#define VBOX_DRMIPC_RX_BUFFER_SIZE (1024)
+/** Maximum amount of TX messages which can be queued. */
+#define VBOX_DRMIPC_TX_QUEUE_SIZE (64)
+/** Maximum number of physical monitor configurations we can process. */
+#define VBOX_DRMIPC_MONITORS_MAX (32)
+
+/** Rectangle structure for geometry of a single screen. */
+struct VBOX_DRMIPC_VMWRECT
+{
+ /** Monitor X offset. */
+ int32_t x;
+ /** Monitor Y offset. */
+ int32_t y;
+ /** Monitor width. */
+ uint32_t w;
+ /** Monitor height. */
+ uint32_t h;
+};
+AssertCompileSize(struct VBOX_DRMIPC_VMWRECT, 16);
+
+/** List of IPC commands issued by client to server. */
+typedef enum VBOXDRMIPCSRVCMD
+{
+ /** Separate server and client commands by starting index. */
+ VBOXDRMIPCSRVCMD_INVALID = 0x00,
+ /** Client reports list of current display offsets. */
+ VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS,
+ /** Termination of commands list. */
+ VBOXDRMIPCSRVCMD_MAX
+} VBOXDRMIPCSRVCMD;
+
+/** List of IPC commands issued by server to client. */
+typedef enum VBOXDRMIPCCLTCMD
+{
+ /** Separate server and client commands by starting index. */
+ VBOXDRMIPCCLTCMD_INVALID = 0x7F,
+ /** Server requests client to set primary screen. */
+ VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY,
+ /** Termination of commands list. */
+ VBOXDRMIPCCLTCMD_MAX
+} VBOXDRMIPCCLTCMD;
+
+/** IPC command header. */
+typedef struct VBOX_DRMIPC_COMMAND_HEADER
+{
+ /** IPC command structure checksum, includes header and payload. */
+ uint64_t u64Crc;
+ /** IPC command identificator (opaque). */
+ uint8_t idCmd;
+ /** Size of payload data. */
+ uint64_t cbData;
+
+} VBOX_DRMIPC_COMMAND_HEADER;
+
+/** Pointer to IPC command header. */
+typedef VBOX_DRMIPC_COMMAND_HEADER *PVBOX_DRMIPC_COMMAND_HEADER;
+
+/** IPC command VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY payload. */
+typedef struct VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY
+{
+ /* IPC command header. */
+ VBOX_DRMIPC_COMMAND_HEADER Hdr;
+ /** ID of display to be set as primary. */
+ uint32_t idDisplay;
+
+} VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY;
+
+/** Pointer to IPC command DRMIPCCOMMAND_SET_PRIMARY_DISPLAY payload. */
+typedef VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY *PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY;
+
+/** IPC command VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS payload. */
+typedef struct VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS
+{
+ /* IPC command header. */
+ VBOX_DRMIPC_COMMAND_HEADER Hdr;
+ /** Number of displays which have changed offsets. */
+ uint32_t cDisplays;
+ /** Offsets data. */
+ struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX];
+} VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS;
+
+/** Pointer to IPC command DRMIPCCOMMAND_SET_PRIMARY_DISPLAY payload. */
+typedef VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS *PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS;
+
+/** DRM IPC TX list entry. */
+typedef struct VBOX_DRMIPC_TX_LIST_ENTRY
+{
+ /** The list node. */
+ RTLISTNODE Node;
+ /* IPC command header. */
+ VBOX_DRMIPC_COMMAND_HEADER Hdr;
+} VBOX_DRMIPC_TX_LIST_ENTRY;
+
+/** Pointer to DRM IPC TX list entry. */
+typedef VBOX_DRMIPC_TX_LIST_ENTRY *PVBOX_DRMIPC_TX_LIST_ENTRY;
+
+/**
+ * A callback function which is called by IPC client session thread when new message arrives.
+ *
+ * @returns IPRT status code.
+ * @param idCmd Command ID to be executed (opaque).
+ * @param pvData Command specific argument data.
+ * @param cbData Size of command argument data as received over IPC.
+ */
+typedef DECLCALLBACKTYPE(int, FNDRMIPCRXCB, (uint8_t idCmd, void *pvData, uint32_t cbData));
+
+/** Pointer to FNDRMIPCRXCB. */
+typedef FNDRMIPCRXCB *PFNDRMIPCRXCB;
+
+/** IPC session private data. */
+typedef struct VBOX_DRMIPC_CLIENT
+{
+ /** Thread handle which dispatches this IPC client session. */
+ RTTHREAD hThread;
+ /** IPC session handle. */
+ RTLOCALIPCSESSION hClientSession;
+ /** TX message queue mutex. */
+ RTCRITSECT CritSect;
+ /** TX message queue (accessed under critsect). */
+ VBOX_DRMIPC_TX_LIST_ENTRY TxList;
+ /** Maximum number of messages which can be queued to TX message queue. */
+ uint32_t cTxListCapacity;
+ /** Actual number of messages currently queued to TX message queue (accessed under critsect). */
+ uint32_t cTxListSize;
+ /** IPC RX callback. */
+ PFNDRMIPCRXCB pfnRxCb;
+} VBOX_DRMIPC_CLIENT;
+
+/** Pointer to IPC session private data. */
+typedef VBOX_DRMIPC_CLIENT *PVBOX_DRMIPC_CLIENT;
+
+/** Static initializer for VBOX_DRMIPC_CLIENT. */
+#define VBOX_DRMIPC_CLIENT_INITIALIZER { NIL_RTTHREAD, 0, { 0 }, { { NULL, NULL }, {0, 0, 0} }, 0, 0, NULL }
+
+/**
+ * Initialize IPC client private data.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC client private data to be initialized.
+ * @param hThread A thread which server IPC client connection.
+ * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX().
+ * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session.
+ * @param pfnRxCb IPC RX callback function pointer.
+ */
+RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession,
+ uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb);
+
+/**
+ * Releases IPC client private data resources.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data to be initialized.
+ */
+RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient);
+
+/**
+ * Verify if remote IPC peer corresponds to a process which is running
+ * from allowed user.
+ *
+ * @return IPRT status code.
+ * @param hClientSession IPC session handle.
+ */
+RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession);
+
+/**
+ * Common function for both IPC server and client which is responsible
+ * for handling IPC communication flow.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC connection private data.
+ */
+RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient);
+
+/**
+ * Request remote IPC peer to set primary display.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param idDisplay ID of display to be set as primary.
+ */
+RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay);
+
+/**
+ * Report to IPC server that display layout offsets have been changed (called by IPC client).
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param cDisplays Number of monitors which have offsets changed.
+ * @param aDisplays Offsets data.
+ */
+RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays);
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
new file mode 100644
index 00000000..168adb9b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
@@ -0,0 +1,460 @@
+/* $Id: display-svga-session.cpp $ */
+/** @file
+ * Guest Additions - VMSVGA Desktop Environment user session assistant.
+ *
+ * This service connects to VBoxDRMClient IPC server, listens for
+ * its commands and reports current display offsets to it. If IPC
+ * server is not available, it forks legacy 'VBoxClient --vmsvga
+ * service and terminates.
+ */
+
+/*
+ * 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
+ */
+
+/*
+ * This service is an IPC client for VBoxDRMClient daemon. It is also
+ * a proxy bridge to a Desktop Environment specific code (so called
+ * Desktop Environment helpers).
+ *
+ * Once started, it will try to enumerate and probe all the registered
+ * helpers and if appropriate helper found, it will forward incoming IPC
+ * commands to it as well as send helper's commands back to VBoxDRMClient.
+ * Generic helper is a special one. It will be used by default if all the
+ * other helpers are failed on probe. Moreover, generic helper provides
+ * helper functions that can be used by other helpers as well. For example,
+ * once Gnome3 Desktop Environment is running on X11, it will be also use
+ * display offsets change notification monitor of a generic helper.
+ *
+ * Multiple instances of this daemon are allowed to run in parallel
+ * with the following limitations.
+ * A single user cannot run multiple daemon instances per single TTY device,
+ * however, multiple instances are allowed for the user on different
+ * TTY devices (i.e. in case if user runs multiple X servers on different
+ * terminals). On multiple TTY devices multiple users can run multiple
+ * daemon instances (i.e. in case of "switch user" DE configuration when
+ * multiple X/Wayland servers are running on separate TTY devices).
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+#include "display-helper.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/localipc.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/linux/sysfs.h>
+
+
+/** Handle to IPC client connection. */
+VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
+
+/** IPC client handle critical section. */
+static RTCRITSECT g_hClientCritSect;
+
+/** List of available Desktop Environment specific display helpers. */
+static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] =
+{
+ &g_DisplayHelperGnome3, /* GNOME3 helper. */
+ &g_DisplayHelperGeneric, /* Generic helper. */
+ NULL, /* Terminate list. */
+};
+
+/** Selected Desktop Environment specific display helper. */
+static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL;
+
+/** IPC connection session handle. */
+static RTLOCALIPCSESSION g_hSession = 0;
+
+/**
+ * Callback for display offsets change events provided by Desktop Environment specific display helper.
+ *
+ * @returns IPRT status code.
+ * @param cDisplays Number of displays which have changed offset.
+ * @param aDisplays Display data.
+ */
+static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
+{
+ int rc = RTCritSectEnter(&g_hClientCritSect);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays);
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSVGASessionInit(void)
+{
+ int rc;
+ RTLOCALIPCSESSION hSession;
+ int idxDisplayHelper = 0;
+
+ /** Custom log prefix to be used for logger instance of this process. */
+ static const char *pszLogPrefix = "VBoxClient VMSVGA:";
+
+ VBClLogSetLogPrefix(pszLogPrefix);
+
+ rc = RTCritSectInit(&g_hClientCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to init locking, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ /* Go through list of available Desktop Environment specific helpers and try to pick up one. */
+ while (g_apDisplayHelpers[idxDisplayHelper])
+ {
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe)
+ {
+ VBClLogInfo("probing Desktop Environment helper '%s'\n",
+ g_apDisplayHelpers[idxDisplayHelper]->pszName);
+
+ rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe();
+
+ /* Found compatible helper. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize it. */
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit)
+ {
+ rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit();
+ }
+
+ /* Some helpers might have no .pfnInit(), that's ok. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Subscribe to display offsets change event. */
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification)
+ {
+ g_apDisplayHelpers[idxDisplayHelper]->
+ pfnSubscribeDisplayOffsetChangeNotification(
+ vbclSVGASessionDisplayOffsetChanged);
+ }
+
+ g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper];
+ break;
+ }
+ else
+ VBClLogError("compatible Desktop Environment "
+ "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc);
+ }
+ }
+
+ idxDisplayHelper++;
+ }
+
+ /* Make sure we found compatible Desktop Environment specific helper. */
+ if (g_pDisplayHelper)
+ {
+ VBClLogInfo("using Desktop Environment specific display helper '%s'\n",
+ g_pDisplayHelper->pszName);
+ }
+ else
+ {
+ VBClLogError("unable to find Desktop Environment specific display helper\n");
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ /* Attempt to connect to VBoxDRMClient IPC server. */
+ rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_SUCCESS(rc))
+ {
+ g_hSession = hSession;
+ }
+ else
+ VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc);
+
+ /* We cannot initialize ourselves, start legacy service and terminate. */
+ if (RT_FAILURE(rc))
+ {
+ /* Free helper resources. */
+ if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
+ g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
+
+ if (g_pDisplayHelper->pfnTerm)
+ {
+ rc = g_pDisplayHelper->pfnTerm();
+ VBClLogInfo("helper service terminated, rc=%Rrc\n", rc);
+ }
+
+ rc = VbglR3DrmLegacyClientStart();
+ VBClLogInfo("starting legacy service, rc=%Rrc\n", rc);
+ /* Force return status, so parent thread wont be trying to start worker thread. */
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ return rc;
+}
+
+/**
+ * A callback function which is triggered on IPC data receive.
+ *
+ * @returns IPRT status code.
+ * @param idCmd DRM IPC command ID.
+ * @param pvData DRM IPC command payload.
+ * @param cbData Size of DRM IPC command payload.
+ */
+static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
+{
+ VBOXDRMIPCCLTCMD enmCmd =
+ (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ?
+ (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID;
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ AssertReturn(pvData, VERR_INVALID_PARAMETER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER);
+
+ switch (enmCmd)
+ {
+ case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY:
+ {
+ if (g_pDisplayHelper->pfnSetPrimaryDisplay)
+ {
+ PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData;
+ static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX;
+
+ if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX
+ && idPrimaryDisplayCached != pCmd->idDisplay)
+ {
+ rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay);
+ /* Update cache. */
+ idPrimaryDisplayCached = pCmd->idDisplay;
+ }
+ else
+ VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay);
+ }
+
+ break;
+ }
+ default:
+ {
+ VBClLogError("received unknown IPC command 0x%x\n", idCmd);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Reconnect to DRM IPC server.
+ */
+static int vbclSVGASessionReconnect(void)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ rc = RTCritSectEnter(&g_hClientCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ /* Check if session was not closed before. */
+ if (RT_VALID_PTR(g_hSession))
+ {
+ rc = RTLocalIpcSessionClose(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc);
+
+ rc = vbDrmIpcClientReleaseResources(&g_hClient);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc);
+ }
+
+ rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc);
+
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Notify parent thread that we started successfully. */
+ rc = RTThreadUserSignal(RTThreadSelf());
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify parent thread about successful start\n");
+
+ rc = RTCritSectEnter(&g_hClientCritSect);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ for (;;)
+ {
+ rc = vbDrmIpcConnectionHandler(&g_hClient);
+
+ /* Try to shutdown thread as soon as possible. */
+ if (ASMAtomicReadBool(pfShutdown))
+ {
+ /* Shutdown requested. */
+ break;
+ }
+
+ /* Normal case, there was no incoming messages for a while. */
+ if (rc == VERR_TIMEOUT)
+ {
+ continue;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc);
+
+ /* Relax a bit before spinning the loop. */
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ /* Try to reconnect to server. */
+ rc = vbclSVGASessionReconnect();
+ }
+ }
+
+ /* Check if session was not closed before. */
+ if (RT_VALID_PTR(g_hSession))
+ {
+ rc2 = RTCritSectEnter(&g_hClientCritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vbDrmIpcClientReleaseResources(&g_hClient);
+ if (RT_FAILURE(rc2))
+ VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2);
+
+ rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSVGASessionStop(void)
+{
+ int rc;
+
+ /* Check if session was not closed before. */
+ if (!RT_VALID_PTR(g_hSession))
+ return;
+
+ /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */
+ rc = RTLocalIpcSessionFlush(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc);
+
+ rc = RTLocalIpcSessionCancel(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclSVGASessionTerm(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (g_hSession)
+ {
+ rc = RTLocalIpcSessionClose(g_hSession);
+ g_hSession = 0;
+
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc);
+ }
+
+ if (g_pDisplayHelper)
+ {
+ if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
+ g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
+
+ if (g_pDisplayHelper->pfnTerm)
+ {
+ rc = g_pDisplayHelper->pfnTerm();
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n",
+ rc, g_pDisplayHelper->pszName);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcDisplaySVGASession =
+{
+ "vmsvga-session", /* szName */
+ "VMSVGA display assistant", /* pszDescription */
+ ".vboxclient-vmsvga-session", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSVGASessionInit, /* pfnInit */
+ vbclSVGASessionWorker, /* pfnWorker */
+ vbclSVGASessionStop, /* pfnStop */
+ vbclSVGASessionTerm, /* pfnTerm */
+};
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 */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp
new file mode 100644
index 00000000..5263ffbf
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp
@@ -0,0 +1,310 @@
+/* $Id: display-svga-xf86cvt.cpp $ */
+/** @file
+ * Guest Additions - Our version of xf86CVTMode.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ * This file is based on x.org server 1.18.0 file xf86cvt.c:
+ *
+ * Copyright 2005-2006 Luc Verhaegen.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#if 0
+/*
+ * The reason for having this function in a file of its own is
+ * so that ../utils/cvt/cvt can link to it, and that xf86CVTMode
+ * code is shared directly.
+ */
+
+#ifdef HAVE_XORG_CONFIG_H
+#include <xorg-config.h>
+#else
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#endif
+
+#include "xf86.h"
+#include "xf86Modes.h"
+
+#include <string.h>
+#else
+# include "VBoxClient.h"
+# include "display-svga-xf86cvt.h"
+#endif
+
+/*
+ * This is a slightly modified version of the xf86CVTMode function from
+ * xf86cvt.c from the xorg xserver source code. Computes several parameters
+ * of a display mode out of horizontal and vertical resolutions. Replicated
+ * here to avoid further dependencies.
+ *
+ *----------------------------------------------------------------------------
+ *
+ * Generate a CVT standard mode from HDisplay, VDisplay and VRefresh.
+ *
+ * These calculations are stolen from the CVT calculation spreadsheet written
+ * by Graham Loveridge. He seems to be claiming no copyright and there seems to
+ * be no license attached to this. He apparently just wants to see his name
+ * mentioned.
+ *
+ * This file can be found at http://www.vesa.org/Public/CVT/CVTd6r1.xls
+ *
+ * Comments and structure corresponds to the comments and structure of the xls.
+ * This should ease importing of future changes to the standard (not very
+ * likely though).
+ *
+ * About margins; i'm sure that they are to be the bit between HDisplay and
+ * HBlankStart, HBlankEnd and HTotal, VDisplay and VBlankStart, VBlankEnd and
+ * VTotal, where the overscan colour is shown. FB seems to call _all_ blanking
+ * outside sync "margin" for some reason. Since we prefer seeing proper
+ * blanking instead of the overscan colour, and since the Crtc* values will
+ * probably get altered after us, we will disable margins altogether. With
+ * these calculations, Margins will plainly expand H/VDisplay, and we don't
+ * want that. -- libv
+ *
+ */
+DisplayModeR VBoxClient_xf86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, bool Reduced, bool Interlaced)
+{
+ DisplayModeR Mode;
+
+ /* 1) top/bottom margin size (% of height) - default: 1.8 */
+#define CVT_MARGIN_PERCENTAGE 1.8
+
+ /* 2) character cell horizontal granularity (pixels) - default 8 */
+#define CVT_H_GRANULARITY 8
+
+ /* 4) Minimum vertical porch (lines) - default 3 */
+#define CVT_MIN_V_PORCH 3
+
+ /* 4) Minimum number of vertical back porch lines - default 6 */
+#define CVT_MIN_V_BPORCH 6
+
+ /* Pixel Clock step (kHz) */
+#define CVT_CLOCK_STEP 250
+
+ bool Margins = false;
+ float VFieldRate, HPeriod;
+ int HDisplayRnd, HMargin;
+ int VDisplayRnd, VMargin, VSync;
+ float Interlace; /* Please rename this */
+
+ /* CVT default is 60.0Hz */
+ if (!VRefresh)
+ VRefresh = 60.0;
+
+ /* 1. Required field rate */
+ if (Interlaced)
+ VFieldRate = VRefresh * 2;
+ else
+ VFieldRate = VRefresh;
+
+ /* 2. Horizontal pixels */
+ HDisplayRnd = HDisplay - (HDisplay % CVT_H_GRANULARITY);
+
+ /* 3. Determine left and right borders */
+ if (Margins) {
+ /* right margin is actually exactly the same as left */
+ HMargin = (int)((float)HDisplayRnd * CVT_MARGIN_PERCENTAGE / 100.0);
+ HMargin -= HMargin % CVT_H_GRANULARITY;
+ }
+ else
+ HMargin = 0;
+
+ /* 4. Find total active pixels */
+ Mode.HDisplay = HDisplayRnd + 2 * HMargin;
+
+ /* 5. Find number of lines per field */
+ if (Interlaced)
+ VDisplayRnd = VDisplay / 2;
+ else
+ VDisplayRnd = VDisplay;
+
+ /* 6. Find top and bottom margins */
+ /* nope. */
+ if (Margins)
+ /* top and bottom margins are equal again. */
+ VMargin = (int)((float)VDisplayRnd * CVT_MARGIN_PERCENTAGE / 100.0);
+ else
+ VMargin = 0;
+
+ Mode.VDisplay = VDisplay + 2 * VMargin;
+
+ /* 7. Interlace */
+ if (Interlaced)
+ Interlace = 0.5;
+ else
+ Interlace = 0.0;
+
+ /* Determine VSync Width from aspect ratio */
+ if (!(VDisplay % 3) && ((VDisplay * 4 / 3) == HDisplay))
+ VSync = 4;
+ else if (!(VDisplay % 9) && ((VDisplay * 16 / 9) == HDisplay))
+ VSync = 5;
+ else if (!(VDisplay % 10) && ((VDisplay * 16 / 10) == HDisplay))
+ VSync = 6;
+ else if (!(VDisplay % 4) && ((VDisplay * 5 / 4) == HDisplay))
+ VSync = 7;
+ else if (!(VDisplay % 9) && ((VDisplay * 15 / 9) == HDisplay))
+ VSync = 7;
+ else /* Custom */
+ VSync = 10;
+
+ if (!Reduced) { /* simplified GTF calculation */
+
+ /* 4) Minimum time of vertical sync + back porch interval (µs)
+ * default 550.0 */
+#define CVT_MIN_VSYNC_BP 550.0
+
+ /* 3) Nominal HSync width (% of line period) - default 8 */
+#define CVT_HSYNC_PERCENTAGE 8
+
+ float HBlankPercentage;
+ int VSyncAndBackPorch, VBackPorch;
+ int HBlank;
+
+ /* 8. Estimated Horizontal period */
+ HPeriod = ((float)(1000000.0 / VFieldRate - CVT_MIN_VSYNC_BP))
+ / (VDisplayRnd + 2 * VMargin + CVT_MIN_V_PORCH + Interlace);
+
+ /* 9. Find number of lines in sync + backporch */
+ if ((int)(CVT_MIN_VSYNC_BP / HPeriod) + 1 < VSync + CVT_MIN_V_PORCH)
+ VSyncAndBackPorch = VSync + CVT_MIN_V_PORCH;
+ else
+ VSyncAndBackPorch = (int)(CVT_MIN_VSYNC_BP / HPeriod) + 1;
+
+ /* 10. Find number of lines in back porch */
+ VBackPorch = VSyncAndBackPorch - VSync;
+ (void) VBackPorch;
+
+ /* 11. Find total number of lines in vertical field */
+ Mode.VTotal = (int)(VDisplayRnd + 2 * VMargin + VSyncAndBackPorch + Interlace + CVT_MIN_V_PORCH);
+
+ /* 5) Definition of Horizontal blanking time limitation */
+ /* Gradient (%/kHz) - default 600 */
+#define CVT_M_FACTOR 600
+
+ /* Offset (%) - default 40 */
+#define CVT_C_FACTOR 40
+
+ /* Blanking time scaling factor - default 128 */
+#define CVT_K_FACTOR 128
+
+ /* Scaling factor weighting - default 20 */
+#define CVT_J_FACTOR 20
+
+#define CVT_M_PRIME (CVT_M_FACTOR * CVT_K_FACTOR / 256)
+#define CVT_C_PRIME ((CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + CVT_J_FACTOR)
+
+ /* 12. Find ideal blanking duty cycle from formula */
+ HBlankPercentage = CVT_C_PRIME - CVT_M_PRIME * HPeriod / 1000.0;
+
+ /* 13. Blanking time */
+ if (HBlankPercentage < 20)
+ HBlankPercentage = 20;
+
+ HBlank = (int)(Mode.HDisplay * HBlankPercentage / (100.0 - HBlankPercentage));
+ HBlank -= HBlank % (2 * CVT_H_GRANULARITY);
+
+ /* 14. Find total number of pixels in a line. */
+ Mode.HTotal = Mode.HDisplay + HBlank;
+
+ /* Fill in HSync values */
+ Mode.HSyncEnd = Mode.HDisplay + HBlank / 2;
+
+ Mode.HSyncStart = Mode.HSyncEnd - (Mode.HTotal * CVT_HSYNC_PERCENTAGE) / 100;
+ Mode.HSyncStart += CVT_H_GRANULARITY - Mode.HSyncStart % CVT_H_GRANULARITY;
+
+ /* Fill in VSync values */
+ Mode.VSyncStart = Mode.VDisplay + CVT_MIN_V_PORCH;
+ Mode.VSyncEnd = Mode.VSyncStart + VSync;
+
+ }
+ else { /* Reduced blanking */
+ /* Minimum vertical blanking interval time (µs) - default 460 */
+#define CVT_RB_MIN_VBLANK 460.0
+
+ /* Fixed number of clocks for horizontal sync */
+#define CVT_RB_H_SYNC 32.0
+
+ /* Fixed number of clocks for horizontal blanking */
+#define CVT_RB_H_BLANK 160.0
+
+ /* Fixed number of lines for vertical front porch - default 3 */
+#define CVT_RB_VFPORCH 3
+
+ int VBILines;
+
+ /* 8. Estimate Horizontal period. */
+ HPeriod = ((float)(1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) / (VDisplayRnd + 2 * VMargin);
+
+ /* 9. Find number of lines in vertical blanking */
+ VBILines = (int)((float)CVT_RB_MIN_VBLANK / HPeriod + 1);
+
+ /* 10. Check if vertical blanking is sufficient */
+ if (VBILines < CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH)
+ VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH;
+
+ /* 11. Find total number of lines in vertical field */
+ Mode.VTotal = (int)(VDisplayRnd + 2 * VMargin + Interlace + VBILines);
+
+ /* 12. Find total number of pixels in a line */
+ Mode.HTotal = (int)(Mode.HDisplay + CVT_RB_H_BLANK);
+
+ /* Fill in HSync values */
+ Mode.HSyncEnd = (int)(Mode.HDisplay + CVT_RB_H_BLANK / 2);
+ Mode.HSyncStart = (int)(Mode.HSyncEnd - CVT_RB_H_SYNC);
+
+ /* Fill in VSync values */
+ Mode.VSyncStart = Mode.VDisplay + CVT_RB_VFPORCH;
+ Mode.VSyncEnd = Mode.VSyncStart + VSync;
+ }
+ /* 15/13. Find pixel clock frequency (kHz for xf86) */
+ Mode.Clock = (int)(Mode.HTotal * 1000.0 / HPeriod);
+ Mode.Clock -= Mode.Clock % CVT_CLOCK_STEP;
+
+ /* 16/14. Find actual Horizontal Frequency (kHz) */
+ Mode.HSync = (float)Mode.Clock / (float)Mode.HTotal;
+
+ /* 17/15. Find actual Field rate */
+ Mode.VRefresh = (1000.0 * (float)Mode.Clock) / (float)(Mode.HTotal * Mode.VTotal);
+
+ /* 18/16. Find actual vertical frame frequency */
+ /* ignore - just set the mode flag for interlaced */
+ if (Interlaced)
+ Mode.VTotal *= 2;
+
+#if 0
+ XNFasprintf(&tmp, "%dx%d", HDisplay, VDisplay);
+ Mode->name = tmp;
+
+ if (Reduced)
+ Mode->Flags |= V_PHSYNC | V_NVSYNC;
+ else
+ Mode->Flags |= V_NHSYNC | V_PVSYNC;
+
+ if (Interlaced)
+ Mode->Flags |= V_INTERLACE;
+#endif
+
+ return Mode;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h
new file mode 100644
index 00000000..431f17c7
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h
@@ -0,0 +1,56 @@
+/* $Id: display-svga-xf86cvt.h $ */
+/** @file
+ * Guest Additions - Header for display-svga-xf86ctv.cpp.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+typedef struct DisplayModeR
+{
+ int Clock;
+ int HDisplay;
+ int HSyncStart;
+ int HSyncEnd;
+ int HTotal;
+ int HSkew;
+ int VDisplay;
+ int VSyncStart;
+ int VSyncEnd;
+ int VTotal;
+ int VScan;
+ float HSync;
+ float VRefresh;
+} DisplayModeR;
+
+DisplayModeR VBoxClient_xf86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, bool Reduced, bool Interlaced);
+
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h */
+
diff --git a/src/VBox/Additions/x11/VBoxClient/display.cpp b/src/VBox/Additions/x11/VBoxClient/display.cpp
new file mode 100644
index 00000000..3edbfd31
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display.cpp
@@ -0,0 +1,315 @@
+/* $Id: display.cpp $ */
+/** @file
+ * X11 guest client - display management.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#include "VBoxClient.h"
+
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/asm.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, bool volatile *pfShutdown)
+{
+ 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 (!ASMAtomicReadBool(pfShutdown))
+ {
+ 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)
+{
+ struct DISPLAYSTATE *pSelf = &g_DisplayState;
+
+ if (!pSelf->mfInit)
+ return VERR_WRONG_ORDER;
+
+ runDisplay(pSelf, pfShutdown);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) stop(void)
+{
+ /* Nothing to do here. Implement empty callback, so
+ * main thread can set pfShutdown=true on process termination. */
+}
+
+VBCLSERVICE g_SvcDisplayLegacy =
+{
+ "dp-legacy-x11", /* szName */
+ "Legacy display assistant", /* pszDescription */
+ ".vboxclient-display", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ init, /* pfnInit */
+ run, /* pfnWorker */
+ stop, /* pfnStop */
+ NULL, /* pfnTerm */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp
new file mode 100644
index 00000000..3edb7e98
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp
@@ -0,0 +1,3877 @@
+/* $Id: draganddrop.cpp $ */
+/** @file
+ * X11 guest client - Drag and drop implementation.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#ifdef VBOX_DND_WITH_XTEST
+# include <X11/extensions/XTest.h>
+#endif
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/critsect.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <iprt/cpp/mtlist.h>
+#include <iprt/cpp/ministring.h>
+
+#include <limits.h>
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/version.h>
+
+#include "VBox/HostServices/DragAndDropSvc.h"
+#include "VBoxClient.h"
+
+
+/* Enable this to handle drag'n drop "promises".
+ * This is needed for supporting certain applications (i.e. PcManFM on LXDE),
+ * which require the drag'n drop meta data a lot earlier than actually needed.
+ * That behavior is similar to macOS' drag'n drop promises, hence the name.
+ *
+ * Those applications query the data right while dragging over them (see GtkWidget::drag-motion),
+ * instead of when the source dropped the data (GtkWidget::drag-drop).
+ *
+ * This might be entirely implementation-specific, so not being a bug in GTK/GDK. Also see #9820.
+ */
+#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES
+# undef VBOX_WITH_DRAG_AND_DROP_PROMISES
+#endif
+
+/**
+ * For X11 guest Xdnd is used. See http://www.acc.umu.se/~vatten/XDND.html for
+ * a walk trough.
+ *
+ * Also useful pages:
+ * - https://www.freedesktop.org/wiki/Draganddropwarts/
+ * - https://www.freedesktop.org/wiki/Specifications/XDNDRevision/
+ *
+ * Host -> Guest:
+ * For X11 this means mainly forwarding all the events from HGCM to the
+ * appropriate X11 events. There exists a proxy window, which is invisible and
+ * used for all the X11 communication. On a HGCM Enter event, we set our proxy
+ * window as XdndSelection owner with the given mime-types. On every HGCM move
+ * event, we move the X11 mouse cursor to the new position and query for the
+ * window below that position. Depending on if it is XdndAware, a new window or
+ * a known window, we send the appropriate X11 messages to it. On HGCM drop, we
+ * send a XdndDrop message to the current window and wait for a X11
+ * SelectionMessage from the target window. Because we didn't have the data in
+ * the requested mime-type, yet, we save that message and ask the host for the
+ * data. When the data is successfully received from the host, we put the data
+ * as a property to the window and send a X11 SelectionNotify event to the
+ * target window.
+ *
+ * Guest -> Host:
+ * This is a lot more trickery than H->G. When a pending event from HGCM
+ * arrives, we ask if there currently is an owner of the XdndSelection
+ * property. If so, our proxy window is shown (1x1, but without backing store)
+ * and some mouse event is triggered. This should be followed by an XdndEnter
+ * event send to the proxy window. From this event we can fetch the necessary
+ * info of the MIME types and allowed actions and send this back to the host.
+ * On a drop request from the host, we query for the selection and should get
+ * the data in the specified mime-type. This data is send back to the host.
+ * After that we send a XdndLeave event to the source window.
+ *
+ ** @todo Cancelling (e.g. with ESC key) doesn't work.
+ ** @todo INCR (incremental transfers) support.
+ ** @todo Really check for the Xdnd version and the supported features.
+ ** @todo Either get rid of the xHelpers class or properly unify the code with the drag instance class.
+ */
+
+/*********************************************************************************************************************************
+ * Definitions *
+ ********************************************************************************************************************************/
+
+/** The Xdnd protocol version we support. */
+#define VBOX_XDND_VERSION (5)
+
+/** No flags specified. */
+#define VBOX_XDND_STATUS_FLAG_NONE 0
+/** Whether the target window accepts the data being dragged over or not. */
+#define VBOX_XDND_STATUS_FLAG_ACCEPT RT_BIT(0)
+/** Whether the target window wants XdndPosition messages while dragging stuff over it. */
+#define VBOX_XDND_STATUS_FLAG_WANTS_POS RT_BIT(1)
+
+/** Whether the target window accepted the drop data or not. */
+#define VBOX_XDND_FINISHED_FLAG_SUCCEEDED RT_BIT(0)
+
+/** How many X properties our proxy window can hold. */
+#define VBOX_MAX_XPROPERTIES (LONG_MAX-1)
+
+/** The notification header text for VBClShowNotify(). */
+#define VBOX_DND_SHOWNOTIFY_HEADER VBOX_PRODUCT " Drag'n Drop"
+
+/**
+ * Structure for storing new X11 events and HGCM messages
+ * into a single event queue.
+ */
+typedef struct DNDEVENT
+{
+ enum DnDEventType
+ {
+ /** Unknown event, do not use. */
+ DnDEventType_Unknown = 0,
+ /** VBGLR3DNDEVENT event. */
+ DnDEventType_HGCM,
+ /** X11 event. */
+ DnDEventType_X11,
+ /** Blow the type up to 32-bit. */
+ DnDEventType_32BIT_HACK = 0x7fffffff
+ };
+ /** Event type. */
+ DnDEventType enmType;
+ union
+ {
+ PVBGLR3DNDEVENT hgcm;
+ XEvent x11;
+ };
+#ifdef IN_GUEST
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+} DNDEVENT;
+/** Pointer to a DnD event. */
+typedef DNDEVENT *PDNDEVENT;
+
+enum XA_Type
+{
+ /* States */
+ XA_WM_STATE = 0,
+ /* Properties */
+ XA_TARGETS,
+ XA_MULTIPLE,
+ XA_INCR,
+ /* Mime Types */
+ XA_image_bmp,
+ XA_image_jpg,
+ XA_image_tiff,
+ XA_image_png,
+ XA_text_uri_list,
+ XA_text_uri,
+ XA_text_plain,
+ XA_TEXT,
+ /* Xdnd */
+ XA_XdndSelection,
+ XA_XdndAware,
+ XA_XdndEnter,
+ XA_XdndLeave,
+ XA_XdndTypeList,
+ XA_XdndActionList,
+ XA_XdndPosition,
+ XA_XdndActionCopy,
+ XA_XdndActionMove,
+ XA_XdndActionLink,
+ XA_XdndStatus,
+ XA_XdndDrop,
+ XA_XdndFinished,
+ /* Our own stop marker */
+ XA_dndstop,
+ /* End marker */
+ XA_End
+};
+
+/**
+ * Xdnd message value indices, sorted by message type.
+ */
+typedef enum XdndMsg
+{
+ /** XdndEnter. */
+ XdndEnterTypeCount = 3, /* Maximum number of types in XdndEnter message. */
+
+ XdndEnterWindow = 0, /* Source window (sender). */
+ XdndEnterFlags, /* Version in high byte, bit 0 => more data types. */
+ XdndEnterType1, /* First available data type. */
+ XdndEnterType2, /* Second available data type. */
+ XdndEnterType3, /* Third available data type. */
+
+ XdndEnterMoreTypesFlag = 1, /* Set if there are more than XdndEnterTypeCount. */
+ XdndEnterVersionRShift = 24, /* Right shift to position version number. */
+ XdndEnterVersionMask = 0xFF, /* Mask to get version after shifting. */
+
+ /** XdndHere. */
+ XdndHereWindow = 0, /* Source window (sender). */
+ XdndHereFlags, /* Reserved. */
+ XdndHerePt, /* X + Y coordinates of mouse (root window coords). */
+ XdndHereTimeStamp, /* Timestamp for requesting data. */
+ XdndHereAction, /* Action requested by user. */
+
+ /** XdndPosition. */
+ XdndPositionWindow = 0, /* Source window (sender). */
+ XdndPositionFlags, /* Flags. */
+ XdndPositionXY, /* X/Y coordinates of the mouse position relative to the root window. */
+ XdndPositionTimeStamp, /* Time stamp for retrieving the data. */
+ XdndPositionAction, /* Action requested by the user. */
+
+ /** XdndStatus. */
+ XdndStatusWindow = 0, /* Target window (sender).*/
+ XdndStatusFlags, /* Flags returned by target. */
+ XdndStatusNoMsgXY, /* X + Y of "no msg" rectangle (root window coords). */
+ XdndStatusNoMsgWH, /* Width + height of "no msg" rectangle. */
+ XdndStatusAction, /* Action accepted by target. */
+
+ XdndStatusAcceptDropFlag = 1, /* Set if target will accept the drop. */
+ XdndStatusSendHereFlag = 2, /* Set if target wants a stream of XdndPosition. */
+
+ /** XdndLeave. */
+ XdndLeaveWindow = 0, /* Source window (sender). */
+ XdndLeaveFlags, /* Reserved. */
+
+ /** XdndDrop. */
+ XdndDropWindow = 0, /* Source window (sender). */
+ XdndDropFlags, /* Reserved. */
+ XdndDropTimeStamp, /* Timestamp for requesting data. */
+
+ /** XdndFinished. */
+ XdndFinishedWindow = 0, /* Target window (sender). */
+ XdndFinishedFlags, /* Since version 5: Bit 0 is set if the current target accepted the drop. */
+ XdndFinishedAction /* Since version 5: Contains the action performed by the target. */
+
+} XdndMsg;
+
+class DragAndDropService;
+
+/** List of Atoms. */
+#define VBoxDnDAtomList RTCList<Atom>
+
+class xHelpers
+{
+public:
+
+ static xHelpers *getInstance(Display *pDisplay = 0)
+ {
+ if (!m_pInstance)
+ {
+ AssertPtrReturn(pDisplay, NULL);
+ m_pInstance = new xHelpers(pDisplay);
+ }
+
+ return m_pInstance;
+ }
+
+ static void destroyInstance(void)
+ {
+ if (m_pInstance)
+ {
+ delete m_pInstance;
+ m_pInstance = NULL;
+ }
+ }
+
+ inline Display *display() const { return m_pDisplay; }
+ inline Atom xAtom(XA_Type e) const { return m_xAtoms[e]; }
+
+ inline Atom stringToxAtom(const char *pcszString) const
+ {
+ return XInternAtom(m_pDisplay, pcszString, False);
+ }
+ inline RTCString xAtomToString(Atom atom) const
+ {
+ if (atom == None) return "None";
+
+ char* pcsAtom = XGetAtomName(m_pDisplay, atom);
+ RTCString strAtom(pcsAtom);
+ XFree(pcsAtom);
+
+ return strAtom;
+ }
+
+ inline RTCString xAtomListToString(const VBoxDnDAtomList &formatList, const RTCString &strSep = DND_FORMATS_SEPARATOR_STR)
+ {
+ RTCString format;
+ for (size_t i = 0; i < formatList.size(); ++i)
+ format += xAtomToString(formatList.at(i)) + strSep;
+ return format;
+ }
+
+ /**
+ * Returns a filtered X11 atom list.
+ *
+ * @returns Filtered list.
+ * @param formatList Atom list to convert.
+ * @param filterList Atom list to filter out.
+ */
+ inline VBoxDnDAtomList xAtomListFiltered(const VBoxDnDAtomList &formatList, const VBoxDnDAtomList &filterList)
+ {
+ VBoxDnDAtomList tempList = formatList;
+ tempList.filter(filterList);
+ return tempList;
+ }
+
+ RTCString xErrorToString(int xRc) const;
+ Window applicationWindowBelowCursor(Window parentWin) const;
+
+private:
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ xHelpers(Display *pDisplay)
+ : m_pDisplay(pDisplay)
+ {
+ /* Not all x11 atoms we use are defined in the headers. Create the
+ * additional one we need here. */
+ for (int i = 0; i < XA_End; ++i)
+ m_xAtoms[i] = XInternAtom(m_pDisplay, m_xAtomNames[i], False);
+ };
+
+ /* Private member vars */
+ static xHelpers *m_pInstance;
+ Display *m_pDisplay;
+ Atom m_xAtoms[XA_End];
+ static const char *m_xAtomNames[XA_End];
+};
+
+/* Some xHelpers convenience defines. */
+#define gX11 xHelpers::getInstance()
+#define xAtom(xa) xHelpers::getInstance()->xAtom((xa))
+#define xAtomToString(xa) xHelpers::getInstance()->xAtomToString((xa))
+
+/*********************************************************************************************************************************
+ * xHelpers implementation. *
+ ********************************************************************************************************************************/
+
+xHelpers *xHelpers::m_pInstance = NULL;
+
+/* Has to be in sync with the XA_Type enum. */
+const char *xHelpers::m_xAtomNames[] =
+{
+ /* States */
+ "WM_STATE",
+ /* Properties */
+ "TARGETS",
+ "MULTIPLE",
+ "INCR",
+ /* Mime Types */
+ "image/bmp",
+ "image/jpg",
+ "image/tiff",
+ "image/png",
+ "text/uri-list",
+ "text/uri",
+ "text/plain",
+ "TEXT",
+ /* Xdnd */
+ "XdndSelection",
+ "XdndAware",
+ "XdndEnter",
+ "XdndLeave",
+ "XdndTypeList",
+ "XdndActionList",
+ "XdndPosition",
+ "XdndActionCopy",
+ "XdndActionMove",
+ "XdndActionLink",
+ "XdndStatus",
+ "XdndDrop",
+ "XdndFinished",
+ /* Our own stop marker */
+ "dndstop"
+};
+
+RTCString xHelpers::xErrorToString(int xRc) const
+{
+ switch (xRc)
+ {
+ case Success: return RTCStringFmt("%d (Success)", xRc); break;
+ case BadRequest: return RTCStringFmt("%d (BadRequest)", xRc); break;
+ case BadValue: return RTCStringFmt("%d (BadValue)", xRc); break;
+ case BadWindow: return RTCStringFmt("%d (BadWindow)", xRc); break;
+ case BadPixmap: return RTCStringFmt("%d (BadPixmap)", xRc); break;
+ case BadAtom: return RTCStringFmt("%d (BadAtom)", xRc); break;
+ case BadCursor: return RTCStringFmt("%d (BadCursor)", xRc); break;
+ case BadFont: return RTCStringFmt("%d (BadFont)", xRc); break;
+ case BadMatch: return RTCStringFmt("%d (BadMatch)", xRc); break;
+ case BadDrawable: return RTCStringFmt("%d (BadDrawable)", xRc); break;
+ case BadAccess: return RTCStringFmt("%d (BadAccess)", xRc); break;
+ case BadAlloc: return RTCStringFmt("%d (BadAlloc)", xRc); break;
+ case BadColor: return RTCStringFmt("%d (BadColor)", xRc); break;
+ case BadGC: return RTCStringFmt("%d (BadGC)", xRc); break;
+ case BadIDChoice: return RTCStringFmt("%d (BadIDChoice)", xRc); break;
+ case BadName: return RTCStringFmt("%d (BadName)", xRc); break;
+ case BadLength: return RTCStringFmt("%d (BadLength)", xRc); break;
+ case BadImplementation: return RTCStringFmt("%d (BadImplementation)", xRc); break;
+ }
+ return RTCStringFmt("%d (unknown)", xRc);
+}
+
+/** @todo Make this iterative. */
+Window xHelpers::applicationWindowBelowCursor(Window wndParent) const
+{
+ /* No parent, nothing to do. */
+ if(wndParent == 0)
+ return 0;
+
+ Window wndApp = 0;
+ int cProps = -1;
+
+ /* Fetch all x11 window properties of the parent window. */
+ Atom *pProps = XListProperties(m_pDisplay, wndParent, &cProps);
+ if (cProps > 0)
+ {
+ /* We check the window for the WM_STATE property. */
+ for (int i = 0; i < cProps; ++i)
+ {
+ if (pProps[i] == xAtom(XA_WM_STATE))
+ {
+ /* Found it. */
+ wndApp = wndParent;
+ break;
+ }
+ }
+
+ /* Cleanup */
+ XFree(pProps);
+ }
+
+ if (!wndApp)
+ {
+ Window wndChild, wndTemp;
+ int tmp;
+ unsigned int utmp;
+
+ /* Query the next child window of the parent window at the current
+ * mouse position. */
+ XQueryPointer(m_pDisplay, wndParent, &wndTemp, &wndChild, &tmp, &tmp, &tmp, &tmp, &utmp);
+
+ /* Recursive call our self to dive into the child tree. */
+ wndApp = applicationWindowBelowCursor(wndChild);
+ }
+
+ return wndApp;
+}
+
+#ifdef DEBUG
+# define VBOX_DND_FN_DECL_LOG(x) inline x /* For LogFlowXXX logging. */
+#else
+# define VBOX_DND_FN_DECL_LOG(x) x
+#endif
+
+/**
+ * Class which handles a single drag'n drop proxy window.
+ ** @todo Move all proxy window-related stuff into this class! Clean up this mess.
+ */
+class VBoxDnDProxyWnd
+{
+
+public:
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ VBoxDnDProxyWnd(void);
+ virtual ~VBoxDnDProxyWnd(void);
+
+public:
+
+ int init(Display *pDisplay);
+ void destroy();
+
+ int sendFinished(Window hWndSource, VBOXDNDACTION dndAction);
+
+public:
+
+ Display *pDisp;
+ /** Proxy window handle. */
+ Window hWnd;
+ int iX;
+ int iY;
+ int iWidth;
+ int iHeight;
+};
+
+/** This class only serve to avoid dragging in generic new() and delete(). */
+class WrappedXEvent
+{
+public:
+ XEvent m_Event;
+
+public:
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ WrappedXEvent(const XEvent &a_rSrcEvent)
+ {
+ m_Event = a_rSrcEvent;
+ }
+
+ WrappedXEvent()
+ {
+ RT_ZERO(m_Event);
+ }
+
+ WrappedXEvent &operator=(const XEvent &a_rSrcEvent)
+ {
+ m_Event = a_rSrcEvent;
+ return *this;
+ }
+};
+
+/**
+ * Class for handling a single drag and drop operation, that is,
+ * one source and one target at a time.
+ *
+ * For now only one DragInstance will exits when the app is running.
+ */
+class DragInstance
+{
+public:
+
+ enum State
+ {
+ Uninitialized = 0,
+ Initialized,
+ Dragging,
+ Dropped,
+ State_32BIT_Hack = 0x7fffffff
+ };
+
+ enum Mode
+ {
+ Unknown = 0,
+ HG,
+ GH,
+ Mode_32Bit_Hack = 0x7fffffff
+ };
+
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ DragInstance(Display *pDisplay, DragAndDropService *pParent);
+
+public:
+
+ int init(uint32_t uScreenID);
+ int term(void);
+ void stop(void);
+ void reset(void);
+
+ /* X11 message processing. */
+ int onX11ClientMessage(const XEvent &e);
+ int onX11MotionNotify(const XEvent &e);
+ int onX11SelectionClear(const XEvent &e);
+ int onX11SelectionNotify(const XEvent &e);
+ int onX11SelectionRequest(const XEvent &evReq);
+ int onX11Event(const XEvent &e);
+ int waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS = 30000);
+ bool waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS = 100);
+ bool waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType, RTMSINTERVAL uTimeoutMS = 100);
+
+ /* Session handling. */
+ int checkForSessionChange(void);
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ /* Guest -> Host handling. */
+ int ghIsDnDPending(void);
+ int ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested);
+#endif
+
+ /* Host -> Guest handling. */
+ int hgEnter(const RTCList<RTCString> &formats, VBOXDNDACTIONLIST dndListActionsAllowed);
+ int hgLeave(void);
+ int hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault);
+ int hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault);
+ int hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta);
+
+ /* X11 helpers. */
+ int mouseCursorFakeMove(void);
+ int mouseCursorMove(int iPosX, int iPosY);
+ void mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress);
+ int proxyWinShow(int *piRootX = NULL, int *piRootY = NULL) const;
+ int proxyWinHide(void);
+
+ /* X11 window helpers. */
+ char *wndX11GetNameA(Window wndThis) const;
+
+ /* Xdnd protocol helpers. */
+ void wndXDnDClearActionList(Window wndThis) const;
+ void wndXDnDClearFormatList(Window wndThis) const;
+ int wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const;
+ int wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const;
+ int wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const;
+ int wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const;
+
+ /* Atom / HGCM formatting helpers. */
+ int appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const;
+ int appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const;
+ static Atom toAtomAction(VBOXDNDACTION dndAction);
+ static int toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms);
+ static uint32_t toHGCMAction(Atom atom);
+ static uint32_t toHGCMActions(const VBoxDnDAtomList &actionsList);
+
+protected:
+
+ /** The instance's own DnD context. */
+ VBGLR3GUESTDNDCMDCTX m_dndCtx;
+ /** Pointer to service instance. */
+ DragAndDropService *m_pParent;
+ /** Pointer to X display operating on. */
+ Display *m_pDisplay;
+ /** X screen ID to operate on. */
+ int m_screenID;
+ /** Pointer to X screen operating on. */
+ Screen *m_pScreen;
+ /** Root window handle. */
+ Window m_wndRoot;
+ /** Proxy window. */
+ VBoxDnDProxyWnd m_wndProxy;
+ /** Current source/target window handle. */
+ Window m_wndCur;
+ /** The XDnD protocol version the current source/target window is using.
+ * Set to 0 if not available / not set yet. */
+ uint8_t m_uXdndVer;
+ /** Last mouse X position (in pixels, absolute to root window).
+ * Set to -1 if not set yet. */
+ int m_lastMouseX;
+ /** Last mouse Y position (in pixels, absolute to root window).
+ * Set to -1 if not set yet. */
+ int m_lastMouseY;
+ /** List of default (Atom) formats required for X11 Xdnd handling.
+ * This list will be included by \a m_lstAtomFormats. */
+ VBoxDnDAtomList m_lstAtomFormatsX11;
+ /** List of (Atom) formats the current source/target window supports. */
+ VBoxDnDAtomList m_lstAtomFormats;
+ /** List of (Atom) actions the current source/target window supports. */
+ VBoxDnDAtomList m_lstAtomActions;
+ /** Buffer for answering the target window's selection request. */
+ void *m_pvSelReqData;
+ /** Size (in bytes) of selection request data buffer. */
+ uint32_t m_cbSelReqData;
+ /** Current operation mode. */
+ volatile uint32_t m_enmMode;
+ /** Current state of operation mode. */
+ volatile uint32_t m_enmState;
+ /** The instance's own X event queue. */
+ RTCMTList<WrappedXEvent> m_eventQueueList;
+ /** Critical section for providing serialized access to list event queue's contents. */
+ RTCRITSECT m_eventQueueCS;
+ /** Event for notifying this instance in case of a new event. */
+ RTSEMEVENT m_eventQueueEvent;
+ /** Critical section for data access. */
+ RTCRITSECT m_dataCS;
+ /** List of allowed formats. */
+ RTCList<RTCString> m_lstAllowedFormats;
+ /** Number of failed attempts by the host
+ * to query for an active drag and drop operation on the guest. */
+ uint16_t m_cFailedPendingAttempts;
+};
+
+/**
+ * Service class which implements drag'n drop.
+ */
+class DragAndDropService
+{
+public:
+ DragAndDropService(void)
+ : m_pDisplay(NULL)
+ , m_hHGCMThread(NIL_RTTHREAD)
+ , m_hX11Thread(NIL_RTTHREAD)
+ , m_hEventSem(NIL_RTSEMEVENT)
+ , m_pCurDnD(NULL)
+ , m_fStop(false)
+ {
+ RT_ZERO(m_dndCtx);
+ }
+
+ int init(void);
+ int worker(bool volatile *pfShutdown);
+ void reset(void);
+ void stop(void);
+ int term(void);
+
+private:
+
+ static DECLCALLBACK(int) hgcmEventThread(RTTHREAD hThread, void *pvUser);
+ static DECLCALLBACK(int) x11EventThread(RTTHREAD hThread, void *pvUser);
+
+ /* Private member vars */
+ Display *m_pDisplay;
+ /** Our (thread-safe) event queue with mixed events (DnD HGCM / X11). */
+ RTCMTList<DNDEVENT> m_eventQueue;
+ /** Critical section for providing serialized access to list
+ * event queue's contents. */
+ RTCRITSECT m_eventQueueCS;
+ /** Thread handle for the HGCM message pumping thread. */
+ RTTHREAD m_hHGCMThread;
+ /** Thread handle for the X11 message pumping thread. */
+ RTTHREAD m_hX11Thread;
+ /** This service' DnD command context. */
+ VBGLR3GUESTDNDCMDCTX m_dndCtx;
+ /** Event semaphore for new DnD events. */
+ RTSEMEVENT m_hEventSem;
+ /** Pointer to the allocated DnD instance.
+ Currently we only support and handle one instance at a time. */
+ DragInstance *m_pCurDnD;
+ /** Stop indicator flag to signal the thread that it should shut down. */
+ bool m_fStop;
+
+ friend class DragInstance;
+} g_Svc;
+
+/*********************************************************************************************************************************
+ * DragInstanc implementation. *
+ ********************************************************************************************************************************/
+
+DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent)
+ : m_pParent(pParent)
+ , m_pDisplay(pDisplay)
+ , m_pScreen(0)
+ , m_wndRoot(0)
+ , m_wndCur(0)
+ , m_uXdndVer(0)
+ , m_pvSelReqData(NULL)
+ , m_cbSelReqData(0)
+ , m_enmMode(Unknown)
+ , m_enmState(Uninitialized)
+{
+ /* Append default targets we support.
+ * Note: The order is sorted by preference; be careful when changing this. */
+ m_lstAtomFormatsX11.append(xAtom(XA_TARGETS));
+ m_lstAtomFormatsX11.append(xAtom(XA_MULTIPLE));
+ /** @todo Support INC (incremental transfers). */
+}
+
+/**
+ * Stops this drag instance.
+ */
+void DragInstance::stop(void)
+{
+ LogFlowFuncEnter();
+
+ int rc2 = VbglR3DnDDisconnect(&m_dndCtx);
+ AssertRC(rc2);
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Terminates (destroys) this drag instance.
+ *
+ * @return VBox status code.
+ */
+int DragInstance::term(void)
+{
+ LogFlowFuncEnter();
+
+ if (m_wndProxy.hWnd != 0)
+ XDestroyWindow(m_pDisplay, m_wndProxy.hWnd);
+
+ int rc = VbglR3DnDDisconnect(&m_dndCtx);
+ AssertRCReturn(rc, rc);
+
+ if (m_pvSelReqData)
+ RTMemFree(m_pvSelReqData);
+
+ rc = RTSemEventDestroy(m_eventQueueEvent);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectDelete(&m_eventQueueCS);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectDelete(&m_dataCS);
+ AssertRCReturn(rc, rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Resets this drag instance.
+ */
+void DragInstance::reset(void)
+{
+ LogFlowFuncEnter();
+
+ /* Hide the proxy win. */
+ proxyWinHide();
+
+ int rc2 = RTCritSectEnter(&m_dataCS);
+ if (RT_SUCCESS(rc2))
+ {
+ /* If we are currently the Xdnd selection owner, clear that. */
+ Window pWnd = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ if (pWnd == m_wndProxy.hWnd)
+ XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), None, CurrentTime);
+
+ /* Clear any other DnD specific data on the proxy window. */
+ wndXDnDClearFormatList(m_wndProxy.hWnd);
+ wndXDnDClearActionList(m_wndProxy.hWnd);
+
+ m_lstAtomActions.clear();
+
+ /* First, clear the formats list and apply the X11-specific default formats,
+ * required for making Xdnd to work. */
+ m_lstAtomFormats.clear();
+ m_lstAtomFormats.append(m_lstAtomFormatsX11);
+
+ m_wndCur = 0;
+ m_uXdndVer = 0;
+ m_lastMouseX = -1;
+ m_lastMouseY = -1;
+ m_enmState = Initialized;
+ m_enmMode = Unknown;
+ m_cFailedPendingAttempts = 0;
+
+ /* Reset the selection request buffer. */
+ if (m_pvSelReqData)
+ {
+ RTMemFree(m_pvSelReqData);
+ m_pvSelReqData = NULL;
+
+ Assert(m_cbSelReqData);
+ m_cbSelReqData = 0;
+ }
+
+ rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ m_eventQueueList.clear();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ RTCritSectLeave(&m_dataCS);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Initializes this drag instance.
+ *
+ * @return IPRT status code.
+ * @param uScreenID X' screen ID to use.
+ */
+int DragInstance::init(uint32_t uScreenID)
+{
+ int rc = VbglR3DnDConnect(&m_dndCtx);
+ /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if (g_cVerbosity)
+ {
+ RTCString strBody = RTCStringFmt("Connected (screen %RU32, verbosity %u)", uScreenID, g_cVerbosity);
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, strBody.c_str());
+ }
+
+ do
+ {
+ rc = RTSemEventCreate(&m_eventQueueEvent);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTCritSectInit(&m_eventQueueCS);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTCritSectInit(&m_dataCS);
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * Enough screens configured in the x11 server?
+ */
+ if ((int)uScreenID > ScreenCount(m_pDisplay))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+#if 0
+ /* Get the screen number from the x11 server. */
+ pDrag->screen = ScreenOfDisplay(m_pDisplay, uScreenID);
+ if (!pDrag->screen)
+ {
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+#endif
+ m_screenID = uScreenID;
+
+ /* Now query the corresponding root window of this screen. */
+ m_wndRoot = RootWindow(m_pDisplay, m_screenID);
+ if (!m_wndRoot)
+ {
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+ /*
+ * Create an invisible window which will act as proxy for the DnD
+ * operation. This window will be used for both the GH and HG
+ * direction.
+ */
+ XSetWindowAttributes attr;
+ RT_ZERO(attr);
+ attr.event_mask = EnterWindowMask | LeaveWindowMask
+ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
+ attr.override_redirect = True;
+ attr.do_not_propagate_mask = NoEventMask;
+
+ if (g_cVerbosity >= 3)
+ {
+ attr.background_pixel = XWhitePixel(m_pDisplay, m_screenID);
+ attr.border_pixel = XBlackPixel(m_pDisplay, m_screenID);
+ m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
+ 100, 100, /* Position */
+ 100, 100, /* Width + height */
+ 2, /* Border width */
+ CopyFromParent, /* Depth */
+ InputOutput, /* Class */
+ CopyFromParent, /* Visual */
+ CWBackPixel
+ | CWBorderPixel
+ | CWOverrideRedirect
+ | CWDontPropagate, /* Value mask */
+ &attr); /* Attributes for value mask */
+ }
+
+ m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
+ 0, 0, /* Position */
+ 1, 1, /* Width + height */
+ 0, /* Border width */
+ CopyFromParent, /* Depth */
+ InputOnly, /* Class */
+ CopyFromParent, /* Visual */
+ CWOverrideRedirect | CWDontPropagate, /* Value mask */
+ &attr); /* Attributes for value mask */
+
+ if (!m_wndProxy.hWnd)
+ {
+ VBClLogError("Error creating proxy window\n");
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+ rc = m_wndProxy.init(m_pDisplay);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error initializing proxy window, rc=%Rrc\n", rc);
+ break;
+ }
+
+ if (g_cVerbosity >= 3) /* Make debug window visible. */
+ {
+ XFlush(m_pDisplay);
+ XMapWindow(m_pDisplay, m_wndProxy.hWnd);
+ XRaiseWindow(m_pDisplay, m_wndProxy.hWnd);
+ XFlush(m_pDisplay);
+ }
+
+ VBClLogInfo("Proxy window=%#x (debug mode: %RTbool), root window=%#x ...\n",
+ m_wndProxy.hWnd, RT_BOOL(g_cVerbosity >= 3), m_wndRoot);
+
+ /* Set the window's name for easier lookup. */
+ XStoreName(m_pDisplay, m_wndProxy.hWnd, "VBoxClientWndDnD");
+
+ /* Make the new window Xdnd aware. */
+ Atom atmVer = VBOX_XDND_VERSION;
+ XChangeProperty(m_pDisplay, m_wndProxy.hWnd, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace,
+ reinterpret_cast<unsigned char*>(&atmVer), 1);
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ {
+ reset();
+ }
+ else
+ VBClLogError("Initializing drag instance for screen %RU32 failed with rc=%Rrc\n", uScreenID, rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Callback handler for a generic client message from a window.
+ *
+ * @return IPRT status code.
+ * @param e X11 event to handle.
+ */
+int DragInstance::onX11ClientMessage(const XEvent &e)
+{
+ AssertReturn(e.type == ClientMessage, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("Event wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str()));
+
+ int rc = VINF_SUCCESS;
+
+ char *pszWndCurName = wndX11GetNameA(m_wndCur);
+ AssertPtrReturn(pszWndCurName, VERR_NO_MEMORY);
+
+ switch (m_enmMode)
+ {
+ case HG:
+ {
+ /*
+ * Client messages are used to inform us about the status of a XdndAware
+ * window, in response of some events we send to them.
+ */
+
+ /* The target window informs us of the current Xdnd status. */
+ if (e.xclient.message_type == xAtom(XA_XdndStatus))
+ {
+ Window wndTgt = static_cast<Window>(e.xclient.data.l[XdndStatusWindow]);
+
+ char *pszWndTgtName = wndX11GetNameA(wndTgt);
+ AssertPtrBreakStmt(pszWndTgtName, VERR_NO_MEMORY);
+
+ /* Does the target accept the drop? */
+ bool const fAcceptDrop = RT_BOOL(e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_ACCEPT);
+ /* Does the target want XdndPosition messages? */
+ bool const fWantsPosition = RT_BOOL(e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_WANTS_POS);
+
+ /*
+ * The XdndStatus message tell us if the window will accept the DnD
+ * event and with which action. We immediately send this info down to
+ * the host as a response of a previous DnD message.
+ */
+ RTCString strActions = xAtomToString(e.xclient.data.l[XdndStatusAction]);
+
+ VBClLogInfo("Target window %#x ('%s')\n", wndTgt, pszWndTgtName);
+ VBClLogInfo(" - %s accept data (actions '%s')\n", fAcceptDrop ? "does" : "does not", strActions.c_str());
+ VBClLogInfo(" - %s want position messages\n", fWantsPosition ? "does" : "does not");
+
+ uint16_t const x = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]);
+ uint16_t const y = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]);
+ uint16_t const cx = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]);
+ uint16_t const cy = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]);
+
+ if (cx && cy)
+ {
+ VBClLogInfo("Target window %#x ('%s') reported dead area at %RU16,%RU16 (%RU16 x %RU16)\n",
+ wndTgt, pszWndTgtName, x, y, cx, cy);
+ /** @todo Save dead area and don't send XdndPosition messages anymore into it. */
+ }
+
+ if (m_wndCur == wndTgt)
+ {
+ VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE; /* Default is ignoring. */
+ /** @todo Compare this with the allowed actions. */
+ if (fAcceptDrop)
+ dndAction = toHGCMAction(static_cast<Atom>(e.xclient.data.l[XdndStatusAction]));
+
+ rc = VbglR3DnDHGSendAckOp(&m_dndCtx, dndAction);
+ }
+ else
+ VBClLogInfo("Target window %#x ('%s') is not our current window, skipping\n", wndTgt, pszWndTgtName);
+
+ RTStrFree(pszWndTgtName);
+ }
+ /* The target window informs us that it finished the Xdnd operation and that we may free all data. */
+ else if (e.xclient.message_type == xAtom(XA_XdndFinished))
+ {
+ Window wndTarget = static_cast<Window>(e.xclient.data.l[XdndFinishedWindow]);
+
+ char *pszWndTgtName = wndX11GetNameA(wndTarget);
+ AssertPtrBreakStmt(pszWndTgtName, VERR_NO_MEMORY);
+
+ if (m_uXdndVer >= 5)
+ {
+ const bool fSucceeded = e.xclient.data.l[XdndFinishedFlags] & VBOX_XDND_FINISHED_FLAG_SUCCEEDED;
+ #if 0 /** @todo Returns garbage -- investigate this! */
+ //const char *pcszAction = fSucceeded ? xAtomToString(e.xclient.data.l[XdndFinishedAction]).c_str() : NULL;
+ #endif
+ VBClLogInfo("Target window %#x ('%s') has %s the data\n",
+ wndTarget, pszWndTgtName, fSucceeded ? "accepted" : "rejected");
+ }
+ else /* Xdnd < version 5 did not have the XdndFinishedFlags / XdndFinishedAction properties. */
+ VBClLogInfo("Target window %#x ('%s') has accepted the data\n", wndTarget, pszWndTgtName);
+
+ RTStrFree(pszWndTgtName);
+
+ reset();
+ }
+ else
+ {
+ LogFlowThisFunc(("Unhandled client message '%s'\n", xAtomToString(e.xclient.message_type).c_str()));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ break;
+ }
+
+ case Unknown: /* Mode not set (yet). */
+ RT_FALL_THROUGH();
+ case GH:
+ {
+ /*
+ * This message marks the beginning of a new drag and drop
+ * operation on the guest.
+ */
+ if (e.xclient.message_type == xAtom(XA_XdndEnter))
+ {
+ /*
+ * Get the window which currently has the XA_XdndSelection
+ * bit set.
+ */
+ Window wndSel = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ char *pszWndSelName = wndX11GetNameA(wndSel);
+ AssertPtrBreakStmt(pszWndSelName, VERR_NO_MEMORY);
+
+ mouseButtonSet(m_wndProxy.hWnd, -1, -1, 1, true /* fPress */);
+
+ /*
+ * Update our state and the window handle to process.
+ */
+ rc = RTCritSectEnter(&m_dataCS);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t const uXdndVer = (uint8_t)e.xclient.data.l[XdndEnterFlags] >> XdndEnterVersionRShift;
+
+ VBClLogInfo("Entered new source window %#x ('%s'), supports Xdnd version %u\n", wndSel, pszWndSelName, uXdndVer);
+#ifdef DEBUG
+ XWindowAttributes xwa;
+ XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
+ LogFlowThisFunc(("wndCur=%#x, x=%d, y=%d, width=%d, height=%d\n", m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
+#endif
+ /*
+ * Retrieve supported formats.
+ */
+
+ /* Check if the MIME types are in the message itself or if we need
+ * to fetch the XdndTypeList property from the window. */
+ bool fMoreTypes = e.xclient.data.l[XdndEnterFlags] & XdndEnterMoreTypesFlag;
+ if (!fMoreTypes)
+ {
+ /* Only up to 3 format types supported. */
+ /* Start with index 2 (first item). */
+ for (int i = 2; i < 5; i++)
+ {
+ LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(e.xclient.data.l[i]).c_str()));
+ m_lstAtomFormats.append(e.xclient.data.l[i]);
+ }
+ }
+ else
+ {
+ /* More than 3 format types supported. */
+ rc = wndXDnDGetFormatList(wndSel, m_lstAtomFormats);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error retrieving supported formats, rc=%Rrc\n", rc);
+ break;
+ }
+
+ /*
+ * Retrieve supported actions.
+ */
+ if (uXdndVer >= 2) /* More than one action allowed since protocol version 2. */
+ {
+ rc = wndXDnDGetActionList(wndSel, m_lstAtomActions);
+ }
+ else /* Only "copy" action allowed on legacy applications. */
+ m_lstAtomActions.append(XA_XdndActionCopy);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error retrieving supported actions, rc=%Rrc\n", rc);
+ break;
+ }
+
+ VBClLogInfo("Source window %#x ('%s')\n", wndSel, pszWndSelName);
+ VBClLogInfo(" - supports the formats ");
+ for (size_t i = 0; i < m_lstAtomFormats.size(); i++)
+ {
+ if (i > 0)
+ VBClLogInfo(", ");
+ VBClLogInfo("%s", gX11->xAtomToString(m_lstAtomFormats[i]).c_str());
+ }
+ VBClLogInfo("\n");
+ VBClLogInfo(" - supports the actions ");
+ for (size_t i = 0; i < m_lstAtomActions.size(); i++)
+ {
+ if (i > 0)
+ VBClLogInfo(", ");
+ VBClLogInfo("%s", gX11->xAtomToString(m_lstAtomActions[i]).c_str());
+ }
+ VBClLogInfo("\n");
+
+ AssertBreakStmt(wndSel == (Window)e.xclient.data.l[XdndEnterWindow],
+ rc = VERR_INVALID_PARAMETER); /* Source window. */
+
+ m_wndCur = wndSel;
+ m_uXdndVer = uXdndVer;
+ m_enmMode = GH;
+ m_enmState = Dragging;
+
+ RTCritSectLeave(&m_dataCS);
+ }
+
+ RTStrFree(pszWndSelName);
+ }
+ else if ( e.xclient.message_type == xAtom(XA_XdndPosition)
+ && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndPositionWindow]))
+ {
+ if (m_enmState != Dragging) /* Wrong mode? Bail out. */
+ {
+ reset();
+ break;
+ }
+#ifdef LOG_ENABLED
+ int32_t iPos = e.xclient.data.l[XdndPositionXY];
+ Atom atmAction = m_uXdndVer >= 2 /* Actions other than "copy" or only supported since protocol version 2. */
+ ? e.xclient.data.l[XdndPositionAction] : xAtom(XA_XdndActionCopy);
+ LogFlowThisFunc(("XA_XdndPosition: wndProxy=%#x, wndCur=%#x, x=%RI32, y=%RI32, strAction=%s\n",
+ m_wndProxy.hWnd, m_wndCur, RT_HIWORD(iPos), RT_LOWORD(iPos),
+ xAtomToString(atmAction).c_str()));
+#endif
+ bool fAcceptDrop = true;
+
+ /* Reply with a XdndStatus message to tell the source whether
+ * the data can be dropped or not. */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = e.xclient.data.l[XdndPositionWindow];
+ m.message_type = xAtom(XA_XdndStatus);
+ m.format = 32;
+ m.data.l[XdndStatusWindow] = m_wndProxy.hWnd;
+ m.data.l[XdndStatusFlags] = fAcceptDrop ? VBOX_XDND_STATUS_FLAG_ACCEPT : VBOX_XDND_STATUS_FLAG_NONE; /* Whether to accept the drop or not. */
+
+ /* We don't want any new XA_XdndPosition messages while being
+ * in our proxy window. */
+ m.data.l[XdndStatusNoMsgXY] = RT_MAKE_U32(m_wndProxy.iY, m_wndProxy.iX);
+ m.data.l[XdndStatusNoMsgWH] = RT_MAKE_U32(m_wndProxy.iHeight, m_wndProxy.iWidth);
+
+ /** @todo Handle default action! */
+ m.data.l[XdndStatusAction] = fAcceptDrop ? toAtomAction(VBOX_DND_ACTION_COPY) : None;
+
+ int xRc = XSendEvent(m_pDisplay, e.xclient.data.l[XdndPositionWindow],
+ False /* Propagate */, NoEventMask, reinterpret_cast<XEvent *>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending position status event to current window %#x ('%s'): %s\n",
+ m_wndCur, pszWndCurName, gX11->xErrorToString(xRc).c_str());
+ }
+ else if ( e.xclient.message_type == xAtom(XA_XdndLeave)
+ && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndLeaveWindow]))
+ {
+ LogFlowThisFunc(("XA_XdndLeave\n"));
+ VBClLogInfo("Guest to host transfer canceled by the guest source window\n");
+
+ /* Start over. */
+ reset();
+ }
+ else if ( e.xclient.message_type == xAtom(XA_XdndDrop)
+ && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndDropWindow]))
+ {
+ LogFlowThisFunc(("XA_XdndDrop\n"));
+
+ if (m_enmState != Dropped) /* Wrong mode? Bail out. */
+ {
+ /* Can occur when dragging from guest->host, but then back in to the guest again. */
+ VBClLogInfo("Could not drop on own proxy window\n"); /* Not fatal. */
+
+ /* Let the source know. */
+ rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE);
+
+ /* Start over. */
+ reset();
+ break;
+ }
+
+ m_eventQueueList.append(e);
+ rc = RTSemEventSignal(m_eventQueueEvent);
+ }
+ else /* Unhandled event, abort. */
+ {
+ VBClLogInfo("Unhandled event from wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str());
+
+ /* Let the source know. */
+ rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE);
+
+ /* Start over. */
+ reset();
+ }
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailed(("Drag and drop mode not implemented: %RU32\n", m_enmMode));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+
+ RTStrFree(pszWndCurName);
+
+ LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+int DragInstance::onX11MotionNotify(const XEvent &e)
+{
+ RT_NOREF1(e);
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback handler for being notified if some other window now
+ * is the owner of the current selection.
+ *
+ * @return IPRT status code.
+ * @param e X11 event to handle.
+ *
+ * @remark
+ */
+int DragInstance::onX11SelectionClear(const XEvent &e)
+{
+ RT_NOREF1(e);
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback handler for a XDnD selection notify from a window. This is needed
+ * to let the us know if a certain window has drag'n drop data to share with us,
+ * e.g. our proxy window.
+ *
+ * @return IPRT status code.
+ * @param e X11 event to handle.
+ */
+int DragInstance::onX11SelectionNotify(const XEvent &e)
+{
+ AssertReturn(e.type == SelectionNotify, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ int rc;
+
+ switch (m_enmMode)
+ {
+ case GH:
+ {
+ if (m_enmState == Dropped)
+ {
+ m_eventQueueList.append(e);
+ rc = RTSemEventSignal(m_eventQueueEvent);
+ }
+ else
+ rc = VERR_WRONG_ORDER;
+ break;
+ }
+
+ default:
+ {
+ LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
+ e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
+ rc = VERR_INVALID_STATE;
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Callback handler for a XDnD selection request from a window. This is needed
+ * to retrieve the data required to complete the actual drag'n drop operation.
+ *
+ * @returns IPRT status code.
+ * @param evReq X11 event to handle.
+ */
+int DragInstance::onX11SelectionRequest(const XEvent &evReq)
+{
+ AssertReturn(evReq.type == SelectionRequest, VERR_INVALID_PARAMETER);
+
+ const XSelectionRequestEvent *pEvReq = &evReq.xselectionrequest;
+
+ char *pszWndSrcName = wndX11GetNameA(pEvReq->owner);
+ AssertPtrReturn(pszWndSrcName, VERR_INVALID_POINTER);
+ char *pszWndTgtName = wndX11GetNameA(pEvReq->requestor);
+ AssertPtrReturn(pszWndTgtName, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("Event owner=%#x ('%s'), requestor=%#x ('%s'), selection=%s, target=%s, prop=%s, time=%u\n",
+ pEvReq->owner, pszWndSrcName,
+ pEvReq->requestor, pszWndTgtName,
+ xAtomToString(pEvReq->selection).c_str(),
+ xAtomToString(pEvReq->target).c_str(),
+ xAtomToString(pEvReq->property).c_str(),
+ pEvReq->time));
+
+ VBClLogInfo("Window '%s' is asking '%s' for '%s' / '%s'\n",
+ pszWndTgtName, pszWndSrcName, xAtomToString(pEvReq->selection).c_str(), xAtomToString(pEvReq->property).c_str());
+
+ RTStrFree(pszWndSrcName);
+ /* Note: pszWndTgtName will be free'd below. */
+
+ int rc;
+
+ switch (m_enmMode)
+ {
+ case HG:
+ {
+ rc = VINF_SUCCESS;
+
+ /*
+ * Start by creating a refusal selection notify message.
+ * That way we only need to care for the success case.
+ */
+
+ XEvent evResp;
+ RT_ZERO(evResp);
+
+ XSelectionEvent *pEvResp = &evResp.xselection;
+
+ pEvResp->type = SelectionNotify;
+ pEvResp->display = pEvReq->display;
+ pEvResp->requestor = pEvReq->requestor;
+ pEvResp->selection = pEvReq->selection;
+ pEvResp->target = pEvReq->target;
+ pEvResp->property = None; /* "None" means refusal. */
+ pEvResp->time = pEvReq->time;
+
+ if (g_cVerbosity)
+ {
+ VBClLogVerbose(1, "Supported formats by VBoxClient:\n");
+ for (size_t i = 0; i < m_lstAtomFormats.size(); i++)
+ VBClLogVerbose(1, "\t%s\n", xAtomToString(m_lstAtomFormats.at(i)).c_str());
+ }
+
+ /* Is the requestor asking for the possible MIME types? */
+ if (pEvReq->target == xAtom(XA_TARGETS))
+ {
+ VBClLogInfo("Target window %#x ('%s') asking for target list\n", pEvReq->requestor, pszWndTgtName);
+
+ /* If so, set the window property with the formats on the requestor
+ * window. */
+ rc = wndXDnDSetFormatList(pEvReq->requestor, pEvReq->property, m_lstAtomFormats);
+ if (RT_SUCCESS(rc))
+ pEvResp->property = pEvReq->property;
+ }
+ /* Is the requestor asking for a specific MIME type (we support)? */
+ else if (m_lstAtomFormats.contains(pEvReq->target))
+ {
+ VBClLogInfo("Target window %#x ('%s') is asking for data as '%s'\n",
+ pEvReq->requestor, pszWndTgtName, xAtomToString(pEvReq->target).c_str());
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES
+# error "Implement me!"
+#else
+ /* Did we not drop our stuff to the guest yet? Bail out. */
+ if (m_enmState != Dropped)
+ {
+ VBClLogError("Data not dropped by the host on the guest yet (client state %RU32, mode %RU32), refusing selection request by guest\n",
+ m_enmState, m_enmMode);
+ }
+ /* Did we not store the requestor's initial selection request yet? Then do so now. */
+ else
+ {
+#endif /* VBOX_WITH_DRAG_AND_DROP_PROMISES */
+ /* Get the data format the requestor wants from us. */
+ VBClLogInfo("Target window %#x ('%s') requested data from host as '%s', rc=%Rrc\n",
+ pEvReq->requestor, pszWndTgtName, xAtomToString(pEvReq->target).c_str(), rc);
+
+ /* Make a copy of the MIME data to be passed back. The X server will be become
+ * the new owner of that data, so no deletion needed. */
+ /** @todo Do we need to do some more conversion here? XConvertSelection? */
+ AssertMsgBreakStmt(m_pvSelReqData != NULL, ("Selection request data is NULL\n"), rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(m_cbSelReqData > 0, ("Selection request data size is 0\n"), rc = VERR_INVALID_PARAMETER);
+
+ void const *pvData = RTMemDup(m_pvSelReqData, m_cbSelReqData);
+ AssertMsgBreakStmt(pvData != NULL, ("Duplicating selection request failed\n"), rc = VERR_NO_MEMORY);
+ uint32_t const cbData = m_cbSelReqData;
+
+ /* Always return the requested property. */
+ evResp.xselection.property = pEvReq->property;
+
+ /* Note: Always seems to return BadRequest. Seems fine. */
+ int xRc = XChangeProperty(pEvResp->display, pEvResp->requestor, pEvResp->property,
+ pEvResp->target, 8, PropModeReplace,
+ reinterpret_cast<const unsigned char*>(pvData), cbData);
+
+ LogFlowFunc(("Changing property '%s' (of type '%s') of window %#x ('%s'): %s\n",
+ xAtomToString(pEvReq->property).c_str(),
+ xAtomToString(pEvReq->target).c_str(),
+ pEvReq->requestor, pszWndTgtName,
+ gX11->xErrorToString(xRc).c_str()));
+ RT_NOREF(xRc);
+#ifndef VBOX_WITH_DRAG_AND_DROP_PROMISES
+ }
+#endif
+ }
+ /* Anything else. */
+ else
+ {
+ VBClLogError("Refusing unknown command/format '%s' of wnd=%#x ('%s')\n",
+ xAtomToString(pEvReq->target).c_str(), pEvReq->requestor, pszWndTgtName);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ VBClLogVerbose(1, "Offering type '%s', property '%s' to window %#x ('%s') ...\n",
+ xAtomToString(pEvReq->target).c_str(),
+ xAtomToString(pEvReq->property).c_str(), pEvReq->requestor, pszWndTgtName);
+
+ int xRc = XSendEvent(pEvReq->display, pEvReq->requestor, True /* Propagate */, 0, &evResp);
+ if (xRc == 0)
+ VBClLogError("Error sending SelectionNotify(1) event to window %#x ('%s'): %s\n",
+ pEvReq->requestor, pszWndTgtName, gX11->xErrorToString(xRc).c_str());
+
+ XFlush(pEvReq->display);
+ break;
+ }
+
+ default:
+ rc = VERR_INVALID_STATE;
+ break;
+ }
+
+ RTStrFree(pszWndTgtName);
+ pszWndTgtName = NULL;
+
+ LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Handles X11 events, called by x11EventThread.
+ *
+ * @returns IPRT status code.
+ * @param e X11 event to handle.
+ */
+int DragInstance::onX11Event(const XEvent &e)
+{
+ int rc;
+
+ LogFlowThisFunc(("X11 event, type=%d\n", e.type));
+ switch (e.type)
+ {
+ /*
+ * This can happen if a guest->host drag operation
+ * goes back from the host to the guest. This is not what
+ * we want and thus resetting everything.
+ */
+ case ButtonPress:
+ RT_FALL_THROUGH();
+ case ButtonRelease:
+ {
+ VBClLogInfo("Mouse button %s\n", e.type == ButtonPress ? "pressed" : "released");
+
+ reset();
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ case ClientMessage:
+ rc = onX11ClientMessage(e);
+ break;
+
+ case SelectionClear:
+ rc = onX11SelectionClear(e);
+ break;
+
+ case SelectionNotify:
+ rc = onX11SelectionNotify(e);
+ break;
+
+ case SelectionRequest:
+ rc = onX11SelectionRequest(e);
+ break;
+
+ case MotionNotify:
+ rc = onX11MotionNotify(e);
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+int DragInstance::waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS /* = 30000 */)
+{
+ const uint64_t uiStart = RTTimeMilliTS();
+ volatile uint32_t enmCurState;
+
+ int rc = VERR_TIMEOUT;
+
+ LogFlowFunc(("enmState=%RU32, uTimeoutMS=%RU32\n", enmState, uTimeoutMS));
+
+ do
+ {
+ enmCurState = ASMAtomicReadU32(&m_enmState);
+ if (enmCurState == enmState)
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ while (RTTimeMilliTS() - uiStart < uTimeoutMS);
+
+ LogFlowThisFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+/**
+ * Waits for an X11 event of a specific type.
+ *
+ * @returns IPRT status code.
+ * @param evX Reference where to store the event into.
+ * @param iType Event type to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait for the event.
+ */
+bool DragInstance::waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS /* = 100 */)
+{
+ LogFlowThisFunc(("iType=%d, uTimeoutMS=%RU32, cEventQueue=%zu\n", iType, uTimeoutMS, m_eventQueueList.size()));
+
+ bool fFound = false;
+ uint64_t const tsStartMs = RTTimeMilliTS();
+
+ do
+ {
+ /* Check if there is a client message in the queue. */
+ for (size_t i = 0; i < m_eventQueueList.size(); i++)
+ {
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ XEvent e = m_eventQueueList.at(i).m_Event;
+
+ fFound = e.type == iType;
+ if (fFound)
+ {
+ m_eventQueueList.removeAt(i);
+ evX = e;
+ }
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+
+ if (fFound)
+ break;
+ }
+ }
+
+ if (fFound)
+ break;
+
+ int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ {
+ LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
+ break;
+ }
+ }
+ while (RTTimeMilliTS() - tsStartMs < uTimeoutMS);
+
+ LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - tsStartMs));
+ return fFound;
+}
+
+/**
+ * Waits for an X11 client message of a specific type.
+ *
+ * @returns IPRT status code.
+ * @param evMsg Reference where to store the event into.
+ * @param aType Event type to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait for the event.
+ */
+bool DragInstance::waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType,
+ RTMSINTERVAL uTimeoutMS /* = 100 */)
+{
+ LogFlowThisFunc(("aType=%s, uTimeoutMS=%RU32, cEventQueue=%zu\n",
+ xAtomToString(aType).c_str(), uTimeoutMS, m_eventQueueList.size()));
+
+ bool fFound = false;
+ const uint64_t uiStart = RTTimeMilliTS();
+ do
+ {
+ /* Check if there is a client message in the queue. */
+ for (size_t i = 0; i < m_eventQueueList.size(); i++)
+ {
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ XEvent e = m_eventQueueList.at(i).m_Event;
+ if ( e.type == ClientMessage
+ && e.xclient.message_type == aType)
+ {
+ m_eventQueueList.removeAt(i);
+ evMsg = e.xclient;
+
+ fFound = true;
+ }
+
+ if (e.type == ClientMessage)
+ {
+ LogFlowThisFunc(("Client message: Type=%ld (%s)\n",
+ e.xclient.message_type, xAtomToString(e.xclient.message_type).c_str()));
+ }
+ else
+ LogFlowThisFunc(("X message: Type=%d\n", e.type));
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+
+ if (fFound)
+ break;
+ }
+ }
+
+ if (fFound)
+ break;
+
+ int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ {
+ LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
+ break;
+ }
+ }
+ while (RTTimeMilliTS() - uiStart < uTimeoutMS);
+
+ LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - uiStart));
+ return fFound;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+/*
+ * Host -> Guest
+ */
+
+/**
+ * Host -> Guest: Event signalling that the host's (mouse) cursor just entered the VM's (guest's) display
+ * area.
+ *
+ * @returns IPRT status code.
+ * @param lstFormats List of supported formats from the host.
+ * @param dndListActionsAllowed (ORed) List of supported actions from the host.
+ */
+int DragInstance::hgEnter(const RTCList<RTCString> &lstFormats, uint32_t dndListActionsAllowed)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ if (m_enmMode != Unknown)
+ return VERR_INVALID_STATE;
+
+ reset();
+
+#ifdef DEBUG
+ LogFlowThisFunc(("dndListActionsAllowed=0x%x, lstFormats=%zu: ", dndListActionsAllowed, lstFormats.size()));
+ for (size_t i = 0; i < lstFormats.size(); ++i)
+ LogFlow(("'%s' ", lstFormats.at(i).c_str()));
+ LogFlow(("\n"));
+#endif
+
+ int rc;
+
+ do
+ {
+ /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
+ rc = checkForSessionChange();
+ AssertRCBreak(rc);
+
+ /* Append all actual (MIME) formats we support to the list.
+ * These must come last, after the default Atoms above. */
+ rc = appendFormatsToList(lstFormats, m_lstAtomFormats);
+ AssertRCBreak(rc);
+
+ rc = wndXDnDSetFormatList(m_wndProxy.hWnd, xAtom(XA_XdndTypeList), m_lstAtomFormats);
+ AssertRCBreak(rc);
+
+ /* Announce the possible actions. */
+ VBoxDnDAtomList lstActions;
+ rc = toAtomActions(dndListActionsAllowed, lstActions);
+ AssertRCBreak(rc);
+
+ rc = wndXDnDSetActionList(m_wndProxy.hWnd, lstActions);
+ AssertRCBreak(rc);
+
+ /* Set the DnD selection owner to our window. */
+ /** @todo Don't use CurrentTime -- according to ICCCM section 2.1. */
+ XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), m_wndProxy.hWnd, CurrentTime);
+
+ if (g_cVerbosity)
+ {
+ RTCString strMsg("Enter: Host -> Guest\n");
+ strMsg += RTCStringFmt("Allowed actions: ");
+ for (size_t i = 0; i < lstActions.size(); i++)
+ {
+ if (i > 0)
+ strMsg += ", ";
+ strMsg += DnDActionToStr(toHGCMAction(lstActions.at(i)));
+ }
+ strMsg += " - Formats: ";
+ for (size_t i = 0; i < lstFormats.size(); i++)
+ {
+ if (i > 0)
+ strMsg += ", ";
+ strMsg += lstFormats.at(i);
+ }
+
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, strMsg.c_str());
+ }
+
+ m_enmMode = HG;
+ m_enmState = Dragging;
+
+ } while (0);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host's (mouse) cursor has left the VM's (guest's)
+ * display area.
+ */
+int DragInstance::hgLeave(void)
+{
+ if (g_cVerbosity)
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, "Leave: Host -> Guest");
+
+ if (m_enmMode == HG) /* Only reset if in the right operation mode. */
+ reset();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host's (mouse) cursor has been moved within the VM's
+ * (guest's) display area.
+ *
+ * @returns IPRT status code.
+ * @param uPosX Relative X position within the guest's display area.
+ * @param uPosY Relative Y position within the guest's display area.
+ * @param dndActionDefault Default action the host wants to perform on the guest
+ * as soon as the operation successfully finishes.
+ */
+int DragInstance::hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault));
+
+ if ( m_enmMode != HG
+ || m_enmState != Dragging)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ int rc = VINF_SUCCESS;
+ int xRc = Success;
+
+ /* Move the mouse cursor within the guest. */
+ mouseCursorMove(uPosX, uPosY);
+
+ /* Search for the application window below the cursor. */
+ Window wndBelowCursor = gX11->applicationWindowBelowCursor(m_wndRoot);
+ char *pszWndBelowCursorName = wndX11GetNameA(wndBelowCursor);
+ AssertPtrReturn(pszWndBelowCursorName, VERR_NO_MEMORY);
+
+ uint8_t uBelowCursorXdndVer = 0; /* 0 means the current window is _not_ XdndAware. */
+
+ if (wndBelowCursor != None)
+ {
+ /* Temp stuff for the XGetWindowProperty call. */
+ Atom atmTmp;
+ int fmt;
+ unsigned long cItems, cbRemaining;
+ unsigned char *pcData = NULL;
+
+ /* Query the XdndAware property from the window. We are interested in
+ * the version and if it is XdndAware at all. */
+ xRc = XGetWindowProperty(m_pDisplay, wndBelowCursor, xAtom(XA_XdndAware),
+ 0, 2, False, AnyPropertyType,
+ &atmTmp, &fmt, &cItems, &cbRemaining, &pcData);
+ if (xRc != Success)
+ {
+ VBClLogError("Error getting properties of cursor window=%#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str());
+ }
+ else
+ {
+ if (pcData == NULL || fmt != 32 || cItems != 1)
+ {
+ /** @todo Do we need to deal with this? */
+ VBClLogError("Wrong window properties for window %#x: pcData=%#x, iFmt=%d, cItems=%ul\n",
+ wndBelowCursor, pcData, fmt, cItems);
+ }
+ else
+ {
+ /* Get the current window's Xdnd version. */
+ uBelowCursorXdndVer = (uint8_t)reinterpret_cast<long *>(pcData)[0];
+ }
+
+ XFree(pcData);
+ }
+ }
+
+ char *pszWndCurName = wndX11GetNameA(m_wndCur);
+ AssertPtrReturn(pszWndCurName, VERR_NO_MEMORY);
+
+ LogFlowThisFunc(("wndCursor=%x ('%s', Xdnd version %u), wndCur=%x ('%s', Xdnd version %u)\n",
+ wndBelowCursor, pszWndBelowCursorName, uBelowCursorXdndVer, m_wndCur, pszWndCurName, m_uXdndVer));
+
+ if ( wndBelowCursor != m_wndCur
+ && m_uXdndVer)
+ {
+ VBClLogInfo("Left old window %#x ('%s'), supported Xdnd version %u\n", m_wndCur, pszWndCurName, m_uXdndVer);
+
+ /* We left the current XdndAware window. Announce this to the current indow. */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = m_wndCur;
+ m.message_type = xAtom(XA_XdndLeave);
+ m.format = 32;
+ m.data.l[XdndLeaveWindow] = m_wndProxy.hWnd;
+
+ xRc = XSendEvent(m_pDisplay, m_wndCur, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending leave event to old window %#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str());
+
+ /* Reset our current window. */
+ m_wndCur = 0;
+ m_uXdndVer = 0;
+ }
+
+ /*
+ * Do we have a new Xdnd-aware window which now is under the cursor?
+ */
+ if ( wndBelowCursor != m_wndCur
+ && uBelowCursorXdndVer)
+ {
+ VBClLogInfo("Entered new window %#x ('%s'), supports Xdnd version=%u\n",
+ wndBelowCursor, pszWndBelowCursorName, uBelowCursorXdndVer);
+
+ /*
+ * We enter a new window. Announce the XdndEnter event to the new
+ * window. The first three mime types are attached to the event (the
+ * others could be requested by the XdndTypeList property from the
+ * window itself).
+ */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = wndBelowCursor;
+ m.message_type = xAtom(XA_XdndEnter);
+ m.format = 32;
+ m.data.l[XdndEnterWindow] = m_wndProxy.hWnd;
+ m.data.l[XdndEnterFlags] = RT_MAKE_U32_FROM_U8(
+ /* Bit 0 is set if the source supports more than three data types. */
+ m_lstAtomFormats.size() > 3 ? RT_BIT(0) : 0,
+ /* Reserved for future use. */
+ 0, 0,
+ /* Protocol version to use. */
+ RT_MIN(VBOX_XDND_VERSION, uBelowCursorXdndVer));
+ m.data.l[XdndEnterType1] = m_lstAtomFormats.value(0, None); /* First data type to use. */
+ m.data.l[XdndEnterType2] = m_lstAtomFormats.value(1, None); /* Second data type to use. */
+ m.data.l[XdndEnterType3] = m_lstAtomFormats.value(2, None); /* Third data type to use. */
+
+ xRc = XSendEvent(m_pDisplay, wndBelowCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending enter event to window %#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str());
+ }
+
+ if (uBelowCursorXdndVer)
+ {
+ Assert(wndBelowCursor != None);
+
+ Atom atmAction = toAtomAction(dndActionDefault);
+ LogFlowThisFunc(("strAction=%s\n", xAtomToString(atmAction).c_str()));
+
+ VBClLogInfo("Sent position event (%RU32 x %RU32) to window %#x ('%s') with actions '%s'\n",
+ uPosX, uPosY, wndBelowCursor, pszWndBelowCursorName, xAtomToString(atmAction).c_str());
+
+ /*
+ * Send a XdndPosition event with the proposed action to the guest.
+ */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = wndBelowCursor;
+ m.message_type = xAtom(XA_XdndPosition);
+ m.format = 32;
+ m.data.l[XdndPositionWindow] = m_wndProxy.hWnd; /* X window ID of source window. */
+ m.data.l[XdndPositionFlags] = 0; /* Reserved, set to 0. */
+ m.data.l[XdndPositionXY] = RT_MAKE_U32(uPosY, uPosX); /* Cursor coordinates relative to the root window. */
+ m.data.l[XdndPositionTimeStamp] = CurrentTime; /* Timestamp for retrieving data. */
+ m.data.l[XdndPositionAction] = atmAction; /* Actions requested by the user. */
+
+ xRc = XSendEvent(m_pDisplay, wndBelowCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending position event to current window %#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str());
+ }
+
+ if (uBelowCursorXdndVer == 0)
+ {
+ /* No window to process, so send a ignore ack event to the host. */
+ rc = VbglR3DnDHGSendAckOp(&m_dndCtx, VBOX_DND_ACTION_IGNORE);
+ }
+ else
+ {
+ Assert(wndBelowCursor != None);
+
+ m_wndCur = wndBelowCursor;
+ m_uXdndVer = uBelowCursorXdndVer;
+ }
+
+ RTStrFree(pszWndBelowCursorName);
+ RTStrFree(pszWndCurName);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host has dropped the data over the VM (guest) window.
+ *
+ * @returns IPRT status code.
+ * @param uPosX Relative X position within the guest's display area.
+ * @param uPosY Relative Y position within the guest's display area.
+ * @param dndActionDefault Default action the host wants to perform on the guest
+ * as soon as the operation successfully finishes.
+ */
+int DragInstance::hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault)
+{
+ RT_NOREF3(uPosX, uPosY, dndActionDefault);
+ LogFlowThisFunc(("wndCur=%RU32, wndProxy=%RU32, mode=%RU32, state=%RU32\n", m_wndCur, m_wndProxy.hWnd, m_enmMode, m_enmState));
+ LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault));
+
+ if ( m_enmMode != HG
+ || m_enmState != Dragging)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ /* Set the state accordingly. */
+ m_enmState = Dropped;
+
+ /*
+ * Ask the host to send the raw data, as we don't (yet) know which format
+ * the guest exactly expects. As blocking in a SelectionRequest message turned
+ * out to be very unreliable (e.g. with KDE apps) we request to start transferring
+ * file/directory data (if any) here.
+ */
+ char szFormat[] = { "text/uri-list" };
+
+ int rc = VbglR3DnDHGSendReqData(&m_dndCtx, szFormat);
+ VBClLogInfo("Drop event from host resulted in: %Rrc\n", rc);
+
+ if (g_cVerbosity)
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, "Drop: Host -> Guest");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host has finished sending drag'n drop
+ * data to the guest for further processing.
+ *
+ * @returns IPRT status code.
+ * @param pMeta Pointer to meta data from host.
+ */
+int DragInstance::hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta)
+{
+ LogFlowThisFunc(("enmMode=%RU32, enmState=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("enmMetaType=%RU32\n", pMeta->enmType));
+
+ if ( m_enmMode != HG
+ || m_enmState != Dropped)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ void *pvData = NULL;
+ size_t cbData = 0;
+
+ int rc = VINF_SUCCESS; /* Shut up GCC. */
+
+ switch (pMeta->enmType)
+ {
+ case VBGLR3GUESTDNDMETADATATYPE_RAW:
+ {
+ AssertBreakStmt(pMeta->u.Raw.pvMeta != NULL, rc = VERR_INVALID_POINTER);
+ pvData = pMeta->u.Raw.pvMeta;
+ AssertBreakStmt(pMeta->u.Raw.cbMeta, rc = VERR_INVALID_PARAMETER);
+ cbData = pMeta->u.Raw.cbMeta;
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ case VBGLR3GUESTDNDMETADATATYPE_URI_LIST:
+ {
+ const char *pcszRootPath = DnDTransferListGetRootPathAbs(&pMeta->u.URI.Transfer);
+ AssertPtrBreakStmt(pcszRootPath, VERR_INVALID_POINTER);
+
+ VBClLogInfo("Transfer list root directory is '%s'\n", pcszRootPath);
+
+ /* Note: Use the URI format here, as X' DnD spec says so. */
+ rc = DnDTransferListGetRootsEx(&pMeta->u.URI.Transfer, DNDTRANSFERLISTFMT_URI, pcszRootPath,
+ DND_PATH_SEPARATOR_STR, (char **)&pvData, &cbData);
+ break;
+ }
+
+ default:
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * At this point all data needed (including sent files/directories) should
+ * be on the guest, so proceed working on communicating with the target window.
+ */
+ VBClLogInfo("Received %RU32 bytes of meta data from host\n", cbData);
+
+ /* Destroy any old data. */
+ if (m_pvSelReqData)
+ {
+ Assert(m_cbSelReqData);
+
+ RTMemFree(m_pvSelReqData); /** @todo RTMemRealloc? */
+ m_cbSelReqData = 0;
+ }
+
+ /** @todo Handle incremental transfers. */
+
+ /* Make a copy of the data. This data later then will be used to fill into
+ * the selection request. */
+ if (cbData)
+ {
+ m_pvSelReqData = RTMemAlloc(cbData);
+ if (!m_pvSelReqData)
+ return VERR_NO_MEMORY;
+
+ memcpy(m_pvSelReqData, pvData, cbData);
+ m_cbSelReqData = cbData;
+ }
+
+ /*
+ * Send a drop event to the current window (target).
+ * This window in turn then will raise a SelectionRequest message to our proxy window,
+ * which we will handle in our onX11SelectionRequest handler.
+ *
+ * The SelectionRequest will tell us in which format the target wants the data from the host.
+ */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = m_wndCur;
+ m.message_type = xAtom(XA_XdndDrop);
+ m.format = 32;
+ m.data.l[XdndDropWindow] = m_wndProxy.hWnd; /* Source window. */
+ m.data.l[XdndDropFlags] = 0; /* Reserved for future use. */
+ m.data.l[XdndDropTimeStamp] = CurrentTime; /* Our DnD data does not rely on any timing, so just use the current time. */
+
+ int xRc = XSendEvent(m_pDisplay, m_wndCur, False /* Propagate */, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending XA_XdndDrop event to window=%#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str());
+ XFlush(m_pDisplay);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Checks if the VM session has changed (can happen when restoring the VM from a saved state)
+ * and do a reconnect to the DnD HGCM service.
+ *
+ * @returns IPRT status code.
+ */
+int DragInstance::checkForSessionChange(void)
+{
+ uint64_t uSessionID;
+ int rc = VbglR3GetSessionId(&uSessionID);
+ if ( RT_SUCCESS(rc)
+ && uSessionID != m_dndCtx.uSessionID)
+ {
+ LogFlowThisFunc(("VM session has changed to %RU64\n", uSessionID));
+
+ rc = VbglR3DnDDisconnect(&m_dndCtx);
+ AssertRC(rc);
+
+ rc = VbglR3DnDConnect(&m_dndCtx);
+ AssertRC(rc);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+/**
+ * Guest -> Host: Event signalling that the host is asking whether there is a pending
+ * drag event on the guest (to the host).
+ *
+ * @returns IPRT status code.
+ */
+int DragInstance::ghIsDnDPending(void)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ int rc;
+
+ RTCString strFormats = DND_PATH_SEPARATOR_STR; /** @todo If empty, IOCTL fails with VERR_ACCESS_DENIED. */
+ VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
+ VBOXDNDACTIONLIST dndActionList = VBOX_DND_ACTION_IGNORE;
+
+ /* Currently in wrong mode? Bail out. */
+ if (m_enmMode == HG)
+ {
+ rc = VERR_INVALID_STATE;
+ }
+ /* Message already processed successfully? */
+ else if ( m_enmMode == GH
+ && ( m_enmState == Dragging
+ || m_enmState == Dropped)
+ )
+ {
+ /* No need to query for the source window again. */
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
+ rc = checkForSessionChange();
+
+ /* Determine the current window which currently has the XdndSelection set. */
+ Window wndSel = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ LogFlowThisFunc(("wndSel=%#x, wndProxy=%#x, wndCur=%#x\n", wndSel, m_wndProxy.hWnd, m_wndCur));
+
+ /* Is this another window which has a Xdnd selection and not our proxy window? */
+ if ( RT_SUCCESS(rc)
+ && wndSel
+ && wndSel != m_wndCur)
+ {
+ char *pszWndSelName = wndX11GetNameA(wndSel);
+ AssertPtrReturn(pszWndSelName, VERR_NO_MEMORY);
+ VBClLogInfo("New guest source window %#x ('%s')\n", wndSel, pszWndSelName);
+
+ /* Start over. */
+ reset();
+
+ /* Map the window on the current cursor position, which should provoke
+ * an XdndEnter event. */
+ rc = proxyWinShow();
+ if (RT_SUCCESS(rc))
+ {
+ rc = mouseCursorFakeMove();
+ if (RT_SUCCESS(rc))
+ {
+ bool fWaitFailed = false; /* Waiting for status changed failed? */
+
+ /* Wait until we're in "Dragging" state. */
+ rc = waitForStatusChange(Dragging, 100 /* 100ms timeout */);
+
+ /*
+ * Note: Don't wait too long here, as this mostly will make
+ * the drag and drop experience on the host being laggy
+ * and unresponsive.
+ *
+ * Instead, let the host query multiple times with 100ms
+ * timeout each (see above) and only report an error if
+ * the overall querying time has been exceeded.<
+ */
+ if (RT_SUCCESS(rc))
+ {
+ m_enmMode = GH;
+ }
+ else if (rc == VERR_TIMEOUT)
+ {
+ /** @todo Make m_cFailedPendingAttempts configurable. For slower window managers? */
+ if (m_cFailedPendingAttempts++ > 50) /* Tolerate up to 5s total (100ms for each slot). */
+ fWaitFailed = true;
+ else
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ fWaitFailed = true;
+
+ if (fWaitFailed)
+ {
+ VBClLogError("Error mapping proxy window to guest source window %#x ('%s'), rc=%Rrc\n",
+ wndSel, pszWndSelName, rc);
+
+ /* Reset the counter in any case. */
+ m_cFailedPendingAttempts = 0;
+ }
+ }
+ }
+
+ RTStrFree(pszWndSelName);
+ }
+ else
+ VBClLogInfo("No guest source window\n");
+ }
+
+ /*
+ * Acknowledge to the host in any case, regardless
+ * if something failed here or not. Be responsive.
+ */
+
+ int rc2 = RTCritSectEnter(&m_dataCS);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Filter out the default X11-specific formats (required for Xdnd, 'TARGET' / 'MULTIPLE');
+ * those will not be supported by VirtualBox. */
+ VBoxDnDAtomList const lstAtomFormatsFiltered = gX11->xAtomListFiltered(m_lstAtomFormats, m_lstAtomFormatsX11);
+
+ /* Anything left to report to the host? */
+ if (lstAtomFormatsFiltered.size())
+ {
+ strFormats = gX11->xAtomListToString(lstAtomFormatsFiltered);
+ dndActionDefault = VBOX_DND_ACTION_COPY; /** @todo Handle default action! */
+ dndActionList = VBOX_DND_ACTION_COPY; /** @todo Ditto. */
+ dndActionList |= toHGCMActions(m_lstAtomActions);
+ }
+
+ RTCritSectLeave(&m_dataCS);
+ }
+
+ if (g_cVerbosity)
+ {
+ char *pszActions = DnDActionListToStrA(dndActionList);
+ AssertPtrReturn(pszActions, VERR_NO_MEMORY);
+ VBClLogVerbose(1, "Reporting formats '%s' (actions '%s' / %#x, default action is '%s' (%#x)\n",
+ strFormats.c_str(), pszActions, dndActionList, DnDActionToStr(dndActionDefault), dndActionDefault);
+ RTStrFree(pszActions);
+ }
+
+ rc2 = VbglR3DnDGHSendAckPending(&m_dndCtx, dndActionDefault, dndActionList,
+ strFormats.c_str(), strFormats.length() + 1 /* Include termination */);
+ LogFlowThisFunc(("uClientID=%RU32, dndActionDefault=0x%x, dndActionList=0x%x, strFormats=%s, rc=%Rrc\n",
+ m_dndCtx.uClientID, dndActionDefault, dndActionList, strFormats.c_str(), rc2));
+ if (RT_FAILURE(rc2))
+ {
+ switch (rc2)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ rc = VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER,
+ "Drag and drop to the host either is not supported or disabled. "
+ "Please enable Guest to Host or Bidirectional drag and drop mode "
+ "or re-install the VirtualBox Guest Additions.");
+ AssertRC(rc);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ VBClLogError("Error reporting pending drag and drop operation status to host: %Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Guest -> Host: Event signalling that the host has dropped the item(s) on the
+ * host side.
+ *
+ * @returns IPRT status code.
+ * @param strFormat Requested format to send to the host.
+ * @param dndActionRequested Requested action to perform on the guest.
+ */
+int DragInstance::ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32, strFormat=%s, dndActionRequested=0x%x\n",
+ m_enmMode, m_enmState, strFormat.c_str(), dndActionRequested));
+
+ /* Currently in wrong mode? Bail out. */
+ if ( m_enmMode == Unknown
+ || m_enmMode == HG)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ if ( m_enmMode == GH
+ && m_enmState != Dragging)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ m_enmState = Dropped;
+
+#ifdef DEBUG
+ XWindowAttributes xwa;
+ XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
+ LogFlowThisFunc(("wndProxy=%RU32, wndCur=%RU32, x=%d, y=%d, width=%d, height=%d\n",
+ m_wndProxy.hWnd, m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
+
+ Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ LogFlowThisFunc(("wndSelection=%#x\n", wndSelection));
+#endif
+
+ /* We send a fake mouse move event to the current window, cause
+ * this should have the grab. */
+ mouseCursorFakeMove();
+
+ /**
+ * The fake button release event above should lead to a XdndDrop event from the
+ * source window. Because of showing our proxy window, other Xdnd events can
+ * occur before, e.g. a XdndPosition event. We are not interested
+ * in those, so just try to get the right one.
+ */
+
+ XClientMessageEvent evDnDDrop;
+ bool fDrop = waitForX11ClientMsg(evDnDDrop, xAtom(XA_XdndDrop), 5 * 1000 /* 5s timeout */);
+ if (fDrop)
+ {
+ LogFlowThisFunc(("XA_XdndDrop\n"));
+
+ /* Request to convert the selection in the specific format and
+ * place it to our proxy window as property. */
+ Assert(evDnDDrop.message_type == xAtom(XA_XdndDrop));
+
+ Window wndSource = evDnDDrop.data.l[XdndDropWindow]; /* Source window which has sent the message. */
+ Assert(wndSource == m_wndCur);
+
+ Atom aFormat = gX11->stringToxAtom(strFormat.c_str());
+
+ Time tsDrop;
+ if (m_uXdndVer >= 1)
+ tsDrop = evDnDDrop.data.l[XdndDropTimeStamp];
+ else
+ tsDrop = CurrentTime;
+
+ XConvertSelection(m_pDisplay, xAtom(XA_XdndSelection), aFormat, xAtom(XA_XdndSelection),
+ m_wndProxy.hWnd, tsDrop);
+
+ /* Wait for the selection notify event. */
+ XEvent evSelNotify;
+ RT_ZERO(evSelNotify);
+ if (waitForX11Msg(evSelNotify, SelectionNotify, 5 * 1000 /* 5s timeout */))
+ {
+ bool fCancel = false;
+
+ /* Make some paranoid checks. */
+ if ( evSelNotify.xselection.type == SelectionNotify
+ && evSelNotify.xselection.display == m_pDisplay
+ && evSelNotify.xselection.selection == xAtom(XA_XdndSelection)
+ && evSelNotify.xselection.requestor == m_wndProxy.hWnd
+ && evSelNotify.xselection.target == aFormat)
+ {
+ LogFlowThisFunc(("Selection notfiy (from wnd=%#x)\n", m_wndCur));
+
+ Atom aPropType;
+ int iPropFormat;
+ unsigned long cItems, cbRemaining;
+ unsigned char *pcData = NULL;
+ int xRc = XGetWindowProperty(m_pDisplay, m_wndProxy.hWnd,
+ xAtom(XA_XdndSelection) /* Property */,
+ 0 /* Offset */,
+ VBOX_MAX_XPROPERTIES /* Length of 32-bit multiples */,
+ True /* Delete property? */,
+ AnyPropertyType, /* Property type */
+ &aPropType, &iPropFormat, &cItems, &cbRemaining, &pcData);
+ if (xRc != Success)
+ VBClLogError("Error getting XA_XdndSelection property of proxy window=%#x: %s\n",
+ m_wndProxy.hWnd, gX11->xErrorToString(xRc).c_str());
+
+ LogFlowThisFunc(("strType=%s, iPropFormat=%d, cItems=%RU32, cbRemaining=%RU32\n",
+ gX11->xAtomToString(aPropType).c_str(), iPropFormat, cItems, cbRemaining));
+
+ if ( aPropType != None
+ && pcData != NULL
+ && iPropFormat >= 8
+ && cItems > 0
+ && cbRemaining == 0)
+ {
+ size_t cbData = cItems * (iPropFormat / 8);
+ LogFlowThisFunc(("cbData=%zu\n", cbData));
+
+ /* For whatever reason some of the string MIME types are not
+ * zero terminated. Check that and correct it when necessary,
+ * because the guest side wants this in any case. */
+ if ( m_lstAllowedFormats.contains(strFormat)
+ && pcData[cbData - 1] != '\0')
+ {
+ unsigned char *pvDataTmp = static_cast<unsigned char*>(RTMemAlloc(cbData + 1));
+ if (pvDataTmp)
+ {
+ memcpy(pvDataTmp, pcData, cbData);
+ pvDataTmp[cbData++] = '\0';
+
+ rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pvDataTmp, cbData);
+ RTMemFree(pvDataTmp);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Send the raw data to the host. */
+ rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pcData, cbData);
+ LogFlowThisFunc(("Sent strFormat=%s, rc=%Rrc\n", strFormat.c_str(), rc));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = m_wndProxy.sendFinished(wndSource, dndActionRequested);
+ }
+ else
+ fCancel = true;
+ }
+ else
+ {
+ if (aPropType == xAtom(XA_INCR))
+ {
+ /** @todo Support incremental transfers. */
+ AssertMsgFailed(("Incremental transfers are not supported yet\n"));
+
+ VBClLogError("Incremental transfers are not supported yet\n");
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ else
+ {
+ VBClLogError("Not supported data type: %s\n", gX11->xAtomToString(aPropType).c_str());
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ fCancel = true;
+ }
+
+ if (fCancel)
+ {
+ VBClLogInfo("Cancelling dropping to host\n");
+
+ /* Cancel the operation -- inform the source window by
+ * sending a XdndFinished message so that the source can toss the required data. */
+ rc = m_wndProxy.sendFinished(wndSource, VBOX_DND_ACTION_IGNORE);
+ }
+
+ /* Cleanup. */
+ if (pcData)
+ XFree(pcData);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_TIMEOUT;
+ }
+ else
+ rc = VERR_TIMEOUT;
+
+ /* Inform the host on error. */
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = VbglR3DnDSendError(&m_dndCtx, rc);
+ LogFlowThisFunc(("Sending error %Rrc to host resulted in %Rrc\n", rc, rc2)); RT_NOREF(rc2);
+ /* This is not fatal for us, just ignore. */
+ }
+
+ /* At this point, we have either successfully transfered any data or not.
+ * So reset our internal state because we are done here for the current (ongoing)
+ * drag and drop operation. */
+ reset();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+/*
+ * Helpers
+ */
+
+/**
+ * Fakes moving the mouse cursor to provoke various drag and drop
+ * events such as entering a target window or moving within a
+ * source window.
+ *
+ * Not the most elegant and probably correct function, but does
+ * the work for now.
+ *
+ * @returns IPRT status code.
+ */
+int DragInstance::mouseCursorFakeMove(void)
+{
+ int iScreenID = XDefaultScreen(m_pDisplay);
+ /** @todo What about multiple screens? Test this! */
+
+ const int iScrX = XDisplayWidth(m_pDisplay, iScreenID);
+ const int iScrY = XDisplayHeight(m_pDisplay, iScreenID);
+
+ int fx, fy, rx, ry;
+ Window wndTemp, wndChild;
+ int wx, wy; unsigned int mask;
+ XQueryPointer(m_pDisplay, m_wndRoot, &wndTemp, &wndChild, &rx, &ry, &wx, &wy, &mask);
+
+ /*
+ * Apply some simple clipping and change the position slightly.
+ */
+
+ /* FakeX */
+ if (rx == 0) fx = 1;
+ else if (rx == iScrX) fx = iScrX - 1;
+ else fx = rx + 1;
+
+ /* FakeY */
+ if (ry == 0) fy = 1;
+ else if (ry == iScrY) fy = iScrY - 1;
+ else fy = ry + 1;
+
+ /*
+ * Move the cursor to trigger the wanted events.
+ */
+ LogFlowThisFunc(("cursorRootX=%d, cursorRootY=%d\n", fx, fy));
+ int rc = mouseCursorMove(fx, fy);
+ if (RT_SUCCESS(rc))
+ {
+ /* Move the cursor back to its original position. */
+ rc = mouseCursorMove(rx, ry);
+ }
+
+ return rc;
+}
+
+/**
+ * Moves the mouse pointer to a specific position.
+ *
+ * @returns IPRT status code.
+ * @param iPosX Absolute X coordinate.
+ * @param iPosY Absolute Y coordinate.
+ */
+int DragInstance::mouseCursorMove(int iPosX, int iPosY)
+{
+ int const iScreenID = XDefaultScreen(m_pDisplay);
+ /** @todo What about multiple screens? Test this! */
+
+ int const iScreenWidth = XDisplayWidth (m_pDisplay, iScreenID);
+ int const iScreenHeight = XDisplayHeight(m_pDisplay, iScreenID);
+
+ iPosX = RT_CLAMP(iPosX, 0, iScreenWidth);
+ iPosY = RT_CLAMP(iPosY, 0, iScreenHeight);
+
+ /* Same mouse position as before? No need to do anything. */
+ if ( m_lastMouseX == iPosX
+ && m_lastMouseY == iPosY)
+ {
+ return VINF_SUCCESS;
+ }
+
+ LogFlowThisFunc(("iPosX=%d, iPosY=%d, m_wndRoot=%#x\n", iPosX, iPosY, m_wndRoot));
+
+ /* Move the guest pointer to the DnD position, so we can find the window
+ * below that position. */
+ int xRc = XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iPosX, iPosY);
+ if (xRc == Success)
+ {
+ XFlush(m_pDisplay);
+
+ m_lastMouseX = iPosX;
+ m_lastMouseY = iPosY;
+ }
+ else
+ VBClLogError("Moving mouse cursor failed: %s", gX11->xErrorToString(xRc).c_str());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends a mouse button event to a specific window.
+ *
+ * @param wndDest Window to send the mouse button event to.
+ * @param rx X coordinate relative to the root window's origin.
+ * @param ry Y coordinate relative to the root window's origin.
+ * @param iButton Mouse button to press/release.
+ * @param fPress Whether to press or release the mouse button.
+ */
+void DragInstance::mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress)
+{
+ LogFlowThisFunc(("wndDest=%#x, rx=%d, ry=%d, iBtn=%d, fPress=%RTbool\n",
+ wndDest, rx, ry, iButton, fPress));
+
+#ifdef VBOX_DND_WITH_XTEST
+ /** @todo Make this check run only once. */
+ int ev, er, ma, mi;
+ if (XTestQueryExtension(m_pDisplay, &ev, &er, &ma, &mi))
+ {
+ LogFlowThisFunc(("XText extension available\n"));
+
+ int xRc = XTestFakeButtonEvent(m_pDisplay, 1, fPress ? True : False, CurrentTime);
+ if (Rc == 0)
+ VBClLogError("Error sending XTestFakeButtonEvent event: %s\n", gX11->xErrorToString(xRc).c_str());
+ XFlush(m_pDisplay);
+ }
+ else
+ {
+#endif
+ LogFlowThisFunc(("Note: XText extension not available or disabled\n"));
+
+ unsigned int mask = 0;
+
+ if ( rx == -1
+ && ry == -1)
+ {
+ Window wndRoot, wndChild;
+ int wx, wy;
+ XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild, &rx, &ry, &wx, &wy, &mask);
+ LogFlowThisFunc(("Mouse pointer is at root x=%d, y=%d\n", rx, ry));
+ }
+
+ XButtonEvent eBtn;
+ RT_ZERO(eBtn);
+
+ eBtn.display = m_pDisplay;
+ eBtn.root = m_wndRoot;
+ eBtn.window = wndDest;
+ eBtn.subwindow = None;
+ eBtn.same_screen = True;
+ eBtn.time = CurrentTime;
+ eBtn.button = iButton;
+ eBtn.state = mask | (iButton == 1 ? Button1MotionMask :
+ iButton == 2 ? Button2MotionMask :
+ iButton == 3 ? Button3MotionMask :
+ iButton == 4 ? Button4MotionMask :
+ iButton == 5 ? Button5MotionMask : 0);
+ eBtn.type = fPress ? ButtonPress : ButtonRelease;
+ eBtn.send_event = False;
+ eBtn.x_root = rx;
+ eBtn.y_root = ry;
+
+ XTranslateCoordinates(m_pDisplay, eBtn.root, eBtn.window, eBtn.x_root, eBtn.y_root, &eBtn.x, &eBtn.y, &eBtn.subwindow);
+ LogFlowThisFunc(("state=0x%x, x=%d, y=%d\n", eBtn.state, eBtn.x, eBtn.y));
+
+ int xRc = XSendEvent(m_pDisplay, wndDest, True /* fPropagate */,
+ ButtonPressMask,
+ reinterpret_cast<XEvent*>(&eBtn));
+ if (xRc == 0)
+ VBClLogError("Error sending XButtonEvent event to window=%#x: %s\n", wndDest, gX11->xErrorToString(xRc).c_str());
+
+ XFlush(m_pDisplay);
+
+#ifdef VBOX_DND_WITH_XTEST
+ }
+#endif
+}
+
+/**
+ * Shows the (invisible) proxy window. The proxy window is needed for intercepting
+ * drags from the host to the guest or from the guest to the host. It acts as a proxy
+ * between the host and the actual (UI) element on the guest OS.
+ *
+ * To not make it miss any actions this window gets spawned across the entire guest
+ * screen (think of an umbrella) to (hopefully) capture everything. A proxy window
+ * which follows the cursor would be far too slow here.
+ *
+ * @returns IPRT status code.
+ * @param piRootX X coordinate relative to the root window's origin. Optional.
+ * @param piRootY Y coordinate relative to the root window's origin. Optional.
+ */
+int DragInstance::proxyWinShow(int *piRootX /* = NULL */, int *piRootY /* = NULL */) const
+{
+ /* piRootX is optional. */
+ /* piRootY is optional. */
+
+ LogFlowThisFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+#if 0
+# ifdef VBOX_DND_WITH_XTEST
+ XTestGrabControl(m_pDisplay, False);
+# endif
+#endif
+
+ /* Get the mouse pointer position and determine if we're on the same screen as the root window
+ * and return the current child window beneath our mouse pointer, if any. */
+ int iRootX, iRootY;
+ int iChildX, iChildY;
+ unsigned int iMask;
+ Window wndRoot, wndChild;
+ Bool fInRootWnd = XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild,
+ &iRootX, &iRootY, &iChildX, &iChildY, &iMask);
+
+ LogFlowThisFunc(("fInRootWnd=%RTbool, wndRoot=%RU32, wndChild=%RU32, iRootX=%d, iRootY=%d\n",
+ RT_BOOL(fInRootWnd), wndRoot, wndChild, iRootX, iRootY)); RT_NOREF(fInRootWnd);
+
+ if (piRootX)
+ *piRootX = iRootX;
+ if (piRootY)
+ *piRootY = iRootY;
+
+ XSynchronize(m_pDisplay, True /* Enable sync */);
+
+ /* Bring our proxy window into foreground. */
+ XMapWindow(m_pDisplay, m_wndProxy.hWnd);
+ XRaiseWindow(m_pDisplay, m_wndProxy.hWnd);
+
+ /* Spawn our proxy window over the entire screen, making it an easy drop target for the host's cursor. */
+ LogFlowThisFunc(("Proxy window x=%d, y=%d, width=%d, height=%d\n",
+ m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight));
+ XMoveResizeWindow(m_pDisplay, m_wndProxy.hWnd, m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight);
+
+ XFlush(m_pDisplay);
+
+ XSynchronize(m_pDisplay, False /* Disable sync */);
+
+#if 0
+# ifdef VBOX_DND_WITH_XTEST
+ XTestGrabControl(m_pDisplay, True);
+# endif
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Hides the (invisible) proxy window.
+ */
+int DragInstance::proxyWinHide(void)
+{
+ LogFlowFuncEnter();
+
+ XUnmapWindow(m_pDisplay, m_wndProxy.hWnd);
+ XFlush(m_pDisplay);
+
+ return VINF_SUCCESS; /** @todo Add error checking. */
+}
+
+/**
+ * Allocates the name (title) of an X window.
+ * The returned pointer must be freed using RTStrFree().
+ *
+ * @returns Pointer to the allocated window name.
+ * @retval NULL on allocation failure.
+ * @retval "<No name>" if window name was not found / invalid window handle.
+ * @param wndThis Window to retrieve name for.
+ */
+char *DragInstance::wndX11GetNameA(Window wndThis) const
+{
+ char *pszName = NULL;
+
+ XTextProperty propName;
+ if ( wndThis != None
+ && XGetWMName(m_pDisplay, wndThis, &propName))
+ {
+ if (propName.value)
+ pszName = RTStrDup((char *)propName.value); /** @todo UTF8? */
+ XFree(propName.value);
+ }
+
+ if (!pszName) /* No window name found? */
+ pszName = RTStrDup("<No name>");
+
+ return pszName;
+}
+
+/**
+ * Clear a window's supported/accepted actions list.
+ *
+ * @param wndThis Window to clear the list for.
+ */
+void DragInstance::wndXDnDClearActionList(Window wndThis) const
+{
+ XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndActionList));
+}
+
+/**
+ * Clear a window's supported/accepted formats list.
+ *
+ * @param wndThis Window to clear the list for.
+ */
+void DragInstance::wndXDnDClearFormatList(Window wndThis) const
+{
+ XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndTypeList));
+}
+
+/**
+ * Retrieves a window's supported/accepted XDnD actions.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to retrieve the XDnD actions for.
+ * @param lstActions Reference to VBoxDnDAtomList to store the action into.
+ */
+int DragInstance::wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const
+{
+ Atom iActType = None;
+ int iActFmt;
+ unsigned long cItems, cbData;
+ unsigned char *pcbData = NULL;
+
+ /* Fetch the possible list of actions, if this property is set. */
+ int xRc = XGetWindowProperty(m_pDisplay, wndThis,
+ xAtom(XA_XdndActionList),
+ 0, VBOX_MAX_XPROPERTIES,
+ False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData);
+ if (xRc != Success)
+ {
+ LogFlowThisFunc(("Error getting XA_XdndActionList atoms from window=%#x: %s\n",
+ wndThis, gX11->xErrorToString(xRc).c_str()));
+ return VERR_NOT_FOUND;
+ }
+
+ LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData));
+
+ if (cItems > 0)
+ {
+ AssertPtr(pcbData);
+ Atom *paData = reinterpret_cast<Atom *>(pcbData);
+
+ for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++)
+ {
+ LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str()));
+ lstActions.append(paData[i]);
+ }
+
+ XFree(pcbData);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Retrieves a window's supported/accepted XDnD formats.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to retrieve the XDnD formats for.
+ * @param lstTypes Reference to VBoxDnDAtomList to store the formats into.
+ */
+int DragInstance::wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const
+{
+ Atom iActType = None;
+ int iActFmt;
+ unsigned long cItems, cbData;
+ unsigned char *pcbData = NULL;
+
+ int xRc = XGetWindowProperty(m_pDisplay, wndThis,
+ xAtom(XA_XdndTypeList),
+ 0, VBOX_MAX_XPROPERTIES,
+ False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData);
+ if (xRc != Success)
+ {
+ LogFlowThisFunc(("Error getting XA_XdndTypeList atoms from window=%#x: %s\n",
+ wndThis, gX11->xErrorToString(xRc).c_str()));
+ return VERR_NOT_FOUND;
+ }
+
+ LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData));
+
+ if (cItems > 0)
+ {
+ AssertPtr(pcbData);
+ Atom *paData = reinterpret_cast<Atom *>(pcbData);
+
+ for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++)
+ {
+ LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str()));
+ lstTypes.append(paData[i]);
+ }
+
+ XFree(pcbData);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets (replaces) a window's XDnD accepted/allowed actions.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to set the format list for.
+ * @param lstActions Reference to list of XDnD actions to set.
+ */
+int DragInstance::wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const
+{
+ if (lstActions.isEmpty())
+ return VINF_SUCCESS;
+
+ XChangeProperty(m_pDisplay, wndThis,
+ xAtom(XA_XdndActionList),
+ XA_ATOM, 32, PropModeReplace,
+ reinterpret_cast<const unsigned char*>(lstActions.raw()),
+ lstActions.size());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets (replaces) a window's XDnD accepted format list.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to set the format list for.
+ * @param atmProp Property to set.
+ * @param lstFormats Reference to list of XDnD formats to set.
+ */
+int DragInstance::wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const
+{
+ if (lstFormats.isEmpty())
+ return VERR_INVALID_PARAMETER;
+
+ /* Add the property with the property data to the window. */
+ XChangeProperty(m_pDisplay, wndThis, atmProp,
+ XA_ATOM, 32, PropModeReplace,
+ reinterpret_cast<const unsigned char*>(lstFormats.raw()),
+ lstFormats.size());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Appends a RTCString list to VBoxDnDAtomList list.
+ *
+ * @returns IPRT status code.
+ * @param lstFormats Reference to RTCString list to convert.
+ * @param lstAtoms Reference to VBoxDnDAtomList list to store results in.
+ */
+int DragInstance::appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const
+{
+ for (size_t i = 0; i < lstFormats.size(); ++i)
+ lstAtoms.append(XInternAtom(m_pDisplay, lstFormats.at(i).c_str(), False));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Appends a raw-data string list to VBoxDnDAtomList list.
+ *
+ * @returns IPRT status code.
+ * @param pvData Pointer to string data to convert.
+ * @param cbData Size (in bytes) to convert.
+ * @param lstAtoms Reference to VBoxDnDAtomList list to store results in.
+ */
+int DragInstance::appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const
+{
+ RT_NOREF1(lstAtoms);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ const char *pszStr = (char *)pvData;
+ uint32_t cbStr = cbData;
+
+ int rc = VINF_SUCCESS;
+
+ VBoxDnDAtomList lstAtom;
+ while (cbStr)
+ {
+ size_t cbSize = RTStrNLen(pszStr, cbStr);
+
+ /* Create a copy with max N chars, so that we are on the save side,
+ * even if the data isn't zero terminated. */
+ char *pszTmp = RTStrDupN(pszStr, cbSize);
+ if (!pszTmp)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ lstAtom.append(XInternAtom(m_pDisplay, pszTmp, False));
+ RTStrFree(pszTmp);
+
+ pszStr += cbSize + 1;
+ cbStr -= cbSize + 1;
+ }
+
+ return rc;
+}
+
+/**
+ * Converts a HGCM-based drag'n drop action to a Atom-based drag'n drop action.
+ *
+ * @returns Converted Atom-based drag'n drop action.
+ * @param dndAction HGCM drag'n drop actions to convert.
+ */
+/* static */
+Atom DragInstance::toAtomAction(VBOXDNDACTION dndAction)
+{
+ /* Ignore is None. */
+ return (isDnDCopyAction(dndAction) ? xAtom(XA_XdndActionCopy) :
+ isDnDMoveAction(dndAction) ? xAtom(XA_XdndActionMove) :
+ isDnDLinkAction(dndAction) ? xAtom(XA_XdndActionLink) :
+ None);
+}
+
+/**
+ * Converts HGCM-based drag'n drop actions to a VBoxDnDAtomList list.
+ *
+ * @returns IPRT status code.
+ * @param dndActionList HGCM drag'n drop actions to convert.
+ * @param lstAtoms Reference to VBoxDnDAtomList to store actions in.
+ */
+/* static */
+int DragInstance::toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms)
+{
+ if (hasDnDCopyAction(dndActionList))
+ lstAtoms.append(xAtom(XA_XdndActionCopy));
+ if (hasDnDMoveAction(dndActionList))
+ lstAtoms.append(xAtom(XA_XdndActionMove));
+ if (hasDnDLinkAction(dndActionList))
+ lstAtoms.append(xAtom(XA_XdndActionLink));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Converts an Atom-based drag'n drop action to a HGCM drag'n drop action.
+ *
+ * @returns HGCM drag'n drop action.
+ * @param atom Atom-based drag'n drop action to convert.
+ */
+/* static */
+uint32_t DragInstance::toHGCMAction(Atom atom)
+{
+ uint32_t uAction = VBOX_DND_ACTION_IGNORE;
+
+ if (atom == xAtom(XA_XdndActionCopy))
+ uAction = VBOX_DND_ACTION_COPY;
+ else if (atom == xAtom(XA_XdndActionMove))
+ uAction = VBOX_DND_ACTION_MOVE;
+ else if (atom == xAtom(XA_XdndActionLink))
+ uAction = VBOX_DND_ACTION_LINK;
+
+ return uAction;
+}
+
+/**
+ * Converts an VBoxDnDAtomList list to an HGCM action list.
+ *
+ * @returns ORed HGCM action list.
+ * @param lstActions List of Atom-based actions to convert.
+ */
+/* static */
+uint32_t DragInstance::toHGCMActions(const VBoxDnDAtomList &lstActions)
+{
+ uint32_t uActions = VBOX_DND_ACTION_IGNORE;
+
+ for (size_t i = 0; i < lstActions.size(); i++)
+ uActions |= toHGCMAction(lstActions.at(i));
+
+ return uActions;
+}
+
+/*********************************************************************************************************************************
+ * VBoxDnDProxyWnd implementation. *
+ ********************************************************************************************************************************/
+
+VBoxDnDProxyWnd::VBoxDnDProxyWnd(void)
+ : pDisp(NULL)
+ , hWnd(0)
+ , iX(0)
+ , iY(0)
+ , iWidth(0)
+ , iHeight(0)
+{
+
+}
+
+VBoxDnDProxyWnd::~VBoxDnDProxyWnd(void)
+{
+ destroy();
+}
+
+int VBoxDnDProxyWnd::init(Display *pDisplay)
+{
+ /** @todo What about multiple screens? Test this! */
+ int iScreenID = XDefaultScreen(pDisplay);
+
+ iWidth = XDisplayWidth(pDisplay, iScreenID);
+ iHeight = XDisplayHeight(pDisplay, iScreenID);
+ pDisp = pDisplay;
+
+ return VINF_SUCCESS;
+}
+
+void VBoxDnDProxyWnd::destroy(void)
+{
+
+}
+
+int VBoxDnDProxyWnd::sendFinished(Window hWndSource, VBOXDNDACTION dndAction)
+{
+ /* Was the drop accepted by the host? That is, anything than ignoring. */
+ bool fDropAccepted = dndAction > VBOX_DND_ACTION_IGNORE;
+
+ LogFlowFunc(("dndAction=0x%x\n", dndAction));
+
+ /* Confirm the result of the transfer to the target window. */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = pDisp;
+ m.window = hWnd;
+ m.message_type = xAtom(XA_XdndFinished);
+ m.format = 32;
+ m.data.l[XdndFinishedWindow] = hWnd; /* Target window. */
+ m.data.l[XdndFinishedFlags] = fDropAccepted ? RT_BIT(0) : 0; /* Was the drop accepted? */
+ m.data.l[XdndFinishedAction] = fDropAccepted ? DragInstance::toAtomAction(dndAction) : None; /* Action used on accept. */
+
+ int xRc = XSendEvent(pDisp, hWndSource, True, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ {
+ VBClLogError("Error sending finished event to source window=%#x: %s\n",
+ hWndSource, gX11->xErrorToString(xRc).c_str());
+
+ return VERR_GENERAL_FAILURE; /** @todo Fudge. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/*********************************************************************************************************************************
+ * DragAndDropService implementation. *
+ ********************************************************************************************************************************/
+
+/** @copydoc VBCLSERVICE::pfnInit */
+int DragAndDropService::init(void)
+{
+ LogFlowFuncEnter();
+
+ /* Connect to the x11 server. */
+ m_pDisplay = XOpenDisplay(NULL);
+ if (!m_pDisplay)
+ {
+ VBClLogFatalError("Unable to connect to X server -- running in a terminal session?\n");
+ return VERR_NOT_FOUND;
+ }
+
+ xHelpers *pHelpers = xHelpers::getInstance(m_pDisplay);
+ if (!pHelpers)
+ return VERR_NO_MEMORY;
+
+ int rc;
+
+ do
+ {
+ rc = RTSemEventCreate(&m_hEventSem);
+ AssertRCBreak(rc);
+
+ rc = RTCritSectInit(&m_eventQueueCS);
+ AssertRCBreak(rc);
+
+ rc = VbglR3DnDConnect(&m_dndCtx);
+ AssertRCBreak(rc);
+
+ /* Event thread for events coming from the HGCM device. */
+ rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this,
+ 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndHGCM");
+ AssertRCBreak(rc);
+
+ rc = RTThreadUserWait(m_hHGCMThread, RT_MS_30SEC);
+ AssertRCBreak(rc);
+
+ if (ASMAtomicReadBool(&m_fStop))
+ break;
+
+ /* Event thread for events coming from the x11 system. */
+ rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this,
+ 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndX11");
+ AssertRCBreak(rc);
+
+ rc = RTThreadUserWait(m_hX11Thread, RT_MS_30SEC);
+ AssertRCBreak(rc);
+
+ if (ASMAtomicReadBool(&m_fStop))
+ break;
+
+ } while (0);
+
+ if (m_fStop)
+ rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to initialize, rc=%Rrc\n", rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc VBCLSERVICE::pfnWorker */
+int DragAndDropService::worker(bool volatile *pfShutdown)
+{
+ int rc;
+ do
+ {
+ m_pCurDnD = new DragInstance(m_pDisplay, this);
+ if (!m_pCurDnD)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Note: For multiple screen support in VBox it is not necessary to use
+ * another screen number than zero. Maybe in the future it will become
+ * necessary if VBox supports multiple X11 screens. */
+ rc = m_pCurDnD->init(0 /* uScreenID */);
+ /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */
+ if (rc != VINF_SUCCESS)
+ {
+ if (RT_FAILURE(rc))
+ VBClLogError("Unable to connect to drag and drop service, rc=%Rrc\n", rc);
+ else if (rc == VINF_PERMISSION_DENIED) /* No error, DnD might be just disabled. */
+ VBClLogInfo("Not available on host, terminating\n");
+ break;
+ }
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* Enter the main event processing loop. */
+ do
+ {
+ DNDEVENT e;
+ RT_ZERO(e);
+
+ LogFlowFunc(("Waiting for new events ...\n"));
+ rc = RTSemEventWait(m_hEventSem, RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t cEvents = 0;
+
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ cEvents = m_eventQueue.size();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ while (cEvents)
+ {
+ rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ if (m_eventQueue.isEmpty())
+ {
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ break;
+ }
+
+ e = m_eventQueue.first();
+ m_eventQueue.removeFirst();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ if (e.enmType == DNDEVENT::DnDEventType_HGCM)
+ {
+ PVBGLR3DNDEVENT pVbglR3Event = e.hgcm;
+ AssertPtrBreak(pVbglR3Event);
+
+ LogFlowThisFunc(("HGCM event enmType=%RU32\n", pVbglR3Event->enmType));
+ switch (pVbglR3Event->enmType)
+ {
+ case VBGLR3DNDEVENTTYPE_HG_ENTER:
+ {
+ if (pVbglR3Event->u.HG_Enter.cbFormats)
+ {
+ RTCList<RTCString> lstFormats =
+ RTCString(pVbglR3Event->u.HG_Enter.pszFormats, pVbglR3Event->u.HG_Enter.cbFormats - 1).split(DND_PATH_SEPARATOR_STR);
+ rc = m_pCurDnD->hgEnter(lstFormats, pVbglR3Event->u.HG_Enter.dndLstActionsAllowed);
+ if (RT_FAILURE(rc))
+ break;
+ /* Enter is always followed by a move event. */
+ }
+ else
+ {
+ AssertMsgFailed(("cbFormats is 0\n"));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ /* Note: After HOST_DND_FN_HG_EVT_ENTER there immediately is a move
+ * event, so fall through is intentional here. */
+ RT_FALL_THROUGH();
+ }
+
+ case VBGLR3DNDEVENTTYPE_HG_MOVE:
+ {
+ rc = m_pCurDnD->hgMove(pVbglR3Event->u.HG_Move.uXpos, pVbglR3Event->u.HG_Move.uYpos,
+ pVbglR3Event->u.HG_Move.dndActionDefault);
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_HG_LEAVE:
+ {
+ rc = m_pCurDnD->hgLeave();
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_HG_DROP:
+ {
+ rc = m_pCurDnD->hgDrop(pVbglR3Event->u.HG_Drop.uXpos, pVbglR3Event->u.HG_Drop.uYpos,
+ pVbglR3Event->u.HG_Drop.dndActionDefault);
+ break;
+ }
+
+ /* Note: VbglR3DnDRecvNextMsg() will return HOST_DND_FN_HG_SND_DATA_HDR when
+ * the host has finished copying over all the data to the guest.
+ *
+ * The actual data transfer (and message processing for it) will be done
+ * internally by VbglR3DnDRecvNextMsg() to not duplicate any code for different
+ * platforms.
+ *
+ * The data header now will contain all the (meta) data the guest needs in
+ * order to complete the DnD operation. */
+ case VBGLR3DNDEVENTTYPE_HG_RECEIVE:
+ {
+ rc = m_pCurDnD->hgDataReceive(&pVbglR3Event->u.HG_Received.Meta);
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_CANCEL:
+ {
+ m_pCurDnD->reset();
+ break;
+ }
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case VBGLR3DNDEVENTTYPE_GH_ERROR:
+ {
+ m_pCurDnD->reset();
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_GH_REQ_PENDING:
+ {
+ rc = m_pCurDnD->ghIsDnDPending();
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_GH_DROP:
+ {
+ rc = m_pCurDnD->ghDropped(pVbglR3Event->u.GH_Drop.pszFormat, pVbglR3Event->u.GH_Drop.dndActionRequested);
+ break;
+ }
+#endif
+ case VBGLR3DNDEVENTTYPE_QUIT:
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ VBClLogError("Received unsupported message type %RU32\n", pVbglR3Event->enmType);
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ LogFlowFunc(("Message %RU32 processed with %Rrc\n", pVbglR3Event->enmType, rc));
+ if (RT_FAILURE(rc))
+ {
+ /* Tell the user. */
+ VBClLogError("Processing message %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc);
+
+ /* If anything went wrong, do a reset and start over. */
+ reset();
+ }
+
+ const bool fQuit = pVbglR3Event->enmType == VBGLR3DNDEVENTTYPE_QUIT;
+
+ VbglR3DnDEventFree(e.hgcm);
+ e.hgcm = NULL;
+
+ if (fQuit)
+ break;
+ }
+ else if (e.enmType == DNDEVENT::DnDEventType_X11)
+ {
+ LogFlowThisFunc(("X11 event (type %#x)\n", e.x11.type));
+ m_pCurDnD->onX11Event(e.x11);
+ }
+ else
+ AssertMsgFailed(("Unknown event queue type %RU32\n", e.enmType));
+
+ --cEvents;
+
+ } /* for */
+
+ /*
+ * Make sure that any X11 requests have actually been sent to the
+ * server, since we are waiting for responses using poll() on
+ * another thread which will not automatically trigger flushing.
+ */
+ XFlush(m_pDisplay);
+
+ if (m_fStop)
+ break;
+
+ } while (!ASMAtomicReadBool(pfShutdown));
+
+ } while (0);
+
+ if (m_pCurDnD)
+ {
+ delete m_pCurDnD;
+ m_pCurDnD = NULL;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Resets the DnD service' data.
+ */
+void DragAndDropService::reset(void)
+{
+ LogFlowFuncEnter();
+
+ if (m_pCurDnD)
+ m_pCurDnD->reset();
+
+ /*
+ * Clear the event queue.
+ */
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ for (size_t i = 0; i < m_eventQueue.size(); i++)
+ {
+ switch (m_eventQueue[i].enmType)
+ {
+ case DNDEVENT::DnDEventType_HGCM:
+ {
+ VbglR3DnDEventFree(m_eventQueue[i].hgcm);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ }
+
+ m_eventQueue.clear();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/** @copydoc VBCLSERVICE::pfnStop */
+void DragAndDropService::stop(void)
+{
+ LogFlowFuncEnter();
+
+ /* Set stop flag first. */
+ ASMAtomicXchgBool(&m_fStop, true);
+
+ /* First, disconnect any instances. */
+ if (m_pCurDnD)
+ m_pCurDnD->stop();
+
+ /* Second, disconnect the service's DnD connection. */
+ VbglR3DnDDisconnect(&m_dndCtx);
+
+ LogFlowFuncLeave();
+}
+
+/** @copydoc VBCLSERVICE::pfnTerm */
+int DragAndDropService::term(void)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Wait for threads to terminate.
+ */
+ int rcThread;
+
+ if (m_hX11Thread != NIL_RTTHREAD)
+ {
+ VBClLogVerbose(2, "Terminating X11 thread ...\n");
+
+ int rc2 = RTThreadWait(m_hX11Thread, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc2))
+ rc2 = rcThread;
+
+ if (RT_FAILURE(rc2))
+ VBClLogError("Error waiting for X11 thread to terminate: %Rrc\n", rc2);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ m_hX11Thread = NIL_RTTHREAD;
+
+ VBClLogVerbose(2, "X11 thread terminated\n");
+ }
+
+ if (m_hHGCMThread != NIL_RTTHREAD)
+ {
+ VBClLogVerbose(2, "Terminating HGCM thread ...\n");
+
+ int rc2 = RTThreadWait(m_hHGCMThread, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc2))
+ rc2 = rcThread;
+
+ if (RT_FAILURE(rc2))
+ VBClLogError("Error waiting for HGCM thread to terminate: %Rrc\n", rc2);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ m_hHGCMThread = NIL_RTTHREAD;
+
+ VBClLogVerbose(2, "HGCM thread terminated\n");
+ }
+
+ reset();
+
+ if (m_pCurDnD)
+ {
+ delete m_pCurDnD;
+ m_pCurDnD = NULL;
+ }
+
+ xHelpers::destroyInstance();
+
+ return rc;
+}
+
+/**
+ * Static callback function for HGCM message processing thread. An internal
+ * message queue will be filled which then will be processed by the according
+ * drag'n drop instance.
+ *
+ * @returns IPRT status code.
+ * @param hThread Thread handle to use.
+ * @param pvUser Pointer to DragAndDropService instance to use.
+ */
+/* static */
+DECLCALLBACK(int) DragAndDropService::hgcmEventThread(RTTHREAD hThread, void *pvUser)
+{
+ AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
+ DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
+
+ /* Let the service instance know in any case. */
+ int rc = RTThreadUserSignal(hThread);
+ AssertRCReturn(rc, rc);
+
+ VBClLogVerbose(2, "HGCM thread started\n");
+
+ /* Number of invalid messages skipped in a row. */
+ int cMsgSkippedInvalid = 0;
+ DNDEVENT e;
+
+ do
+ {
+ RT_ZERO(e);
+ e.enmType = DNDEVENT::DnDEventType_HGCM;
+
+ /* Wait for new events. */
+ rc = VbglR3DnDEventGetNext(&pThis->m_dndCtx, &e.hgcm);
+ if (RT_SUCCESS(rc))
+ {
+ cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
+
+ int rc2 = RTCritSectEnter(&pThis->m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ VBClLogVerbose(2, "Received new HGCM message (type %#x)\n", e.hgcm->enmType);
+
+ pThis->m_eventQueue.append(e);
+
+ rc2 = RTCritSectLeave(&pThis->m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ rc = RTSemEventSignal(pThis->m_hEventSem);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ {
+ VBClLogError("Processing next message failed with rc=%Rrc\n", rc);
+
+ /* Old(er) hosts either are broken regarding DnD support or otherwise
+ * don't support the stuff we do on the guest side, so make sure we
+ * don't process invalid messages forever. */
+
+ if (cMsgSkippedInvalid++ > 32)
+ {
+ VBClLogError("Too many invalid/skipped messages from host, exiting ...\n");
+ break;
+ }
+ }
+
+ } while (!ASMAtomicReadBool(&pThis->m_fStop));
+
+ VBClLogVerbose(2, "HGCM thread ended\n");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Static callback function for X11 message processing thread. All X11 messages
+ * will be directly routed to the according drag'n drop instance.
+ *
+ * @returns IPRT status code.
+ * @param hThread Thread handle to use.
+ * @param pvUser Pointer to DragAndDropService instance to use.
+ */
+/* static */
+DECLCALLBACK(int) DragAndDropService::x11EventThread(RTTHREAD hThread, void *pvUser)
+{
+ AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
+ DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
+ AssertPtr(pThis);
+
+ int rc = VINF_SUCCESS;
+
+ /* Note: Nothing to initialize here (yet). */
+
+ /* Let the service instance know in any case. */
+ int rc2 = RTThreadUserSignal(hThread);
+ AssertRC(rc2);
+
+ VBClLogVerbose(2, "X11 thread started\n");
+
+ DNDEVENT e;
+ RT_ZERO(e);
+ e.enmType = DNDEVENT::DnDEventType_X11;
+
+ do
+ {
+ /*
+ * Wait for new events. We can't use XIfEvent here, cause this locks
+ * the window connection with a mutex and if no X11 events occurs this
+ * blocks any other calls we made to X11. So instead check for new
+ * events and if there are not any new one, sleep for a certain amount
+ * of time.
+ */
+ unsigned cNewEvents = 0;
+ unsigned cQueued = XEventsQueued(pThis->m_pDisplay, QueuedAfterFlush);
+ while (cQueued)
+ {
+ /* XNextEvent will block until a new X event becomes available. */
+ XNextEvent(pThis->m_pDisplay, &e.x11);
+ {
+ rc2 = RTCritSectEnter(&pThis->m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ LogFlowFunc(("Added new X11 event, type=%d\n", e.x11.type));
+
+ pThis->m_eventQueue.append(e);
+ cNewEvents++;
+
+ rc2 = RTCritSectLeave(&pThis->m_eventQueueCS);
+ AssertRC(rc2);
+ }
+ }
+
+ cQueued--;
+ }
+
+ if (cNewEvents)
+ {
+ rc = RTSemEventSignal(pThis->m_hEventSem);
+ if (RT_FAILURE(rc))
+ break;
+
+ continue;
+ }
+
+ /* No new events; wait a bit. */
+ RTThreadSleep(25 /* ms */);
+
+ } while (!ASMAtomicReadBool(&pThis->m_fStop));
+
+ VBClLogVerbose(2, "X11 thread ended\n");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclDnDInit(void)
+{
+ return g_Svc.init();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclDnDWorker(bool volatile *pfShutdown)
+{
+ return g_Svc.worker(pfShutdown);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclDnDStop(void)
+{
+ g_Svc.stop();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclDnDTerm(void)
+{
+ return g_Svc.term();
+}
+
+VBCLSERVICE g_SvcDragAndDrop =
+{
+ "dnd", /* szName */
+ "Drag'n'Drop", /* pszDescription */
+ ".vboxclient-draganddrop", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclDnDInit, /* pfnInit */
+ vbclDnDWorker, /* pfnWorker */
+ vbclDnDStop, /* pfnStop*/
+ vbclDnDTerm /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/hostversion.cpp b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp
new file mode 100644
index 00000000..d2da9094
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp
@@ -0,0 +1,130 @@
+/* $Id: hostversion.cpp $ */
+/** @file
+ * X11 guest client - Host version check.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+#include <stdio.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/ldr.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#ifdef VBOX_OSE
+# include <VBox/version.h>
+#endif
+
+#include "VBoxClient.h"
+
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclHostVerWorker(bool volatile *pfShutdown)
+{
+ /** @todo Move this part in VbglR3 and just provide a callback for the platform-specific
+ notification stuff, since this is very similar to the VBoxTray code. */
+
+ RT_NOREF(pfShutdown);
+
+ LogFlowFuncEnter();
+
+ int rc;
+#ifdef VBOX_WITH_GUEST_PROPS
+ uint32_t uGuestPropSvcClientID;
+ rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Cannot connect to guest property service while chcking for host version, rc = %Rrc\n", rc);
+ return rc;
+ }
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* Because we need desktop notifications to be displayed, wait
+ * some time to make the desktop environment load (as a work around). */
+ if (g_fDaemonized)
+ RTThreadSleep(RT_MS_30SEC);
+
+ char *pszHostVersion;
+ char *pszGuestVersion;
+ bool fUpdate;
+
+ rc = VbglR3HostVersionCheckForUpdate(uGuestPropSvcClientID, &fUpdate, &pszHostVersion, &pszGuestVersion);
+ if (RT_SUCCESS(rc))
+ {
+ if (fUpdate)
+ {
+ char szMsg[1024];
+ char szTitle[64];
+
+ /** @todo add some translation macros here */
+ RTStrPrintf(szTitle, sizeof(szTitle), "VirtualBox Guest Additions update available!");
+# ifndef VBOX_OSE
+ RTStrPrintf(szMsg, sizeof(szMsg), "Your guest is currently running the Guest Additions version %s. "
+ "We recommend updating to the latest version (%s) by choosing the "
+ "install option from the Devices menu.", pszGuestVersion, pszHostVersion);
+# else
+/* This is the message which appears for non-Oracle builds of the
+* Guest Additions. Distributors are encouraged to customise this. */
+ RTStrPrintf(szMsg, sizeof(szMsg), "Your virtual machine is currently running the Guest Additions version %s. Since you are running a version of the Guest Additions provided by the operating system you installed in the virtual machine we recommend that you update it to at least version %s using that system's update features, or alternatively that you remove this version and then install the " VBOX_VENDOR_SHORT " Guest Additions package using the install option from the Devices menu. Please consult the documentation for the operating system you are running to find out how to update or remove the current Guest Additions package.", pszGuestVersion, pszHostVersion);
+# endif /* VBOX_OSE */
+ rc = VBClShowNotify(szTitle, szMsg);
+ }
+
+ /* Store host version to not notify again */
+ int rc2 = VbglR3HostVersionLastCheckedStore(uGuestPropSvcClientID, pszHostVersion);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ VbglR3GuestPropReadValueFree(pszHostVersion);
+ VbglR3GuestPropReadValueFree(pszGuestVersion);
+ }
+
+ VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
+#else /* !VBOX_WITH_GUEST_PROPS */
+ rc = VERR_NOT_SUPPORTED;
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ return rc;
+}
+
+VBCLSERVICE g_SvcHostVersion =
+{
+ "hostversion", /* szName */
+ "VirtualBox host version check", /* pszDescription */
+ ".vboxclient-hostversion", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ NULL, /* pfnInit */
+ vbclHostVerWorker, /* pfnWorker */
+ NULL, /* pfnStop*/
+ NULL /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/logging.cpp b/src/VBox/Additions/x11/VBoxClient/logging.cpp
new file mode 100644
index 00000000..6591e2fc
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/logging.cpp
@@ -0,0 +1,458 @@
+/* $Id: logging.cpp $ */
+/** @file
+ * VirtualBox Guest Additions - X11 Client.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+#include <sys/wait.h>
+#include <stdlib.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/file.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+
+#ifdef VBOX_WITH_DBUS
+# include <VBox/dbus.h>
+#endif
+#include <VBox/VBoxGuestLib.h>
+
+#include <package-generated.h>
+#include "VBoxClient.h"
+
+/** Logging parameters. */
+/** @todo Make this configurable later. */
+static PRTLOGGER g_pLoggerRelease = NULL;
+static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
+static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */
+static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
+
+/** Custom log prefix (to be set externally). */
+static char *g_pszCustomLogPrefix;
+
+extern unsigned g_cRespawn;
+
+
+/**
+ * Fallback notification helper using 'notify-send'.
+ *
+ * @returns VBox status code.
+ * @returns VERR_NOT_SUPPORTED if 'notify-send' is not available, or there was an error while running 'notify-send'.
+ * @param pszMessage Message to notify desktop environment with.
+ */
+int vbclNotifyFallbackNotifySend(const char *pszMessage)
+{
+ AssertPtrReturn(pszMessage, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (g_cRespawn == 0)
+ {
+ char *pszCommand = RTStrAPrintf2("notify-send \"VBoxClient: %s\"", pszMessage);
+ if (pszCommand)
+ {
+ int status = system(pszCommand);
+
+ RTStrFree(pszCommand);
+
+ if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
+ {
+ pszCommand = RTStrAPrintf2("xmessage -buttons OK:0 -center \"VBoxClient: %s\"",
+ pszMessage);
+ if (pszCommand)
+ {
+ status = system(pszCommand);
+ if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
+ rc = VERR_NOT_SUPPORTED;
+
+ RTStrFree(pszCommand);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Shows a notification on the desktop.
+ *
+ * @returns VBox status code.
+ * @returns VERR_NOT_SUPPORTED if the current desktop environment is not supported.
+ * @param pszHeader Header text to show.
+ * @param pszBody Body text to show.
+ *
+ * @note How this notification will look like depends on the actual desktop environment implementing
+ * the actual notification service. Currently only D-BUS-compatible environments are supported.
+ *
+ * Most notification implementations have length limits on their header / body texts, so keep
+ * the text(s) short.
+ */
+int VBClShowNotify(const char *pszHeader, const char *pszBody)
+{
+ AssertPtrReturn(pszHeader, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszBody, VERR_INVALID_POINTER);
+
+ int rc;
+# ifdef VBOX_WITH_DBUS
+ rc = RTDBusLoadLib(); /** @todo Does this init / load the lib only once? Need to check this. */
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("D-Bus seems not to be installed; no desktop notifications available\n");
+ return rc;
+ }
+
+ DBusConnection *conn;
+ DBusMessage* msg = NULL;
+ conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
+ if (conn == NULL)
+ {
+ VBClLogError("Could not retrieve D-BUS session bus\n");
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ {
+ msg = dbus_message_new_method_call("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "Notify");
+ if (msg == NULL)
+ {
+ VBClLogError("Could not create D-BUS message!\n");
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t msg_replace_id = 0;
+ const char *msg_app = "VBoxClient";
+ const char *msg_icon = "";
+ const char *msg_summary = pszHeader;
+ const char *msg_body = pszBody;
+ int32_t msg_timeout = -1; /* Let the notification server decide */
+
+ DBusMessageIter iter;
+ DBusMessageIter array;
+ /*DBusMessageIter dict; - unused */
+ /*DBusMessageIter value; - unused */
+ /*DBusMessageIter variant; - unused */
+ /*DBusMessageIter data; - unused */
+
+ /* Format: UINT32 org.freedesktop.Notifications.Notify
+ * (STRING app_name, UINT32 replaces_id, STRING app_icon, STRING summary, STRING body,
+ * ARRAY actions, DICT hints, INT32 expire_timeout)
+ */
+ dbus_message_iter_init_append(msg,&iter);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_app);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_UINT32,&msg_replace_id);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_icon);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_summary);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_body);
+ dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING,&array);
+ dbus_message_iter_close_container(&iter,&array);
+ dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,"{sv}",&array);
+ dbus_message_iter_close_container(&iter,&array);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_INT32,&msg_timeout);
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ DBusMessage *reply;
+ reply = dbus_connection_send_with_reply_and_block(conn, msg, 30 * 1000 /* 30 seconds timeout */, &err);
+ if (dbus_error_is_set(&err))
+ VBClLogError("D-BUS returned an error while sending the notification: %s", err.message);
+ else if (reply)
+ {
+ dbus_connection_flush(conn);
+ dbus_message_unref(reply);
+ }
+ if (dbus_error_is_set(&err))
+ dbus_error_free(&err);
+ }
+ if (msg != NULL)
+ dbus_message_unref(msg);
+# else
+ /** @todo Implement me */
+ RT_NOREF(pszHeader, pszBody);
+ rc = VERR_NOT_SUPPORTED;
+# endif /* VBOX_WITH_DBUS */
+
+ /* Try to use a fallback if the stuff above fails or is not available. */
+ if (RT_FAILURE(rc))
+ rc = vbclNotifyFallbackNotifySend(pszBody);
+
+ /* If everything fails, still print out our notification to stdout, in the hope
+ * someone still gets aware of it. */
+ if (RT_FAILURE(rc))
+ VBClLogInfo("*** Notification: %s - %s ***\n", pszHeader, pszBody);
+
+ return rc;
+}
+
+
+
+/**
+ * Logs a verbose message.
+ *
+ * @param pszFormat The message text.
+ * @param va Format arguments.
+ */
+static void vbClLogV(const char *pszFormat, va_list va)
+{
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, va);
+ AssertPtrReturnVoid(psz);
+ LogRel(("%s", psz));
+ RTStrFree(psz);
+}
+
+/**
+ * Logs a fatal error, notifies the desktop environment via a message and
+ * exits the application immediately.
+ *
+ * @param pszFormat Format string to log.
+ * @param ... Variable arguments for format string. Optional.
+ */
+void VBClLogFatalError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtrReturnVoid(psz);
+ LogFunc(("Fatal Error: %s", psz));
+ LogRel(("Fatal Error: %s", psz));
+
+ VBClShowNotify("VBoxClient - Fatal Error", psz);
+
+ RTStrFree(psz);
+}
+
+/**
+ * Logs an error message to the (release) logging instance.
+ *
+ * @param pszFormat Format string to log.
+ */
+void VBClLogError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtrReturnVoid(psz);
+ LogFunc(("Error: %s", psz));
+ LogRel(("Error: %s", psz));
+
+ RTStrFree(psz);
+}
+
+/**
+ * Logs an info message to the (release) logging instance.
+ *
+ * @param pszFormat Format string to log.
+ */
+void VBClLogInfo(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ vbClLogV(pszFormat, args);
+ va_end(args);
+}
+
+/**
+ * Displays a verbose message based on the currently
+ * set global verbosity level.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ vbClLogV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+/**
+ * @callback_method_impl{FNRTLOGPHASE, Release logger callback}
+ */
+static DECLCALLBACK(void) vbClLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
+{
+ /* Some introductory information. */
+ static RTTIMESPEC s_TimeSpec;
+ char szTmp[256];
+ if (enmPhase == RTLOGPHASE_BEGIN)
+ RTTimeNow(&s_TimeSpec);
+ RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
+
+ switch (enmPhase)
+ {
+ case RTLOGPHASE_BEGIN:
+ {
+ pfnLog(pLoggerRelease,
+ "VBoxClient %s r%s (verbosity: %u) %s (%s %s) release log\n"
+ "Log opened %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBOX_BUILD_TARGET,
+ __DATE__, __TIME__, szTmp);
+
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
+
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ pfnLog(pLoggerRelease,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+ break;
+ }
+
+ case RTLOGPHASE_PREROTATE:
+ pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_POSTROTATE:
+ pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_END:
+ pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
+ break;
+
+ default:
+ /* nothing */
+ break;
+ }
+}
+
+static DECLCALLBACK(size_t) vbClLogPrefixCb(PRTLOGGER pLogger, char *pchBuf, size_t cchBuf, void *pvUser)
+{
+ size_t cbPrefix = 0;
+
+ RT_NOREF(pLogger);
+ RT_NOREF(pvUser);
+
+ if (g_pszCustomLogPrefix)
+ {
+ cbPrefix = RT_MIN(strlen(g_pszCustomLogPrefix), cchBuf);
+ memcpy(pchBuf, g_pszCustomLogPrefix, cbPrefix);
+ }
+
+ return cbPrefix;
+}
+
+/**
+ * Creates the default release logger outputting to the specified file.
+ *
+ * Pass NULL to disabled logging.
+ *
+ * @return IPRT status code.
+ * @param pszLogFile Filename for log output. NULL disables custom handling.
+ */
+int VBClLogCreate(const char *pszLogFile)
+{
+ if (!pszLogFile)
+ return VINF_SUCCESS;
+
+ /* Create release logger (stdout + file). */
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME | RTLOGFLAGS_PREFIX_CUSTOM;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXCLIENT_RELEASE_LOG", fFlags, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER,
+ vbClLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : "");
+ if (RT_SUCCESS(rc))
+ {
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLoggerRelease);
+
+ rc = RTLogSetCustomPrefixCallback(g_pLoggerRelease, vbClLogPrefixCb, NULL);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to register custom log prefix callback\n");
+
+ /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */
+ RTLogFlush(g_pLoggerRelease);
+ }
+
+ return rc;
+}
+
+/**
+ * Set custom log prefix.
+ *
+ * @param pszPrefix Custom log prefix string.
+ */
+void VBClLogSetLogPrefix(const char *pszPrefix)
+{
+ g_pszCustomLogPrefix = (char *)pszPrefix;
+}
+
+/**
+ * Destroys the currently active logging instance.
+ */
+void VBClLogDestroy(void)
+{
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+}
+
diff --git a/src/VBox/Additions/x11/VBoxClient/main.cpp b/src/VBox/Additions/x11/VBoxClient/main.cpp
new file mode 100644
index 00000000..f276223a
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/main.cpp
@@ -0,0 +1,885 @@
+/* $Id: main.cpp $ */
+/** @file
+ * VirtualBox Guest Additions - X11 Client.
+ */
+
+/*
+ * Copyright (C) 2006-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 <sys/wait.h>
+#include <stdlib.h> /* For exit */
+#include <signal.h>
+#include <X11/Xlib.h>
+#include "product-generated.h"
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/critsect.h>
+#include <iprt/errno.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/env.h>
+#include <iprt/process.h>
+#include <iprt/linux/sysfs.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include "VBoxClient.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define VBOXCLIENT_OPT_SERVICES 980
+#define VBOXCLIENT_OPT_CHECKHOSTVERSION VBOXCLIENT_OPT_SERVICES
+#define VBOXCLIENT_OPT_CLIPBOARD VBOXCLIENT_OPT_SERVICES + 1
+#define VBOXCLIENT_OPT_DRAGANDDROP VBOXCLIENT_OPT_SERVICES + 2
+#define VBOXCLIENT_OPT_SEAMLESS VBOXCLIENT_OPT_SERVICES + 3
+#define VBOXCLIENT_OPT_VMSVGA VBOXCLIENT_OPT_SERVICES + 4
+#define VBOXCLIENT_OPT_VMSVGA_SESSION VBOXCLIENT_OPT_SERVICES + 5
+#define VBOXCLIENT_OPT_DISPLAY VBOXCLIENT_OPT_SERVICES + 6
+
+
+/*********************************************************************************************************************************
+* Local structures *
+*********************************************************************************************************************************/
+/**
+ * The global service state.
+ */
+typedef struct VBCLSERVICESTATE
+{
+ /** Pointer to the service descriptor. */
+ PVBCLSERVICE pDesc;
+ /** The worker thread. NIL_RTTHREAD if it's the main thread. */
+ RTTHREAD Thread;
+ /** Whether Pre-init was called. */
+ bool fPreInited;
+ /** Shutdown indicator. */
+ bool volatile fShutdown;
+ /** Indicator set by the service thread exiting. */
+ bool volatile fStopped;
+ /** Whether the service was started or not. */
+ bool fStarted;
+} VBCLSERVICESTATE;
+/** Pointer to a service state. */
+typedef VBCLSERVICESTATE *PVBCLSERVICESTATE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The global service state. */
+VBCLSERVICESTATE g_Service = { 0 };
+
+/** Set by the signal handler when being called. */
+static volatile bool g_fSignalHandlerCalled = false;
+/** Critical section for the signal handler. */
+static RTCRITSECT g_csSignalHandler;
+/** Flag indicating Whether the service starts in daemonized mode or not. */
+bool g_fDaemonized = false;
+/** The name of our pidfile. It is global for the benefit of the cleanup
+ * routine. */
+static char g_szPidFile[RTPATH_MAX] = "";
+/** The file handle of our pidfile. It is global for the benefit of the
+ * cleanup routine. */
+static RTFILE g_hPidFile;
+/** The name of pidfile for parent (control) process. */
+static char g_szControlPidFile[RTPATH_MAX] = "";
+/** The file handle of parent process pidfile. */
+static RTFILE g_hControlPidFile;
+
+/** Global critical section held during the clean-up routine (to prevent it
+ * being called on multiple threads at once) or things which may not happen
+ * during clean-up (e.g. pausing and resuming the service).
+ */
+static RTCRITSECT g_critSect;
+/** Counter of how often our daemon has been respawned. */
+unsigned g_cRespawn = 0;
+/** Logging verbosity level. */
+unsigned g_cVerbosity = 0;
+/** Absolute path to log file, if any. */
+static char g_szLogFile[RTPATH_MAX + 128] = "";
+/** Set by the signal handler when SIGUSR1 received. */
+static volatile bool g_fProcessReloadRequested = false;
+
+/**
+ * Tries to determine if the session parenting this process is of Xwayland.
+ * NB: XDG_SESSION_TYPE is a systemd(1) environment variable and is unlikely
+ * set in non-systemd environments or remote logins.
+ * Therefore we check the Wayland specific display environment variable first.
+ */
+bool VBClHasWayland(void)
+{
+ const char *const pDisplayType = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY);
+ const char *pSessionType;
+
+ if (pDisplayType != NULL)
+ return true;
+
+ pSessionType = RTEnvGet(VBCL_ENV_XDG_SESSION_TYPE);
+ if ((pSessionType != NULL) && (RTStrIStartsWith(pSessionType, "wayland")))
+ return true;
+
+ return false;
+}
+
+/**
+ * Shut down if we get a signal or something.
+ *
+ * This is extern so that we can call it from other compilation units.
+ */
+void VBClShutdown(bool fExit /*=true*/)
+{
+ /* We never release this, as we end up with a call to exit(3) which is not
+ * async-safe. Unless we fix this application properly, we should be sure
+ * never to exit from anywhere except from this method. */
+ int rc = RTCritSectEnter(&g_critSect);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failure while acquiring the global critical section, rc=%Rrc\n", rc);
+
+ /* Ask service to stop. */
+ if (g_Service.pDesc &&
+ g_Service.pDesc->pfnStop)
+ {
+ ASMAtomicWriteBool(&g_Service.fShutdown, true);
+ g_Service.pDesc->pfnStop();
+
+ }
+
+ if (g_szPidFile[0] && g_hPidFile)
+ VbglR3ClosePidFile(g_szPidFile, g_hPidFile);
+
+ VBClLogDestroy();
+
+ if (fExit)
+ exit(RTEXITCODE_SUCCESS);
+}
+
+/**
+ * Xlib error handler for certain errors that we can't avoid.
+ */
+static int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError)
+{
+ char errorText[1024];
+
+ XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText));
+ VBClLogError("An X Window protocol error occurred: %s (error code %d). Request code: %d, minor code: %d, serial number: %d\n", errorText, pError->error_code, pError->request_code, pError->minor_code, pError->serial);
+ return 0;
+}
+
+/**
+ * Xlib error handler for fatal errors. This often means that the programme is still running
+ * when X exits.
+ */
+static int vboxClientXLibIOErrorHandler(Display *pDisplay)
+{
+ RT_NOREF1(pDisplay);
+ VBClLogError("A fatal guest X Window error occurred. This may just mean that the Window system was shut down while the client was still running\n");
+ VBClShutdown();
+ return 0; /* We should never reach this. */
+}
+
+/**
+ * A standard signal handler which cleans up and exits.
+ */
+static void vboxClientSignalHandler(int iSignal)
+{
+ int rc = RTCritSectEnter(&g_csSignalHandler);
+ if (RT_SUCCESS(rc))
+ {
+ if (g_fSignalHandlerCalled)
+ {
+ RTCritSectLeave(&g_csSignalHandler);
+ return;
+ }
+
+ VBClLogVerbose(2, "Received signal %d\n", iSignal);
+ g_fSignalHandlerCalled = true;
+
+ /* In our internal convention, when VBoxClient process receives SIGUSR1,
+ * this is a trigger for restarting a process with exec() call. Usually
+ * happens as a result of Guest Additions update in order to seamlessly
+ * restart newly installed binaries. */
+ if (iSignal == SIGUSR1)
+ g_fProcessReloadRequested = true;
+
+ /* Leave critical section before stopping the service. */
+ RTCritSectLeave(&g_csSignalHandler);
+
+ if ( g_Service.pDesc
+ && g_Service.pDesc->pfnStop)
+ {
+ VBClLogVerbose(2, "Notifying service to stop ...\n");
+
+ /* Signal the service to stop. */
+ ASMAtomicWriteBool(&g_Service.fShutdown, true);
+
+ g_Service.pDesc->pfnStop();
+
+ VBClLogVerbose(2, "Service notified to stop, waiting on worker thread to stop ...\n");
+ }
+ }
+}
+
+/**
+ * Reset all standard termination signals to call our signal handler.
+ */
+static int vboxClientSignalHandlerInstall(void)
+{
+ struct sigaction sigAction;
+ sigAction.sa_handler = vboxClientSignalHandler;
+ sigemptyset(&sigAction.sa_mask);
+ sigAction.sa_flags = 0;
+ sigaction(SIGHUP, &sigAction, NULL);
+ sigaction(SIGINT, &sigAction, NULL);
+ sigaction(SIGQUIT, &sigAction, NULL);
+ sigaction(SIGPIPE, &sigAction, NULL);
+ sigaction(SIGALRM, &sigAction, NULL);
+ sigaction(SIGTERM, &sigAction, NULL);
+ sigaction(SIGUSR1, &sigAction, NULL);
+ sigaction(SIGUSR2, &sigAction, NULL);
+
+ return RTCritSectInit(&g_csSignalHandler);
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static int vboxClientSignalHandlerUninstall(void)
+{
+ signal(SIGTERM, SIG_DFL);
+#ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+#endif
+
+ return RTCritSectDelete(&g_csSignalHandler);
+}
+
+/**
+ * Print out a usage message and exit with success.
+ */
+static void vboxClientUsage(const char *pcszFileName)
+{
+ RTPrintf(VBOX_PRODUCT " VBoxClient "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+
+ RTPrintf("Usage: %s "
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ "--clipboard|"
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ "--draganddrop|"
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ "--checkhostversion|"
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ "--seamless|"
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ "--vmsvga|"
+ "--vmsvga-session"
+#endif
+ "\n[-d|--nodaemon]\n", pcszFileName);
+ RTPrintf("\n");
+ RTPrintf("Options:\n");
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ RTPrintf(" --clipboard starts the shared clipboard service\n");
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ RTPrintf(" --draganddrop starts the drag and drop service\n");
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ RTPrintf(" --checkhostversion starts the host version notifier service\n");
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ RTPrintf(" --seamless starts the seamless windows service\n");
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for X11/Wayland guests\n");
+#ifdef RT_OS_LINUX
+ RTPrintf(" --vmsvga-session starts Desktop Environment specific screen assistant for X11/Wayland guests\n"
+ " (VMSVGA graphics adapter only)\n");
+#else
+ RTPrintf(" --vmsvga-session an alias for --vmsvga\n");
+#endif
+ RTPrintf(" --display starts VMSVGA dynamic resizing for legacy guests\n");
+#endif
+ RTPrintf(" -f, --foreground run in the foreground (no daemonizing)\n");
+ RTPrintf(" -d, --nodaemon continues running as a system service\n");
+ RTPrintf(" -h, --help shows this help text\n");
+ RTPrintf(" -l, --logfile <path> enables logging to a file\n");
+ RTPrintf(" -v, --verbose increases logging verbosity level\n");
+ RTPrintf(" -V, --version shows version information\n");
+ RTPrintf("\n");
+}
+
+/**
+ * Complains about seeing more than one service specification.
+ *
+ * @returns RTEXITCODE_SYNTAX.
+ */
+static int vbclSyntaxOnlyOneService(void)
+{
+ RTMsgError("More than one service specified! Only one, please.");
+ return RTEXITCODE_SYNTAX;
+}
+
+/**
+ * The service thread.
+ *
+ * @returns Whatever the worker function returns.
+ * @param ThreadSelf My thread handle.
+ * @param pvUser The service index.
+ */
+static DECLCALLBACK(int) vbclThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ PVBCLSERVICESTATE pState = (PVBCLSERVICESTATE)pvUser;
+ AssertPtrReturn(pState, VERR_INVALID_POINTER);
+
+#ifndef RT_OS_WINDOWS
+ /*
+ * Block all signals for this thread. Only the main thread will handle signals.
+ */
+ sigset_t signalMask;
+ sigfillset(&signalMask);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+#endif
+
+ AssertPtrReturn(pState->pDesc->pfnWorker, VERR_INVALID_POINTER);
+ int rc = pState->pDesc->pfnWorker(&pState->fShutdown);
+
+ VBClLogVerbose(2, "Worker loop ended with %Rrc\n", rc);
+
+ ASMAtomicXchgBool(&pState->fShutdown, true);
+ RTThreadUserSignal(ThreadSelf);
+ return rc;
+}
+
+/**
+ * Wait for SIGUSR1 and re-exec.
+ */
+static void vbclHandleUpdateStarted(char *const argv[])
+{
+ /* Context of parent process */
+ sigset_t signalMask;
+ int iSignal;
+ int rc;
+
+ /* Release reference to guest driver. */
+ VbglR3Term();
+
+ sigemptyset(&signalMask);
+ sigaddset(&signalMask, SIGUSR1);
+ rc = pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ if (rc == 0)
+ {
+ LogRel(("%s: waiting for Guest Additions installation to be completed\n",
+ g_Service.pDesc->pszDesc));
+
+ /* Wait for SIGUSR1. */
+ rc = sigwait(&signalMask, &iSignal);
+ if (rc == 0)
+ {
+ LogRel(("%s: Guest Additions installation to be completed, reloading service\n",
+ g_Service.pDesc->pszDesc));
+
+ /* Release pidfile, otherwise new VBoxClient instance won't be able to quire it. */
+ VBClShutdown(false);
+
+ rc = RTProcCreate(argv[0], argv, RTENV_DEFAULT,
+ RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL);
+ if (RT_SUCCESS(rc))
+ LogRel(("%s: service restarted\n", g_Service.pDesc->pszDesc));
+ else
+ LogRel(("%s: cannot replace running image with %s (%s), automatic service reloading has failed\n",
+ g_Service.pDesc->pszDesc, argv[0], strerror(errno)));
+ }
+ else
+ LogRel(("%s: cannot wait for signal (%s), automatic service reloading has failed\n",
+ g_Service.pDesc->pszDesc, strerror(errno)));
+ }
+ else
+ LogRel(("%s: failed to setup signal handler, automatic service reloading has failed\n",
+ g_Service.pDesc->pszDesc));
+
+ exit(RT_BOOL(rc != 0));
+}
+
+/**
+ * Compose pidfile name.
+ *
+ * @returns IPRT status code.
+ * @param szBuf Buffer to store pidfile name into.
+ * @param cbBuf Size of buffer.
+ * @param szTemplate Null-terminated string which contains pidfile name.
+ * @param fParentProcess Whether pidfile path should be composed for
+ * parent (control) process or for a child (actual
+ * service) process.
+ */
+static int vbclGetPidfileName(char *szBuf, size_t cbBuf, const char *szTemplate,
+ bool fParentProcess)
+{
+ int rc;
+ char pszActiveTTY[128];
+ size_t cchRead;
+
+ RT_ZERO(pszActiveTTY);
+
+ AssertPtrReturn(szBuf, VERR_INVALID_PARAMETER);
+ AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(szTemplate, VERR_INVALID_PARAMETER);
+
+ rc = RTPathUserHome(szBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("%s: getting home directory failed: %Rrc\n",
+ g_Service.pDesc->pszDesc, rc);
+
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szBuf, cbBuf, szTemplate);
+
+#ifdef RT_OS_LINUX
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsReadStrFile(pszActiveTTY, sizeof(pszActiveTTY) - 1 /* reserve last byte for string termination */,
+ &cchRead, "class/tty/tty0/active");
+ if (RT_SUCCESS(rc))
+ {
+ RTStrCat(szBuf, cbBuf, "-");
+ RTStrCat(szBuf, cbBuf, pszActiveTTY);
+ }
+ else
+ VBClLogInfo("%s: cannot detect currently active tty device, "
+ "multiple service instances for a single user will not be allowed, rc=%Rrc",
+ g_Service.pDesc->pszDesc, rc);
+#endif /* RT_OS_LINUX */
+
+ if (RT_SUCCESS(rc))
+ RTStrCat(szBuf, cbBuf, fParentProcess ? "-control.pid" : "-service.pid");
+
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("%s: reating PID file path failed: %Rrc\n",
+ g_Service.pDesc->pszDesc, rc);
+
+ return rc;
+}
+
+/**
+ * The main loop for the VBoxClient daemon.
+ */
+int main(int argc, char *argv[])
+{
+ /* Note: No VBClLogXXX calls before actually creating the log. */
+
+ /* Initialize our runtime before all else. */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /* A flag which is returned to the parent process when Guest Additions update started. */
+ bool fUpdateStarted = false;
+
+ /* This should never be called twice in one process - in fact one Display
+ * object should probably never be used from multiple threads anyway. */
+ if (!XInitThreads())
+ return RTMsgErrorExitFailure("Failed to initialize X11 threads\n");
+
+ /* Get our file name for usage info and hints. */
+ const char *pcszFileName = RTPathFilename(argv[0]);
+ if (!pcszFileName)
+ pcszFileName = "VBoxClient";
+
+ /* Parse our option(s). */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--nodaemon", 'd', RTGETOPT_REQ_NOTHING },
+ { "--foreground", 'f', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--logfile", 'l', RTGETOPT_REQ_STRING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+
+ /* Services */
+#ifdef VBOX_WITH_GUEST_PROPS
+ { "--checkhostversion", VBOXCLIENT_OPT_CHECKHOSTVERSION, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ { "--clipboard", VBOXCLIENT_OPT_CLIPBOARD, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ { "--draganddrop", VBOXCLIENT_OPT_DRAGANDDROP, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ { "--seamless", VBOXCLIENT_OPT_SEAMLESS, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ { "--vmsvga", VBOXCLIENT_OPT_VMSVGA, RTGETOPT_REQ_NOTHING },
+ { "--vmsvga-session", VBOXCLIENT_OPT_VMSVGA_SESSION, RTGETOPT_REQ_NOTHING },
+ { "--display", VBOXCLIENT_OPT_DISPLAY, RTGETOPT_REQ_NOTHING },
+#endif
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to parse command line options, rc=%Rrc\n", rc);
+
+ AssertRC(rc);
+
+ bool fDaemonise = true;
+ bool fRespawn = true;
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'd':
+ {
+ fDaemonise = false;
+ break;
+ }
+
+ case 'h':
+ {
+ vboxClientUsage(pcszFileName);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case 'f':
+ {
+ fDaemonise = false;
+ fRespawn = false;
+ break;
+ }
+
+ case 'l':
+ {
+ rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Unable to set log file path, rc=%Rrc\n", rc);
+ break;
+ }
+
+ case 'n':
+ {
+ fRespawn = false;
+ break;
+ }
+
+ case 'v':
+ {
+ g_cVerbosity++;
+ break;
+ }
+
+ case 'V':
+ {
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /* Services */
+#ifdef VBOX_WITH_GUEST_PROPS
+ case VBOXCLIENT_OPT_CHECKHOSTVERSION:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcHostVersion;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ case VBOXCLIENT_OPT_CLIPBOARD:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcClipboard;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ case VBOXCLIENT_OPT_DRAGANDDROP:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcDragAndDrop;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ case VBOXCLIENT_OPT_SEAMLESS:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcSeamless;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ case VBOXCLIENT_OPT_VMSVGA:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcDisplaySVGA;
+ break;
+ }
+
+ case VBOXCLIENT_OPT_VMSVGA_SESSION:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+# ifdef RT_OS_LINUX
+ g_Service.pDesc = &g_SvcDisplaySVGASession;
+# else
+ g_Service.pDesc = &g_SvcDisplaySVGA;
+# endif
+ break;
+ }
+
+ case VBOXCLIENT_OPT_DISPLAY:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcDisplayLegacy;
+ break;
+ }
+#endif
+ case VINF_GETOPT_NOT_OPTION:
+ break;
+
+ case VERR_GETOPT_UNKNOWN_OPTION:
+ RT_FALL_THROUGH();
+ default:
+ {
+ if ( g_Service.pDesc
+ && g_Service.pDesc->pfnOption)
+ {
+ rc = g_Service.pDesc->pfnOption(NULL, argc, argv, &GetState.iNext);
+ }
+ else /* No service specified yet. */
+ rc = VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("unrecognized option '%s'", ValueUnion.psz);
+ RTMsgInfo("Try '%s --help' for more information", pcszFileName);
+ return RTEXITCODE_SYNTAX;
+ }
+ break;
+ }
+
+ } /* switch */
+ } /* while RTGetOpt */
+
+ if (!g_Service.pDesc)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No service specified. Quitting because nothing to do!");
+
+ /* Initialize VbglR3 before we do anything else with the logger. */
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("VbglR3InitUser failed: %Rrc", rc);
+
+ rc = VBClLogCreate(g_szLogFile[0] ? g_szLogFile : "");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to create release log '%s', rc=%Rrc\n",
+ g_szLogFile[0] ? g_szLogFile : "<None>", rc);
+
+ if (!fDaemonise)
+ {
+ /* If the user is running in "no daemon" mode, send critical logging to stdout as well. */
+ PRTLOGGER pReleaseLog = RTLogRelGetDefaultInstance();
+ if (pReleaseLog)
+ {
+ rc = RTLogDestinations(pReleaseLog, "stdout");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to redivert error output, rc=%Rrc", rc);
+ }
+ }
+
+ VBClLogInfo("VBoxClient %s r%s started. Verbose level = %d. Wayland environment detected: %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBClHasWayland() ? "yes" : "no");
+ VBClLogInfo("Service: %s\n", g_Service.pDesc->pszDesc);
+
+ rc = RTCritSectInit(&g_critSect);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Initializing critical section failed: %Rrc\n", rc);
+ if (g_Service.pDesc->pszPidFilePathTemplate)
+ {
+ /* Get pidfile name for parent (control) process. */
+ rc = vbclGetPidfileName(g_szControlPidFile, sizeof(g_szControlPidFile), g_Service.pDesc->pszPidFilePathTemplate, true);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ /* Get pidfile name for service process. */
+ rc = vbclGetPidfileName(g_szPidFile, sizeof(g_szPidFile), g_Service.pDesc->pszPidFilePathTemplate, false);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (fDaemonise)
+ {
+ rc = VbglR3DaemonizeEx(false /* fNoChDir */, false /* fNoClose */, fRespawn, &g_cRespawn,
+ true /* fReturnOnUpdate */, &fUpdateStarted, g_szControlPidFile, &g_hControlPidFile);
+ /* This combination only works in context of parent process. */
+ if (RT_SUCCESS(rc) && fUpdateStarted)
+ vbclHandleUpdateStarted(argv);
+ }
+
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Daemonizing service failed: %Rrc\n", rc);
+
+ if (g_szPidFile[0])
+ {
+ rc = VbglR3PidFile(g_szPidFile, &g_hPidFile);
+ if (rc == VERR_FILE_LOCK_VIOLATION) /* Already running. */
+ {
+ VBClLogInfo("%s: service already running, exitting\n",
+ g_Service.pDesc->pszDesc);
+ return RTEXITCODE_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Creating PID file %s failed: %Rrc\n", g_szPidFile, rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+
+#ifndef VBOXCLIENT_WITHOUT_X11
+ /* Set an X11 error handler, so that we don't die when we get unavoidable
+ * errors. */
+ XSetErrorHandler(vboxClientXLibErrorHandler);
+ /* Set an X11 I/O error handler, so that we can shutdown properly on
+ * fatal errors. */
+ XSetIOErrorHandler(vboxClientXLibIOErrorHandler);
+#endif
+
+ bool fSignalHandlerInstalled = false;
+ if (RT_SUCCESS(rc))
+ {
+ rc = vboxClientSignalHandlerInstall();
+ if (RT_SUCCESS(rc))
+ fSignalHandlerInstalled = true;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && g_Service.pDesc->pfnInit)
+ {
+ VBClLogInfo("Initializing service ...\n");
+ rc = g_Service.pDesc->pfnInit();
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogInfo("Creating worker thread ...\n");
+ rc = RTThreadCreate(&g_Service.Thread, vbclThread, (void *)&g_Service, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_Service.pDesc->pszName);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Creating worker thread failed, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ g_Service.fStarted = true;
+
+ /* Wait for the thread to initialize. */
+ /** @todo There is a race between waiting and checking
+ * the fShutdown flag of a thread here and processing
+ * the thread's actual worker loop. If the thread decides
+ * to exit the loop before we skipped the fShutdown check
+ * below the service will fail to start! */
+ /** @todo This presumably means either a one-shot service or that
+ * something has gone wrong. In the second case treating it as failure
+ * to start is probably right, so we need a way to signal the first
+ * rather than leaving the idle thread hanging around. A flag in the
+ * service description? */
+ RTThreadUserWait(g_Service.Thread, RT_MS_1MIN);
+ if (g_Service.fShutdown)
+ {
+ VBClLogError("Service failed to start!\n");
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ {
+ VBClLogInfo("Service started\n");
+
+ int rcThread;
+ rc = RTThreadWait(g_Service.Thread, RT_INDEFINITE_WAIT, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Waiting on worker thread to stop failed, rc=%Rrc\n", rc);
+
+ if (g_Service.pDesc->pfnTerm)
+ {
+ VBClLogInfo("Terminating service\n");
+
+ int rc2 = g_Service.pDesc->pfnTerm();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogInfo("Service terminated\n");
+ }
+ else
+ VBClLogError("Service failed to terminate, rc=%Rrc\n", rc);
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NOT_AVAILABLE)
+ VBClLogInfo("Service is not availabe, skipping\n");
+ else if (rc == VERR_NOT_SUPPORTED)
+ VBClLogInfo("Service is not supported on this platform, skipping\n");
+ else
+ VBClLogError("Service ended with error %Rrc\n", rc);
+ }
+ else
+ VBClLogVerbose(2, "Service ended\n");
+
+ if (fSignalHandlerInstalled)
+ {
+ int rc2 = vboxClientSignalHandlerUninstall();
+ AssertRC(rc2);
+ }
+
+ VBClShutdown(false /*fExit*/);
+
+ /** @todo r=andy Should we return an appropriate exit code if the service failed to init?
+ * Must be tested carefully with our init scripts first. */
+ return g_fProcessReloadRequested ? VBGLR3EXITCODERELOAD : RTEXITCODE_SUCCESS;
+}
+
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;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless-x11.h b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h
new file mode 100644
index 00000000..f8ab185d
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h
@@ -0,0 +1,275 @@
+/* $Id: seamless-x11.h $ */
+/** @file
+ * Seamless mode - X11 guests.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/log.h>
+#include <iprt/avl.h>
+#ifdef RT_NEED_NEW_AND_DELETE
+# include <iprt/mem.h>
+# include <new>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/shape.h>
+
+#define WM_TYPE_PROP "_NET_WM_WINDOW_TYPE"
+#define WM_TYPE_DESKTOP_PROP "_NET_WM_WINDOW_TYPE_DESKTOP"
+
+/* This is defined wrong in my X11 header files! */
+#define VBoxShapeNotify 64
+
+/**
+ * Callback which provides the interface for notifying the host of changes to
+ * the X11 window configuration, mainly split out from @a VBoxGuestSeamlessHost
+ * to simplify the unit test.
+ */
+typedef void FNSENDREGIONUPDATE(RTRECT *pRects, size_t cRects);
+typedef FNSENDREGIONUPDATE *PFNSENDREGIONUPDATE;
+
+/** Structure containing information about a guest window's position and visible area.
+ Used inside of VBoxGuestWindowList. */
+struct VBoxGuestWinInfo
+{
+public:
+ /** Header structure for insertion into an AVL tree */
+ AVLU32NODECORE Core;
+ /** Is the window currently mapped? */
+ bool mhasShape;
+ /** Co-ordinates in the guest screen. */
+ int mX, mY;
+ /** Window dimensions. */
+ int mWidth, mHeight;
+ /** Number of rectangles used to represent the visible area. */
+ int mcRects;
+ /** Rectangles representing the visible area. These must be allocated
+ * by XMalloc and will be freed automatically if non-null when the class
+ * is destroyed. */
+ XRectangle *mpRects;
+ /** Constructor. */
+ VBoxGuestWinInfo(bool hasShape, int x, int y, int w, int h, int cRects, XRectangle *pRects)
+ : mhasShape(hasShape), mX(x), mY(y), mWidth(w), mHeight(h)
+ , mcRects(cRects), mpRects(pRects)
+ {}
+
+ /** Destructor */
+ ~VBoxGuestWinInfo()
+ {
+ if (mpRects)
+ XFree(mpRects);
+ }
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+
+private:
+ // We don't want a copy constructor or assignment operator
+ VBoxGuestWinInfo(const VBoxGuestWinInfo &);
+ VBoxGuestWinInfo &operator=(const VBoxGuestWinInfo &);
+};
+
+/** Callback type used for "DoWithAll" calls */
+typedef DECLCALLBACKTYPE(int, FNVBOXGUESTWINCALLBACK,(VBoxGuestWinInfo *, void *));
+/** Pointer to VBOXGUESTWINCALLBACK */
+typedef FNVBOXGUESTWINCALLBACK *PFNVBOXGUESTWINCALLBACK;
+
+static inline DECLCALLBACK(int) VBoxGuestWinCleanup(VBoxGuestWinInfo *pInfo, void *)
+{
+ delete pInfo;
+ return VINF_SUCCESS;
+}
+
+/**
+ * This class is just a wrapper around a map of structures containing
+ * information about the windows on the guest system. It has a function for
+ * adding a structure (see addWindow) and one for removing it by window
+ * handle (see removeWindow).
+ */
+class VBoxGuestWindowList
+{
+private:
+ // We don't want a copy constructor or an assignment operator
+ VBoxGuestWindowList(const VBoxGuestWindowList&);
+ VBoxGuestWindowList& operator=(const VBoxGuestWindowList&);
+
+ // Private class members
+ AVLU32TREE mWindows;
+
+public:
+ // Constructor
+ VBoxGuestWindowList(void) : mWindows(NULL) {}
+ // Destructor
+ ~VBoxGuestWindowList()
+ {
+ /** @todo having this inside the container class hard codes that the
+ * elements have to be allocated with the "new" operator, and
+ * I don't see a need to require this. */
+ doWithAll(VBoxGuestWinCleanup, NULL);
+ }
+
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+
+ // Standard operations
+ VBoxGuestWinInfo *find(Window hWin)
+ {
+ return (VBoxGuestWinInfo *)RTAvlU32Get(&mWindows, hWin);
+ }
+
+ void detachAll(PFNVBOXGUESTWINCALLBACK pfnCallback, void *pvParam)
+ {
+ RTAvlU32Destroy(&mWindows, (PAVLU32CALLBACK)pfnCallback, pvParam);
+ }
+
+ int doWithAll(PFNVBOXGUESTWINCALLBACK pfnCallback, void *pvParam)
+ {
+ return RTAvlU32DoWithAll(&mWindows, 1, (PAVLU32CALLBACK)pfnCallback, pvParam);
+ }
+
+ bool addWindow(Window hWin, bool isMapped, int x, int y, int w, int h, int cRects,
+ XRectangle *pRects)
+ {
+ LogRelFlowFunc(("hWin=%lu, isMapped=%RTbool, x=%d, y=%d, w=%d, h=%d, cRects=%d\n",
+ (unsigned long) hWin, isMapped, x, y, w, h, cRects));
+ VBoxGuestWinInfo *pInfo = new VBoxGuestWinInfo(isMapped, x, y, w, h, cRects, pRects);
+ pInfo->Core.Key = hWin;
+ LogRelFlowFuncLeave();
+ return RTAvlU32Insert(&mWindows, &pInfo->Core);
+ }
+
+ VBoxGuestWinInfo *removeWindow(Window hWin)
+ {
+ LogRelFlowFuncEnter();
+ return (VBoxGuestWinInfo *)RTAvlU32Remove(&mWindows, hWin);
+ }
+};
+
+class SeamlessX11
+{
+private:
+ // We don't want a copy constructor or assignment operator
+ SeamlessX11(const SeamlessX11&);
+ SeamlessX11& operator=(const SeamlessX11&);
+
+ // Private member variables
+ /** Pointer to the host callback. */
+ PFNSENDREGIONUPDATE mHostCallback;
+ /** Our connection to the X11 display we are running on. */
+ Display *mDisplay;
+ /** Class to keep track of visible guest windows. */
+ VBoxGuestWindowList mGuestWindows;
+ /** The current set of seamless rectangles. */
+ RTRECT *mpRects;
+ /** The current number of seamless rectangles. */
+ int mcRects;
+ /** Do we support the X shaped window extension? */
+ bool mSupportsShape;
+ /** Is seamless mode currently enabled? */
+ bool mEnabled;
+ /** Have there been changes since the last time we sent a notification? */
+ bool mChanged;
+
+ // Private methods
+
+ // Methods to manage guest window information
+ /**
+ * Store information about a desktop window and register for structure events on it.
+ * If it is mapped, go through the list of it's children and add information about
+ * mapped children to the tree of visible windows, making sure that those windows are
+ * not already in our list of desktop windows.
+ *
+ * @param hWin the window concerned - should be a "desktop" window
+ */
+ void monitorClientList(void);
+ void unmonitorClientList(void);
+ void rebuildWindowTree(void);
+ void addClients(const Window hRoot);
+ bool isVirtualRoot(Window hWin);
+ void addClientWindow(Window hWin);
+ void freeWindowTree(void);
+ void updateHostSeamlessInfo(void);
+ int updateRects(void);
+
+public:
+ /**
+ * Initialise the guest and ensure that it is capable of handling seamless mode
+ * @param pHostCallback Host interface callback to notify of window configuration
+ * changes.
+ *
+ * @returns iprt status code
+ */
+ int init(PFNSENDREGIONUPDATE pHostCallback);
+
+ /**
+ * Shutdown seamless event monitoring.
+ */
+ void uninit(void);
+
+ /**
+ * Initialise seamless event reporting in the guest.
+ *
+ * @returns IPRT status code
+ */
+ int start(void);
+ /** Stop reporting seamless events. */
+ void stop(void);
+ /** Get the current list of visible rectangles. */
+ RTRECT *getRects(void);
+ /** Get the number of visible rectangles in the current list */
+ size_t getRectCount(void);
+
+ /** Process next event in the guest event queue - called by the event thread. */
+ void nextConfigurationEvent(void);
+ /** Wake up the event thread if it is waiting for an event so that it can exit. */
+ bool interruptEventWait(void);
+
+ /* Methods to handle X11 events. These are public so that the unit test
+ * can call them. */
+ void doConfigureEvent(Window hWin);
+ void doShapeEvent(Window hWin);
+
+ SeamlessX11(void)
+ : mHostCallback(NULL), mDisplay(NULL), mpRects(NULL), mcRects(0),
+ mSupportsShape(false), mEnabled(false), mChanged(false) {}
+
+ ~SeamlessX11()
+ {
+ uninit();
+ }
+
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+};
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.cpp b/src/VBox/Additions/x11/VBoxClient/seamless.cpp
new file mode 100644
index 00000000..365b31f6
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless.cpp
@@ -0,0 +1,360 @@
+/* $Id: seamless.cpp $ */
+/** @file
+ * X11 Guest client - seamless mode: main logic, communication with the host and
+ * wrapper interface for the main code of the VBoxClient deamon. The
+ * X11-specific parts are split out into their own file for ease of testing.
+ */
+
+/*
+ * Copyright (C) 2006-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 <new>
+
+#include <X11/Xlib.h>
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+
+#include "VBoxClient.h"
+#include "seamless.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Struct for keeping a service instance.
+ */
+struct SEAMLESSSERVICE
+{
+ /** Seamless service object. */
+ SeamlessMain mSeamless;
+};
+
+/** Service instance data. */
+static SEAMLESSSERVICE g_Svc;
+
+
+SeamlessMain::SeamlessMain(void)
+{
+ mX11MonitorThread = NIL_RTTHREAD;
+ mX11MonitorThreadStopping = false;
+
+ mMode = VMMDev_Seamless_Disabled;
+ mfPaused = true;
+}
+
+SeamlessMain::~SeamlessMain()
+{
+ /* Stopping will be done via main.cpp. */
+}
+
+/**
+ * Update the set of visible rectangles in the host.
+ */
+static void sendRegionUpdate(RTRECT *pRects, size_t cRects)
+{
+ if ( cRects
+ && !pRects) /* Assertion */
+ {
+ VBClLogError(("Region update called with NULL pointer\n"));
+ return;
+ }
+ VbglR3SeamlessSendRects(cRects, pRects);
+}
+
+/** @copydoc VBCLSERVICE::pfnInit */
+int SeamlessMain::init(void)
+{
+ int rc;
+ const char *pcszStage;
+
+ do
+ {
+ pcszStage = "Connecting to the X server";
+ rc = mX11Monitor.init(sendRegionUpdate);
+ if (RT_FAILURE(rc))
+ break;
+ pcszStage = "Setting guest IRQ filter mask";
+ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ break;
+ pcszStage = "Reporting support for seamless capability";
+ rc = VbglR3SeamlessSetCap(true);
+ if (RT_FAILURE(rc))
+ break;
+ rc = startX11MonitorThread();
+ if (RT_FAILURE(rc))
+ break;
+
+ } while(0);
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to start in stage '%s' -- error %Rrc\n", pcszStage, rc);
+
+ return rc;
+}
+
+/** @copydoc VBCLSERVICE::pfnWorker */
+int SeamlessMain::worker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* This will only exit if something goes wrong. */
+ for (;;)
+ {
+ if (ASMAtomicReadBool(pfShutdown))
+ break;
+
+ rc = nextStateChangeEvent();
+
+ if (rc == VERR_TRY_AGAIN)
+ rc = VINF_SUCCESS;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ASMAtomicReadBool(pfShutdown))
+ break;
+
+ /* If we are not stopping, sleep for a bit to avoid using up too
+ much CPU while retrying. */
+ RTThreadYield();
+ }
+
+ return rc;
+}
+
+/** @copydoc VBCLSERVICE::pfnStop */
+void SeamlessMain::stop(void)
+{
+ VbglR3SeamlessSetCap(false);
+ VbglR3CtlFilterMask(0, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST);
+ stopX11MonitorThread();
+}
+
+/** @copydoc VBCLSERVICE::pfnTerm */
+int SeamlessMain::term(void)
+{
+ mX11Monitor.uninit();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Waits for a seamless state change events from the host and dispatch it.
+ *
+ * @returns VBox return code, or
+ * VERR_TRY_AGAIN if no new status is available and we have to try it again
+ * at some later point in time.
+ */
+int SeamlessMain::nextStateChangeEvent(void)
+{
+ VMMDevSeamlessMode newMode = VMMDev_Seamless_Disabled;
+
+ int rc = VbglR3SeamlessWaitEvent(&newMode);
+ if (RT_SUCCESS(rc))
+ {
+ mMode = newMode;
+ switch (newMode)
+ {
+ case VMMDev_Seamless_Visible_Region:
+ /* A simplified seamless mode, obtained by making the host VM window
+ * borderless and making the guest desktop transparent. */
+ VBClLogVerbose(2, "\"Visible region\" mode requested\n");
+ break;
+ case VMMDev_Seamless_Disabled:
+ VBClLogVerbose(2, "\"Disabled\" mode requested\n");
+ break;
+ case VMMDev_Seamless_Host_Window:
+ /* One host window represents one guest window. Not yet implemented. */
+ VBClLogVerbose(2, "Unsupported \"host window\" mode requested\n");
+ return VERR_NOT_SUPPORTED;
+ default:
+ VBClLogError("Unsupported mode %d requested\n", newMode);
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_TRY_AGAIN)
+ {
+ if (mMode == VMMDev_Seamless_Visible_Region)
+ mfPaused = false;
+ else
+ mfPaused = true;
+ mX11Monitor.interruptEventWait();
+ }
+ else
+ VBClLogError("VbglR3SeamlessWaitEvent returned %Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * The actual X11 window configuration change monitor thread function.
+ */
+int SeamlessMain::x11MonitorThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+
+ SeamlessMain *pThis = (SeamlessMain *)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ RTThreadUserSignal(hThreadSelf);
+
+ VBClLogVerbose(2, "X11 monitor thread started\n");
+
+ while (!pThis->mX11MonitorThreadStopping)
+ {
+ if (!pThis->mfPaused)
+ {
+ rc = pThis->mX11Monitor.start();
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failed to change the X11 seamless service state, mfPaused=%RTbool, rc=%Rrc\n",
+ pThis->mfPaused, rc);
+ }
+
+ pThis->mX11Monitor.nextConfigurationEvent();
+
+ if ( pThis->mfPaused
+ || pThis->mX11MonitorThreadStopping)
+ {
+ pThis->mX11Monitor.stop();
+ }
+ }
+
+ VBClLogVerbose(2, "X11 monitor thread ended\n");
+
+ return rc;
+}
+
+/**
+ * Start the X11 window configuration change monitor thread.
+ */
+int SeamlessMain::startX11MonitorThread(void)
+{
+ mX11MonitorThreadStopping = false;
+
+ if (isX11MonitorThreadRunning())
+ return VINF_SUCCESS;
+
+ int rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThread, this, 0,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
+ "seamless x11");
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(mX11MonitorThread, RT_MS_30SEC);
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to start X11 monitor thread, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Stops the monitor thread.
+ */
+int SeamlessMain::stopX11MonitorThread(void)
+{
+ if (!isX11MonitorThreadRunning())
+ return VINF_SUCCESS;
+
+ mX11MonitorThreadStopping = true;
+ if (!mX11Monitor.interruptEventWait())
+ {
+ VBClLogError("Unable to notify X11 monitor thread\n");
+ return VERR_INVALID_STATE;
+ }
+
+ int rcThread;
+ int rc = RTThreadWait(mX11MonitorThread, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+
+ if (RT_SUCCESS(rc))
+ {
+ mX11MonitorThread = NIL_RTTHREAD;
+ }
+ else
+ VBClLogError("Waiting for X11 monitor thread to stop failed, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSeamlessInit(void)
+{
+ return g_Svc.mSeamless.init();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSeamlessWorker(bool volatile *pfShutdown)
+{
+ return g_Svc.mSeamless.worker(pfShutdown);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSeamlessStop(void)
+{
+ return g_Svc.mSeamless.stop();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclSeamlessTerm(void)
+{
+ return g_Svc.mSeamless.term();
+}
+
+VBCLSERVICE g_SvcSeamless =
+{
+ "seamless", /* szName */
+ "Seamless Mode Support", /* pszDescription */
+ ".vboxclient-seamless", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSeamlessInit, /* pfnInit */
+ vbclSeamlessWorker, /* pfnWorker */
+ vbclSeamlessStop, /* pfnStop*/
+ vbclSeamlessTerm /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.h b/src/VBox/Additions/x11/VBoxClient/seamless.h
new file mode 100644
index 00000000..3e9041a8
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless.h
@@ -0,0 +1,109 @@
+/* $Id: seamless.h $ */
+/** @file
+ * X11 Guest client - seamless mode, missing proper description while using the
+ * potentially confusing word 'host'.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_seamless_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_seamless_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/thread.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h> /* for the R3 guest library functions */
+
+#include "seamless-x11.h"
+
+/**
+ * Interface to the host
+ */
+class SeamlessMain
+{
+private:
+ // We don't want a copy constructor or assignment operator
+ SeamlessMain(const SeamlessMain&);
+ SeamlessMain& operator=(const SeamlessMain&);
+
+ /** X11 event monitor object */
+ SeamlessX11 mX11Monitor;
+
+ /** Thread to start and stop when we enter and leave seamless mode which
+ * monitors X11 windows in the guest. */
+ RTTHREAD mX11MonitorThread;
+ /** Should the X11 monitor thread be stopping? */
+ volatile bool mX11MonitorThreadStopping;
+
+ /** The current seamless mode we are in. */
+ VMMDevSeamlessMode mMode;
+ /** Is the service currently paused? */
+ volatile bool mfPaused;
+
+ /**
+ * Waits for a seamless state change events from the host and dispatch it. This is
+ * meant to be called by the host event monitor thread exclusively.
+ *
+ * @returns IRPT return code.
+ */
+ int nextStateChangeEvent(void);
+
+ /** Thread function to monitor X11 window configuration changes. */
+ static DECLCALLBACK(int) x11MonitorThread(RTTHREAD self, void *pvUser);
+
+ /** Helper to start the X11 monitor thread. */
+ int startX11MonitorThread(void);
+
+ /** Helper to stop the X11 monitor thread again. */
+ int stopX11MonitorThread(void);
+
+ /** Is the service currently actively monitoring X11 windows? */
+ bool isX11MonitorThreadRunning()
+ {
+ return mX11MonitorThread != NIL_RTTHREAD;
+ }
+
+public:
+ SeamlessMain(void);
+ virtual ~SeamlessMain();
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+
+ /** @copydoc VBCLSERVICE::pfnInit */
+ int init(void);
+
+ /** @copydoc VBCLSERVICE::pfnWorker */
+ int worker(bool volatile *pfShutdown);
+
+ /** @copydoc VBCLSERVICE::pfnStop */
+ void stop(void);
+
+ /** @copydoc VBCLSERVICE::pfnTerm */
+ int term(void);
+};
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup b/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup
diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp
new file mode 100644
index 00000000..0a90684d
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp
@@ -0,0 +1,791 @@
+/* $Id: tstSeamlessX11-auto.cpp $ */
+/** @file
+ * Automated test of the X11 seamless Additions code.
+ * @todo Better separate test data from implementation details!
+ */
+
+/*
+ * Copyright (C) 2007-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
+ */
+
+#include <stdlib.h> /* exit() */
+
+#include <X11/Xatom.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "../seamless.h"
+
+#undef DefaultRootWindow
+
+/******************************************************
+* Mock X11 functions needed by the seamless X11 class *
+******************************************************/
+
+int XFree(void *data)
+{
+ RTMemFree(data);
+ return 0;
+}
+
+#define TEST_DISPLAY ((Display *)0xffff)
+#define TEST_ROOT ((Window)1)
+
+void VBClLogError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Error: %s", psz);
+
+ RTStrFree(psz);
+}
+
+/** Exit with a fatal error. */
+void VBClLogFatalError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Fatal error: %s", psz);
+
+ RTStrFree(psz);
+
+ exit(1);
+}
+
+extern "C" Display *XOpenDisplay(const char *display_name);
+Display *XOpenDisplay(const char *display_name)
+{
+ RT_NOREF1(display_name);
+ return TEST_DISPLAY;
+}
+
+extern "C" int XCloseDisplay(Display *display);
+int XCloseDisplay(Display *display)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ return 0;
+}
+
+enum
+{
+ ATOM_PROP = 1,
+ ATOM_DESKTOP_PROP
+};
+
+extern "C" Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists);
+Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists)
+{
+ RT_NOREF2(only_if_exists, display);
+ Assert(display == TEST_DISPLAY);
+ if (!RTStrCmp(atom_name, WM_TYPE_PROP))
+ return (Atom) ATOM_PROP;
+ if (!RTStrCmp(atom_name, WM_TYPE_DESKTOP_PROP))
+ return (Atom) ATOM_DESKTOP_PROP;
+ AssertFailed();
+ return (Atom)0;
+}
+
+/** The window (if any) on which the WM_TYPE_PROP property is set to the
+ * WM_TYPE_DESKTOP_PROP atom. */
+static Window g_hSmlsDesktopWindow = 0;
+
+extern "C" int XGetWindowProperty(Display *display, Window w, Atom property,
+ long long_offset, long long_length,
+ Bool delProp, Atom req_type,
+ Atom *actual_type_return,
+ int *actual_format_return,
+ unsigned long *nitems_return,
+ unsigned long *bytes_after_return,
+ unsigned char **prop_return);
+int XGetWindowProperty(Display *display, Window w, Atom property,
+ long long_offset, long long_length, Bool delProp,
+ Atom req_type, Atom *actual_type_return,
+ int *actual_format_return,
+ unsigned long *nitems_return,
+ unsigned long *bytes_after_return,
+ unsigned char **prop_return)
+{
+ RT_NOREF2(display, long_length);
+ Assert(display == TEST_DISPLAY);
+ Atom atomType = XInternAtom (display, WM_TYPE_PROP, true);
+ Atom atomTypeDesktop = XInternAtom (display, WM_TYPE_DESKTOP_PROP, true);
+ /* We only handle things we expect. */
+ AssertReturn((req_type == XA_ATOM) || (req_type == AnyPropertyType),
+ 0xffff);
+ AssertReturn(property == atomType, 0xffff);
+ *actual_type_return = XA_ATOM;
+ *actual_format_return = sizeof(Atom) * 8;
+ *nitems_return = 0;
+ *bytes_after_return = sizeof(Atom);
+ *prop_return = NULL;
+ if ((w != g_hSmlsDesktopWindow) || (g_hSmlsDesktopWindow == 0))
+ return Success;
+ AssertReturn(long_offset == 0, 0);
+ AssertReturn(delProp == false, 0);
+ unsigned char *pProp;
+ pProp = (unsigned char *)RTMemDup(&atomTypeDesktop,
+ sizeof(atomTypeDesktop));
+ AssertReturn(pProp, 0xffff);
+ *nitems_return = 1;
+ *prop_return = pProp;
+ *bytes_after_return = 0;
+ return 0;
+}
+
+#if 0 /* unused */
+/** Sets the current set of properties for all mock X11 windows */
+static void smlsSetDesktopWindow(Window hWin)
+{
+ g_hSmlsDesktopWindow = hWin;
+}
+#endif
+
+extern "C" Bool XShapeQueryExtension(Display *dpy, int *event_basep, int *error_basep);
+Bool XShapeQueryExtension(Display *dpy, int *event_basep, int *error_basep)
+{
+ RT_NOREF3(dpy, event_basep, error_basep);
+ Assert(dpy == TEST_DISPLAY);
+ return true;
+}
+
+/* We silently ignore this for now. */
+extern "C" int XSelectInput(Display *display, Window w, long event_mask);
+int XSelectInput(Display *display, Window w, long event_mask)
+{
+ RT_NOREF3(display, w, event_mask);
+ Assert(display == TEST_DISPLAY);
+ return 0;
+}
+
+/* We silently ignore this for now. */
+extern "C" void XShapeSelectInput(Display *display, Window w, unsigned long event_mask);
+void XShapeSelectInput(Display *display, Window w, unsigned long event_mask)
+{
+ RT_NOREF3(display, w, event_mask);
+ Assert(display == TEST_DISPLAY);
+}
+
+extern "C" Window XDefaultRootWindow(Display *display);
+Window XDefaultRootWindow(Display *display)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ return TEST_ROOT;
+}
+
+static unsigned g_cSmlsWindows = 0;
+static Window *g_paSmlsWindows = NULL;
+static XWindowAttributes *g_paSmlsWinAttribs = NULL;
+static const char **g_papszSmlsWinNames = NULL;
+
+extern "C" Status XQueryTree(Display *display, Window w, Window *root_return,
+ Window *parent_return, Window **children_return,
+ unsigned int *nchildren_return);
+Status XQueryTree(Display *display, Window w, Window *root_return,
+ Window *parent_return, Window **children_return,
+ unsigned int *nchildren_return)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ AssertReturn(w == TEST_ROOT, False); /* We support nothing else */
+ AssertPtrReturn(children_return, False);
+ AssertReturn(g_paSmlsWindows, False);
+ if (root_return)
+ *root_return = TEST_ROOT;
+ if (parent_return)
+ *parent_return = TEST_ROOT;
+ *children_return = (Window *)RTMemDup(g_paSmlsWindows,
+ g_cSmlsWindows * sizeof(Window));
+ if (nchildren_return)
+ *nchildren_return = g_cSmlsWindows;
+ return (g_cSmlsWindows != 0);
+}
+
+extern "C" Window XmuClientWindow(Display *dpy, Window win);
+Window XmuClientWindow(Display *dpy, Window win)
+{
+ RT_NOREF1(dpy);
+ Assert(dpy == TEST_DISPLAY);
+ return win;
+}
+
+extern "C" Status XGetWindowAttributes(Display *display, Window w,
+ XWindowAttributes *window_attributes_return);
+Status XGetWindowAttributes(Display *display, Window w,
+ XWindowAttributes *window_attributes_return)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ AssertPtrReturn(window_attributes_return, 1);
+ for (unsigned i = 0; i < g_cSmlsWindows; ++i)
+ if (g_paSmlsWindows[i] == w)
+ {
+ *window_attributes_return = g_paSmlsWinAttribs[i];
+ return 1;
+ }
+ return 0;
+}
+
+extern "C" Status XGetWMNormalHints(Display *display, Window w,
+ XSizeHints *hints_return,
+ long *supplied_return);
+
+Status XGetWMNormalHints(Display *display, Window w,
+ XSizeHints *hints_return, long *supplied_return)
+{
+ RT_NOREF4(display, w, hints_return, supplied_return);
+ Assert(display == TEST_DISPLAY);
+ return 1;
+}
+
+static void smlsSetWindowAttributes(XWindowAttributes *pAttribs,
+ Window *pWindows, unsigned cAttribs,
+ const char **paNames)
+{
+ g_paSmlsWinAttribs = pAttribs;
+ g_paSmlsWindows = pWindows;
+ g_cSmlsWindows = cAttribs;
+ g_papszSmlsWinNames = paNames;
+}
+
+static Window g_SmlsShapedWindow = 0;
+static int g_cSmlsShapeRectangles = 0;
+static XRectangle *g_pSmlsShapeRectangles = NULL;
+
+extern "C" XRectangle *XShapeGetRectangles (Display *dpy, Window window,
+ int kind, int *count,
+ int *ordering);
+XRectangle *XShapeGetRectangles (Display *dpy, Window window, int kind,
+ int *count, int *ordering)
+{
+ RT_NOREF2(dpy, kind);
+ Assert(dpy == TEST_DISPLAY);
+ if ((window != g_SmlsShapedWindow) || (window == 0))
+ return NULL; /* Probably not correct, but works for us. */
+ *count = g_cSmlsShapeRectangles;
+ *ordering = 0;
+ return (XRectangle *)RTMemDup(g_pSmlsShapeRectangles,
+ sizeof(XRectangle)
+ * g_cSmlsShapeRectangles);
+}
+
+static void smlsSetShapeRectangles(Window window, int cRects,
+ XRectangle *pRects)
+{
+ g_SmlsShapedWindow = window;
+ g_cSmlsShapeRectangles = cRects;
+ g_pSmlsShapeRectangles = pRects;
+}
+
+static int g_SmlsEventType = 0;
+static Window g_SmlsEventWindow = 0;
+
+/* This should not be needed in the bits of the code we test. */
+extern "C" int XNextEvent(Display *display, XEvent *event_return);
+int XNextEvent(Display *display, XEvent *event_return)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ event_return->xany.type = g_SmlsEventType;
+ event_return->xany.window = g_SmlsEventWindow;
+ event_return->xmap.window = g_SmlsEventWindow;
+ return True;
+}
+
+/* Mock XPending(): this also should not be needed. Just in case, always
+ * return that at least one event is pending to be processed. */
+extern "C" int XPending(Display *display);
+int XPending(Display *display)
+{
+ RT_NOREF1(display);
+ return 1;
+}
+
+static void smlsSetNextEvent(int type, Window window)
+{
+ g_SmlsEventType = type;
+ g_SmlsEventWindow = window;
+}
+
+/* This should not be needed in the bits of the code we test. */
+extern "C" Status XSendEvent(Display *display, Window w, Bool propagate,
+ long event_mask, XEvent *event_send);
+Status XSendEvent(Display *display, Window w, Bool propagate,
+ long event_mask, XEvent *event_send)
+{
+ RT_NOREF5(display, w, propagate, event_mask, event_send);
+ Assert(display == TEST_DISPLAY);
+ AssertFailedReturn(0);
+}
+
+/* This should not be needed in the bits of the code we test. */
+extern "C" int XFlush(Display *display);
+int XFlush(Display *display)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ AssertFailedReturn(0);
+}
+
+/** Global "received a notification" flag. */
+static bool g_fNotified = false;
+
+/** Dummy host call-back. */
+static void sendRegionUpdate(RTRECT *pRects, size_t cRects)
+{
+ RT_NOREF2(pRects, cRects);
+ g_fNotified = true;
+}
+
+static bool gotNotification(void)
+{
+ if (!g_fNotified)
+ return false;
+ g_fNotified = false;
+ return true;
+}
+
+/*****************************
+* The actual tests to be run *
+*****************************/
+
+/** The name of the unit test */
+static const char *g_pszTestName = NULL;
+
+/*** Test fixture data and data structures ***/
+
+/** A structure describing a test fixture to be run through. Each fixture
+ * describes the state of the windows visible (and unmapped) on the X server
+ * before and after a particular event is delivered, and the expected
+ * on-screen positions of all interesting visible windows at the end of the
+ * fixture as reported by the code (currently in the order it is likely to
+ * report them in, @todo sort this). We expect that the set of visible
+ * windows will be the same whether we start the code before the event and
+ * handle it or start the code after the event.
+ */
+struct SMLSFIXTURE
+{
+ /** The number of windows visible before the event */
+ unsigned cWindowsBefore;
+ /** An array of Window IDs for the visible and unmapped windows before
+ * the event */
+ Window *pahWindowsBefore;
+ /** The window attributes matching the windows in @a paWindowsBefore */
+ XWindowAttributes *paAttribsBefore;
+ /** The window names matching the windows in @a paWindowsBefore */
+ const char **papszNamesBefore;
+ /** The shaped window before the event - we allow at most one of these.
+ * Zero for none. */
+ Window hShapeWindowBefore;
+ /** The number of rectangles in the shaped window before the event. */
+ int cShapeRectsBefore;
+ /** The rectangles in the shaped window before the event */
+ XRectangle *paShapeRectsBefore;
+ /** The number of windows visible after the event */
+ unsigned cWindowsAfter;
+ /** An array of Window IDs for the visible and unmapped windows after
+ * the event */
+ Window *pahWindowsAfter;
+ /** The window attributes matching the windows in @a paWindowsAfter */
+ XWindowAttributes *paAttribsAfter;
+ /** The window names matching the windows in @a paWindowsAfter */
+ const char **papszNamesAfter;
+ /** The shaped window after the event - we allow at most one of these.
+ * Zero for none. */
+ Window hShapeWindowAfter;
+ /** The number of rectangles in the shaped window after the event. */
+ int cShapeRectsAfter;
+ /** The rectangles in the shaped window after the event */
+ XRectangle *paShapeRectsAfter;
+ /** The event to delivered */
+ int x11EventType;
+ /** The window for which the event in @enmEvent is delivered */
+ Window hEventWindow;
+ /** The number of windows expected to be reported at the end of the
+ * fixture */
+ unsigned cReportedRects;
+ /** The onscreen positions of those windows. */
+ RTRECT *paReportedRects;
+ /** Do we expect notification after the event? */
+ bool fExpectNotification;
+};
+
+/*** Test fixture to test the code against X11 configure (move) events ***/
+
+static Window g_ahWin1[] = { 20 };
+static XWindowAttributes g_aAttrib1Before[] =
+{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
+};
+static XRectangle g_aRectangle1[] =
+{
+ { 0, 0, 50, 50 },
+ { 50, 50, 150, 250 }
+};
+static XWindowAttributes g_aAttrib1After[] =
+{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
+};
+static const char *g_apszNames1[] = { "Test Window" };
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1Before));
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1After));
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_apszNames1));
+
+static RTRECT g_aRects1[] =
+{
+ { 200, 300, 250, 350 },
+ { 250, 350, 400, 600 }
+};
+
+static SMLSFIXTURE g_testMove =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ ConfigureNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** Test fixture to test the code against X11 configure (resize) events ***/
+
+static XWindowAttributes g_aAttrib2Before[] =
+{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
+};
+static XRectangle g_aRectangle2Before[] =
+{
+ { 0, 0, 50, 50 },
+ { 50, 50, 100, 100 }
+};
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib2Before));
+
+static SMLSFIXTURE g_testResize =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib2Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle2Before),
+ g_aRectangle2Before,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ ConfigureNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** Test fixture to test the code against X11 map events ***/
+
+static XWindowAttributes g_aAttrib3Before[] =
+{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped }
+};
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib3Before));
+
+static SMLSFIXTURE g_testMap =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib3Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ MapNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** Test fixtures to test the code against X11 unmap events ***/
+
+static XWindowAttributes g_aAttrib4After[] =
+{ { 100, 200, 300, 400, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped }
+};
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib4After));
+
+static SMLSFIXTURE g_testUnmap =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib4After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ UnmapNotify,
+ 20,
+ 0,
+ NULL,
+ true
+};
+
+/*** A window we are not monitoring has been unmapped. Nothing should
+ *** happen, especially nothing bad. ***/
+
+static RTRECT g_aRects2[] =
+{
+ { 100, 200, 150, 250 },
+ { 150, 250, 300, 500 }
+};
+
+static SMLSFIXTURE g_testUnmapOther =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ UnmapNotify,
+ 21,
+ RT_ELEMENTS(g_aRects2),
+ g_aRects2,
+ false
+};
+
+/*** Test fixture to test the code against X11 shape events ***/
+
+static XRectangle g_aRectangle5Before[] =
+{
+ { 0, 0, 200, 200 }
+};
+
+static SMLSFIXTURE g_testShape =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle5Before),
+ g_aRectangle5Before,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ VBoxShapeNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** And the test code proper ***/
+
+/** Compare two RTRECT structures */
+static bool smlsCompRect(RTRECT *pFirst, RTRECT *pSecond)
+{
+ return ( (pFirst->xLeft == pSecond->xLeft)
+ && (pFirst->yTop == pSecond->yTop)
+ && (pFirst->xRight == pSecond->xRight)
+ && (pFirst->yBottom == pSecond->yBottom));
+}
+
+static void smlsPrintDiffRects(RTRECT *pExp, RTRECT *pGot)
+{
+ RTPrintf(" Expected: %d, %d, %d, %d. Got: %d, %d, %d, %d\n",
+ pExp->xLeft, pExp->yTop, pExp->xRight, pExp->yBottom,
+ pGot->xLeft, pGot->yTop, pGot->xRight, pGot->yBottom);
+}
+
+/** Run through a test fixture */
+static unsigned smlsDoFixture(SMLSFIXTURE *pFixture, const char *pszDesc)
+{
+ SeamlessX11 subject;
+ unsigned cErrs = 0;
+
+ subject.init(sendRegionUpdate);
+ smlsSetWindowAttributes(pFixture->paAttribsBefore,
+ pFixture->pahWindowsBefore,
+ pFixture->cWindowsBefore,
+ pFixture->papszNamesBefore);
+ smlsSetShapeRectangles(pFixture->hShapeWindowBefore,
+ pFixture->cShapeRectsBefore,
+ pFixture->paShapeRectsBefore);
+ subject.start();
+ smlsSetWindowAttributes(pFixture->paAttribsAfter,
+ pFixture->pahWindowsAfter,
+ pFixture->cWindowsAfter,
+ pFixture->papszNamesAfter);
+ smlsSetShapeRectangles(pFixture->hShapeWindowAfter,
+ pFixture->cShapeRectsAfter,
+ pFixture->paShapeRectsAfter);
+ smlsSetNextEvent(pFixture->x11EventType, pFixture->hEventWindow);
+ if (gotNotification()) /* Initial window tree rebuild */
+ {
+ RTPrintf("%s: fixture: %s. Notification was set before the first event!!!\n",
+ g_pszTestName, pszDesc);
+ ++cErrs;
+ }
+ subject.nextConfigurationEvent();
+ if (!gotNotification())
+ {
+ RTPrintf("%s: fixture: %s. No notification was sent for the initial window tree rebuild.\n",
+ g_pszTestName, pszDesc);
+ ++cErrs;
+ }
+ smlsSetNextEvent(0, 0);
+ subject.nextConfigurationEvent();
+ if (pFixture->fExpectNotification && !gotNotification())
+ {
+ RTPrintf("%s: fixture: %s. No notification was sent after the event.\n",
+ g_pszTestName, pszDesc);
+ ++cErrs;
+ }
+ RTRECT *pRects = subject.getRects();
+ size_t cRects = subject.getRectCount();
+ if (cRects != pFixture->cReportedRects)
+ {
+ RTPrintf("%s: fixture: %s. Wrong number of rectangles reported after processing event (expected %u, got %u).\n",
+ g_pszTestName, pszDesc, pFixture->cReportedRects,
+ cRects);
+ ++cErrs;
+ }
+ else
+ for (unsigned i = 0; i < cRects; ++i)
+ if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i]))
+ {
+ RTPrintf("%s: fixture: %s. Rectangle %u wrong after processing event.\n",
+ g_pszTestName, pszDesc, i);
+ smlsPrintDiffRects(&pFixture->paReportedRects[i],
+ &pRects[i]);
+ ++cErrs;
+ break;
+ }
+ subject.stop();
+ subject.start();
+ if (cRects != pFixture->cReportedRects)
+ {
+ RTPrintf("%s: fixture: %s. Wrong number of rectangles reported without processing event (expected %u, got %u).\n",
+ g_pszTestName, pszDesc, pFixture->cReportedRects,
+ cRects);
+ ++cErrs;
+ }
+ else
+ for (unsigned i = 0; i < cRects; ++i)
+ if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i]))
+ {
+ RTPrintf("%s: fixture: %s. Rectangle %u wrong without processing event.\n",
+ g_pszTestName, pszDesc, i);
+ smlsPrintDiffRects(&pFixture->paReportedRects[i],
+ &pRects[i]);
+ ++cErrs;
+ break;
+ }
+ return cErrs;
+}
+
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+ unsigned cErrs = 0;
+ g_pszTestName = RTPathFilename(argv[0]);
+
+ RTPrintf("%s: TESTING\n", g_pszTestName);
+
+/** @todo r=bird: This testcase is broken and we didn't notice because we
+ * don't run it on the testboxes! @bugref{9842} */
+if (argc == 1)
+{
+ RTPrintf("%s: Note! This testcase is broken, skipping!\n", g_pszTestName);
+ return RTEXITCODE_SUCCESS;
+}
+
+ cErrs += smlsDoFixture(&g_testMove,
+ "ConfigureNotify event (window moved)");
+ // Currently not working
+ cErrs += smlsDoFixture(&g_testResize,
+ "ConfigureNotify event (window resized)");
+ cErrs += smlsDoFixture(&g_testMap, "MapNotify event");
+ cErrs += smlsDoFixture(&g_testUnmap, "UnmapNotify event");
+ cErrs += smlsDoFixture(&g_testUnmapOther,
+ "UnmapNotify event for unmonitored window");
+ cErrs += smlsDoFixture(&g_testShape, "ShapeNotify event");
+ if (cErrs > 0)
+ RTPrintf("%u errors\n", cErrs);
+ return cErrs == 0 ? 0 : 1;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp
new file mode 100644
index 00000000..06bd5c8e
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp
@@ -0,0 +1,185 @@
+/* $Id: tstSeamlessX11.cpp $ */
+/** @file
+ * Linux seamless guest additions simulator in host.
+ */
+
+/*
+ * Copyright (C) 2007-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
+ */
+
+#include <stdlib.h> /* exit() */
+
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <VBox/VBoxGuestLib.h>
+
+#include "../seamless.h"
+
+static RTSEMEVENT eventSem;
+
+void VBClLogError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Error: %s", psz);
+
+ RTStrFree(psz);
+}
+
+/** Exit with a fatal error. */
+void VBClLogFatalError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Fatal error: %s", psz);
+
+ RTStrFree(psz);
+
+ exit(1);
+}
+
+void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...)
+{
+ RT_NOREF(iLevel);
+
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintf("%s", pszFormat);
+ va_end(va);
+}
+
+int VBClStartVTMonitor()
+{
+ return VINF_SUCCESS;
+}
+
+int VbglR3SeamlessSendRects(uint32_t cRects, PRTRECT pRects)
+{
+ RTPrintf("Received rectangle update (%u rectangles):\n", cRects);
+ for (unsigned i = 0; i < cRects; ++i)
+ {
+ RTPrintf(" xLeft: %d yTop: %d xRight: %d yBottom: %d\n",
+ pRects[i].xLeft, pRects[i].yTop, pRects[i].xRight,
+ pRects[i].yBottom);
+ }
+ return VINF_SUCCESS;
+}
+
+int VbglR3SeamlessSetCap(bool bState)
+{
+ RTPrintf("%s\n", bState ? "Seamless capability set"
+ : "Seamless capability unset");
+ return VINF_SUCCESS;
+}
+
+int VbglR3CtlFilterMask(uint32_t u32OrMask, uint32_t u32NotMask)
+{
+ RTPrintf("IRQ filter mask changed. Or mask: 0x%x. Not mask: 0x%x\n",
+ u32OrMask, u32NotMask);
+ return VINF_SUCCESS;
+}
+
+int VbglR3SeamlessWaitEvent(VMMDevSeamlessMode *pMode)
+{
+ static bool active = false;
+
+ int rc = VINF_SUCCESS;
+ if (!active)
+ {
+ active = true;
+ *pMode = VMMDev_Seamless_Visible_Region;
+ }
+ else
+ rc = RTSemEventWait(eventSem, RT_INDEFINITE_WAIT);
+ return rc;
+}
+
+VBGLR3DECL(int) VbglR3InitUser(void) { return VINF_SUCCESS; }
+VBGLR3DECL(void) VbglR3Term(void) {}
+
+/**
+ * Xlib error handler for certain errors that we can't avoid.
+ */
+int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError)
+{
+ char errorText[1024];
+
+ if (pError->error_code == BadWindow)
+ {
+ /* This can be triggered if a guest application destroys a window before we notice. */
+ RTPrintf("ignoring BadAtom error and returning\n");
+ return 0;
+ }
+ XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText));
+ RTPrintf("An X Window protocol error occurred: %s\n"
+ " Request code: %d\n"
+ " Minor code: %d\n"
+ " Serial number of the failed request: %d\n\n"
+ "exiting.\n",
+ errorText, (int)pError->request_code, (int)pError->minor_code,
+ (int)pError->serial);
+ exit(1);
+}
+
+int main( int argc, char **argv)
+{
+ int rc = VINF_SUCCESS;
+
+ RTR3InitExe(argc, &argv, 0);
+ RTPrintf("VirtualBox guest additions X11 seamless mode testcase\n");
+ if (0 == XInitThreads())
+ {
+ RTPrintf("Failed to initialise X11 threading, exiting.\n");
+ exit(1);
+ }
+ /* Set an X11 error handler, so that we don't die when we get unavoidable errors. */
+ XSetErrorHandler(vboxClientXLibErrorHandler);
+ RTPrintf("\nType Ctrl-C to exit...\n");
+ RTSemEventCreate(&eventSem);
+ /** Our instance of the seamless class. */
+ SeamlessMain seamless;
+ LogRel(("Starting seamless Guest Additions...\n"));
+ rc = seamless.init();
+ if (rc != VINF_SUCCESS)
+ {
+ RTPrintf("Failed to initialise seamless Additions, rc = %Rrc\n", rc);
+ }
+ bool fShutdown = false;
+ rc = seamless.worker(&fShutdown);
+ if (rc != VINF_SUCCESS)
+ {
+ RTPrintf("Failed to run seamless Additions, rc = %Rrc\n", rc);
+ }
+ return rc;
+}