diff options
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient')
17 files changed, 7874 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..d5de3c88 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk @@ -0,0 +1,141 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VirtualBox Guest Addition X11 Client. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# VBoxClient - clipboard and seamless. +# +PROGRAMS += VBoxClient + +VBoxClient_TEMPLATE = NewVBoxGuestR3Exe +VBoxClient_DEFS += VBOX_X11_CLIPBOARD VBOX_WITH_HGCM +ifdef VBOX_WITH_DBUS + VBoxClient_DEFS += VBOX_WITH_DBUS +endif +VBoxClient_DEFS.linux += _GNU_SOURCE +VBoxClient_INCS = $(VBOX_GRAPHICS_INCS) +VBoxClient_SOURCES = \ + main.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp \ + clipboard.cpp \ + display-svga.cpp \ + display-svga-x11.cpp \ + seamless.cpp \ + seamless-x11.cpp \ + display.cpp \ + hostversion.cpp \ + check3d.cpp +VBoxClient_SOURCES.linux = \ + chk_stubs.c +VBoxClient_LIBPATH = \ + $(VBOX_LIBPATH32_X11) +VBoxClient_LIBS.freebsd = \ + iconv +VBoxClient_LIBS.linux = \ + dl +VBoxClient_LIBS.solaris = \ + dl +VBoxClient_LIBS = \ + X11 Xrandr Xt Xext Xmu + +# 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 + +# These are static replacements for gcc-specific parts of libstdc++ +VBoxClient_LIBS += \ + supc++ \ + gcc_eh + +# 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 +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 + +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 + 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..4feb09de --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h @@ -0,0 +1,88 @@ +/* $Id: VBoxClient.h $ */ +/** @file + * + * VirtualBox additions user session daemon. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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> + +/** Exit with a fatal error. */ +#define VBClFatalError(format) \ +do { \ + char *pszMessage = RTStrAPrintf2 format; \ + LogRel(format); \ + vbclFatalError(pszMessage); \ +} while(0) + +/** Exit with a fatal error. */ +extern DECLNORETURN(void) vbclFatalError(char *pszMessage); + +/** Call clean-up for the current service and exit. */ +extern void VBClCleanUp(bool fExit = true); + +/** A simple interface describing a service. VBoxClient will run exactly one + * service per invocation. */ +struct VBCLSERVICE +{ + /** 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 *(*getPidFilePath)(void); + /** Special initialisation, if needed. @a pause and @a resume are + * guaranteed not to be called until after this returns. */ + int (*init)(struct VBCLSERVICE **ppInterface); + /** Run the service main loop */ + int (*run)(struct VBCLSERVICE **ppInterface, bool fDaemonised); + /** Clean up any global resources before we shut down hard. The last calls + * to @a pause and @a resume are guaranteed to finish before this is called. + */ + void (*cleanup)(struct VBCLSERVICE **ppInterface); +}; + +/** Default handler for various struct VBCLSERVICE member functions. */ +DECLINLINE(int) VBClServiceDefaultHandler(struct VBCLSERVICE **pSelf) +{ + RT_NOREF1(pSelf); + return VINF_SUCCESS; +} + +/** Default handler for the struct VBCLSERVICE clean-up member function. + * Usually used because the service is cleaned up automatically when the user + * process/X11 exits. */ +DECLINLINE(void) VBClServiceDefaultCleanup(struct VBCLSERVICE **ppInterface) +{ + NOREF(ppInterface); +} + +extern struct VBCLSERVICE **VBClGetClipboardService(); +extern struct VBCLSERVICE **VBClGetSeamlessService(); +extern struct VBCLSERVICE **VBClGetDisplayService(); +extern struct VBCLSERVICE **VBClGetHostVersionService(); +#ifdef VBOX_WITH_DRAG_AND_DROP +extern struct VBCLSERVICE **VBClGetDragAndDropService(); +#endif /* VBOX_WITH_DRAG_AND_DROP */ +extern struct VBCLSERVICE **VBClCheck3DService(); +extern struct VBCLSERVICE **VBClDisplaySVGAService(); +extern struct VBCLSERVICE **VBClDisplaySVGAX11Service(); + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/check3d.cpp b/src/VBox/Additions/x11/VBoxClient/check3d.cpp new file mode 100644 index 00000000..d091f369 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/check3d.cpp @@ -0,0 +1,73 @@ +/* $Id: check3d.cpp $ */ +/** @file + * X11 guest client - 3D pass-through check. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "VBoxClient.h" + +#include <VBox/VBoxGuest.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/VBoxCrOpenGLSvc.h> + +#include <stdlib.h> + +#define CR_VBOX_CAP_HOST_CAPS_NOT_SUFFICIENT 0x00000020 + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + int rc; + HGCMCLIENTID idClient; + CRVBOXHGCMGETCAPS caps; + LogFlowFunc(("\n")); + + NOREF(ppInterface); + NOREF(fDaemonised); + /* Initialise the guest library. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + exit(1); + rc = VbglR3HGCMConnect("VBoxSharedCrOpenGL", &idClient); + if (RT_FAILURE(rc)) + exit(1); + + VBGL_HGCM_HDR_INIT(&caps.hdr, idClient, SHCRGL_GUEST_FN_GET_CAPS_LEGACY, SHCRGL_CPARMS_GET_CAPS_LEGACY); + caps.Caps.type = VMMDevHGCMParmType_32bit; + caps.Caps.u.value32 = 0; + rc = VbglR3HGCMCall(&caps.hdr, sizeof(caps)); + if (RT_FAILURE(rc)) + exit(1); + if (caps.Caps.u.value32 & CR_VBOX_CAP_HOST_CAPS_NOT_SUFFICIENT) + exit(1); + VbglR3HGCMDisconnect(idClient); + VbglR3Term(); + LogFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + +static struct VBCLSERVICE g_vbclCheck3DInterface = +{ + NULL, + VBClServiceDefaultHandler, /* init */ + run, + VBClServiceDefaultCleanup +}; +static struct VBCLSERVICE *g_pvbclCheck3DInterface = &g_vbclCheck3DInterface; + +/* Static factory */ +struct VBCLSERVICE **VBClCheck3DService() +{ + return &g_pvbclCheck3DInterface; +} + 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..1a794469 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c @@ -0,0 +1,59 @@ +/* $Id: chk_stubs.c $ */ +/** @file + * glibc stubs for the VirtualBox Guest Addition X11 Client. + */ + +/* + * Copyright (C) 2018-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* 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..145eba9c --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp @@ -0,0 +1,335 @@ +/** $Id: clipboard.cpp $ */ +/** @file + * Guest Additions - X11 Shared Clipboard. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloc.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/string.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 "VBoxClient.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** + * Global clipboard context information. + */ +struct _VBOXCLIPBOARDCONTEXT +{ + /** Client ID for the clipboard subsystem */ + uint32_t client; + + /** Pointer to the X11 clipboard backend */ + CLIPBACKEND *pBackend; +}; + +/** Only one client is supported. There seems to be no need for more clients. */ +static VBOXCLIPBOARDCONTEXT g_ctx; + + +/** + * Transfer clipboard data from the guest to the host. + * + * @returns VBox result code + * @param u32Format The format of the data being sent + * @param pv Pointer to the data being sent + * @param cb Size of the data being sent in bytes + */ +static int vboxClipboardSendData(uint32_t u32Format, void *pv, uint32_t cb) +{ + int rc; + LogRelFlowFunc(("u32Format=%d, pv=%p, cb=%d\n", u32Format, pv, cb)); + rc = VbglR3ClipboardWriteData(g_ctx.client, u32Format, pv, cb); + LogRelFlowFunc(("rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Get clipboard data from the host. + * + * @returns VBox result code + * @param pCtx Our context information + * @param u32Format The format of the data being requested + * @retval ppv On success and if pcb > 0, this will point to a buffer + * to be freed with RTMemFree containing the data read. + * @retval pcb On success, this contains the number of bytes of data + * returned + */ +int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format, void **ppv, uint32_t *pcb) +{ + RT_NOREF1(pCtx); + int rc = VINF_SUCCESS; + uint32_t cb = 1024; + void *pv = RTMemAlloc(cb); + + *ppv = 0; + LogRelFlowFunc(("u32Format=%u\n", u32Format)); + if (RT_UNLIKELY(!pv)) + rc = VERR_NO_MEMORY; + if (RT_SUCCESS(rc)) + rc = VbglR3ClipboardReadData(g_ctx.client, u32Format, pv, cb, pcb); + if (RT_SUCCESS(rc) && (rc != VINF_BUFFER_OVERFLOW)) + *ppv = pv; + /* 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) + { + cb = *pcb; + RTMemFree(pv); + pv = RTMemAlloc(cb); + if (RT_UNLIKELY(!pv)) + rc = VERR_NO_MEMORY; + if (RT_SUCCESS(rc)) + rc = VbglR3ClipboardReadData(g_ctx.client, u32Format, pv, cb, pcb); + if (RT_SUCCESS(rc) && (rc != VINF_BUFFER_OVERFLOW)) + *ppv = pv; + } + /* 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. + */ + if (RT_FAILURE(rc) || (VINF_BUFFER_OVERFLOW == rc)) + { + *pcb = 0; + if (pv != NULL) + RTMemFree(pv); + } + LogRelFlowFunc(("returning %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + LogRelFlow((" *pcb=%d\n", *pcb)); + 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. */ + uint32_t u32Format; +}; + +/** + * Tell the host that new clipboard formats are available. + * + * @param pCtx Our context information + * @param u32Formats The formats to advertise + */ +void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Formats) +{ + RT_NOREF1(pCtx); + LogRelFlowFunc(("u32Formats=%d\n", u32Formats)); + int rc = VbglR3ClipboardReportFormats(g_ctx.client, u32Formats); + LogRelFlowFunc(("rc=%Rrc\n", rc)); +} + +/** This is called by the backend to tell us that a request for data from + * X11 has completed. + * @param pCtx Our context information + * @param rc the iprt result code of the request + * @param pReq the request structure that we passed in when we started + * the request. We RTMemFree() this in this function. + * @param pv the clipboard data returned from X11 if the request + * succeeded (see @a rc) + * @param cb the size of the data in @a pv + */ +void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb) +{ + RT_NOREF1(pCtx); + if (RT_SUCCESS(rc)) + vboxClipboardSendData(pReq->u32Format, pv, cb); + else + vboxClipboardSendData(0, NULL, 0); + RTMemFree(pReq); +} + +/** + * Connect the guest clipboard to the host. + * + * @returns VBox status code + */ +int vboxClipboardConnect(void) +{ + int rc = VINF_SUCCESS; + LogRelFlowFunc(("\n")); + + /* Sanity */ + AssertReturn(g_ctx.client == 0, VERR_WRONG_ORDER); + g_ctx.pBackend = ClipConstructX11(&g_ctx, false); + if (!g_ctx.pBackend) + rc = VERR_NO_MEMORY; + if (RT_SUCCESS(rc)) + rc = ClipStartX11(g_ctx.pBackend); + if (RT_SUCCESS(rc)) + { + rc = VbglR3ClipboardConnect(&g_ctx.client); + if (RT_FAILURE(rc)) + LogRel(("Error connecting to host. rc=%Rrc\n", rc)); + else if (!g_ctx.client) + { + LogRel(("Invalid client ID of 0\n")); + rc = VERR_NOT_SUPPORTED; + } + } + + if (rc != VINF_SUCCESS && g_ctx.pBackend) + ClipDestructX11(g_ctx.pBackend); + LogRelFlowFunc(("g_ctx.client=%u rc=%Rrc\n", g_ctx.client, rc)); + return rc; +} + +/** + * The main loop of our clipboard reader. + */ +int vboxClipboardMain(void) +{ + int rc; + LogRelFlowFunc(("Starting guest clipboard service\n")); + bool fExiting = false; + + while (!fExiting) + { + uint32_t Msg; + uint32_t fFormats; + rc = VbglR3ClipboardGetHostMsg(g_ctx.client, &Msg, &fFormats); + if (RT_SUCCESS(rc)) + { + switch (Msg) + { + case VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS: + { + /* The host has announced available clipboard formats. + * Save the information so that it is available for + * future requests from guest applications. + */ + LogRelFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS fFormats=%x\n", fFormats)); + ClipAnnounceFormatToX11(g_ctx.pBackend, fFormats); + break; + } + + case VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA: + { + /* The host needs data in the specified format. */ + LogRelFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA fFormats=%x\n", fFormats)); + CLIPREADCBREQ *pReq; + pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(*pReq)); + if (!pReq) + { + rc = VERR_NO_MEMORY; + fExiting = true; + } + else + { + pReq->u32Format = fFormats; + ClipRequestDataFromX11(g_ctx.pBackend, fFormats, + pReq); + } + break; + } + + case VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT: + { + /* The host is terminating. */ + LogRelFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT\n")); + if (RT_SUCCESS(ClipStopX11(g_ctx.pBackend))) + ClipDestructX11(g_ctx.pBackend); + fExiting = true; + break; + } + + default: + LogRel2(("Unsupported message from host!!!\n")); + } + } + + LogRelFlow(("processed host event rc = %d\n", rc)); + } + LogRelFlowFunc(("rc=%d\n", rc)); + return rc; +} + +static const char *getPidFilePath() +{ + return ".vboxclient-clipboard.pid"; +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + RT_NOREF2(ppInterface, fDaemonised); + + /* Initialise the guest library. */ + int rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc)); + rc = vboxClipboardConnect(); + /* Not RT_SUCCESS: VINF_PERMISSION_DENIED is host service not present. */ + if (rc == VINF_SUCCESS) + rc = vboxClipboardMain(); + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; /* Prevent automatic restart. */ + if (RT_FAILURE(rc)) + LogRelFunc(("guest clipboard service terminated abnormally: return code %Rrc\n", rc)); + return rc; +} + +static void cleanup(struct VBCLSERVICE **ppInterface) +{ + NOREF(ppInterface); + VbglR3Term(); +} + +struct VBCLSERVICE vbclClipboardInterface = +{ + getPidFilePath, + VBClServiceDefaultHandler, /* init */ + run, + cleanup +}; + +struct CLIPBOARDSERVICE +{ + struct VBCLSERVICE *pInterface; +}; + +struct VBCLSERVICE **VBClGetClipboardService() +{ + struct CLIPBOARDSERVICE *pService = + (struct CLIPBOARDSERVICE *)RTMemAlloc(sizeof(*pService)); + + if (!pService) + VBClFatalError(("Out of memory\n")); + pService->pInterface = &vbclClipboardInterface; + return &pService->pInterface; +} 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..08b3194e --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp @@ -0,0 +1,320 @@ +/* $Id: display-svga-x11.cpp $ */ +/** @file + * X11 guest client - VMSVGA emulation resize event pass-through to X.Org + * guest driver. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* + * 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. + */ + +#include "VBoxClient.h" + +#include <VBox/VBoxGuestLib.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/string.h> + +#include <sys/utsname.h> + +/** 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 + +/* VMWare X.Org driver control parts definitions. */ + +#include <X11/Xlibint.h> + +static bool checkRecentLinuxKernel(void) +{ + struct utsname name; + + if (uname(&name)) + VBClFatalError(("Failed to get kernel name.\n")); + if (strcmp(name.sysname, "Linux")) + return false; + return (RTStrVersionCompare(name.release, "4.6") >= 0); +} + +struct X11CONTEXT +{ + Display *pDisplay; + int hRandRMajor; + int hVMWMajor; +}; + +static void x11Connect(struct X11CONTEXT *pContext) +{ + int dummy; + + if (pContext->pDisplay != NULL) + VBClFatalError(("%s called with bad argument\n", __func__)); + pContext->pDisplay = XOpenDisplay(NULL); + if (pContext->pDisplay == NULL) + return; + if ( !XQueryExtension(pContext->pDisplay, "RANDR", + &pContext->hRandRMajor, &dummy, &dummy) + || !XQueryExtension(pContext->pDisplay, "VMWARE_CTRL", + &pContext->hVMWMajor, &dummy, &dummy)) + { + XCloseDisplay(pContext->pDisplay); + pContext->pDisplay = NULL; + } +} + +#define X11_VMW_TOPOLOGY_REQ 2 +struct X11VMWRECT /* xXineramaScreenInfo in Xlib headers. */ +{ + int16_t x; + int16_t y; + uint16_t w; + uint16_t h; +}; +AssertCompileSize(struct X11VMWRECT, 8); + +struct X11REQHEADER +{ + uint8_t hMajor; + uint8_t idType; + uint16_t cd; +}; + +struct X11VMWTOPOLOGYREQ +{ + struct X11REQHEADER header; + uint32_t idX11Screen; + uint32_t cScreens; + uint32_t u32Pad; + struct X11VMWRECT aRects[1]; +}; +AssertCompileSize(struct X11VMWTOPOLOGYREQ, 24); + +#define X11_VMW_TOPOLOGY_REPLY_SIZE 32 + +#define X11_VMW_RESOLUTION_REQUEST 1 +struct X11VMWRESOLUTIONREQ +{ + struct X11REQHEADER header; + uint32_t idX11Screen; + uint32_t w; + uint32_t h; +}; +AssertCompileSize(struct X11VMWRESOLUTIONREQ, 16); + +#define X11_VMW_RESOLUTION_REPLY_SIZE 32 + +#define X11_RANDR_GET_SCREEN_REQUEST 5 +struct X11RANDRGETSCREENREQ +{ + struct X11REQHEADER header; + uint32_t hWindow; +}; +AssertCompileSize(struct X11RANDRGETSCREENREQ, 8); + +#define X11_RANDR_GET_SCREEN_REPLY_SIZE 32 + +/* This was a macro in old Xlib versions and a function in newer ones; the + * display members touched by the macro were declared as ABI for compatibility + * reasons. To simplify building with different generations, we duplicate the + * code. */ +static void x11GetRequest(struct X11CONTEXT *pContext, uint8_t hMajor, + uint8_t idType, size_t cb, struct X11REQHEADER **ppReq) +{ + if (pContext->pDisplay->bufptr + cb > pContext->pDisplay->bufmax) + _XFlush(pContext->pDisplay); + if (pContext->pDisplay->bufptr + cb > pContext->pDisplay->bufmax) + VBClFatalError(("%s display buffer overflow.\n", __func__)); + if (cb % 4 != 0) + VBClFatalError(("%s bad parameter.\n", __func__)); + pContext->pDisplay->last_req = pContext->pDisplay->bufptr; + *ppReq = (struct X11REQHEADER *)pContext->pDisplay->bufptr; + (*ppReq)->hMajor = hMajor; + (*ppReq)->idType = idType; + (*ppReq)->cd = cb / 4; + pContext->pDisplay->bufptr += cb; + pContext->pDisplay->request++; +} + +static void x11SendHints(struct X11CONTEXT *pContext, struct X11VMWRECT *pRects, + unsigned cRects) +{ + unsigned i; + struct X11VMWTOPOLOGYREQ *pReqTopology; + uint8_t repTopology[X11_VMW_TOPOLOGY_REPLY_SIZE]; + struct X11VMWRESOLUTIONREQ *pReqResolution; + uint8_t repResolution[X11_VMW_RESOLUTION_REPLY_SIZE]; + + if (!VALID_PTR(pContext->pDisplay)) + VBClFatalError(("%s bad display argument.\n", __func__)); + if (cRects == 0) + return; + /* Try a topology (multiple screen) request. */ + x11GetRequest(pContext, pContext->hVMWMajor, X11_VMW_TOPOLOGY_REQ, + sizeof(struct X11VMWTOPOLOGYREQ) + + sizeof(struct X11VMWRECT) * (cRects - 1), + (struct X11REQHEADER **)&pReqTopology); + pReqTopology->idX11Screen = DefaultScreen(pContext->pDisplay); + pReqTopology->cScreens = cRects; + for (i = 0; i < cRects; ++i) + pReqTopology->aRects[i] = pRects[i]; + _XSend(pContext->pDisplay, NULL, 0); + if (_XReply(pContext->pDisplay, (xReply *)&repTopology, 0, xTrue)) + return; + /* That failed, so try the old single screen set resolution. We prefer + * simpler code to negligeably improved efficiency, so we just always try + * both requests instead of doing version checks or caching. */ + x11GetRequest(pContext, pContext->hVMWMajor, X11_VMW_RESOLUTION_REQUEST, + sizeof(struct X11VMWTOPOLOGYREQ), + (struct X11REQHEADER **)&pReqResolution); + pReqResolution->idX11Screen = DefaultScreen(pContext->pDisplay); + pReqResolution->w = pRects[0].w; + pReqResolution->h = pRects[0].h; + if (_XReply(pContext->pDisplay, (xReply *)&repResolution, 0, xTrue)) + return; + /* What now? */ + VBClFatalError(("%s failed to set resolution\n", __func__)); +} + +/** Call RRGetScreenInfo to wake up the server to the new modes. */ +static void x11GetScreenInfo(struct X11CONTEXT *pContext) +{ + struct X11RANDRGETSCREENREQ *pReqGetScreen; + uint8_t repGetScreen[X11_RANDR_GET_SCREEN_REPLY_SIZE]; + + if (!VALID_PTR(pContext->pDisplay)) + VBClFatalError(("%s bad display argument.\n", __func__)); + x11GetRequest(pContext, pContext->hRandRMajor, X11_RANDR_GET_SCREEN_REQUEST, + sizeof(struct X11RANDRGETSCREENREQ), + (struct X11REQHEADER **)&pReqGetScreen); + pReqGetScreen->hWindow = DefaultRootWindow(pContext->pDisplay); + _XSend(pContext->pDisplay, NULL, 0); + if (!_XReply(pContext->pDisplay, (xReply *)&repGetScreen, 0, xTrue)) + VBClFatalError(("%s failed to set resolution\n", __func__)); +} + +static const char *getPidFilePath() +{ + return ".vboxclient-display-svga-x11.pid"; +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + (void)ppInterface; + (void)fDaemonised; + struct X11CONTEXT x11Context = { NULL }; + unsigned i; + int rc; + uint32_t acx[VMW_MAX_HEADS] = { 0 }; + uint32_t acy[VMW_MAX_HEADS] = { 0 }; + uint32_t adx[VMW_MAX_HEADS] = { 0 }; + uint32_t ady[VMW_MAX_HEADS] = { 0 }; + uint32_t afEnabled[VMW_MAX_HEADS] = { false }; + struct X11VMWRECT aRects[VMW_MAX_HEADS]; + unsigned cHeads; + + if (checkRecentLinuxKernel()) + return VINF_SUCCESS; + x11Connect(&x11Context); + if (x11Context.pDisplay == NULL) + return VINF_SUCCESS; + /* Initialise the guest library. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc)); + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to request display change events, rc=%Rrc\n", rc)); + rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false); + if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */ + return VINF_SUCCESS; + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to register resizing support, rc=%Rrc\n", rc)); + for (;;) + { + uint32_t events; + + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events); + if (RT_FAILURE(rc)) + VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc)); + while (rc != VERR_TIMEOUT) + { + uint32_t cx, cy, cBits, dx, dy, idx; + bool fEnabled, fChangeOrigin; + + rc = VbglR3GetDisplayChangeRequest(&cx, &cy, &cBits, &idx, &dx, &dy, &fEnabled, &fChangeOrigin, true); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to get display change request, rc=%Rrc\n", rc)); + if (idx < VMW_MAX_HEADS) + { + acx[idx] = cx; + acy[idx] = cy; + if (fChangeOrigin) + adx[idx] = dx < INT32_MAX ? dx : 0; + if (fChangeOrigin) + ady[idx] = dy < INT32_MAX ? dy : 0; + afEnabled[idx] = fEnabled; + } + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0, &events); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT && rc != VERR_INTERRUPTED) + VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc)); + } + for (i = 0, cHeads = 0; i < VMW_MAX_HEADS; ++i) + { + if (afEnabled[i]) + { + aRects[cHeads].x = (int16_t)adx[i]; + aRects[cHeads].y = (int16_t)ady[i]; + aRects[cHeads].w = (uint16_t)acx[i]; + aRects[cHeads].h = (uint16_t)acy[i]; + ++cHeads; + } + } + x11SendHints(&x11Context, aRects, cHeads); + x11GetScreenInfo(&x11Context); + } +} + +static struct VBCLSERVICE interface = +{ + getPidFilePath, + VBClServiceDefaultHandler, /* Init */ + run, + VBClServiceDefaultCleanup +}, *pInterface = &interface; + +struct VBCLSERVICE **VBClDisplaySVGAX11Service() +{ + return &pInterface; +} diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga.cpp new file mode 100644 index 00000000..15252591 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-svga.cpp @@ -0,0 +1,263 @@ +/* $Id: display-svga.cpp $ */ +/** @file + * X11 guest client - VMSVGA emulation resize event pass-through to drm guest + * driver. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* + * 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. + */ + +#include "VBoxClient.h" + +#include <VBox/VBoxGuestLib.h> + +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/string.h> + +/** 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 + +/* VMWare kernel driver control parts definitions. */ + +#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 + +#define DRM_DRIVER_NAME "vmwgfx" + +/** 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 *)); + +/** Rectangle structure for geometry of a single screen. */ +struct DRMVMWRECT +{ + int32_t x; + int32_t y; + uint32_t w; + uint32_t h; +}; +AssertCompileSize(struct DRMVMWRECT, 16); + +#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION) + +struct DRMCONTEXT +{ + RTFILE hDevice; +}; + +static void drmConnect(struct DRMCONTEXT *pContext) +{ + unsigned i; + RTFILE hDevice; + + if (pContext->hDevice != NIL_RTFILE) + VBClFatalError(("%s called with bad argument\n", __func__)); + /* Try to open the SVGA DRM device. */ + for (i = 0; i < 128; ++i) + { + char szPath[64]; + struct DRMVERSION version; + char szName[sizeof(DRM_DRIVER_NAME)]; + int rc; + + /* 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. Try + * both types. */ + if (i % 2 == 0) + rc = RTStrPrintf(szPath, sizeof(szPath), "/dev/dri/renderD%u", i / 2 + 128); + else + rc = RTStrPrintf(szPath, sizeof(szPath), "/dev/dri/controlD%u", i / 2 + 64); + if (RT_FAILURE(rc)) + VBClFatalError(("RTStrPrintf of device path failed, rc=%Rrc\n", rc)); + rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + continue; + RT_ZERO(version); + version.cbName = sizeof(szName); + version.pszName = szName; + rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &version, sizeof(version), NULL); + if ( RT_SUCCESS(rc) + && !strncmp(szName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) + && ( version.cMajor > 2 + || (version.cMajor == 2 && version.cMinor > 9))) + break; + hDevice = NIL_RTFILE; + } + pContext->hDevice = hDevice; +} + +/** 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); + +#define DRM_IOCTL_VMW_UPDATE_LAYOUT \ + _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT) + +static void drmSendHints(struct DRMCONTEXT *pContext, struct DRMVMWRECT *paRects, + unsigned cHeads) +{ + int rc; + struct DRMVMWUPDATELAYOUT ioctlLayout; + + if (pContext->hDevice == NIL_RTFILE) + VBClFatalError(("%s bad device argument.\n", __func__)); + ioctlLayout.cOutputs = cHeads; + ioctlLayout.ptrRects = (uint64_t)paRects; + rc = RTFileIoCtl(pContext->hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT, + &ioctlLayout, sizeof(ioctlLayout), NULL); + if (RT_FAILURE(rc) && rc != VERR_INVALID_PARAMETER) + VBClFatalError(("Failure updating layout, rc=%Rrc\n", rc)); +} + +static const char *getPidFilePath() +{ + return ".vboxclient-display-svga.pid"; +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + (void)ppInterface; + (void)fDaemonised; + struct DRMCONTEXT drmContext = { NIL_RTFILE }; + unsigned i; + int rc; + uint32_t acx[VMW_MAX_HEADS] = { 0 }; + uint32_t acy[VMW_MAX_HEADS] = { 0 }; + uint32_t adx[VMW_MAX_HEADS] = { 0 }; + uint32_t ady[VMW_MAX_HEADS] = { 0 }; + uint32_t afEnabled[VMW_MAX_HEADS] = { false }; + struct DRMVMWRECT aRects[VMW_MAX_HEADS]; + unsigned cHeads; + + drmConnect(&drmContext); + if (drmContext.hDevice == NIL_RTFILE) + return VINF_SUCCESS; + /* Initialise the guest library. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc)); + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to request display change events, rc=%Rrc\n", rc)); + rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false); + if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */ + return VINF_SUCCESS; + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to register resizing support, rc=%Rrc\n", rc)); + for (;;) + { + uint32_t events; + + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events); + if (RT_FAILURE(rc)) + VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc)); + while (rc != VERR_TIMEOUT) + { + uint32_t cx, cy, cBits, dx, dy, idx; + bool fEnabled, fChangeOrigin; + + rc = VbglR3GetDisplayChangeRequest(&cx, &cy, &cBits, &idx, &dx, &dy, &fEnabled, &fChangeOrigin, true); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to get display change request, rc=%Rrc\n", rc)); + if (idx < VMW_MAX_HEADS) + { + acx[idx] = cx; + acy[idx] = cy; + if (fChangeOrigin) + adx[idx] = dx < INT32_MAX ? dx : 0; + if (fChangeOrigin) + ady[idx] = dy < INT32_MAX ? dy : 0; + afEnabled[idx] = fEnabled; + } + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0, &events); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT && rc != VERR_INTERRUPTED) + VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc)); + } + for (i = 0, cHeads = 0; i < VMW_MAX_HEADS; ++i) + { + if (afEnabled[i]) + { + if ((i == 0) || (adx[i] || ady[i])) + { + aRects[cHeads].x = (int32_t)adx[i]; + aRects[cHeads].y = (int32_t)ady[i]; + } else { + aRects[cHeads].x = (int32_t)(adx[i - 1] + acx[i - 1]); + aRects[cHeads].y = (int32_t)ady[i - 1]; + } + aRects[cHeads].w = acx[i]; + aRects[cHeads].h = acy[i]; + ++cHeads; + } + } + drmSendHints(&drmContext, aRects, cHeads); + } +} + +static struct VBCLSERVICE interface = +{ + getPidFilePath, + VBClServiceDefaultHandler, /* Init */ + run, + VBClServiceDefaultCleanup +}, *pInterface = &interface; + +struct VBCLSERVICE **VBClDisplaySVGAService() +{ + return &pInterface; +} diff --git a/src/VBox/Additions/x11/VBoxClient/display.cpp b/src/VBox/Additions/x11/VBoxClient/display.cpp new file mode 100644 index 00000000..6a168a2a --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display.cpp @@ -0,0 +1,281 @@ +/* $Id: display.cpp $ */ +/** @file + * X11 guest client - display management. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "VBoxClient.h" + +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xrandr.h> + +/** @todo this should probably be replaced by something IPRT */ +/* For system() and WEXITSTATUS() */ +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <time.h> + +/* 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. */ + +/** Display magic number, start of a UUID. */ +#define DISPLAYSTATE_MAGIC UINT32_C(0xf0029993) + +/** State information needed for the service. The main VBoxClient code provides + * the daemon logic needed by all services. */ +struct DISPLAYSTATE +{ + /** The service interface. */ + struct VBCLSERVICE *pInterface; + /** Magic number for sanity checks. */ + uint32_t magic; + /** 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; +}; + +static unsigned char *getRootProperty(struct DISPLAYSTATE *pState, const char *pszName, + long cItems, Atom type) +{ + Atom actualType = None; + int iFormat = 0; + unsigned long cReturned = 0; + unsigned long cAfter = 0; + unsigned char *pData = 0; + + if (XGetWindowProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), + XInternAtom(pState->pDisplay, pszName, 0), 0, cItems, + False /* delete */, type, &actualType, &iFormat, + &cReturned, &cAfter, &pData)) + return NULL; + return pData; +} + +static void doResize(struct DISPLAYSTATE *pState) +{ + /** @note The xrandr command can fail if something else accesses RandR at + * the same time. We just ignore failure for now as we do not know what + * someone else is doing. */ + if (!pState->fHaveRandR12) + { + char szCommand[256]; + unsigned char *pData; + + pData = getRootProperty(pState, "VBOXVIDEO_PREFERRED_MODE", 1, XA_INTEGER); + if (pData != NULL) + { + RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u", + pState->pcszXrandr, ((unsigned long *)pData)[0] >> 16, ((unsigned long *)pData)[0] & 0xFFFF); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + XFree(pData); + } + } + else + { + const char szCommandBase[] = + "%s --output VGA-0 --auto --output VGA-1 --auto --right-of VGA-0 " + "--output VGA-2 --auto --right-of VGA-1 --output VGA-3 --auto --right-of VGA-2 " + "--output VGA-4 --auto --right-of VGA-3 --output VGA-5 --auto --right-of VGA-4 " + "--output VGA-6 --auto --right-of VGA-5 --output VGA-7 --auto --right-of VGA-6 " + "--output VGA-8 --auto --right-of VGA-7 --output VGA-9 --auto --right-of VGA-8 " + "--output VGA-10 --auto --right-of VGA-9 --output VGA-11 --auto --right-of VGA-10 " + "--output VGA-12 --auto --right-of VGA-11 --output VGA-13 --auto --right-of VGA-12 " + "--output VGA-14 --auto --right-of VGA-13 --output VGA-15 --auto --right-of VGA-14 " + "--output VGA-16 --auto --right-of VGA-15 --output VGA-17 --auto --right-of VGA-16 " + "--output VGA-18 --auto --right-of VGA-17 --output VGA-19 --auto --right-of VGA-18 " + "--output VGA-20 --auto --right-of VGA-19 --output VGA-21 --auto --right-of VGA-20 " + "--output VGA-22 --auto --right-of VGA-21 --output VGA-23 --auto --right-of VGA-22 " + "--output VGA-24 --auto --right-of VGA-23 --output VGA-25 --auto --right-of VGA-24 " + "--output VGA-26 --auto --right-of VGA-25 --output VGA-27 --auto --right-of VGA-26 " + "--output VGA-28 --auto --right-of VGA-27 --output VGA-29 --auto --right-of VGA-28 " + "--output VGA-30 --auto --right-of VGA-29 --output VGA-31 --auto --right-of VGA-30"; + char szCommand[sizeof(szCommandBase) + 256]; + RTStrPrintf(szCommand, sizeof(szCommand), szCommandBase, pState->pcszXrandr); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + } +} + +/** Main loop: handle display hot-plug events, property updates (which can + * signal VT switches hot-plug in old X servers). */ +static void runDisplay(struct DISPLAYSTATE *pState) +{ + Display *pDisplay = pState->pDisplay; + long cValue = 1; + + /* One way or another we want the preferred mode at server start-up. */ + doResize(pState); + XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask | StructureNotifyMask); + if (pState->fHaveRandR12) + XRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask); + /* Semantics: when VBOXCLIENT_STARTED is set, pre-1.3 X.Org Server driver + * assumes that a client capable of handling mode hints will be present for the + * rest of the X session. If we crash things will not work as they should. + * I thought that preferable to implementing complex crash-handling logic. + */ + XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOXCLIENT_STARTED", 0), + XA_INTEGER, 32, PropModeReplace, (unsigned char *)&cValue, 1); + /* Interrupting this cleanly will be more work than making it robust + * against spontaneous termination, especially as it will never get + * properly tested, so I will go for the second. */ + while (true) + { + XEvent event; + struct pollfd PollFd; + int pollTimeOut = -1; + int cFds; + + /* Do not handle overflow. */ + if (pState->timeLastModeHint > 0 && pState->timeLastModeHint < INT_MAX - 2) + pollTimeOut = 2 - (time(0) - pState->timeLastModeHint); + PollFd.fd = ConnectionNumber(pDisplay); + PollFd.events = POLLIN; /* Hang-up is always reported. */ + XFlush(pDisplay); + cFds = poll(&PollFd, 1, pollTimeOut >= 0 ? pollTimeOut * 1000 : -1); + while (XPending(pDisplay)) + { + XNextEvent(pDisplay, &event); + /* This property is deleted when the server regains the virtual + * terminal. Force the main thread to call xrandr again, as old X + * servers could not handle it while switched out. */ + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyDelete + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False)) + doResize(pState); + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyNewValue + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_PREFERRED_MODE", False)) + doResize(pState); + if ( pState->fHaveRandR12 + && event.type == pState->cRREventBase + RRScreenChangeNotify) + pState->timeLastModeHint = time(0); + if ( event.type == ConfigureNotify + && event.xproperty.window == DefaultRootWindow(pDisplay)) + pState->timeLastModeHint = 0; + } + if (cFds == 0 && pState->timeLastModeHint > 0) + doResize(pState); + } +} + +static int initDisplay(struct DISPLAYSTATE *pState) +{ + char szCommand[256]; + int status; + + pState->pDisplay = XOpenDisplay(NULL); + if (!pState->pDisplay) + return VERR_NOT_FOUND; + if (!XRRQueryExtension(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. */ + VBClFatalError(("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; +} + +static const char *getPidFilePath() +{ + return ".vboxclient-display.pid"; +} + +static struct DISPLAYSTATE *getStateFromInterface(struct VBCLSERVICE **ppInterface) +{ + struct DISPLAYSTATE *pSelf = (struct DISPLAYSTATE *)ppInterface; + if (pSelf->magic != DISPLAYSTATE_MAGIC) + VBClFatalError(("Bad display service object!\n")); + return pSelf; +} + +static int init(struct VBCLSERVICE **ppInterface) +{ + struct DISPLAYSTATE *pSelf = getStateFromInterface(ppInterface); + 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; +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + RT_NOREF1(fDaemonised); + struct DISPLAYSTATE *pSelf = getStateFromInterface(ppInterface); + + if (!pSelf->mfInit) + return VERR_WRONG_ORDER; + runDisplay(pSelf); + return VERR_INTERNAL_ERROR; /* "Should never reach here." */ +} + +struct VBCLSERVICE vbclDisplayInterface = +{ + getPidFilePath, + init, + run, + VBClServiceDefaultCleanup +}; + +struct VBCLSERVICE **VBClGetDisplayService() +{ + struct DISPLAYSTATE *pService = (struct DISPLAYSTATE *)RTMemAlloc(sizeof(*pService)); + + if (!pService) + VBClFatalError(("Out of memory\n")); + pService->pInterface = &vbclDisplayInterface; + pService->magic = DISPLAYSTATE_MAGIC; + pService->mfInit = false; + return &pService->pInterface; +} diff --git a/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp new file mode 100644 index 00000000..7ebac8ea --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp @@ -0,0 +1,3547 @@ +/* $Id: draganddrop.cpp $ */ +/** @file + * X11 guest client - Drag and drop implementation. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <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/HostServices/DragAndDropSvc.h" +#include "VBoxClient.h" + +/* Enable this define to see the proxy window(s) when debugging + * their behavior. Don't have this enabled in release builds! */ +#ifdef DEBUG +//# define VBOX_DND_DEBUG_WND +#endif + +/** + * For X11 guest Xdnd is used. See http://www.acc.umu.se/~vatten/XDND.html for + * a walk trough. + * + * 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. + */ + +#define VBOX_XDND_VERSION (4) +#define VBOX_MAX_XPROPERTIES (LONG_MAX-1) + +/** + * Structure for storing new X11 events and HGCM messages + * into a single event queue. + */ +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; + }; +}; + +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 indexes, 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, /* Version 5: Bit 0 is set if the current target accepted the drop. */ + XdndFinishedAction /* Version 5: Contains the action performed by the target. */ + +} XdndMsg; + +class DragAndDropService; + +/** List of Atoms. */ +#define VBoxDnDAtomList RTCList<Atom> + +/******************************************************************************* + * + * xHelpers Declaration + * + ******************************************************************************/ + +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) + { + RTCString format; + for (size_t i = 0; i < formatList.size(); ++i) + format += xAtomToString(formatList.at(i)) + "\r\n"; + return format; + } + + RTCString xErrorToString(int xRc) const; + Window applicationWindowBelowCursor(Window parentWin) const; + +private: + + 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; +} + +/******************************************************************************* + * + * DragInstance Declaration + * + ******************************************************************************/ + +#ifdef DEBUG +# define VBOX_DND_FN_DECL_LOG(x) inline x /* For LogFlowXXX logging. */ +#else +# define VBOX_DND_FN_DECL_LOG(x) x +#endif + +/** @todo Move all proxy window-related stuff into this class! Clean up this mess. */ +class VBoxDnDProxyWnd +{ + +public: + + 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; +}; + +/** + * 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 + }; + + DragInstance(Display *pDisplay, DragAndDropService *pParent); + +public: + + int init(uint32_t uScreenID); + void uninit(void); + void reset(void); + + /* Logging. */ + VBOX_DND_FN_DECL_LOG(void) logInfo(const char *pszFormat, ...); + VBOX_DND_FN_DECL_LOG(void) logError(const char *pszFormat, ...); + + /* 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 &e); + 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 pMetaData); + + /* X11 helpers. */ + int mouseCursorFakeMove(void) const; + int mouseCursorMove(int iPosX, int iPosY) const; + 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 toAtomList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const; + int toAtomList(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. */ + long m_curVer; + /** List of (Atom) formats the source window supports. */ + VBoxDnDAtomList m_lstFormats; + /** List of (Atom) actions the source window supports. */ + VBoxDnDAtomList m_lstActions; + /** 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<XEvent> 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; +}; + +/******************************************************************************* + * + * DragAndDropService Declaration + * + ******************************************************************************/ + +class DragAndDropService +{ +public: + DragAndDropService(void) + : m_pDisplay(NULL) + , m_hHGCMThread(NIL_RTTHREAD) + , m_hX11Thread(NIL_RTTHREAD) + , m_hEventSem(NIL_RTSEMEVENT) + , m_pCurDnD(NULL) + , m_fSrvStopping(false) + {} + + int init(void); + int run(bool fDaemonised = false); + void cleanup(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; + RTTHREAD m_hHGCMThread; + RTTHREAD m_hX11Thread; + RTSEMEVENT m_hEventSem; + DragInstance *m_pCurDnD; + bool m_fSrvStopping; + + friend class DragInstance; +}; + +/******************************************************************************* + * + * DragInstanc Implementation + * + ******************************************************************************/ + +DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent) + : m_pParent(pParent) + , m_pDisplay(pDisplay) + , m_pScreen(0) + , m_wndRoot(0) + , m_wndCur(0) + , m_curVer(-1) + , m_pvSelReqData(NULL) + , m_cbSelReqData(0) + , m_enmMode(Unknown) + , m_enmState(Uninitialized) +{ +} + +/** + * Unitializes (destroys) this drag instance. + */ +void DragInstance::uninit(void) +{ + LogFlowFuncEnter(); + + if (m_wndProxy.hWnd != 0) + XDestroyWindow(m_pDisplay, m_wndProxy.hWnd); + + int rc2 = VbglR3DnDDisconnect(&m_dndCtx); + + if (m_pvSelReqData) + RTMemFree(m_pvSelReqData); + + rc2 = RTSemEventDestroy(m_eventQueueEvent); + AssertRC(rc2); + + rc2 = RTCritSectDelete(&m_eventQueueCS); + AssertRC(rc2); + + rc2 = RTCritSectDelete(&m_dataCS); + AssertRC(rc2); +} + +/** + * 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); + + /* Reset the internal state. */ + m_lstActions.clear(); + m_lstFormats.clear(); + m_wndCur = 0; + m_curVer = -1; + m_enmState = Initialized; + m_enmMode = Unknown; + m_eventQueueList.clear(); + m_cFailedPendingAttempts = 0; + + /* Reset the selection request buffer. */ + if (m_pvSelReqData) + { + RTMemFree(m_pvSelReqData); + m_pvSelReqData = NULL; + + Assert(m_cbSelReqData); + m_cbSelReqData = 0; + } + + RTCritSectLeave(&m_dataCS); + } +} + +/** + * 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; + + 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; +#ifdef VBOX_DND_DEBUG_WND + 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 */ +#else + 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 */ +#endif + if (!m_wndProxy.hWnd) + { + LogRel(("DnD: Error creating proxy window\n")); + rc = VERR_GENERAL_FAILURE; + break; + } + + rc = m_wndProxy.init(m_pDisplay); + if (RT_FAILURE(rc)) + { + LogRel(("DnD: Error initializing proxy window, rc=%Rrc\n", rc)); + break; + } + +#ifdef VBOX_DND_DEBUG_WND + XFlush(m_pDisplay); + XMapWindow(m_pDisplay, m_wndProxy.hWnd); + XRaiseWindow(m_pDisplay, m_wndProxy.hWnd); + XFlush(m_pDisplay); +#endif + logInfo("Proxy window=%RU32, root window=%RU32 ...\n", m_wndProxy.hWnd, m_wndRoot); + + /* Set the window's name for easier lookup. */ + XStoreName(m_pDisplay, m_wndProxy.hWnd, "VBoxClientWndDnD"); + + /* Make the new window Xdnd aware. */ + Atom ver = VBOX_XDND_VERSION; + XChangeProperty(m_pDisplay, m_wndProxy.hWnd, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace, + reinterpret_cast<unsigned char*>(&ver), 1); + } while (0); + + if (RT_SUCCESS(rc)) + { + reset(); + } + else + logError("Initializing drag instance for screen %RU32 failed with rc=%Rrc\n", uScreenID, rc); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Logs an error message to the (release) logging instance. + * + * @param pszFormat Format string to log. + */ +VBOX_DND_FN_DECL_LOG(void) DragInstance::logError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + LogFlowFunc(("%s", psz)); + LogRel(("DnD: %s", psz)); + + RTStrFree(psz); +} + +/** + * Logs an info message to the (release) logging instance. + * + * @param pszFormat Format string to log. + */ +VBOX_DND_FN_DECL_LOG(void) DragInstance::logInfo(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + LogFlowFunc(("%s", psz)); + LogRel2(("DnD: %s", psz)); + + RTStrFree(psz); +} + +/** + * 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; + + 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. + */ + if ( e.xclient.message_type == xAtom(XA_XdndStatus) + && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndStatusWindow])) + { + bool fAcceptDrop = ASMBitTest (&e.xclient.data.l[XdndStatusFlags], 0); /* Does the target accept the drop? */ + RTCString strActions = xAtomToString( e.xclient.data.l[XdndStatusAction]); +#ifdef LOG_ENABLED + bool fWantsPosition = ASMBitTest (&e.xclient.data.l[XdndStatusFlags], 1); /* Does the target want XdndPosition messages? */ + char *pszWndName = wndX11GetNameA(e.xclient.data.l[XdndStatusWindow]); + AssertPtr(pszWndName); + + /* + * 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. + */ + LogFlowThisFunc(("XA_XdndStatus: wnd=%#x ('%s'), fAcceptDrop=%RTbool, fWantsPosition=%RTbool, strActions=%s\n", + e.xclient.data.l[XdndStatusWindow], pszWndName, fAcceptDrop, fWantsPosition, strActions.c_str())); + + RTStrFree(pszWndName); + + uint16_t x = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]); + uint16_t y = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]); + uint16_t cx = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]); + uint16_t cy = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]); + LogFlowThisFunc(("\tReported dead area: x=%RU16, y=%RU16, cx=%RU16, cy=%RU16\n", x, y, cx, cy)); +#endif + 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 if (e.xclient.message_type == xAtom(XA_XdndFinished)) + { +#ifdef LOG_ENABLED + bool fSucceeded = ASMBitTest(&e.xclient.data.l[XdndFinishedFlags], 0); + + char *pszWndName = wndX11GetNameA(e.xclient.data.l[XdndFinishedWindow]); + AssertPtr(pszWndName); + + /* This message is sent on an un/successful DnD drop request. */ + LogFlowThisFunc(("XA_XdndFinished: wnd=%#x ('%s'), success=%RTbool, action=%s\n", + e.xclient.data.l[XdndFinishedWindow], pszWndName, fSucceeded, + xAtomToString(e.xclient.data.l[XdndFinishedAction]).c_str())); + + RTStrFree(pszWndName); +#endif + + reset(); + } + else + { + char *pszWndName = wndX11GetNameA(e.xclient.data.l[0]); + AssertPtr(pszWndName); + LogFlowThisFunc(("Unhandled: wnd=%#x ('%s'), msg=%s\n", + e.xclient.data.l[0], pszWndName, xAtomToString(e.xclient.message_type).c_str())); + RTStrFree(pszWndName); + + rc = VERR_NOT_SUPPORTED; + } + + break; + } + + case Unknown: /* Mode not set (yet). */ + 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)) + { + LogFlowFunc(("XA_XdndEnter\n")); + + /* + * Get the window which currently has the XA_XdndSelection + * bit set. + */ + Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection)); + + char *pszWndName = wndX11GetNameA(wndSelection); + AssertPtr(pszWndName); + LogFlowThisFunc(("wndSelection=%RU32 ('%s'), wndProxy=%RU32\n", wndSelection, pszWndName, m_wndProxy.hWnd)); + RTStrFree(pszWndName); + + mouseButtonSet(m_wndProxy.hWnd, -1, -1, 1, true /* fPress */); + + /* + * Update our state and the window handle to process. + */ + int rc2 = RTCritSectEnter(&m_dataCS); + if (RT_SUCCESS(rc2)) + { + m_wndCur = wndSelection; + m_curVer = e.xclient.data.l[XdndEnterFlags] >> XdndEnterVersionRShift; + Assert(m_wndCur == (Window)e.xclient.data.l[XdndEnterWindow]); /* Source window. */ +#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; + LogFlowThisFunc(("XdndVer=%d, fMoreTypes=%RTbool\n", m_curVer, fMoreTypes)); + 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_lstFormats.append(e.xclient.data.l[i]); + } + } + else + { + /* More than 3 format types supported. */ + rc = wndXDnDGetFormatList(wndSelection, m_lstFormats); + } + + /* + * Retrieve supported actions. + */ + if (RT_SUCCESS(rc)) + { + if (m_curVer >= 2) /* More than one action allowed since protocol version 2. */ + { + rc = wndXDnDGetActionList(wndSelection, m_lstActions); + } + else /* Only "copy" action allowed on legacy applications. */ + m_lstActions.append(XA_XdndActionCopy); + } + + if (RT_SUCCESS(rc)) + { + m_enmMode = GH; + m_enmState = Dragging; + } + + RTCritSectLeave(&m_dataCS); + } + } + 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_curVer >= 2 /* Actions other than "copy" or only supported since protocol version 2. */ + ? e.xclient.data.l[XdndPositionAction] : xAtom(XA_XdndActionCopy); + LogFlowThisFunc(("XA_XdndPosition: wndProxy=%RU32, wndCur=%RU32, 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 ? RT_BIT(0) : 0; /* 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) + logError("Error sending position XA_XdndStatus event to current window=%#x: %s\n", + m_wndCur, 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")); + logInfo("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. */ + logInfo("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. */ + { + logInfo("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; + } + } + + 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 e X11 event to handle. + */ +int DragInstance::onX11SelectionRequest(const XEvent &e) +{ + AssertReturn(e.type == SelectionRequest, VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + LogFlowThisFunc(("Event owner=%#x, requestor=%#x, selection=%s, target=%s, prop=%s, time=%u\n", + e.xselectionrequest.owner, + e.xselectionrequest.requestor, + xAtomToString(e.xselectionrequest.selection).c_str(), + xAtomToString(e.xselectionrequest.target).c_str(), + xAtomToString(e.xselectionrequest.property).c_str(), + e.xselectionrequest.time)); + int rc; + + switch (m_enmMode) + { + case HG: + { + rc = VINF_SUCCESS; + + char *pszWndName = wndX11GetNameA(e.xselectionrequest.requestor); + AssertPtr(pszWndName); + + /* + * Start by creating a refusal selection notify message. + * That way we only need to care for the success case. + */ + + XEvent s; + RT_ZERO(s); + s.xselection.type = SelectionNotify; + s.xselection.display = e.xselectionrequest.display; + s.xselection.requestor = e.xselectionrequest.requestor; + s.xselection.selection = e.xselectionrequest.selection; + s.xselection.target = e.xselectionrequest.target; + s.xselection.property = None; /* "None" means refusal. */ + s.xselection.time = e.xselectionrequest.time; + + const XSelectionRequestEvent *pReq = &e.xselectionrequest; + +#ifdef DEBUG + LogFlowFunc(("Supported formats:\n")); + for (size_t i = 0; i < m_lstFormats.size(); i++) + LogFlowFunc(("\t%s\n", xAtomToString(m_lstFormats.at(i)).c_str())); +#endif + /* Is the requestor asking for the possible MIME types? */ + if (pReq->target == xAtom(XA_TARGETS)) + { + logInfo("Target window %#x ('%s') asking for target list\n", e.xselectionrequest.requestor, pszWndName); + + /* If so, set the window property with the formats on the requestor + * window. */ + rc = wndXDnDSetFormatList(pReq->requestor, pReq->property, m_lstFormats); + if (RT_SUCCESS(rc)) + s.xselection.property = pReq->property; + } + /* Is the requestor asking for a specific MIME type (we support)? */ + else if (m_lstFormats.contains(pReq->target)) + { + logInfo("Target window %#x ('%s') is asking for data as '%s'\n", + pReq->requestor, pszWndName, xAtomToString(pReq->target).c_str()); + + /* Did we not drop our stuff to the guest yet? Bail out. */ + if (m_enmState != Dropped) + { + LogFlowThisFunc(("Wrong state (%RU32), refusing request\n", m_enmState)); + } + /* Did we not store the requestor's initial selection request yet? Then do so now. */ + else + { + /* Get the data format the requestor wants from us. */ + RTCString strFormat = xAtomToString(pReq->target); + Assert(strFormat.isNotEmpty()); + logInfo("Target window=%#x requested data from host as '%s', rc=%Rrc\n", + pReq->requestor, strFormat.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? */ + void *pvData = RTMemDup(m_pvSelReqData, m_cbSelReqData); + uint32_t cbData = m_cbSelReqData; + + /* Always return the requested property. */ + s.xselection.property = pReq->property; + + /* Note: Always seems to return BadRequest. Seems fine. */ + int xRc = XChangeProperty(s.xselection.display, s.xselection.requestor, s.xselection.property, + s.xselection.target, 8, PropModeReplace, + reinterpret_cast<const unsigned char*>(pvData), cbData); + + LogFlowFunc(("Changing property '%s' (target '%s') of window=%RU32: %s\n", + xAtomToString(pReq->property).c_str(), + xAtomToString(pReq->target).c_str(), + pReq->requestor, + gX11->xErrorToString(xRc).c_str())); + NOREF(xRc); + } + } + /* Anything else. */ + else + { + logError("Refusing unknown command/format '%s' of wnd=%#x ('%s')\n", + xAtomToString(e.xselectionrequest.target).c_str(), pReq->requestor, pszWndName); + rc = VERR_NOT_SUPPORTED; + } + + LogFlowThisFunc(("Offering type '%s', property '%s' to wnd=%#x ...\n", + xAtomToString(pReq->target).c_str(), + xAtomToString(pReq->property).c_str(), pReq->requestor)); + + int xRc = XSendEvent(pReq->display, pReq->requestor, True /* Propagate */, 0, &s); + if (xRc == 0) + logError("Error sending SelectionNotify(1) event to wnd=%#x: %s\n", pReq->requestor, + gX11->xErrorToString(xRc).c_str()); + XFlush(pReq->display); + + if (pszWndName) + RTStrFree(pszWndName); + break; + } + + default: + rc = VERR_INVALID_STATE; + break; + } + + 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: + case ButtonRelease: + LogFlowThisFunc(("Mouse button press/release\n")); + rc = VINF_SUCCESS; + + reset(); + 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; + 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); + + 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() - uiStart < uTimeoutMS); + + LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - uiStart)); + 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); + 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(); + if (RT_FAILURE(rc)) + break; + + rc = toAtomList(lstFormats, m_lstFormats); + if (RT_FAILURE(rc)) + break; + + /* If we have more than 3 formats we have to use the type list extension. */ + if (m_lstFormats.size() > 3) + { + rc = wndXDnDSetFormatList(m_wndProxy.hWnd, xAtom(XA_XdndTypeList), m_lstFormats); + if (RT_FAILURE(rc)) + break; + } + + /* Announce the possible actions. */ + VBoxDnDAtomList lstActions; + rc = toAtomActions(dndListActionsAllowed, lstActions); + if (RT_FAILURE(rc)) + break; + rc = wndXDnDSetActionList(m_wndProxy.hWnd, lstActions); + + /* 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); + + 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 (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); + + long newVer = -1; /* This means the current window is _not_ XdndAware. */ + + /* Search for the application window below the cursor. */ + Window wndCursor = gX11->applicationWindowBelowCursor(m_wndRoot); + if (wndCursor != None) + { + /* Temp stuff for the XGetWindowProperty call. */ + Atom atmp; + 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, wndCursor, xAtom(XA_XdndAware), + 0, 2, False, AnyPropertyType, + &atmp, &fmt, &cItems, &cbRemaining, &pcData); + if (xRc != Success) + { + logError("Error getting properties of cursor window=%#x: %s\n", wndCursor, gX11->xErrorToString(xRc).c_str()); + } + else + { + if (pcData == NULL || fmt != 32 || cItems != 1) + { + /** @todo Do we need to deal with this? */ + logError("Wrong window properties for window %#x: pcData=%#x, iFmt=%d, cItems=%ul\n", + wndCursor, pcData, fmt, cItems); + } + else + { + /* Get the current window's Xdnd version. */ + newVer = reinterpret_cast<long *>(pcData)[0]; + } + + XFree(pcData); + } + } + +#ifdef DEBUG + char *pszNameCursor = wndX11GetNameA(wndCursor); + AssertPtr(pszNameCursor); + char *pszNameCur = wndX11GetNameA(m_wndCur); + AssertPtr(pszNameCur); + + LogFlowThisFunc(("wndCursor=%x ('%s', Xdnd version %ld), wndCur=%x ('%s', Xdnd version %ld)\n", + wndCursor, pszNameCursor, newVer, m_wndCur, pszNameCur, m_curVer)); + + RTStrFree(pszNameCursor); + RTStrFree(pszNameCur); +#endif + + if ( wndCursor != m_wndCur + && m_curVer != -1) + { + LogFlowThisFunc(("XA_XdndLeave: window=%#x\n", m_wndCur)); + + char *pszWndName = wndX11GetNameA(m_wndCur); + AssertPtr(pszWndName); + logInfo("Left old window %#x ('%s'), Xdnd version=%ld\n", m_wndCur, pszWndName, newVer); + RTStrFree(pszWndName); + + /* 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) + logError("Error sending XA_XdndLeave event to old window=%#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str()); + + /* Reset our current window. */ + m_wndCur = 0; + m_curVer = -1; + } + + /* + * Do we have a new Xdnd-aware window which now is under the cursor? + */ + if ( wndCursor != m_wndCur + && newVer != -1) + { + LogFlowThisFunc(("XA_XdndEnter: window=%#x\n", wndCursor)); + + char *pszWndName = wndX11GetNameA(wndCursor); + AssertPtr(pszWndName); + logInfo("Entered new window %#x ('%s'), supports Xdnd version=%ld\n", wndCursor, pszWndName, newVer); + RTStrFree(pszWndName); + + /* + * 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 = wndCursor; + 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_lstFormats.size() > 3 ? RT_BIT(0) : 0, + /* Reserved for future use. */ + 0, 0, + /* Protocol version to use. */ + RT_MIN(VBOX_XDND_VERSION, newVer)); + m.data.l[XdndEnterType1] = m_lstFormats.value(0, None); /* First data type to use. */ + m.data.l[XdndEnterType2] = m_lstFormats.value(1, None); /* Second data type to use. */ + m.data.l[XdndEnterType3] = m_lstFormats.value(2, None); /* Third data type to use. */ + + xRc = XSendEvent(m_pDisplay, wndCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + logError("Error sending XA_XdndEnter event to window=%#x: %s\n", wndCursor, gX11->xErrorToString(xRc).c_str()); + } + + if (newVer != -1) + { + Assert(wndCursor != None); + + LogFlowThisFunc(("XA_XdndPosition: xPos=%RU32, yPos=%RU32 to window=%#x\n", uPosX, uPosY, wndCursor)); + + /* + * Send a XdndPosition event with the proposed action to the guest. + */ + Atom pa = toAtomAction(dndActionDefault); + LogFlowThisFunc(("strAction=%s\n", xAtomToString(pa).c_str())); + + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = m_pDisplay; + m.window = wndCursor; + 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[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] = pa; /* Actions requested by the user. */ + + xRc = XSendEvent(m_pDisplay, wndCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + logError("Error sending XA_XdndPosition event to current window=%#x: %s\n", wndCursor, gX11->xErrorToString(xRc).c_str()); + } + + if (newVer == -1) + { + /* No window to process, so send a ignore ack event to the host. */ + rc = VbglR3DnDHGSendAckOp(&m_dndCtx, VBOX_DND_ACTION_IGNORE); + } + else + { + Assert(wndCursor != None); + + m_wndCur = wndCursor; + m_curVer = newVer; + } + + 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); + logInfo("Drop event from host resulted in: %Rrc\n", rc); + + 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 pMetaData Pointer to meta data from host. + */ +int DragInstance::hgDataReceive(PVBGLR3GUESTDNDMETADATA pMetaData) +{ + LogFlowThisFunc(("enmMode=%RU32, enmState=%RU32\n", m_enmMode, m_enmState)); + LogFlowThisFunc(("enmMetaDataType=%RU32\n", pMetaData->enmType)); + + if ( m_enmMode != HG + || m_enmState != Dropped) + { + return VERR_INVALID_STATE; + } + + if ( pMetaData->pvMeta == NULL + || pMetaData->cbMeta == 0) + { + return VERR_INVALID_PARAMETER; + } + + int rc = VINF_SUCCESS; + + const void *pvData = pMetaData->pvMeta; + const uint32_t cbData = pMetaData->cbMeta; + + /* + * At this point all data needed (including sent files/directories) should + * be on the guest, so proceed working on communicating with the target window. + */ + logInfo("Received %RU32 bytes of URI list 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) + logError("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 = "\r\n"; /** @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 wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection)); + LogFlowThisFunc(("wndSelection=%#x, wndProxy=%#x, wndCur=%#x\n", wndSelection, m_wndProxy.hWnd, m_wndCur)); + + /* Is this another window which has a Xdnd selection and not our proxy window? */ + if ( RT_SUCCESS(rc) + && wndSelection + && wndSelection != m_wndCur) + { + char *pszWndName = wndX11GetNameA(wndSelection); + AssertPtr(pszWndName); + logInfo("New guest source window %#x ('%s')\n", wndSelection, pszWndName); + + /* 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) + { + logError("Error mapping proxy window to guest source window %#x ('%s'), rc=%Rrc\n", + wndSelection, pszWndName, rc); + + /* Reset the counter in any case. */ + m_cFailedPendingAttempts = 0; + } + } + } + + RTStrFree(pszWndName); + } + else + logInfo("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)) + { + RTCString strFormatsCur = gX11->xAtomListToString(m_lstFormats); + if (!strFormatsCur.isEmpty()) + { + strFormats = strFormatsCur; + dndActionDefault = VBOX_DND_ACTION_COPY; /** @todo Handle default action! */ + dndActionList = VBOX_DND_ACTION_COPY; /** @todo Ditto. */ + dndActionList |= toHGCMActions(m_lstActions); + } + + RTCritSectLeave(&m_dataCS); + } + + 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)) + { + logError("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_curVer >= 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) + logError("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")); + + logError("Incremental transfers are not supported yet\n"); + rc = VERR_NOT_IMPLEMENTED; + } + else + { + logError("Not supported data type: %s\n", gX11->xAtomToString(aPropType).c_str()); + rc = VERR_NOT_SUPPORTED; + } + + fCancel = true; + } + + if (fCancel) + { + logInfo("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 = VbglR3DnDGHSendError(&m_dndCtx, rc); + LogFlowThisFunc(("Sending error %Rrc to host resulted in %Rrc\n", rc, rc2)); 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) const +{ + 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) const +{ + 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); + + iPosX = RT_CLAMP(iPosX, 0, iScrX); + iPosY = RT_CLAMP(iPosY, 0, iScrY); + + LogFlowThisFunc(("iPosX=%d, iPosY=%d\n", iPosX, iPosY)); + + /* Move the guest pointer to the DnD position, so we can find the window + * below that position. */ + XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iPosX, iPosY); + 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) + logError("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) + logError("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)); 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); + + m_eventQueueList.clear(); + + 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. + * @param wndThis Window to retrieve name for. + * + * @remark If the window title is not available, the text + * "<No name>" will be returned. + */ +char *DragInstance::wndX11GetNameA(Window wndThis) const +{ + char *pszName = NULL; + + XTextProperty propName; + if (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. + * + * @remark + */ +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; + + /* We support TARGETS and the data types. */ + VBoxDnDAtomList lstFormatsExt(lstFormats.size() + 1); + lstFormatsExt.append(xAtom(XA_TARGETS)); + lstFormatsExt.append(lstFormats); + + /* Add the property with the property data to the window. */ + XChangeProperty(m_pDisplay, wndThis, atmProp, + XA_ATOM, 32, PropModeReplace, + reinterpret_cast<const unsigned char*>(lstFormatsExt.raw()), + lstFormatsExt.size()); + + return VINF_SUCCESS; +} + +/** + * Converts 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::toAtomList(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; +} + +/** + * Converts 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::toAtomList(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) + { + LogRel(("DnD: Error sending XA_XdndFinished event to source window=%#x: %s\n", + hWndSource, gX11->xErrorToString(xRc).c_str())); + + return VERR_GENERAL_FAILURE; /** @todo Fudge. */ + } + + return VINF_SUCCESS; +} + +/******************************************************************************* + * DragAndDropService implementation. + ******************************************************************************/ + +/** + * Initializes the drag and drop service. + * + * @returns IPRT status code. + */ +int DragAndDropService::init(void) +{ + LogFlowFuncEnter(); + + /* Initialise the guest library. */ + int rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + { + VBClFatalError(("DnD: Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc)); + return rc; + } + + /* Connect to the x11 server. */ + m_pDisplay = XOpenDisplay(NULL); + if (!m_pDisplay) + { + VBClFatalError(("DnD: 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; + + do + { + rc = RTSemEventCreate(&m_hEventSem); + if (RT_FAILURE(rc)) + break; + + rc = RTCritSectInit(&m_eventQueueCS); + if (RT_FAILURE(rc)) + break; + + /* Event thread for events coming from the HGCM device. */ + rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this, + 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndHGCM"); + if (RT_FAILURE(rc)) + break; + + rc = RTThreadUserWait(m_hHGCMThread, 10 * 1000 /* 10s timeout */); + if (RT_FAILURE(rc)) + break; + + if (ASMAtomicReadBool(&m_fSrvStopping)) + break; + + /* Event thread for events coming from the x11 system. */ + rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this, + 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndX11"); + if (RT_FAILURE(rc)) + break; + + rc = RTThreadUserWait(m_hX11Thread, 10 * 1000 /* 10s timeout */); + if (RT_FAILURE(rc)) + break; + + if (ASMAtomicReadBool(&m_fSrvStopping)) + break; + + } while (0); + + if (m_fSrvStopping) + rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */ + + if (RT_FAILURE(rc)) + LogRel(("DnD: Failed to initialize, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main loop for the drag and drop service which does the HGCM message + * processing and routing to the according drag and drop instance(s). + * + * @returns IPRT status code. + * @param fDaemonised Whether to run in daemonized or not. Does not + * apply for this service. + */ +int DragAndDropService::run(bool fDaemonised /* = false */) +{ + RT_NOREF1(fDaemonised); + LogFlowThisFunc(("fDaemonised=%RTbool\n", fDaemonised)); + + 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)) + LogRel(("DnD: Unable to connect to drag and drop service, rc=%Rrc\n", rc)); + else if (rc == VINF_PERMISSION_DENIED) + LogRel(("DnD: Not available on host, terminating\n")); + break; + } + + LogRel(("DnD: Started\n")); + LogRel2(("DnD: %sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr())); + + /* Enter the main event processing loop. */ + do + { + DnDEvent e; + RT_ZERO(e); + + LogFlowFunc(("Waiting for new event ...\n")); + rc = RTSemEventWait(m_hEventSem, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + AssertMsg(m_eventQueue.size(), ("Event queue is empty when it shouldn't\n")); + + e = m_eventQueue.first(); + m_eventQueue.removeFirst(); + + 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("\r\n"); + 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_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_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_HG_CANCEL: + { + m_pCurDnD->reset(); /** @todo Test this! */ + 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 + default: + { + m_pCurDnD->logError("Received unsupported message '%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. */ + m_pCurDnD->logError("Processing message %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc); + + /* If anything went wrong, do a reset and start over. */ + m_pCurDnD->reset(); + } + + VbglR3DnDEventFree(e.hgcm); + e.hgcm = NULL; + } + else if (e.enmType == DnDEvent::DnDEventType_X11) + { + m_pCurDnD->onX11Event(e.x11); + } + else + AssertMsgFailed(("Unknown event queue type %RU32\n", e.enmType)); + + /* + * 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); + + } while (!ASMAtomicReadBool(&m_fSrvStopping)); + + LogRel(("DnD: Stopped with rc=%Rrc\n", rc)); + + } while (0); + + if (m_pCurDnD) + { + delete m_pCurDnD; + m_pCurDnD = NULL; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +void DragAndDropService::cleanup(void) +{ + LogFlowFuncEnter(); + + LogRel2(("DnD: Terminating threads ...\n")); + + ASMAtomicXchgBool(&m_fSrvStopping, true); + + /* + * Wait for threads to terminate. + */ + int rcThread, rc2; + if (m_hHGCMThread != NIL_RTTHREAD) + { +#if 0 /** @todo Does not work because we don't cancel the HGCM call! */ + rc2 = RTThreadWait(m_hHGCMThread, 30 * 1000 /* 30s timeout */, &rcThread); +#else + rc2 = RTThreadWait(m_hHGCMThread, 200 /* 200ms timeout */, &rcThread); +#endif + if (RT_SUCCESS(rc2)) + rc2 = rcThread; + + if (RT_FAILURE(rc2)) + LogRel(("DnD: Error waiting for HGCM thread to terminate: %Rrc\n", rc2)); + } + + if (m_hX11Thread != NIL_RTTHREAD) + { +#if 0 + rc2 = RTThreadWait(m_hX11Thread, 30 * 1000 /* 30s timeout */, &rcThread); +#else + rc2 = RTThreadWait(m_hX11Thread, 200 /* 200ms timeout */, &rcThread); +#endif + if (RT_SUCCESS(rc2)) + rc2 = rcThread; + + if (RT_FAILURE(rc2)) + LogRel(("DnD: Error waiting for X11 thread to terminate: %Rrc\n", rc2)); + } + + LogRel2(("DnD: Terminating threads done\n")); + + xHelpers::destroyInstance(); + + VbglR3Term(); +} + +/** + * 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); + AssertPtr(pThis); + + /* This thread has an own DnD context, e.g. an own client ID. */ + VBGLR3GUESTDNDCMDCTX dndCtx; + + /* + * Initialize thread. + */ + int rc = VbglR3DnDConnect(&dndCtx); + + /* Set stop indicator on failure. */ + if (RT_FAILURE(rc)) + ASMAtomicXchgBool(&pThis->m_fSrvStopping, true); + + /* Let the service instance know in any case. */ + int rc2 = RTThreadUserSignal(hThread); + AssertRC(rc2); + + if (RT_FAILURE(rc)) + return rc; + + /* 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(&dndCtx, &e.hgcm); + if (RT_SUCCESS(rc)) + { + cMsgSkippedInvalid = 0; /* Reset skipped messages count. */ + pThis->m_eventQueue.append(e); + + rc = RTSemEventSignal(pThis->m_hEventSem); + if (RT_FAILURE(rc)) + break; + } + else + { + LogRel(("DnD: 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) + { + LogRel(("DnD: Too many invalid/skipped messages from host, exiting ...\n")); + break; + } + } + + } while (!ASMAtomicReadBool(&pThis->m_fSrvStopping)); + + VbglR3DnDDisconnect(&dndCtx); + + 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). */ + + /* Set stop indicator on failure. */ + if (RT_FAILURE(rc)) + ASMAtomicXchgBool(&pThis->m_fSrvStopping, true); + + /* Let the service instance know in any case. */ + int rc2 = RTThreadUserSignal(hThread); + AssertRC(rc2); + + DnDEvent e; + 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. + */ + if (XEventsQueued(pThis->m_pDisplay, QueuedAfterFlush) > 0) + { + RT_ZERO(e); + e.enmType = DnDEvent::DnDEventType_X11; + + /* XNextEvent will block until a new X event becomes available. */ + XNextEvent(pThis->m_pDisplay, &e.x11); + { +#ifdef DEBUG + switch (e.x11.type) + { + case ClientMessage: + { + XClientMessageEvent *pEvent = reinterpret_cast<XClientMessageEvent*>(&e); + AssertPtr(pEvent); + + RTCString strType = xAtomToString(pEvent->message_type); + LogFlowFunc(("ClientMessage: %s from wnd=%#x\n", strType.c_str(), pEvent->window)); + break; + } + + default: + LogFlowFunc(("Received X event type=%d\n", e.x11.type)); + break; + } +#endif + /* At the moment we only have one drag instance. */ + DragInstance *pInstance = pThis->m_pCurDnD; + AssertPtr(pInstance); + + pInstance->onX11Event(e.x11); + } + } + else + RTThreadSleep(25 /* ms */); + + } while (!ASMAtomicReadBool(&pThis->m_fSrvStopping)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** Drag and drop magic number, start of a UUID. */ +#define DRAGANDDROPSERVICE_MAGIC 0x67c97173 + +/** VBoxClient service class wrapping the logic for the service while + * the main VBoxClient code provides the daemon logic needed by all services. + */ +struct DRAGANDDROPSERVICE +{ + /** The service interface. */ + struct VBCLSERVICE *pInterface; + /** Magic number for sanity checks. */ + uint32_t uMagic; + /** Service object. */ + DragAndDropService mDragAndDrop; +}; + +static const char *getPidFilePath() +{ + return ".vboxclient-draganddrop.pid"; +} + +static int init(struct VBCLSERVICE **ppInterface) +{ + struct DRAGANDDROPSERVICE *pSelf = (struct DRAGANDDROPSERVICE *)ppInterface; + + if (pSelf->uMagic != DRAGANDDROPSERVICE_MAGIC) + VBClFatalError(("Bad DnD service object!\n")); + return pSelf->mDragAndDrop.init(); +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + struct DRAGANDDROPSERVICE *pSelf = (struct DRAGANDDROPSERVICE *)ppInterface; + + if (pSelf->uMagic != DRAGANDDROPSERVICE_MAGIC) + VBClFatalError(("Bad DnD service object!\n")); + return pSelf->mDragAndDrop.run(fDaemonised); +} + +static void cleanup(struct VBCLSERVICE **ppInterface) +{ + struct DRAGANDDROPSERVICE *pSelf = (struct DRAGANDDROPSERVICE *)ppInterface; + + if (pSelf->uMagic != DRAGANDDROPSERVICE_MAGIC) + VBClFatalError(("Bad DnD service object!\n")); + return pSelf->mDragAndDrop.cleanup(); +} + +struct VBCLSERVICE vbclDragAndDropInterface = +{ + getPidFilePath, + init, + run, + cleanup +}; + +/* Static factory. */ +struct VBCLSERVICE **VBClGetDragAndDropService(void) +{ + struct DRAGANDDROPSERVICE *pService = + (struct DRAGANDDROPSERVICE *)RTMemAlloc(sizeof(*pService)); + + if (!pService) + VBClFatalError(("Out of memory\n")); + pService->pInterface = &vbclDragAndDropInterface; + pService->uMagic = DRAGANDDROPSERVICE_MAGIC; + new(&pService->mDragAndDrop) DragAndDropService(); + return &pService->pInterface; +} diff --git a/src/VBox/Additions/x11/VBoxClient/hostversion.cpp b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp new file mode 100644 index 00000000..489e715e --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp @@ -0,0 +1,227 @@ +/* $Id: hostversion.cpp $ */ +/** @file + * X11 guest client - host version check. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ +#include <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> + +#ifdef VBOX_WITH_DBUS +# include <VBox/dbus.h> +#endif +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> +#ifdef VBOX_OSE +# include <VBox/version.h> +#endif + +#include "VBoxClient.h" + +static const char *getPidFilePath() +{ + return ".vboxclient-hostversion.pid"; +} + +static int showNotify(const char *pszHeader, const char *pszBody) +{ + int rc; +# ifdef VBOX_WITH_DBUS + DBusConnection *conn; + DBusMessage* msg = NULL; + conn = dbus_bus_get (DBUS_BUS_SESSION, NULL); + if (conn == NULL) + { + LogRelFlowFunc(("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) + { + LogRel(("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)) + LogRel(("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 = VINF_SUCCESS; +# endif /* VBOX_WITH_DBUS */ + return rc; +} + +/** @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. */ +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + int rc; + LogFlowFunc(("\n")); + + NOREF(ppInterface); + /* Initialise the guest library. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc)); + /* Because we need desktop notifications to be displayed, wait + * some time to make the desktop environment load (as a work around). */ + if (fDaemonised) + RTThreadSleep(30 * 1000 /* Wait 30 seconds */); + +# ifdef VBOX_WITH_DBUS + rc = RTDBusLoadLib(); + if (RT_FAILURE(rc)) + LogRel(("VBoxClient: D-Bus seems not to be installed; no host version check/notification done.\n")); +# else + rc = VERR_NOT_IMPLEMENTED; +# endif /* VBOX_WITH_DBUS */ + +# ifdef VBOX_WITH_GUEST_PROPS + uint32_t uGuestPropSvcClientID; + if (RT_SUCCESS(rc)) + { + rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_FAILURE(rc)) + LogRel(("VBoxClient: Cannot connect to guest property service while chcking for host version! rc = %Rrc\n", rc)); + } + + if (RT_SUCCESS(rc)) + { + char *pszHostVersion; + char *pszGuestVersion; + bool bUpdate; + + rc = VbglR3HostVersionCheckForUpdate(uGuestPropSvcClientID, &bUpdate, &pszHostVersion, &pszGuestVersion); + if (RT_SUCCESS(rc)) + { + if (bUpdate) + { + 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 + rc = showNotify(szTitle, szMsg); + LogRel(("VBoxClient: VirtualBox Guest Additions update available!")); + if (RT_FAILURE(rc)) + LogRel(("VBoxClient: Could not show version notifier tooltip! rc = %d\n", rc)); + } + + /* Store host version to not notify again */ + rc = VbglR3HostVersionLastCheckedStore(uGuestPropSvcClientID, pszHostVersion); + + VbglR3GuestPropReadValueFree(pszHostVersion); + VbglR3GuestPropReadValueFree(pszGuestVersion); + } + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + } +# endif /* VBOX_WITH_GUEST_PROPS */ + VbglR3Term(); + LogFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + +struct VBCLSERVICE vbclHostVersionInterface = +{ + getPidFilePath, + VBClServiceDefaultHandler, /* init */ + run, + VBClServiceDefaultCleanup +}; + +struct HOSTVERSIONSERVICE +{ + struct VBCLSERVICE *pInterface; +}; + +/* Static factory */ +struct VBCLSERVICE **VBClGetHostVersionService() +{ + struct HOSTVERSIONSERVICE *pService = + (struct HOSTVERSIONSERVICE *)RTMemAlloc(sizeof(*pService)); + + if (!pService) + VBClFatalError(("Out of memory\n")); + pService->pInterface = &vbclHostVersionInterface; + return &pService->pInterface; +} + diff --git a/src/VBox/Additions/x11/VBoxClient/main.cpp b/src/VBox/Additions/x11/VBoxClient/main.cpp new file mode 100644 index 00000000..b5429fbc --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/main.cpp @@ -0,0 +1,425 @@ +/* $Id: main.cpp $ */ +/** @file + * VirtualBox Guest Additions - X11 Client. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/types.h> +#include <sys/wait.h> +#include <stdlib.h> /* For exit */ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <poll.h> +#include <signal.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <iprt/buildconfig.h> +#include <iprt/critsect.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/types.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include "VBoxClient.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/*static int (*gpfnOldIOErrorHandler)(Display *) = NULL; - unused */ + +/** Object representing the service we are running. This has to be global + * so that the cleanup routine can access it. */ +struct VBCLSERVICE **g_pService; +/** 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; +/** 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). + */ +RTCRITSECT g_critSect; +/** Counter of how often our deamon has been respawned. */ +unsigned cRespawn = 0; + + + +/** + * Exit with a fatal error. + * + * This is used by the VBClFatalError macro and thus needs to be external. + */ +void vbclFatalError(char *pszMessage) +{ + char *pszCommand; + int status; + if (pszMessage && cRespawn == 0) + { + pszCommand = RTStrAPrintf2("notify-send \"VBoxClient: %s\"", pszMessage); + if (pszCommand) + { + status = system(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. */ + { + RTPrintf("VBoxClient: %s", pszMessage); + } + } + } + } + } + _exit(RTEXITCODE_FAILURE); +} + +/** + * Clean up if we get a signal or something. + * + * This is extern so that we can call it from other compilation units. + */ +void VBClCleanUp(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)) + VBClFatalError(("VBoxClient: Failure while acquiring the global critical section, rc=%Rrc\n", rc)); + if (g_pService) + (*g_pService)->cleanup(g_pService); + if (g_szPidFile[0] && g_hPidFile) + VbglR3ClosePidFile(g_szPidFile, g_hPidFile); + if (fExit) + exit(RTEXITCODE_SUCCESS); +} + +/** + * A standard signal handler which cleans up and exits. + */ +static void vboxClientSignalHandler(int cSignal) +{ + LogRel(("VBoxClient: terminated with signal %d\n", cSignal)); + /** Disable seamless mode */ + RTPrintf(("VBoxClient: terminating...\n")); + VBClCleanUp(); +} + +/** + * 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)); + LogRelFlow(("VBoxClient: 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); + LogRel(("VBoxClient: 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")); + VBClCleanUp(); + return 0; /* We should never reach this. */ +} + +/** + * Reset all standard termination signals to call our signal handler, which + * cleans up and exits. + */ +static void vboxClientSetSignalHandlers(void) +{ + struct sigaction sigAction; + + LogRelFlowFunc(("\n")); + 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); + LogRelFlowFunc(("returning\n")); +} + +/** + * Print out a usage message and exit with success. + */ +static void vboxClientUsage(const char *pcszFileName) +{ + RTPrintf("Usage: %s --clipboard|" +#ifdef VBOX_WITH_DRAG_AND_DROP + "--draganddrop|" +#endif + "--display|" +# ifdef VBOX_WITH_GUEST_PROPS + "--checkhostversion|" +#endif + "--seamless|check3d|" + "--vmsvga|--vmsvga-x11" + "[-d|--nodaemon]\n", pcszFileName); + RTPrintf("Starts the VirtualBox DRM/X Window System guest services.\n\n"); + RTPrintf("Options:\n"); + RTPrintf(" --clipboard starts the shared clipboard service\n"); +#ifdef VBOX_WITH_DRAG_AND_DROP + RTPrintf(" --draganddrop starts the drag and drop service\n"); +#endif + RTPrintf(" --display starts the display management service\n"); +#ifdef VBOX_WITH_GUEST_PROPS + RTPrintf(" --checkhostversion starts the host version notifier service\n"); +#endif + RTPrintf(" --check3d tests whether 3D pass-through is enabled\n"); + RTPrintf(" --seamless starts the seamless windows service\n"); + RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for DRM\n"); + RTPrintf(" --vmsvga-x11 starts VMSVGA dynamic resizing for X11\n"); + 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(" -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 main loop for the VBoxClient daemon. + * @todo Clean up for readability. + */ +int main(int argc, char *argv[]) +{ + /* Initialise our runtime before all else. */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* 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()) + VBClFatalError(("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) */ + /** @todo Use RTGetOpt() if the arguments become more complex. */ + bool fDaemonise = true; + bool fRespawn = true; + for (int i = 1; i < argc; ++i) + { + if ( !strcmp(argv[i], "-f") + || !strcmp(argv[i], "--foreground") + || !strcmp(argv[i], "-d") + || !strcmp(argv[i], "--nodaemon")) + { + /* If the user is running in "no daemon" mode anyway, send critical + * logging to stdout as well. */ + /** @todo r=bird: Since the release logger isn't created until the service + * calls VbglR3InitUser or VbglR3Init or RTLogCreate, this whole + * exercise is pointless. Added --init-vbgl-user and --init-vbgl-full + * for getting some work done. */ + PRTLOGGER pReleaseLog = RTLogRelGetDefaultInstance(); + if (pReleaseLog) + rc = RTLogDestinations(pReleaseLog, "stdout"); + if (pReleaseLog && RT_FAILURE(rc)) + return RTMsgErrorExitFailure("failed to redivert error output, rc=%Rrc", rc); + + fDaemonise = false; + if ( !strcmp(argv[i], "-f") + || !strcmp(argv[i], "--foreground")) + fRespawn = false; + } + else if (!strcmp(argv[i], "--no-respawn")) + { + fRespawn = false; + } + else if (!strcmp(argv[i], "--clipboard")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClGetClipboardService(); + } + else if (!strcmp(argv[i], "--display")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClGetDisplayService(); + } + else if (!strcmp(argv[i], "--seamless")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClGetSeamlessService(); + } + else if (!strcmp(argv[i], "--checkhostversion")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClGetHostVersionService(); + } +#ifdef VBOX_WITH_DRAG_AND_DROP + else if (!strcmp(argv[i], "--draganddrop")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClGetDragAndDropService(); + } +#endif /* VBOX_WITH_DRAG_AND_DROP */ + else if (!strcmp(argv[i], "--check3d")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClCheck3DService(); + } + else if (!strcmp(argv[i], "--vmsvga")) + { + if (g_pService) + return vbclSyntaxOnlyOneService(); + g_pService = VBClDisplaySVGAService(); + } + else if (!strcmp(argv[i], "--vmsvga-x11")) + { + if (g_pService) + break; + g_pService = VBClDisplaySVGAX11Service(); + } + /* bird: this is just a quick hack to get something out of the LogRel statements in the code. */ + else if (!strcmp(argv[i], "--init-vbgl-user")) + { + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("VbglR3InitUser failed: %Rrc", rc); + } + else if (!strcmp(argv[i], "--init-vbgl-full")) + { + rc = VbglR3Init(); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("VbglR3Init failed: %Rrc", rc); + } + else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) + { + vboxClientUsage(pcszFileName); + return RTEXITCODE_SUCCESS; + } + else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) + { + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return RTEXITCODE_SUCCESS; + } + else + { + RTMsgError("unrecognized option `%s'", argv[i]); + RTMsgInfo("Try `%s --help' for more information", pcszFileName); + return RTEXITCODE_SYNTAX; + } + } + if (!g_pService) + { + RTMsgError("No service specified. Quitting because nothing to do!"); + return RTEXITCODE_SYNTAX; + } + + rc = RTCritSectInit(&g_critSect); + if (RT_FAILURE(rc)) + VBClFatalError(("Initialising critical section failed: %Rrc\n", rc)); + if ((*g_pService)->getPidFilePath) + { + rc = RTPathUserHome(g_szPidFile, sizeof(g_szPidFile)); + if (RT_FAILURE(rc)) + VBClFatalError(("Getting home directory for PID file failed: %Rrc\n", rc)); + rc = RTPathAppend(g_szPidFile, sizeof(g_szPidFile), + (*g_pService)->getPidFilePath()); + if (RT_FAILURE(rc)) + VBClFatalError(("Creating PID file path failed: %Rrc\n", rc)); + if (fDaemonise) + rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */, fRespawn, &cRespawn); + if (RT_FAILURE(rc)) + VBClFatalError(("Daemonizing failed: %Rrc\n", rc)); + if (g_szPidFile[0]) + rc = VbglR3PidFile(g_szPidFile, &g_hPidFile); + if (rc == VERR_FILE_LOCK_VIOLATION) /* Already running. */ + return RTEXITCODE_SUCCESS; + if (RT_FAILURE(rc)) + VBClFatalError(("Creating PID file failed: %Rrc\n", rc)); + } + /* Set signal handlers to clean up on exit. */ + vboxClientSetSignalHandlers(); +#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 + rc = (*g_pService)->init(g_pService); + if (RT_SUCCESS(rc)) + { + rc = (*g_pService)->run(g_pService, fDaemonise); + if (RT_FAILURE(rc)) + LogRel2(("Running service failed: %Rrc\n", rc)); + } + else + { + /** @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. */ + LogRel2(("Initializing service failed: %Rrc\n", rc)); + } + VBClCleanUp(false /*fExit*/); + return 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..26c2cf53 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp @@ -0,0 +1,520 @@ +/* $Id: seamless-x11.cpp $ */ +/** @file + * X11 Seamless mode. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header files * +*********************************************************************************************************************************/ + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/vector.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) +{ + LogRelFlowFunc(("\n")); + 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; + + LogRelFlowFunc(("returning\n")); + return propVal; +} + +/** + * Initialise the guest and ensure that it is capable of handling seamless mode + * + * @param pHostCallback host callback. + * @returns true if it can handle seamless, false otherwise + */ +int SeamlessX11::init(PFNSENDREGIONUPDATE pHostCallback) +{ + int rc = VINF_SUCCESS; + + LogRelFlowFunc(("\n")); + if (mHostCallback != NULL) /* Assertion */ + { + LogRel(("VBoxClient: ERROR: attempt to initialise seamless guest object twice!\n")); + return VERR_INTERNAL_ERROR; + } + if (!(mDisplay = XOpenDisplay(NULL))) + { + LogRel(("VBoxClient: seamless guest object failed to acquire a connection to the display.\n")); + return VERR_ACCESS_DENIED; + } + mHostCallback = pHostCallback; + mEnabled = false; + unmonitorClientList(); + LogRelFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + +/** + * 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; + + LogRelFlowFunc(("\n")); + if (mEnabled) + return VINF_SUCCESS; + mSupportsShape = XShapeQueryExtension(mDisplay, &event, &error); + mEnabled = true; + monitorClientList(); + rebuildWindowTree(); + LogRelFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + +/** Stop reporting seamless events to the host. Free information about guest windows + and stop requesting updates. */ +void SeamlessX11::stop(void) +{ + LogRelFlowFunc(("\n")); + if (!mEnabled) + return; + mEnabled = false; + unmonitorClientList(); + freeWindowTree(); + LogRelFlowFunc(("returning\n")); +} + +void SeamlessX11::monitorClientList(void) +{ + LogRelFlowFunc(("called\n")); + XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask | SubstructureNotifyMask); +} + +void SeamlessX11::unmonitorClientList(void) +{ + LogRelFlowFunc(("called\n")); + 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) +{ + LogRelFlowFunc(("called\n")); + 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; + + LogRelFlowFunc(("\n")); + if (!XQueryTree(mDisplay, hRoot, &hRealRoot, &hParent, &phChildrenRaw, &cChildren)) + return; + phChildren = phChildrenRaw; + for (unsigned i = 0; i < cChildren; ++i) + addClientWindow(phChildren[i]); + XFree(phChildrenRaw); + LogRelFlowFunc(("returning\n")); +} + + +void SeamlessX11::addClientWindow(const Window hWin) +{ + LogRelFlowFunc(("\n")); + XWindowAttributes winAttrib; + bool fAddWin = true; + Window hClient = XmuClientWindow(mDisplay, hWin); + + if (isVirtualRoot(hClient)) + fAddWin = false; + if (fAddWin && !XGetWindowAttributes(mDisplay, hWin, &winAttrib)) + { + LogRelFunc(("VBoxClient: 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); + } + LogRelFlowFunc(("returning\n")); +} + + +/** + * 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; + + LogRelFlowFunc(("\n")); + 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. */ + LogRelFlowFunc(("\n")); + mGuestWindows.detachAll(VBoxGuestWinFree, mDisplay); + LogRelFlowFunc(("returning\n")); +} + + +/** + * Waits for a position or shape-related event from guest windows + * + * @note Called from the guest event thread. + */ +void SeamlessX11::nextConfigurationEvent(void) +{ + XEvent event; + + LogRelFlowFunc(("\n")); + /* 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; + /* We execute this even when seamless is disabled, as it also waits for + * enable and disable notification. */ + XNextEvent(mDisplay, &event); + 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) +{ + LogRelFlowFunc(("\n")); + 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; + } + LogRelFlowFunc(("returning\n")); +} + +/** + * 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) + +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) +{ + LogRelFlowFunc(("\n")); + struct RectList rects = RTVEC_INITIALIZER; + + if (mcRects != 0) + { + int rc = RectListReserve(&rects, mcRects * 2); + if (RT_FAILURE(rc)) + return rc; + } + mGuestWindows.doWithAll((PVBOXGUESTWINCALLBACK)getRectsCallback, + &rects); + if (mpRects) + RTMemFree(mpRects); + mcRects = RectListSize(&rects); + mpRects = RectListDetach(&rects); + LogRelFlowFunc(("returning\n")); + 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); + + LogRelFlowFunc(("\n")); + if (pDisplay == NULL) + VBClFatalError(("Failed to open X11 display.\n")); + /* Message contents set to zero. */ + XClientMessageEvent clientMessage = { ClientMessage, 0, 0, 0, 0, 0, 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..f7c0c268 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h @@ -0,0 +1,261 @@ +/* $Id: seamless-x11.h $ */ +/** @file + * + * Seamless mode: + * Linux guest. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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> + +#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); + } + +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 DECLCALLBACK(int) VBOXGUESTWINCALLBACK(VBoxGuestWinInfo *, void *); +/** Pointer to VBOXGUESTWINCALLBACK */ +typedef VBOXGUESTWINCALLBACK *PVBOXGUESTWINCALLBACK; + +DECLCALLBACK(int) inline 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); + } + + // Standard operations + VBoxGuestWinInfo *find(Window hWin) + { + return (VBoxGuestWinInfo *)RTAvlU32Get(&mWindows, hWin); + } + + void detachAll(PVBOXGUESTWINCALLBACK pCallback, void *pvParam) + { + RTAvlU32Destroy(&mWindows, (PAVLU32CALLBACK)pCallback, pvParam); + } + + int doWithAll(PVBOXGUESTWINCALLBACK pCallback, void *pvParam) + { + return RTAvlU32DoWithAll(&mWindows, 1, (PAVLU32CALLBACK)pCallback, + 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; + LogRelFlowFunc(("returning\n")); + return RTAvlU32Insert(&mWindows, &pInfo->Core); + } + + VBoxGuestWinInfo *removeWindow(Window hWin) + { + LogRelFlowFunc(("called\n")); + 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) + { + if (mHostCallback) + stop(); + mHostCallback = NULL; + if (mDisplay) + XCloseDisplay(mDisplay); + mDisplay = NULL; + } + + /** + * 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(); + } +}; + +#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..62822bf7 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless.cpp @@ -0,0 +1,344 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header files * +*********************************************************************************************************************************/ +#include <X11/Xlib.h> + +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> + +#include "VBoxClient.h" +#include "seamless.h" + +#include <new> + +SeamlessMain::SeamlessMain(void) +{ + LogRelFlowFunc(("\n")); + mX11MonitorThread = NIL_RTTHREAD; + mX11MonitorThreadStopping = false; + mMode = VMMDev_Seamless_Disabled; + mfPaused = true; +} + +SeamlessMain::~SeamlessMain() +{ + LogRelFlowFunc(("\n")); + stop(); +} + +/** + * Update the set of visible rectangles in the host. + */ +static void sendRegionUpdate(RTRECT *pRects, size_t cRects) +{ + LogRelFlowFunc(("\n")); + if (cRects && !pRects) /* Assertion */ + { + LogRelFunc(("ERROR: called with null pointer!\n")); + return; + } + VbglR3SeamlessSendRects(cRects, pRects); + LogRelFlowFunc(("returning\n")); +} + +/** + * initialise the service. + */ +int SeamlessMain::init(void) +{ + int rc; + const char *pcszStage; + + LogRelFlowFunc(("\n")); + 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)) + VBClFatalError(("VBoxClient (seamless): failed to start. Stage: \"%s\" Error: %Rrc\n", + pcszStage, rc)); + return rc; +} + +/** + * Run the main service thread which listens for host state change + * notifications. + * @returns iprt status value. Service will be set to the stopped state on + * failure. + */ +int SeamlessMain::run(void) +{ + int rc = VINF_SUCCESS; + + LogRelFlowFunc(("\n")); + /* This will only exit if something goes wrong. */ + while (RT_SUCCESS(rc) || rc == VERR_INTERRUPTED) + { + if (RT_FAILURE(rc)) + /* If we are not stopping, sleep for a bit to avoid using up too + much CPU while retrying. */ + RTThreadYield(); + rc = nextStateChangeEvent(); + } + if (RT_FAILURE(rc)) + { + LogRel(("VBoxClient (seamless): event loop failed with error: %Rrc\n", + rc)); + stop(); + } + return rc; +} + +/** Stops the service. */ +void SeamlessMain::stop() +{ + LogRelFlowFunc(("\n")); + VbglR3SeamlessSetCap(false); + VbglR3CtlFilterMask(0, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST); + stopX11MonitorThread(); + mX11Monitor.uninit(); + LogRelFlowFunc(("returning\n")); +} + +/** + * Waits for a seamless state change events from the host and dispatch it. + * + * @returns IRPT return code. + */ +int SeamlessMain::nextStateChangeEvent(void) +{ + VMMDevSeamlessMode newMode = VMMDev_Seamless_Disabled; + + LogRelFlowFunc(("\n")); + 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. */ + LogRelFlowFunc(("\"Visible region\" mode requested (VBoxClient).\n")); + break; + case VMMDev_Seamless_Disabled: + LogRelFlowFunc(("\"Disabled\" mode requested (VBoxClient).\n")); + break; + case VMMDev_Seamless_Host_Window: + /* One host window represents one guest window. Not yet implemented. */ + LogRelFunc(("Unsupported \"host window\" mode requested (VBoxClient).\n")); + return VERR_NOT_SUPPORTED; + default: + LogRelFunc(("Unsupported mode %d requested (VBoxClient).\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 + { + LogRelFunc(("VbglR3SeamlessWaitEvent returned %Rrc (VBoxClient)\n", rc)); + } + LogRelFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + +/** + * The actual X11 window configuration change monitor thread function. + */ +int SeamlessMain::x11MonitorThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF1(hThreadSelf); + SeamlessMain *pHost = (SeamlessMain *)pvUser; + int rc = VINF_SUCCESS; + + LogRelFlowFunc(("\n")); + while (!pHost->mX11MonitorThreadStopping) + { + if (!pHost->mfPaused) + { + rc = pHost->mX11Monitor.start(); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to change the X11 seamless service state, mfPaused=%RTbool, rc=%Rrc\n", + pHost->mfPaused, rc)); + } + pHost->mX11Monitor.nextConfigurationEvent(); + if (pHost->mfPaused || pHost->mX11MonitorThreadStopping) + pHost->mX11Monitor.stop(); + } + LogRelFlowFunc(("returning %Rrc\n", rc)); + return rc; +} + +/** + * Start the X11 window configuration change monitor thread. + */ +int SeamlessMain::startX11MonitorThread(void) +{ + int rc; + + mX11MonitorThreadStopping = false; + if (isX11MonitorThreadRunning()) + return VINF_SUCCESS; + rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThread, this, 0, + RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, + "X11 events"); + if (RT_FAILURE(rc)) + LogRelFunc(("Warning: failed to start X11 monitor thread (VBoxClient).\n")); + return rc; +} + +/** + * Send a signal to the thread function that it should exit + */ +int SeamlessMain::stopX11MonitorThread(void) +{ + int rc; + + mX11MonitorThreadStopping = true; + if (!isX11MonitorThreadRunning()) + return VINF_SUCCESS; + mX11Monitor.interruptEventWait(); + rc = RTThreadWait(mX11MonitorThread, RT_INDEFINITE_WAIT, NULL); + if (RT_SUCCESS(rc)) + mX11MonitorThread = NIL_RTTHREAD; + else + LogRelThisFunc(("Failed to stop X11 monitor thread, rc=%Rrc!\n", + rc)); + return rc; +} + +/** Service magic number, start of a UUID. */ +#define SEAMLESSSERVICE_MAGIC 0xd28ba727 + +/** VBoxClient service class wrapping the logic for the seamless service while + * the main VBoxClient code provides the daemon logic needed by all services. + */ +struct SEAMLESSSERVICE +{ + /** The service interface. */ + struct VBCLSERVICE *pInterface; + /** Magic number for sanity checks. */ + uint32_t magic; + /** Seamless service object. */ + SeamlessMain mSeamless; + /** Are we initialised yet? */ + bool mIsInitialised; +}; + +static const char *getPidFilePath(void) +{ + return ".vboxclient-seamless.pid"; +} + +static struct SEAMLESSSERVICE *getClassFromInterface(struct VBCLSERVICE ** + ppInterface) +{ + struct SEAMLESSSERVICE *pSelf = (struct SEAMLESSSERVICE *)ppInterface; + if (pSelf->magic != SEAMLESSSERVICE_MAGIC) + VBClFatalError(("Bad seamless service object!\n")); + return pSelf; +} + +static int init(struct VBCLSERVICE **ppInterface) +{ + struct SEAMLESSSERVICE *pSelf = getClassFromInterface(ppInterface); + int rc; + + if (pSelf->mIsInitialised) + return VERR_INTERNAL_ERROR; + /* Initialise the guest library. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc)); + rc = pSelf->mSeamless.init(); + if (RT_FAILURE(rc)) + return rc; + pSelf->mIsInitialised = true; + return VINF_SUCCESS; +} + +static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised) +{ + RT_NOREF1(fDaemonised); + struct SEAMLESSSERVICE *pSelf = getClassFromInterface(ppInterface); + int rc; + + if (!pSelf->mIsInitialised) + return VERR_INTERNAL_ERROR; + /* This only exits on error. */ + rc = pSelf->mSeamless.run(); + pSelf->mIsInitialised = false; + return rc; +} + +static void cleanup(struct VBCLSERVICE **ppInterface) +{ + NOREF(ppInterface); + VbglR3SeamlessSetCap(false); + VbglR3Term(); +} + +struct VBCLSERVICE vbclSeamlessInterface = +{ + getPidFilePath, + init, + run, + cleanup +}; + +struct VBCLSERVICE **VBClGetSeamlessService() +{ + struct SEAMLESSSERVICE *pService = + (struct SEAMLESSSERVICE *)RTMemAlloc(sizeof(*pService)); + + if (!pService) + VBClFatalError(("Out of memory\n")); + pService->pInterface = &vbclSeamlessInterface; + pService->magic = SEAMLESSSERVICE_MAGIC; + new(&pService->mSeamless) SeamlessMain(); + pService->mIsInitialised = false; + return &pService->pInterface; +} diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.h b/src/VBox/Additions/x11/VBoxClient/seamless.h new file mode 100644 index 00000000..478ab58c --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless.h @@ -0,0 +1,113 @@ +/* $Id: seamless.h $ */ +/** @file + * X11 Guest client - seamless mode, missing proper description while using the + * potentially confusing word 'host'. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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(); + + /** + * Initialise the service. + */ + int init(void); + + /** + * Run the service. + * @returns iprt status value + */ + int run(void); + + /** + * Stops the service. + */ + void stop(); + + /** Pause the service loop. This must be safe to call on a different thread + * and potentially before @a run is or after it exits. + * This is called by the VT monitoring thread to allow the service to disable + * itself when the X server is switched out. If the monitoring functionality + * is available then @a pause or @a resume will be called as soon as it starts + * up. */ + int pause(); + /** Resume after pausing. The same applies here as for @a pause. */ + int resume(); + + /** Run a few tests to be sure everything is working as intended. */ + int selfTest(); +}; + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_h */ 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..b18f1fd9 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp @@ -0,0 +1,738 @@ +/* $Id: tstSeamlessX11-auto.cpp $ */ +/** @file + * Automated test of the X11 seamless Additions code. + * @todo Better separate test data from implementation details! + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <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) + +extern void vbclFatalError(char *psz) +{ + RTPrintf("Fatal error: %s\n", 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; +} + +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); + 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..07916cec --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp @@ -0,0 +1,139 @@ +/* $Id: tstSeamlessX11.cpp $ */ +/** @file + * Linux seamless guest additions simulator in host. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <stdlib.h> /* exit() */ + +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> +#include <VBox/VBoxGuestLib.h> + +#include "../seamless.h" + +static RTSEMEVENT eventSem; + +/** Exit with a fatal error. */ +void vbclFatalError(char *pszMessage) +{ + RTPrintf("Fatal error: %s", pszMessage); + exit(1); +} + +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); + } + rc = seamless.run(); + if (rc != VINF_SUCCESS) + { + RTPrintf("Failed to run seamless Additions, rc = %Rrc\n", rc); + } + return rc; +} |