summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedClipboard
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostServices/SharedClipboard')
-rw-r--r--src/VBox/HostServices/SharedClipboard/Makefile.kmk91
-rw-r--r--src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp1248
-rw-r--r--src/VBox/HostServices/SharedClipboard/VBoxClipboard.h103
-rw-r--r--src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp1047
-rw-r--r--src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc51
-rw-r--r--src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp404
-rw-r--r--src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h34
-rw-r--r--src/VBox/HostServices/SharedClipboard/darwin.cpp274
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk33
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp286
-rw-r--r--src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp615
-rw-r--r--src/VBox/HostServices/SharedClipboard/x11-stub.cpp137
12 files changed, 4323 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk
new file mode 100644
index 00000000..5ae7b1a6
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk
@@ -0,0 +1,91 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Shared Clipboard Host Service.
+#
+
+#
+# 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
+
+# Include sub-makefile(s).
+include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+
+#
+# The shared folder service DLL.
+#
+DLLS += VBoxSharedClipboard
+VBoxSharedClipboard_TEMPLATE = VBOXR3
+VBoxSharedClipboard_DEFS = VBOX_WITH_HGCM
+VBoxSharedClipboard_INCS.win = \
+ $(VBOX_PATH_SDK)
+
+VBoxSharedClipboard_SOURCES = \
+ VBoxSharedClipboardSvc.cpp
+VBoxSharedClipboard_SOURCES.win = \
+ VBoxClipboard-win.cpp \
+ VBoxSharedClipboardSvc.rc
+VBoxSharedClipboard_SOURCES.darwin = \
+ darwin.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \
+ darwin-pasteboard.cpp
+if1of ($(KBUILD_TARGET), linux solaris freebsd) ## @todo X11
+ ifndef VBOX_HEADLESS
+ VBoxSharedClipboard_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp \
+ x11-clipboard.cpp
+ else
+ VBoxSharedClipboard_SOURCES += \
+ x11-stub.cpp
+ endif
+endif
+
+VBoxSharedClipboard_LIBS = \
+ $(LIB_VMM) \
+ $(LIB_RUNTIME) \
+ $(LIB_REM)
+if1of ($(KBUILD_TARGET), linux solaris freebsd)
+ ifndef VBOX_HEADLESS
+ VBoxSharedClipboard_LIBPATH = \
+ $(VBOX_LIBPATH_X11)
+ VBoxSharedClipboard_LIBS += \
+ Xt \
+ X11
+ endif
+endif
+
+VBoxSharedClipboard_LDFLAGS.darwin = \
+ -framework ApplicationServices -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib
+
+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 += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run
+ PROGRAMS += tstClipboardX11-2
+ TESTING += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run
+ tstClipboardX11-2_TEMPLATE = VBOXR3TSTEXE
+ tstClipboardX11-2_DEFS = VBOX_WITH_HGCM TESTCASE
+ tstClipboardX11-2_SOURCES = x11-clipboard.cpp
+ tstClipboardX11-2_LIBS = $(LIB_RUNTIME)
+ tstClipboardX11-2_CLEANS = $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run
+
+$$(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run: $$(tstClipboardX11-2_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstClipboardX11-2_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+ endif # 1of ($(KBUILD_TARGET),freebsd linux netbsd openbsd solaris)
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp b/src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp
new file mode 100644
index 00000000..0f069a92
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp
@@ -0,0 +1,1248 @@
+/* $Id: VBoxClipboard-win.cpp $ */
+/** @file
+ * Shared Clipboard Service - Win32 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.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <iprt/win/windows.h>
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/thread.h>
+#include <iprt/ldr.h>
+#include <process.h>
+
+#include "VBoxClipboard.h"
+
+#define dprintf Log
+
+static char gachWindowClassName[] = "VBoxSharedClipboardClass";
+
+enum { CBCHAIN_TIMEOUT = 5000 /* ms */ };
+
+/* Dynamically load clipboard functions from User32.dll. */
+typedef BOOL WINAPI FNADDCLIPBOARDFORMATLISTENER(HWND);
+typedef FNADDCLIPBOARDFORMATLISTENER *PFNADDCLIPBOARDFORMATLISTENER;
+
+typedef BOOL WINAPI FNREMOVECLIPBOARDFORMATLISTENER(HWND);
+typedef FNREMOVECLIPBOARDFORMATLISTENER *PFNREMOVECLIPBOARDFORMATLISTENER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int ConvertCFHtmlToMime(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pch);
+static int ConvertMimeToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput);
+static bool IsWindowsHTML(const char *source);
+
+
+#ifndef WM_CLIPBOARDUPDATE
+#define WM_CLIPBOARDUPDATE 0x031D
+#endif
+
+struct _VBOXCLIPBOARDCONTEXT
+{
+ HWND hwnd;
+ HWND hwndNextInChain;
+
+ UINT timerRefresh;
+
+ bool fCBChainPingInProcess;
+
+ RTTHREAD thread;
+
+ HANDLE hRenderEvent;
+
+ VBOXCLIPBOARDCLIENTDATA *pClient;
+
+ PFNADDCLIPBOARDFORMATLISTENER pfnAddClipboardFormatListener;
+ PFNREMOVECLIPBOARDFORMATLISTENER pfnRemoveClipboardFormatListener;
+
+};
+
+/* Only one client is supported. There seems to be no need for more clients. */
+static VBOXCLIPBOARDCONTEXT g_ctx;
+
+
+#ifdef LOG_ENABLED
+void vboxClipboardDump(const void *pv, size_t cb, uint32_t u32Format)
+{
+ if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ Log(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT:\n"));
+ if (pv && cb)
+ Log(("%ls\n", pv));
+ else
+ Log(("%p %d\n", pv, cb));
+ }
+ else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ dprintf(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_BITMAP\n"));
+ else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ {
+ Log(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_HTML:\n"));
+ if (pv && cb)
+ {
+ Log(("%s\n", pv));
+
+ //size_t cb = RTStrNLen(pv, );
+ char *pszBuf = (char *)RTMemAllocZ(cb + 1);
+ RTStrCopy(pszBuf, cb + 1, (const char *)pv);
+ for (size_t off = 0; off < cb; ++off)
+ {
+ if (pszBuf[off] == '\n' || pszBuf[off] == '\r')
+ pszBuf[off] = ' ';
+ }
+
+ Log(("%s\n", pszBuf));
+ RTMemFree(pszBuf);
+ }
+ else
+ Log(("%p %d\n", pv, cb));
+ }
+ else
+ dprintf(("DUMP: invalid format %02X\n", u32Format));
+}
+#else /* !LOG_ENABLED */
+# define vboxClipboardDump(__pv, __cb, __format) do { NOREF(__pv); NOREF(__cb); NOREF(__format); } while (0)
+#endif /* !LOG_ENABLED */
+
+
+static void vboxClipboardInitNewAPI(VBOXCLIPBOARDCONTEXT *pCtx)
+{
+ RTLDRMOD hUser32 = NIL_RTLDRMOD;
+ int rc = RTLdrLoadSystem("User32.dll", /* fNoUnload = */ true, &hUser32);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hUser32, "AddClipboardFormatListener", (void**)&pCtx->pfnAddClipboardFormatListener);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hUser32, "RemoveClipboardFormatListener", (void**)&pCtx->pfnRemoveClipboardFormatListener);
+ }
+
+ RTLdrClose(hUser32);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ Log(("New Clipboard API is enabled\n"));
+ }
+ else
+ {
+ pCtx->pfnAddClipboardFormatListener = NULL;
+ pCtx->pfnRemoveClipboardFormatListener = NULL;
+ Log(("New Clipboard API is not available. rc = %Rrc\n", rc));
+ }
+}
+
+static bool vboxClipboardIsNewAPI(VBOXCLIPBOARDCONTEXT *pCtx)
+{
+ return pCtx->pfnAddClipboardFormatListener != NULL;
+}
+
+
+static int vboxOpenClipboard(HWND hwnd)
+{
+ /* "OpenClipboard fails if another window has the clipboard open."
+ * So try a few times and wait up to 1 second.
+ */
+ BOOL fOpened = FALSE;
+
+ int i = 0;
+ for (;;)
+ {
+ if (OpenClipboard(hwnd))
+ {
+ fOpened = TRUE;
+ break;
+ }
+
+ if (i >= 10) /* sleep interval = [1..512] ms */
+ break;
+
+ RTThreadSleep(1 << i);
+ ++i;
+ }
+
+#ifdef LOG_ENABLED
+ if (i > 0)
+ LogFlowFunc(("%d times tried to open clipboard.\n", i + 1));
+#endif
+
+ int rc;
+ if (fOpened)
+ rc = VINF_SUCCESS;
+ else
+ {
+ const DWORD err = GetLastError();
+ LogFlowFunc(("error %d\n", err));
+ rc = RTErrConvertFromWin32(err);
+ }
+
+ return rc;
+}
+
+
+/** @todo Someone please explain the protocol wrt overflows... */
+static void vboxClipboardGetData (uint32_t u32Format, const void *pvSrc, uint32_t cbSrc,
+ void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst)
+{
+ dprintf (("vboxClipboardGetData.\n"));
+
+ LogFlow(("vboxClipboardGetData cbSrc = %d, cbDst = %d\n", cbSrc, cbDst));
+
+ if ( u32Format == VBOX_SHARED_CLIPBOARD_FMT_HTML
+ && IsWindowsHTML((const char *)pvSrc))
+ {
+ /** @todo r=bird: Why the double conversion? */
+ char *pszBuf = NULL;
+ uint32_t cbBuf = 0;
+ int rc = ConvertCFHtmlToMime((const char*)pvSrc, cbSrc, &pszBuf, &cbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbActualDst = cbBuf;
+ if (cbBuf > cbDst)
+ {
+ /* Do not copy data. The dst buffer is not enough. */
+ RTMemFree(pszBuf);
+ return;
+ }
+ memcpy(pvDst, pszBuf, cbBuf);
+ RTMemFree(pszBuf);
+ }
+ else
+ *pcbActualDst = 0;
+ }
+ else
+ {
+ *pcbActualDst = cbSrc;
+
+ if (cbSrc > cbDst)
+ {
+ /* Do not copy data. The dst buffer is not enough. */
+ return;
+ }
+
+ memcpy(pvDst, pvSrc, cbSrc);
+ }
+
+ vboxClipboardDump(pvDst, cbSrc, u32Format);
+
+ return;
+}
+
+static int vboxClipboardReadDataFromClient (VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format)
+{
+ Assert(pCtx->pClient);
+ Assert(pCtx->hRenderEvent);
+ Assert(pCtx->pClient->data.pv == NULL && pCtx->pClient->data.cb == 0 && pCtx->pClient->data.u32Format == 0);
+
+ LogFlow(("vboxClipboardReadDataFromClient u32Format = %02X\n", u32Format));
+
+ ResetEvent (pCtx->hRenderEvent);
+
+ vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA, u32Format);
+
+ DWORD ret = WaitForSingleObject(pCtx->hRenderEvent, INFINITE);
+ LogFlow(("vboxClipboardReadDataFromClient wait completed, ret 0x%08X, err %d\n",
+ ret, GetLastError())); NOREF(ret);
+
+ return VINF_SUCCESS;
+}
+
+static void vboxClipboardChanged (VBOXCLIPBOARDCONTEXT *pCtx)
+{
+ LogFlow(("vboxClipboardChanged\n"));
+
+ if (pCtx->pClient == NULL)
+ {
+ return;
+ }
+
+ /* Query list of available formats and report to host. */
+ int rc = vboxOpenClipboard(pCtx->hwnd);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t u32Formats = 0;
+
+ UINT format = 0;
+
+ while ((format = EnumClipboardFormats (format)) != 0)
+ {
+ LogFlow(("vboxClipboardChanged format %#x\n", format));
+ switch (format)
+ {
+ case CF_UNICODETEXT:
+ case CF_TEXT:
+ u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
+ break;
+
+ case CF_DIB:
+ case CF_BITMAP:
+ u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
+ break;
+
+ default:
+ if (format >= 0xC000)
+ {
+ TCHAR szFormatName[256];
+
+ int cActual = GetClipboardFormatName(format, szFormatName, sizeof(szFormatName)/sizeof (TCHAR));
+
+ if (cActual)
+ {
+ if (strcmp (szFormatName, "HTML Format") == 0)
+ {
+ u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_HTML;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ CloseClipboard ();
+
+ LogFlow(("vboxClipboardChanged u32Formats %02X\n", u32Formats));
+
+ vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, u32Formats);
+ }
+ else
+ {
+ LogFlow(("vboxClipboardChanged: error in open clipboard. hwnd: %x. err: %Rrc\n", pCtx->hwnd, rc));
+ }
+}
+
+/* Add ourselves into the chain of cliboard listeners */
+static void addToCBChain (VBOXCLIPBOARDCONTEXT *pCtx)
+{
+ if (vboxClipboardIsNewAPI(pCtx))
+ pCtx->pfnAddClipboardFormatListener(pCtx->hwnd);
+ else
+ pCtx->hwndNextInChain = SetClipboardViewer(pCtx->hwnd);
+}
+
+/* Remove ourselves from the chain of cliboard listeners */
+static void removeFromCBChain (VBOXCLIPBOARDCONTEXT *pCtx)
+{
+ if (vboxClipboardIsNewAPI(pCtx))
+ {
+ pCtx->pfnRemoveClipboardFormatListener(pCtx->hwnd);
+ }
+ else
+ {
+ ChangeClipboardChain(pCtx->hwnd, pCtx->hwndNextInChain);
+ pCtx->hwndNextInChain = NULL;
+ }
+}
+
+/* Callback which is invoked when we have successfully pinged ourselves down the
+ * clipboard chain. We simply unset a boolean flag to say that we are responding.
+ * There is a race if a ping returns after the next one is initiated, but nothing
+ * very bad is likely to happen. */
+VOID CALLBACK CBChainPingProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult)
+{
+ (void) hwnd;
+ (void) uMsg;
+ (void) lResult;
+ VBOXCLIPBOARDCONTEXT *pCtx = (VBOXCLIPBOARDCONTEXT *)dwData;
+ pCtx->fCBChainPingInProcess = FALSE;
+}
+
+static LRESULT CALLBACK vboxClipboardWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT rc = 0;
+
+ VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
+
+ switch (msg)
+ {
+ case WM_CLIPBOARDUPDATE:
+ {
+ Log(("WM_CLIPBOARDUPDATE\n"));
+
+ if (GetClipboardOwner() != hwnd)
+ {
+ /* Clipboard was updated by another application. */
+ vboxClipboardChanged(pCtx);
+ }
+ } break;
+
+ case WM_CHANGECBCHAIN:
+ {
+ Log(("WM_CHANGECBCHAIN\n"));
+
+ if (vboxClipboardIsNewAPI(pCtx))
+ {
+ rc = DefWindowProc(hwnd, msg, wParam, lParam);
+ break;
+ }
+
+ HWND hwndRemoved = (HWND)wParam;
+ HWND hwndNext = (HWND)lParam;
+
+ if (hwndRemoved == pCtx->hwndNextInChain)
+ {
+ /* The window that was next to our in the chain is being removed.
+ * Relink to the new next window.
+ */
+ pCtx->hwndNextInChain = hwndNext;
+ }
+ else
+ {
+ if (pCtx->hwndNextInChain)
+ {
+ /* Pass the message further. */
+ DWORD_PTR dwResult;
+ rc = SendMessageTimeout(pCtx->hwndNextInChain, WM_CHANGECBCHAIN, wParam, lParam, 0, CBCHAIN_TIMEOUT, &dwResult);
+ if (!rc)
+ rc = (LRESULT)dwResult;
+ }
+ }
+ } break;
+
+ case WM_DRAWCLIPBOARD:
+ {
+ Log(("WM_DRAWCLIPBOARD\n"));
+
+ if (GetClipboardOwner () != hwnd)
+ {
+ /* Clipboard was updated by another application. */
+ vboxClipboardChanged (pCtx);
+ }
+
+ if (pCtx->hwndNextInChain)
+ {
+ Log(("WM_DRAWCLIPBOARD next %p\n", pCtx->hwndNextInChain));
+ /* Pass the message to next windows in the clipboard chain. */
+ DWORD_PTR dwResult;
+ rc = SendMessageTimeout(pCtx->hwndNextInChain, msg, wParam, lParam, 0, CBCHAIN_TIMEOUT, &dwResult);
+ if (!rc)
+ rc = dwResult;
+ }
+ } break;
+
+ case WM_TIMER:
+ {
+ if (vboxClipboardIsNewAPI(pCtx))
+ break;
+
+ HWND hViewer = GetClipboardViewer();
+
+ /* Re-register ourselves in the clipboard chain if our last ping
+ * timed out or there seems to be no valid chain. */
+ if (!hViewer || pCtx->fCBChainPingInProcess)
+ {
+ removeFromCBChain(pCtx);
+ addToCBChain(pCtx);
+ }
+ /* Start a new ping by passing a dummy WM_CHANGECBCHAIN to be
+ * processed by ourselves to the chain. */
+ pCtx->fCBChainPingInProcess = TRUE;
+ hViewer = GetClipboardViewer();
+ if (hViewer)
+ SendMessageCallback(hViewer, WM_CHANGECBCHAIN, (WPARAM)pCtx->hwndNextInChain, (LPARAM)pCtx->hwndNextInChain, CBChainPingProc, (ULONG_PTR) pCtx);
+ } break;
+
+ case WM_RENDERFORMAT:
+ {
+ /* Insert the requested clipboard format data into the clipboard. */
+ uint32_t u32Format = 0;
+
+ UINT format = (UINT)wParam;
+
+ Log(("WM_RENDERFORMAT %d\n", format));
+
+ switch (format)
+ {
+ case CF_UNICODETEXT:
+ u32Format |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
+ break;
+
+ case CF_DIB:
+ u32Format |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
+ break;
+
+ default:
+ if (format >= 0xC000)
+ {
+ TCHAR szFormatName[256];
+
+ int cActual = GetClipboardFormatName(format, szFormatName, sizeof(szFormatName)/sizeof (TCHAR));
+
+ if (cActual)
+ {
+ if (strcmp (szFormatName, "HTML Format") == 0)
+ {
+ u32Format |= VBOX_SHARED_CLIPBOARD_FMT_HTML;
+ }
+ }
+ }
+ break;
+ }
+
+ if (u32Format == 0 || pCtx->pClient == NULL)
+ {
+ /* Unsupported clipboard format is requested. */
+ Log(("WM_RENDERFORMAT unsupported format requested or client is not active.\n"));
+ EmptyClipboard ();
+ }
+ else
+ {
+ int vboxrc = vboxClipboardReadDataFromClient (pCtx, u32Format);
+
+ dprintf(("vboxClipboardReadDataFromClient vboxrc = %d, pv %p, cb %d, u32Format %d\n",
+ vboxrc, pCtx->pClient->data.pv, pCtx->pClient->data.cb, pCtx->pClient->data.u32Format));
+
+ if ( RT_SUCCESS (vboxrc)
+ && pCtx->pClient->data.pv != NULL
+ && pCtx->pClient->data.cb > 0
+ && pCtx->pClient->data.u32Format == u32Format)
+ {
+ HANDLE hMem = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, pCtx->pClient->data.cb);
+
+ dprintf(("hMem %p\n", hMem));
+
+ if (hMem)
+ {
+ void *pMem = GlobalLock (hMem);
+
+ dprintf(("pMem %p, GlobalSize %d\n", pMem, GlobalSize (hMem)));
+
+ if (pMem)
+ {
+ Log(("WM_RENDERFORMAT setting data\n"));
+
+ if (pCtx->pClient->data.pv)
+ {
+ memcpy (pMem, pCtx->pClient->data.pv, pCtx->pClient->data.cb);
+
+ RTMemFree (pCtx->pClient->data.pv);
+ pCtx->pClient->data.pv = NULL;
+ }
+
+ pCtx->pClient->data.cb = 0;
+ pCtx->pClient->data.u32Format = 0;
+
+ /* The memory must be unlocked before inserting to the Clipboard. */
+ GlobalUnlock (hMem);
+
+ /* 'hMem' contains the host clipboard data.
+ * size is 'cb' and format is 'format'.
+ */
+ HANDLE hClip = SetClipboardData (format, hMem);
+
+ dprintf(("vboxClipboardHostEvent hClip %p\n", hClip));
+
+ if (hClip)
+ {
+ /* The hMem ownership has gone to the system. Nothing to do. */
+ break;
+ }
+ }
+
+ GlobalFree (hMem);
+ }
+ }
+
+ RTMemFree (pCtx->pClient->data.pv);
+ pCtx->pClient->data.pv = NULL;
+ pCtx->pClient->data.cb = 0;
+ pCtx->pClient->data.u32Format = 0;
+
+ /* Something went wrong. */
+ EmptyClipboard ();
+ }
+ } break;
+
+ case WM_RENDERALLFORMATS:
+ {
+ Log(("WM_RENDERALLFORMATS\n"));
+
+ /* Do nothing. The clipboard formats will be unavailable now, because the
+ * windows is to be destroyed and therefore the guest side becomes inactive.
+ */
+ int vboxrc = vboxOpenClipboard(hwnd);
+ if (RT_SUCCESS(vboxrc))
+ {
+ EmptyClipboard();
+
+ CloseClipboard();
+ }
+ else
+ {
+ LogFlow(("vboxClipboardWndProc: WM_RENDERALLFORMATS: error in open clipboard. hwnd: %x, rc: %Rrc\n", hwnd, vboxrc));
+ }
+ } break;
+
+ case WM_USER:
+ {
+ if (pCtx->pClient == NULL || pCtx->pClient->fMsgFormats)
+ {
+ /* Host has pending formats message. Ignore the guest announcement,
+ * because host clipboard has more priority.
+ */
+ Log(("WM_USER ignored\n"));
+ break;
+ }
+
+ /* Announce available formats. Do not insert data, they will be inserted in WM_RENDER*. */
+ uint32_t u32Formats = (uint32_t)lParam;
+
+ Log(("WM_USER u32Formats = %02X\n", u32Formats));
+
+ int vboxrc = vboxOpenClipboard(hwnd);
+ if (RT_SUCCESS(vboxrc))
+ {
+ EmptyClipboard();
+
+ Log(("WM_USER emptied clipboard\n"));
+
+ HANDLE hClip = NULL;
+
+ if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT\n"));
+
+ hClip = SetClipboardData (CF_UNICODETEXT, NULL);
+ }
+
+ if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ {
+ dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_BITMAP\n"));
+
+ hClip = SetClipboardData (CF_DIB, NULL);
+ }
+
+ if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ {
+ UINT format = RegisterClipboardFormat ("HTML Format");
+ dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_HTML 0x%04X\n", format));
+ if (format != 0)
+ {
+ hClip = SetClipboardData (format, NULL);
+ }
+ }
+
+ CloseClipboard();
+
+ dprintf(("window proc WM_USER: hClip %p, err %d\n", hClip, GetLastError ()));
+ }
+ else
+ {
+ dprintf(("window proc WM_USER: failed to open clipboard. rc: %Rrc\n", vboxrc));
+ }
+ } break;
+
+ case WM_DESTROY:
+ {
+ /* MS recommends to remove from Clipboard chain in this callback */
+ Assert(pCtx->hwnd);
+ removeFromCBChain(pCtx);
+ if (pCtx->timerRefresh)
+ KillTimer(pCtx->hwnd, 0);
+ PostQuitMessage(0);
+ } break;
+
+ default:
+ {
+ Log(("WM_ %p\n", msg));
+ rc = DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+ }
+
+ Log(("WM_ rc %d\n", rc));
+ return rc;
+}
+
+DECLCALLBACK(int) VBoxClipboardThread (RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF2(hThreadSelf, pvUser);
+ /* Create a window and make it a clipboard viewer. */
+ int rc = VINF_SUCCESS;
+
+ LogFlow(("VBoxClipboardThread\n"));
+
+ VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
+
+ HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
+
+ /* Register the Window Class. */
+ WNDCLASS wc;
+ RT_ZERO(wc);
+
+ wc.style = CS_NOCLOSE;
+ wc.lpfnWndProc = vboxClipboardWndProc;
+ wc.hInstance = hInstance;
+ wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
+ wc.lpszClassName = gachWindowClassName;
+
+ ATOM atomWindowClass = RegisterClass(&wc);
+
+ if (atomWindowClass == 0)
+ {
+ Log(("Failed to register window class\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ /* Create the window. */
+ pCtx->hwnd = CreateWindowEx (WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
+ gachWindowClassName, gachWindowClassName,
+ WS_POPUPWINDOW,
+ -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
+
+ if (pCtx->hwnd == NULL)
+ {
+ Log(("Failed to create window\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ SetWindowPos(pCtx->hwnd, HWND_TOPMOST, -200, -200, 0, 0,
+ SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
+
+ addToCBChain(pCtx);
+ if (!vboxClipboardIsNewAPI(pCtx))
+ pCtx->timerRefresh = SetTimer(pCtx->hwnd, 0, 10 * 1000, NULL);
+
+ MSG msg;
+ BOOL msgret = 0;
+ while ((msgret = GetMessage(&msg, NULL, 0, 0)) > 0)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ /*
+ * Window procedure can return error,
+ * but this is exceptional situation
+ * that should be identified in testing
+ */
+ Assert(msgret >= 0);
+ Log(("VBoxClipboardThread Message loop finished. GetMessage returned %d, message id: %d \n", msgret, msg.message));
+ }
+ }
+
+ pCtx->hwnd = NULL;
+
+ if (atomWindowClass != 0)
+ {
+ UnregisterClass (gachWindowClassName, hInstance);
+ atomWindowClass = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Public platform dependent functions.
+ */
+int vboxClipboardInit (void)
+{
+ int rc = VINF_SUCCESS;
+
+ RT_ZERO(g_ctx);
+
+ /* Check that new Clipboard API is available */
+ vboxClipboardInitNewAPI(&g_ctx);
+
+ g_ctx.hRenderEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ rc = RTThreadCreate (&g_ctx.thread, VBoxClipboardThread, NULL, 65536,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
+
+ if (RT_FAILURE (rc))
+ {
+ CloseHandle (g_ctx.hRenderEvent);
+ }
+
+ return rc;
+}
+
+void vboxClipboardDestroy (void)
+{
+ Log(("vboxClipboardDestroy\n"));
+
+ if (g_ctx.hwnd)
+ {
+ PostMessage (g_ctx.hwnd, WM_CLOSE, 0, 0);
+ }
+
+ CloseHandle (g_ctx.hRenderEvent);
+
+ /* Wait for the window thread to terminate. */
+ RTThreadWait (g_ctx.thread, RT_INDEFINITE_WAIT, NULL);
+
+ g_ctx.thread = NIL_RTTHREAD;
+}
+
+int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless)
+{
+ NOREF(fHeadless);
+ Log(("vboxClipboardConnect\n"));
+
+ if (g_ctx.pClient != NULL)
+ {
+ /* One client only. */
+ return VERR_NOT_SUPPORTED;
+ }
+
+ pClient->pCtx = &g_ctx;
+
+ pClient->pCtx->pClient = pClient;
+
+ /* Sync the host clipboard content with the client. */
+ vboxClipboardSync (pClient);
+
+ return VINF_SUCCESS;
+}
+
+int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ /* Sync the host clipboard content with the client. */
+ vboxClipboardChanged (pClient->pCtx);
+
+ return VINF_SUCCESS;
+}
+
+void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ RT_NOREF1(pClient);
+ Log(("vboxClipboardDisconnect\n"));
+
+ g_ctx.pClient = NULL;
+}
+
+void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Formats)
+{
+ /*
+ * The guest announces formats. Forward to the window thread.
+ */
+ PostMessage (pClient->pCtx->hwnd, WM_USER, 0, u32Formats);
+}
+
+int DumpHtml(const char *pszSrc, size_t cb)
+{
+ size_t cchIgnored = 0;
+ int rc = RTStrNLenEx(pszSrc, cb, &cchIgnored);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszBuf = (char *)RTMemAllocZ(cb + 1);
+ if (pszBuf != NULL)
+ {
+ rc = RTStrCopy(pszBuf, cb + 1, (const char *)pszSrc);
+ if (RT_SUCCESS(rc))
+ {
+ for (size_t i = 0; i < cb; ++i)
+ if (pszBuf[i] == '\n' || pszBuf[i] == '\r')
+ pszBuf[i] = ' ';
+ }
+ else
+ Log(("Error in copying string.\n"));
+ Log(("Removed \\r\\n: %s\n", pszBuf));
+ RTMemFree(pszBuf);
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ Log(("Not enough memory to allocate buffer.\n"));
+ }
+ }
+ return rc;
+}
+
+int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Format, void *pv, uint32_t cb, uint32_t *pcbActual)
+{
+ LogFlow(("vboxClipboardReadData: u32Format = %02X\n", u32Format));
+
+ HANDLE hClip = NULL;
+
+ /*
+ * The guest wants to read data in the given format.
+ */
+ int rc = vboxOpenClipboard(pClient->pCtx->hwnd);
+ if (RT_SUCCESS(rc))
+ {
+ dprintf(("Clipboard opened.\n"));
+
+ if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ {
+ hClip = GetClipboardData (CF_DIB);
+
+ if (hClip != NULL)
+ {
+ LPVOID lp = GlobalLock (hClip);
+
+ if (lp != NULL)
+ {
+ dprintf(("CF_DIB\n"));
+
+ vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_BITMAP, lp, GlobalSize (hClip),
+ pv, cb, pcbActual);
+
+ GlobalUnlock(hClip);
+ }
+ else
+ {
+ hClip = NULL;
+ }
+ }
+ }
+ else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ hClip = GetClipboardData(CF_UNICODETEXT);
+
+ if (hClip != NULL)
+ {
+ LPWSTR uniString = (LPWSTR)GlobalLock (hClip);
+
+ if (uniString != NULL)
+ {
+ dprintf(("CF_UNICODETEXT\n"));
+
+ vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, uniString, (lstrlenW (uniString) + 1) * 2,
+ pv, cb, pcbActual);
+
+ GlobalUnlock(hClip);
+ }
+ else
+ {
+ hClip = NULL;
+ }
+ }
+ }
+ else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ {
+ UINT format = RegisterClipboardFormat ("HTML Format");
+
+ if (format != 0)
+ {
+ hClip = GetClipboardData (format);
+
+ if (hClip != NULL)
+ {
+ LPVOID lp = GlobalLock (hClip);
+
+ if (lp != NULL)
+ {
+ dprintf(("CF_HTML\n"));
+
+ vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_HTML, lp, GlobalSize (hClip),
+ pv, cb, pcbActual);
+ LogRelFlowFunc(("Raw HTML clipboard data from host :"));
+ DumpHtml((char*)pv, cb);
+ GlobalUnlock(hClip);
+ }
+ else
+ {
+ hClip = NULL;
+ }
+ }
+ }
+ }
+
+ CloseClipboard ();
+ }
+ else
+ {
+ dprintf(("vboxClipboardReadData: failed to open clipboard, rc: %Rrc\n", rc));
+ }
+
+ if (hClip == NULL)
+ {
+ /* Reply with empty data. */
+ vboxClipboardGetData (0, NULL, 0,
+ pv, cb, pcbActual);
+ }
+
+ return VINF_SUCCESS;
+}
+
+void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient, void *pv, uint32_t cb, uint32_t u32Format)
+{
+ LogFlow(("vboxClipboardWriteData\n"));
+
+ /*
+ * The guest returns data that was requested in the WM_RENDERFORMAT handler.
+ */
+ Assert(pClient->data.pv == NULL && pClient->data.cb == 0 && pClient->data.u32Format == 0);
+
+ vboxClipboardDump(pv, cb, u32Format);
+
+ if (cb > 0)
+ {
+ char *pszResult = NULL;
+
+ if ( u32Format == VBOX_SHARED_CLIPBOARD_FMT_HTML
+ && !IsWindowsHTML((const char*)pv))
+ {
+ /* check that this is not already CF_HTML */
+ uint32_t cbResult;
+ int rc = ConvertMimeToCFHTML((const char *)pv, cb, &pszResult, &cbResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pszResult != NULL && cbResult != 0)
+ {
+ pClient->data.pv = pszResult;
+ pClient->data.cb = cbResult;
+ pClient->data.u32Format = u32Format;
+ }
+ }
+ }
+ else
+ {
+ pClient->data.pv = RTMemDup(pv, cb);
+ if (pClient->data.pv)
+ {
+ pClient->data.cb = cb;
+ pClient->data.u32Format = u32Format;
+ }
+ }
+ }
+
+ SetEvent(pClient->pCtx->hRenderEvent);
+}
+
+
+/**
+ * Extracts field value from CF_HTML struct
+ *
+ * @returns VBox status code
+ * @param pszSrc source in CF_HTML format
+ * @param pszOption Name of CF_HTML field
+ * @param puValue Where to return extracted value of CF_HTML field
+ */
+static int GetHeaderValue(const char *pszSrc, const char *pszOption, uint32_t *puValue)
+{
+ int rc = VERR_INVALID_PARAMETER;
+
+ Assert(pszSrc);
+ Assert(pszOption);
+
+ const char *pszOptionValue = RTStrStr(pszSrc, pszOption);
+ if (pszOptionValue)
+ {
+ size_t cchOption = strlen(pszOption);
+ Assert(cchOption);
+
+ rc = RTStrToUInt32Ex(pszOptionValue + cchOption, NULL, 10, puValue);
+ }
+ return rc;
+}
+
+
+/**
+ * Check that the source string contains CF_HTML struct
+ *
+ * @param pszSource source string.
+ *
+ * @returns @c true if the @a pszSource string is in CF_HTML format
+ */
+static bool IsWindowsHTML(const char *pszSource)
+{
+ return RTStrStr(pszSource, "Version:") != NULL
+ && RTStrStr(pszSource, "StartHTML:") != NULL;
+}
+
+
+/*
+ * Converts clipboard data from CF_HTML format to mimie clipboard format
+ *
+ * Returns allocated buffer that contains html converted to text/html mime type
+ *
+ * @returns VBox status code.
+ * @param pszSource The input.
+ * @param cch The length of the input.
+ * @param ppszOutput Where to return the result. Free using RTMemFree.
+ * @param pcbOutput Where to the return length of the result (bytes/chars).
+ */
+static int ConvertCFHtmlToMime(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pcbOutput)
+{
+ Assert(pszSource);
+ Assert(cch);
+ Assert(ppszOutput);
+ Assert(pcbOutput);
+
+ uint32_t offStart;
+ int rc = GetHeaderValue(pszSource, "StartFragment:", &offStart);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offEnd;
+ rc = GetHeaderValue(pszSource, "EndFragment:", &offEnd);
+ if (RT_SUCCESS(rc))
+ {
+ if ( offStart > 0
+ && offEnd > 0
+ && offEnd > offStart
+ && offEnd <= cch)
+ {
+ uint32_t cchSubStr = offEnd - offStart;
+ char *pszResult = (char *)RTMemAlloc(cchSubStr + 1);
+ if (pszResult)
+ {
+ rc = RTStrCopyEx(pszResult, cchSubStr + 1, pszSource + offStart, cchSubStr);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszOutput = pszResult;
+ *pcbOutput = (uint32_t)(cchSubStr + 1);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
+ RTMemFree(pszResult);
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment.\n"));
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: CF_HTML out of bounds - offStart=%#x offEnd=%#x cch=%#x\n", offStart, offEnd, cch));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc.\n", rc));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected StartFragment. rc = %Rrc.\n", rc));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+
+/**
+ * Converts source UTF-8 MIME HTML clipboard data to UTF-8 CF_HTML format.
+ *
+ * This is just encapsulation work, slapping a header on the data.
+ *
+ * It allocates
+ *
+ * Calculations:
+ * Header length = format Length + (2*(10 - 5('%010d'))('digits')) - 2('%s') = format length + 8
+ * EndHtml = Header length + fragment length
+ * StartHtml = 105(constant)
+ * StartFragment = 141(constant) may vary if the header html content will be extended
+ * EndFragment = Header length + fragment length - 38(ending length)
+ *
+ * @param pszSource Source buffer that contains utf-16 string in mime html format
+ * @param cb Size of source buffer in bytes
+ * @param ppszOutput Where to return the allocated output buffer to put converted UTF-8
+ * CF_HTML clipboard data. This function allocates memory for this.
+ * @param pcbOutput Where to return the size of allocated result buffer in bytes/chars, including zero terminator
+ *
+ * @note output buffer should be free using RTMemFree()
+ * @note Everything inside of fragment can be UTF8. Windows allows it. Everything in header should be Latin1.
+ */
+static int ConvertMimeToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput)
+{
+ Assert(ppszOutput);
+ Assert(pcbOutput);
+ Assert(pszSource);
+ Assert(cb);
+
+ /* construct CF_HTML formatted string */
+ char *pszResult = NULL;
+ size_t cchFragment;
+ int rc = RTStrNLenEx(pszSource, cb, &cchFragment);
+ if (!RT_SUCCESS(rc))
+ {
+ LogRelFlowFunc(("Error: invalid source fragment. rc = %Rrc.\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ @StartHtml - pos before <html>
+ @EndHtml - whole size of text excluding ending zero char
+ @StartFragment - pos after <!--StartFragment-->
+ @EndFragment - pos before <!--EndFragment-->
+ @note: all values includes CR\LF inserted into text
+ Calculations:
+ Header length = format Length + (3*6('digits')) - 2('%s') = format length + 16 (control value - 183)
+ EndHtml = Header length + fragment length
+ StartHtml = 105(constant)
+ StartFragment = 143(constant)
+ EndFragment = Header length + fragment length - 40(ending length)
+ */
+ static const char s_szFormatSample[] =
+ /* 0: */ "Version:1.0\r\n"
+ /* 13: */ "StartHTML:000000101\r\n"
+ /* 34: */ "EndHTML:%0000009u\r\n" // END HTML = Header length + fragment length
+ /* 53: */ "StartFragment:000000137\r\n"
+ /* 78: */ "EndFragment:%0000009u\r\n"
+ /* 101: */ "<html>\r\n"
+ /* 109: */ "<body>\r\n"
+ /* 117: */ "<!--StartFragment-->"
+ /* 137: */ "%s"
+ /* 137+2: */ "<!--EndFragment-->\r\n"
+ /* 157+2: */ "</body>\r\n"
+ /* 166+2: */ "</html>\r\n";
+ /* 175+2: */
+ AssertCompile(sizeof(s_szFormatSample) == 175+2+1);
+
+ /* calculate parameters of CF_HTML header */
+ size_t cchHeader = sizeof(s_szFormatSample) - 1;
+ size_t offEndHtml = cchHeader + cchFragment;
+ size_t offEndFragment = cchHeader + cchFragment - 38; /* 175-137 = 38 */
+ pszResult = (char *)RTMemAlloc(offEndHtml + 1);
+ if (pszResult == NULL)
+ {
+ LogRelFlowFunc(("Error: Cannot allocate memory for result buffer. rc = %Rrc.\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ /* format result CF_HTML string */
+ size_t cchFormatted = RTStrPrintf(pszResult, offEndHtml + 1,
+ s_szFormatSample, offEndHtml, offEndFragment, pszSource);
+ Assert(offEndHtml == cchFormatted); NOREF(cchFormatted);
+
+#ifdef VBOX_STRICT
+ /* Control calculations. check consistency.*/
+ static const char s_szStartFragment[] = "<!--StartFragment-->";
+ static const char s_szEndFragment[] = "<!--EndFragment-->";
+
+ /* check 'StartFragment:' value */
+ const char *pszRealStartFragment = RTStrStr(pszResult, s_szStartFragment);
+ Assert(&pszRealStartFragment[sizeof(s_szStartFragment) - 1] - pszResult == 137);
+
+ /* check 'EndFragment:' value */
+ const char *pszRealEndFragment = RTStrStr(pszResult, s_szEndFragment);
+ Assert((size_t)(pszRealEndFragment - pszResult) == offEndFragment);
+#endif
+
+ *ppszOutput = pszResult;
+ *pcbOutput = (uint32_t)cchFormatted + 1;
+ Assert(*pcbOutput == cchFormatted + 1);
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/HostServices/SharedClipboard/VBoxClipboard.h b/src/VBox/HostServices/SharedClipboard/VBoxClipboard.h
new file mode 100644
index 00000000..2eb9c7a8
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/VBoxClipboard.h
@@ -0,0 +1,103 @@
+/* $Id: VBoxClipboard.h $ */
+/** @file
+ * Shared Clipboard Service - Internal Header.
+ */
+
+/*
+ * 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 VBOX_INCLUDED_SRC_SharedClipboard_VBoxClipboard_h
+#define VBOX_INCLUDED_SRC_SharedClipboard_VBoxClipboard_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/hgcmsvc.h>
+#include <VBox/log.h>
+
+struct _VBOXCLIPBOARDCONTEXT;
+typedef struct _VBOXCLIPBOARDCONTEXT VBOXCLIPBOARDCONTEXT;
+
+
+typedef struct _VBOXCLIPBOARDCLIENTDATA
+{
+ struct _VBOXCLIPBOARDCLIENTDATA *pNext;
+ struct _VBOXCLIPBOARDCLIENTDATA *pPrev;
+
+ VBOXCLIPBOARDCONTEXT *pCtx;
+
+ uint32_t u32ClientID;
+
+ bool fAsync; /* Guest is waiting for a message. */
+ bool fReadPending; /* The guest is waiting for data from the host */
+
+ bool fMsgQuit;
+ bool fMsgReadData;
+ bool fMsgFormats;
+
+ struct {
+ VBOXHGCMCALLHANDLE callHandle;
+ VBOXHGCMSVCPARM *paParms;
+ } async;
+
+ struct {
+ VBOXHGCMCALLHANDLE callHandle;
+ VBOXHGCMSVCPARM *paParms;
+ } asyncRead;
+
+ struct {
+ void *pv;
+ uint32_t cb;
+ uint32_t u32Format;
+ } data;
+
+ uint32_t u32AvailableFormats;
+ uint32_t u32RequestedFormat;
+
+} VBOXCLIPBOARDCLIENTDATA;
+
+/*
+ * The service functions. Locking is between the service thread and the platform dependent windows thread.
+ */
+bool vboxSvcClipboardLock (void);
+void vboxSvcClipboardUnlock (void);
+
+void vboxSvcClipboardReportMsg (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Msg, uint32_t u32Formats);
+
+void vboxSvcClipboardCompleteReadData(VBOXCLIPBOARDCLIENTDATA *pClient, int rc, uint32_t cbActual);
+
+bool vboxSvcClipboardGetHeadless(void);
+
+/*
+ * Platform dependent functions.
+ */
+int vboxClipboardInit (void);
+void vboxClipboardDestroy (void);
+
+int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless);
+void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient);
+
+void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Formats);
+
+int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Format, void *pv, uint32_t cb, uint32_t *pcbActual);
+
+void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient, void *pv, uint32_t cb, uint32_t u32Format);
+
+int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient);
+
+/* Host unit testing interface */
+#ifdef UNIT_TEST
+uint32_t TestClipSvcGetMode(void);
+#endif
+
+#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxClipboard_h */
+
diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp
new file mode 100644
index 00000000..1b9b9cbc
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp
@@ -0,0 +1,1047 @@
+/* $Id: VBoxSharedClipboardSvc.cpp $ */
+/** @file
+ * Shared Clipboard Service - Host service entry points.
+ */
+
+/*
+ * 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.
+ */
+
+
+/** @page pg_hostclip The Shared Clipboard Host Service
+ *
+ * The shared clipboard host service provides a proxy between the host's
+ * clipboard and a similar proxy running on a guest. The service is split
+ * into a platform-independent core and platform-specific backends. The
+ * service defines two communication protocols - one to communicate with the
+ * clipboard service running on the guest, and one to communicate with the
+ * backend. These will be described in a very skeletal fashion here.
+ *
+ * @section sec_hostclip_guest_proto The guest communication protocol
+ *
+ * The guest clipboard service communicates with the host service via HGCM
+ * (the host service runs as an HGCM service). The guest clipboard must
+ * connect to the host service before all else (Windows hosts currently only
+ * support one simultaneous connection). Once it has connected, it can send
+ * HGCM messages to the host services, some of which will receive replies from
+ * the host. The host can only reply to a guest message, it cannot initiate
+ * any communication. The guest can in theory send any number of messages in
+ * parallel (see the descriptions of the messages for the practice), and the
+ * host will receive these in sequence, and may reply to them at once
+ * (releasing the caller in the guest) or defer the reply until later.
+ *
+ * There are currently four messages defined. The first is
+ * VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG, which waits for a message from the
+ * host. Host messages currently defined are
+ * VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT (unused),
+ * VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA (request that the guest send the
+ * contents of its clipboard to the host) and
+ * VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS (to notify the guest that new
+ * clipboard data is available). If a host message is sent while the guest is
+ * not waiting, it will be queued until the guest requests it. At most one
+ * host message of each type will be kept in the queue. The host code only
+ * supports a single simultaneous VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG call
+ * from the guest.
+ *
+ * The second guest message is VBOX_SHARED_CLIPBOARD_FN_FORMATS, which tells
+ * the host that the guest has new clipboard data available. The third is
+ * VBOX_SHARED_CLIPBOARD_FN_READ_DATA, which asks the host to send its
+ * clipboard data and waits until it arrives. The host supports at most one
+ * simultaneous VBOX_SHARED_CLIPBOARD_FN_READ_DATA call from the guest - if a
+ * second call is made before the first has returned, the first will be
+ * aborted.
+ *
+ * The last guest message is VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA, which is
+ * used to send the contents of the guest clipboard to the host. This call
+ * should be used after the host has requested data from the guest.
+ *
+ * @section sec_hostclip_backend_proto The communication protocol with the
+ * platform-specific backend
+ *
+ * This section may be written in the future :)
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/HostServices/VBoxClipboardExt.h>
+
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <VBox/err.h>
+#include <VBox/vmm/ssm.h>
+
+#include "VBoxClipboard.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static PVBOXHGCMSVCHELPERS g_pHelpers;
+
+static RTCRITSECT critsect;
+static uint32_t g_u32Mode;
+
+static PFNHGCMSVCEXT g_pfnExtension;
+static void *g_pvExtension;
+
+static VBOXCLIPBOARDCLIENTDATA *g_pClient;
+
+/* Serialization of data reading and format announcements from the RDP client. */
+static bool g_fReadingData = false;
+static bool g_fDelayedAnnouncement = false;
+static uint32_t g_u32DelayedFormats = 0;
+
+/** Is the clipboard running in headless mode? */
+static bool g_fHeadless = false;
+
+
+static void VBoxHGCMParmUInt32Set (VBOXHGCMSVCPARM *pParm, uint32_t u32)
+{
+ pParm->type = VBOX_HGCM_SVC_PARM_32BIT;
+ pParm->u.uint32 = u32;
+}
+
+static int VBoxHGCMParmUInt32Get (VBOXHGCMSVCPARM *pParm, uint32_t *pu32)
+{
+ if (pParm->type == VBOX_HGCM_SVC_PARM_32BIT)
+ {
+ *pu32 = pParm->u.uint32;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+
+#if 0
+static void VBoxHGCMParmPtrSet (VBOXHGCMSVCPARM *pParm, void *pv, uint32_t cb)
+{
+ pParm->type = VBOX_HGCM_SVC_PARM_PTR;
+ pParm->u.pointer.size = cb;
+ pParm->u.pointer.addr = pv;
+}
+#endif
+
+static int VBoxHGCMParmPtrGet (VBOXHGCMSVCPARM *pParm, void **ppv, uint32_t *pcb)
+{
+ if (pParm->type == VBOX_HGCM_SVC_PARM_PTR)
+ {
+ *ppv = pParm->u.pointer.addr;
+ *pcb = pParm->u.pointer.size;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+
+
+static uint32_t vboxSvcClipboardMode (void)
+{
+ return g_u32Mode;
+}
+
+#ifdef UNIT_TEST
+/** Testing interface, getter for clipboard mode */
+uint32_t TestClipSvcGetMode(void)
+{
+ return vboxSvcClipboardMode();
+}
+#endif
+
+/** Getter for headless setting */
+bool vboxSvcClipboardGetHeadless(void)
+{
+ return g_fHeadless;
+}
+
+static void vboxSvcClipboardModeSet (uint32_t u32Mode)
+{
+ switch (u32Mode)
+ {
+ case VBOX_SHARED_CLIPBOARD_MODE_OFF:
+ case VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST:
+ case VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST:
+ case VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL:
+ g_u32Mode = u32Mode;
+ break;
+
+ default:
+ g_u32Mode = VBOX_SHARED_CLIPBOARD_MODE_OFF;
+ }
+}
+
+bool vboxSvcClipboardLock (void)
+{
+ return RT_SUCCESS(RTCritSectEnter (&critsect));
+}
+
+void vboxSvcClipboardUnlock (void)
+{
+ RTCritSectLeave (&critsect);
+}
+
+/* Set the HGCM parameters according to pending messages.
+ * Executed under the clipboard lock.
+ */
+static bool vboxSvcClipboardReturnMsg (VBOXCLIPBOARDCLIENTDATA *pClient, VBOXHGCMSVCPARM paParms[])
+{
+ /* Message priority is taken into account. */
+ if (pClient->fMsgQuit)
+ {
+ LogRelFlow(("vboxSvcClipboardReturnMsg: Quit\n"));
+ VBoxHGCMParmUInt32Set (&paParms[0], VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT);
+ VBoxHGCMParmUInt32Set (&paParms[1], 0);
+ pClient->fMsgQuit = false;
+ }
+ else if (pClient->fMsgReadData)
+ {
+ uint32_t fFormat = 0;
+
+ LogRelFlow(("vboxSvcClipboardReturnMsg: ReadData %02X\n", pClient->u32RequestedFormat));
+ if (pClient->u32RequestedFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ fFormat = VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
+ else if (pClient->u32RequestedFormat & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ fFormat = VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
+ else if (pClient->u32RequestedFormat & VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ fFormat = VBOX_SHARED_CLIPBOARD_FMT_HTML;
+ else
+ AssertStmt(pClient->u32RequestedFormat == 0, pClient->u32RequestedFormat = 0);
+ pClient->u32RequestedFormat &= ~fFormat;
+ VBoxHGCMParmUInt32Set (&paParms[0], VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ VBoxHGCMParmUInt32Set (&paParms[1], fFormat);
+ if (pClient->u32RequestedFormat == 0)
+ pClient->fMsgReadData = false;
+ }
+ else if (pClient->fMsgFormats)
+ {
+ LogRelFlow(("vboxSvcClipboardReturnMsg: Formats %02X\n", pClient->u32AvailableFormats));
+ VBoxHGCMParmUInt32Set (&paParms[0], VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS);
+ VBoxHGCMParmUInt32Set (&paParms[1], pClient->u32AvailableFormats);
+ pClient->fMsgFormats = false;
+ }
+ else
+ {
+ /* No pending messages. */
+ LogRelFlow(("vboxSvcClipboardReturnMsg: no message\n"));
+ return false;
+ }
+
+ /* Message information assigned. */
+ return true;
+}
+
+void vboxSvcClipboardReportMsg (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Msg, uint32_t u32Formats)
+{
+ if (vboxSvcClipboardLock ())
+ {
+ switch (u32Msg)
+ {
+ case VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT:
+ {
+ LogRelFlow(("vboxSvcClipboardReportMsg: Quit\n"));
+ pClient->fMsgQuit = true;
+ } break;
+ case VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ /* Skip the message. */
+ break;
+ }
+
+ LogRelFlow(("vboxSvcClipboardReportMsg: ReadData %02X\n", u32Formats));
+ pClient->u32RequestedFormat = u32Formats;
+ pClient->fMsgReadData = true;
+ } break;
+ case VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS:
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ /* Skip the message. */
+ break;
+ }
+
+ LogRelFlow(("vboxSvcClipboardReportMsg: Formats %02X\n", u32Formats));
+ pClient->u32AvailableFormats = u32Formats;
+ pClient->fMsgFormats = true;
+ } break;
+ default:
+ {
+ /* Invalid message. */
+ LogRelFlow(("vboxSvcClipboardReportMsg: invalid message %d\n", u32Msg));
+ } break;
+ }
+
+ if (pClient->fAsync)
+ {
+ /* The client waits for a response. */
+ bool fMessageReturned = vboxSvcClipboardReturnMsg (pClient, pClient->async.paParms);
+
+ /* Make a copy of the handle. */
+ VBOXHGCMCALLHANDLE callHandle = pClient->async.callHandle;
+
+ if (fMessageReturned)
+ {
+ /* There is a response. */
+ pClient->fAsync = false;
+ }
+
+ vboxSvcClipboardUnlock ();
+
+ if (fMessageReturned)
+ {
+ LogRelFlow(("vboxSvcClipboardReportMsg: CallComplete\n"));
+ g_pHelpers->pfnCallComplete (callHandle, VINF_SUCCESS);
+ }
+ }
+ else
+ {
+ vboxSvcClipboardUnlock ();
+ }
+ }
+}
+
+static int svcInit (void)
+{
+ int rc = RTCritSectInit (&critsect);
+
+ if (RT_SUCCESS (rc))
+ {
+ vboxSvcClipboardModeSet (VBOX_SHARED_CLIPBOARD_MODE_OFF);
+
+ rc = vboxClipboardInit ();
+
+ /* Clean up on failure, because 'svnUnload' will not be called
+ * if the 'svcInit' returns an error.
+ */
+ if (RT_FAILURE (rc))
+ {
+ RTCritSectDelete (&critsect);
+ }
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) svcUnload (void *)
+{
+ vboxClipboardDestroy ();
+ RTCritSectDelete (&critsect);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Disconnect the host side of the shared clipboard and send a "host disconnected" message
+ * to the guest side.
+ */
+static DECLCALLBACK(int) svcDisconnect (void *, uint32_t u32ClientID, void *pvClient)
+{
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ LogRel2(("svcDisconnect: u32ClientID = %d\n", u32ClientID));
+
+ vboxSvcClipboardReportMsg (pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT, 0);
+
+ vboxSvcClipboardCompleteReadData(pClient, VERR_NO_DATA, 0);
+
+ vboxClipboardDisconnect (pClient);
+
+ memset (pClient, 0, sizeof (*pClient));
+
+ g_pClient = NULL;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) svcConnect (void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring)
+{
+ RT_NOREF(fRequestor, fRestoring);
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ int rc = VINF_SUCCESS;
+
+ /* If there is already a client connected then we want to release it first. */
+ if (g_pClient != NULL)
+ {
+ uint32_t u32OldClientID = g_pClient->u32ClientID;
+
+ svcDisconnect(NULL, u32OldClientID, g_pClient);
+ /* And free the resources in the hgcm subsystem. */
+ g_pHelpers->pfnDisconnectClient(g_pHelpers->pvInstance, u32OldClientID);
+ }
+
+ /* Register the client. */
+ memset (pClient, 0, sizeof (*pClient));
+
+ pClient->u32ClientID = u32ClientID;
+
+ rc = vboxClipboardConnect (pClient, vboxSvcClipboardGetHeadless());
+
+ if (RT_SUCCESS (rc))
+ {
+ g_pClient = pClient;
+ }
+
+ LogRel2(("vboxClipboardConnect: rc = %Rrc\n", rc));
+
+ return rc;
+}
+
+static DECLCALLBACK(void) svcCall (void *,
+ VBOXHGCMCALLHANDLE callHandle,
+ uint32_t u32ClientID,
+ void *pvClient,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM paParms[],
+ uint64_t tsArrival)
+{
+ RT_NOREF_PV(tsArrival);
+ int rc = VINF_SUCCESS;
+
+ LogRel2(("svcCall: u32ClientID = %d, fn = %d, cParms = %d, pparms = %d\n",
+ u32ClientID, u32Function, cParms, paParms));
+
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ bool fAsynchronousProcessing = false;
+
+#ifdef DEBUG
+ uint32_t i;
+
+ for (i = 0; i < cParms; i++)
+ {
+ /** @todo parameters other than 32 bit */
+ LogRel2((" pparms[%d]: type %d value %d\n", i, paParms[i].type, paParms[i].u.uint32));
+ }
+#endif
+
+ switch (u32Function)
+ {
+ case VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG:
+ {
+ /* The quest requests a host message. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_GET_HOST_MSG)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* msg */
+ || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* formats */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ /* Atomically verify the client's state. */
+ if (vboxSvcClipboardLock ())
+ {
+ bool fMessageReturned = vboxSvcClipboardReturnMsg (pClient, paParms);
+
+ if (fMessageReturned)
+ {
+ /* Just return to the caller. */
+ pClient->fAsync = false;
+ }
+ else
+ {
+ /* No event available at the time. Process asynchronously. */
+ fAsynchronousProcessing = true;
+
+ pClient->fAsync = true;
+ pClient->async.callHandle = callHandle;
+ pClient->async.paParms = paParms;
+
+ LogRel2(("svcCall: async.\n"));
+ }
+
+ vboxSvcClipboardUnlock ();
+ }
+ else
+ {
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_FN_FORMATS:
+ {
+ /* The guest reports that some formats are available. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_FORMATS\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_FORMATS)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* formats */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t u32Formats;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Formats);
+
+ if (RT_SUCCESS (rc))
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (g_pfnExtension)
+ {
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ parms.u32Format = u32Formats;
+
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, &parms, sizeof (parms));
+ }
+ else
+ {
+ vboxClipboardFormatAnnounce (pClient, u32Formats);
+ }
+ }
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_FN_READ_DATA:
+ {
+ /* The guest wants to read data in the given format. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_READ_DATA\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_READ_DATA)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* format */
+ || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* ptr */
+ || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* size */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t u32Format;
+ void *pv;
+ uint32_t cb;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Format);
+
+ if (RT_SUCCESS (rc))
+ {
+ rc = VBoxHGCMParmPtrGet (&paParms[1], &pv, &cb);
+
+ if (RT_SUCCESS (rc))
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ uint32_t cbActual = 0;
+
+ if (g_pfnExtension)
+ {
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ parms.u32Format = u32Format;
+ parms.u.pvData = pv;
+ parms.cbData = cb;
+
+ g_fReadingData = true;
+ rc = g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof (parms));
+ LogRelFlow(("DATA: g_fDelayedAnnouncement = %d, g_u32DelayedFormats = 0x%x\n", g_fDelayedAnnouncement, g_u32DelayedFormats));
+ if (g_fDelayedAnnouncement)
+ {
+ vboxSvcClipboardReportMsg (g_pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, g_u32DelayedFormats);
+ g_fDelayedAnnouncement = false;
+ g_u32DelayedFormats = 0;
+ }
+ g_fReadingData = false;
+
+ if (RT_SUCCESS (rc))
+ {
+ cbActual = parms.cbData;
+ }
+ }
+ else
+ {
+ /* Release any other pending read, as we only
+ * support one pending read at one time. */
+ vboxSvcClipboardCompleteReadData(pClient, VERR_NO_DATA, 0);
+ rc = vboxClipboardReadData (pClient, u32Format, pv, cb, &cbActual);
+ }
+
+ /* Remember our read request until it is completed.
+ * See the protocol description above for more
+ * information. */
+ if (rc == VINF_HGCM_ASYNC_EXECUTE)
+ {
+ if (vboxSvcClipboardLock())
+ {
+ pClient->asyncRead.callHandle = callHandle;
+ pClient->asyncRead.paParms = paParms;
+ pClient->fReadPending = true;
+ fAsynchronousProcessing = true;
+ vboxSvcClipboardUnlock();
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else if (RT_SUCCESS (rc))
+ {
+ VBoxHGCMParmUInt32Set (&paParms[2], cbActual);
+ }
+ }
+ }
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA:
+ {
+ /* The guest writes the requested data. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_WRITE_DATA)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* format */
+ || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* ptr */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ void *pv;
+ uint32_t cb;
+ uint32_t u32Format;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Format);
+
+ if (RT_SUCCESS (rc))
+ {
+ rc = VBoxHGCMParmPtrGet (&paParms[1], &pv, &cb);
+
+ if (RT_SUCCESS (rc))
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (g_pfnExtension)
+ {
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ parms.u32Format = u32Format;
+ parms.u.pvData = pv;
+ parms.cbData = cb;
+
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof (parms));
+ }
+ else
+ {
+ vboxClipboardWriteData (pClient, pv, cb, u32Format);
+ }
+ }
+ }
+ }
+ } break;
+
+ default:
+ {
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+
+ LogRelFlow(("svcCall: rc = %Rrc\n", rc));
+
+ if (!fAsynchronousProcessing)
+ {
+ g_pHelpers->pfnCallComplete (callHandle, rc);
+ }
+}
+
+/** If the client in the guest is waiting for a read operation to complete
+ * then complete it, otherwise return. See the protocol description in the
+ * shared clipboard module description. */
+void vboxSvcClipboardCompleteReadData(VBOXCLIPBOARDCLIENTDATA *pClient, int rc, uint32_t cbActual)
+{
+ VBOXHGCMCALLHANDLE callHandle = NULL;
+ VBOXHGCMSVCPARM *paParms = NULL;
+ bool fReadPending = false;
+ if (vboxSvcClipboardLock()) /* if not can we do anything useful? */
+ {
+ callHandle = pClient->asyncRead.callHandle;
+ paParms = pClient->asyncRead.paParms;
+ fReadPending = pClient->fReadPending;
+ pClient->fReadPending = false;
+ vboxSvcClipboardUnlock();
+ }
+ if (fReadPending)
+ {
+ VBoxHGCMParmUInt32Set (&paParms[2], cbActual);
+ g_pHelpers->pfnCallComplete (callHandle, rc);
+ }
+}
+
+/*
+ * We differentiate between a function handler for the guest and one for the host.
+ */
+static DECLCALLBACK(int) svcHostCall (void *,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM paParms[])
+{
+ int rc = VINF_SUCCESS;
+
+ LogRel2(("svcHostCall: fn = %d, cParms = %d, pparms = %d\n",
+ u32Function, cParms, paParms));
+
+ switch (u32Function)
+ {
+ case VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE:
+ {
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE\n"));
+
+ if (cParms != 1)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* mode */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t u32Mode = VBOX_SHARED_CLIPBOARD_MODE_OFF;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Mode);
+
+ /* The setter takes care of invalid values. */
+ vboxSvcClipboardModeSet (u32Mode);
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS:
+ {
+ uint32_t u32Headless = g_fHeadless;
+
+ rc = VERR_INVALID_PARAMETER;
+ if (cParms != 1)
+ break;
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Headless);
+ if (RT_SUCCESS(rc))
+ LogRelFlow(("svcCall: VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS, u32Headless=%u\n",
+ (unsigned) u32Headless));
+ g_fHeadless = RT_BOOL(u32Headless);
+ } break;
+
+ default:
+ break;
+ }
+
+ LogRelFlow(("svcHostCall: rc = %Rrc\n", rc));
+ return rc;
+}
+
+#ifndef UNIT_TEST
+/**
+ * SSM descriptor table for the VBOXCLIPBOARDCLIENTDATA structure.
+ */
+static SSMFIELD const g_aClipboardClientDataFields[] =
+{
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, u32ClientID), /* for validation purposes */
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, fMsgQuit),
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, fMsgReadData),
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, fMsgFormats),
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, u32RequestedFormat),
+ SSMFIELD_ENTRY_TERM()
+};
+#endif
+
+static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM)
+{
+#ifndef UNIT_TEST
+ /*
+ * When the state will be restored, pending requests will be reissued
+ * by VMMDev. The service therefore must save state as if there were no
+ * pending request.
+ * Pending requests, if any, will be completed in svcDisconnect.
+ */
+ LogRel2 (("svcSaveState: u32ClientID = %d\n", u32ClientID));
+
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ /* This field used to be the length. We're using it as a version field
+ with the high bit set. */
+ SSMR3PutU32 (pSSM, UINT32_C (0x80000002));
+ int rc = SSMR3PutStructEx (pSSM, pClient, sizeof(*pClient), 0 /*fFlags*/, &g_aClipboardClientDataFields[0], NULL);
+ AssertRCReturn (rc, rc);
+
+#else /* UNIT_TEST */
+ RT_NOREF3(u32ClientID, pvClient, pSSM);
+#endif /* UNIT_TEST */
+ return VINF_SUCCESS;
+}
+
+/**
+ * This structure corresponds to the original layout of the
+ * VBOXCLIPBOARDCLIENTDATA structure. As the structure was saved as a whole
+ * when saving state, we need to remember it forever in order to preserve
+ * compatibility.
+ *
+ * (Starting with 3.1 this is no longer used.)
+ *
+ * @remarks Putting this outside svcLoadState to avoid visibility warning caused
+ * by -Wattributes.
+ */
+typedef struct CLIPSAVEDSTATEDATA
+{
+ struct CLIPSAVEDSTATEDATA *pNext;
+ struct CLIPSAVEDSTATEDATA *pPrev;
+
+ VBOXCLIPBOARDCONTEXT *pCtx;
+
+ uint32_t u32ClientID;
+
+ bool fAsync: 1; /* Guest is waiting for a message. */
+
+ bool fMsgQuit: 1;
+ bool fMsgReadData: 1;
+ bool fMsgFormats: 1;
+
+ struct {
+ VBOXHGCMCALLHANDLE callHandle;
+ VBOXHGCMSVCPARM *paParms;
+ } async;
+
+ struct {
+ void *pv;
+ uint32_t cb;
+ uint32_t u32Format;
+ } data;
+
+ uint32_t u32AvailableFormats;
+ uint32_t u32RequestedFormat;
+
+} CLIPSAVEDSTATEDATA;
+
+static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+#ifndef UNIT_TEST
+ RT_NOREF(uVersion);
+ LogRel2 (("svcLoadState: u32ClientID = %d\n", u32ClientID));
+
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ /* Existing client can not be in async state yet. */
+ Assert (!pClient->fAsync);
+
+ /* Save the client ID for data validation. */
+ /** @todo isn't this the same as u32ClientID? Playing safe for now... */
+ uint32_t const u32ClientIDOld = pClient->u32ClientID;
+
+ /* Restore the client data. */
+ uint32_t lenOrVer;
+ int rc = SSMR3GetU32 (pSSM, &lenOrVer);
+ AssertRCReturn (rc, rc);
+ if (lenOrVer == UINT32_C (0x80000002))
+ {
+ rc = SSMR3GetStructEx (pSSM, pClient, sizeof(*pClient), 0 /*fFlags*/, &g_aClipboardClientDataFields[0], NULL);
+ AssertRCReturn (rc, rc);
+ }
+ else if (lenOrVer == (SSMR3HandleHostBits (pSSM) == 64 ? 72U : 48U))
+ {
+ /**
+ * SSM descriptor table for the CLIPSAVEDSTATEDATA structure.
+ */
+ static SSMFIELD const s_aClipSavedStateDataFields30[] =
+ {
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pNext),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pPrev),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pCtx),
+ SSMFIELD_ENTRY( CLIPSAVEDSTATEDATA, u32ClientID),
+ SSMFIELD_ENTRY_CUSTOM(fMsgQuit + fMsgReadData + fMsgFormats, RT_UOFFSETOF(CLIPSAVEDSTATEDATA, u32ClientID) + 4, 4),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, async.callHandle),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, async.paParms),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.pv),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.cb),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.u32Format),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, u32AvailableFormats),
+ SSMFIELD_ENTRY( CLIPSAVEDSTATEDATA, u32RequestedFormat),
+ SSMFIELD_ENTRY_TERM()
+ };
+
+ CLIPSAVEDSTATEDATA savedState;
+ RT_ZERO (savedState);
+ rc = SSMR3GetStructEx (pSSM, &savedState, sizeof(savedState), SSMSTRUCT_FLAGS_MEM_BAND_AID,
+ &s_aClipSavedStateDataFields30[0], NULL);
+ AssertRCReturn (rc, rc);
+
+ pClient->fMsgQuit = savedState.fMsgQuit;
+ pClient->fMsgReadData = savedState.fMsgReadData;
+ pClient->fMsgFormats = savedState.fMsgFormats;
+ pClient->u32RequestedFormat = savedState.u32RequestedFormat;
+ }
+ else
+ {
+ LogRel (("Client data size mismatch: got %#x\n", lenOrVer));
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ }
+
+ /* Verify the client ID. */
+ if (pClient->u32ClientID != u32ClientIDOld)
+ {
+ LogRel (("Client ID mismatch: expected %d, got %d\n", u32ClientIDOld, pClient->u32ClientID));
+ pClient->u32ClientID = u32ClientIDOld;
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ }
+
+ /* Actual host data are to be reported to guest (SYNC). */
+ vboxClipboardSync (pClient);
+
+#else /* UNIT_TEST*/
+ RT_NOREF(u32ClientID, pvClient, pSSM, uVersion);
+#endif /* UNIT_TEST */
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) extCallback (uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData)
+{
+ RT_NOREF2(pvData, cbData);
+ if (g_pClient != NULL)
+ {
+ switch (u32Function)
+ {
+ case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE:
+ {
+ LogRelFlow(("ANNOUNCE: g_fReadingData = %d\n", g_fReadingData));
+ if (g_fReadingData)
+ {
+ g_fDelayedAnnouncement = true;
+ g_u32DelayedFormats = u32Format;
+ }
+ else
+ {
+ vboxSvcClipboardReportMsg (g_pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, u32Format);
+ }
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_DATA_READ:
+ {
+ vboxSvcClipboardReportMsg (g_pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA, u32Format);
+ } break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) svcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension)
+{
+ LogRelFlowFunc(("pfnExtension = %p\n", pfnExtension));
+
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ if (pfnExtension)
+ {
+ /* Install extension. */
+ g_pfnExtension = pfnExtension;
+ g_pvExtension = pvExtension;
+
+ parms.u.pfnCallback = extCallback;
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof (parms));
+ }
+ else
+ {
+ if (g_pfnExtension)
+ {
+ parms.u.pfnCallback = NULL;
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof (parms));
+ }
+
+ /* Uninstall extension. */
+ g_pfnExtension = NULL;
+ g_pvExtension = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable)
+{
+ int rc = VINF_SUCCESS;
+
+ LogRelFlowFunc(("ptable = %p\n", ptable));
+
+ if (!ptable)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ LogRel2(("VBoxHGCMSvcLoad: ptable->cbSize = %d, ptable->u32Version = 0x%08X\n", ptable->cbSize, ptable->u32Version));
+
+ if ( ptable->cbSize != sizeof (VBOXHGCMSVCFNTABLE)
+ || ptable->u32Version != VBOX_HGCM_SVC_VERSION)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ g_pHelpers = ptable->pHelpers;
+
+ ptable->cbClient = sizeof (VBOXCLIPBOARDCLIENTDATA);
+
+ ptable->pfnUnload = svcUnload;
+ ptable->pfnConnect = svcConnect;
+ ptable->pfnDisconnect = svcDisconnect;
+ ptable->pfnCall = svcCall;
+ ptable->pfnHostCall = svcHostCall;
+ ptable->pfnSaveState = svcSaveState;
+ ptable->pfnLoadState = svcLoadState;
+ ptable->pfnRegisterExtension = svcRegisterExtension;
+ ptable->pfnNotify = NULL;
+ ptable->pvService = NULL;
+
+ /* Service specific initialization. */
+ rc = svcInit ();
+ }
+ }
+
+ return rc;
+}
diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc
new file mode 100644
index 00000000..4ec3753f
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.rc
@@ -0,0 +1,51 @@
+/* $Id: VBoxSharedClipboardSvc.rc $ */
+/** @file
+ * Shared Clipboard Service - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-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 <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Shared Clipboard Host Service\0"
+ VALUE "InternalName", "VBoxSharedClipboard\0"
+ VALUE "OriginalFilename", "VBoxSharedClipboard.dll\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp
new file mode 100644
index 00000000..88d4c2f9
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.cpp
@@ -0,0 +1,404 @@
+/* $Id: darwin-pasteboard.cpp $ */
+/** @file
+ * Shared Clipboard Service - Mac OS X host implementation.
+ */
+
+/*
+ * Includes contributions from François Revol
+ *
+ * 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 *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <Carbon/Carbon.h>
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/errcore.h>
+#include <iprt/utf16.h>
+
+#include "VBox/log.h"
+#include "VBox/HostServices/VBoxClipboardSvc.h"
+#include "VBox/GuestHost/clipboard-helper.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* For debugging */
+//#define SHOW_CLIPBOARD_CONTENT
+
+
+/**
+ * Initialize the global pasteboard and return a reference to it.
+ *
+ * @param pPasteboardRef Reference to the global pasteboard.
+ *
+ * @returns IPRT status code.
+ */
+int initPasteboard(PasteboardRef *pPasteboardRef)
+{
+ int rc = VINF_SUCCESS;
+
+ if (PasteboardCreate(kPasteboardClipboard, pPasteboardRef))
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+/**
+ * Release the reference to the global pasteboard.
+ *
+ * @param pPasteboardRef Reference to the global pasteboard.
+ */
+void destroyPasteboard(PasteboardRef *pPasteboardRef)
+{
+ CFRelease(*pPasteboardRef);
+ *pPasteboardRef = NULL;
+}
+
+/**
+ * Inspect the global pasteboard for new content. Check if there is some type
+ * that is supported by vbox and return it.
+ *
+ * @param pPasteboard Reference to the global pasteboard.
+ * @param pfFormats Pointer for the bit combination of the
+ * supported types.
+ * @param pfChanged True if something has changed after the
+ * last call.
+ *
+ * @returns IPRT status code. (Always VINF_SUCCESS atm.)
+ */
+int queryNewPasteboardFormats(PasteboardRef pPasteboard, uint32_t *pfFormats, bool *pfChanged)
+{
+ Log(("queryNewPasteboardFormats\n"));
+
+ OSStatus err = noErr;
+ *pfChanged = true;
+
+ PasteboardSyncFlags syncFlags;
+ /* Make sure all is in sync */
+ syncFlags = PasteboardSynchronize(pPasteboard);
+ /* If nothing changed return */
+ if (!(syncFlags & kPasteboardModified))
+ {
+ *pfChanged = false;
+ return VINF_SUCCESS;
+ }
+
+ /* Are some items in the pasteboard? */
+ ItemCount itemCount;
+ err = PasteboardGetItemCount(pPasteboard, &itemCount);
+ if (itemCount < 1)
+ return VINF_SUCCESS;
+
+ /* The id of the first element in the pasteboard */
+ int rc = VINF_SUCCESS;
+ PasteboardItemID itemID;
+ if (!(err = PasteboardGetItemIdentifier(pPasteboard, 1, &itemID)))
+ {
+ /* Retrieve all flavors in the pasteboard, maybe there
+ * is something we can use. */
+ CFArrayRef flavorTypeArray;
+ if (!(err = PasteboardCopyItemFlavors(pPasteboard, itemID, &flavorTypeArray)))
+ {
+ CFIndex flavorCount;
+ flavorCount = CFArrayGetCount(flavorTypeArray);
+ for (CFIndex flavorIndex = 0; flavorIndex < flavorCount; flavorIndex++)
+ {
+ CFStringRef flavorType;
+ flavorType = static_cast <CFStringRef>(CFArrayGetValueAtIndex(flavorTypeArray,
+ flavorIndex));
+ /* Currently only unicode supported */
+ if (UTTypeConformsTo(flavorType, kUTTypeUTF8PlainText) ||
+ UTTypeConformsTo(flavorType, kUTTypeUTF16PlainText))
+ {
+ Log(("Unicode flavor detected.\n"));
+ *pfFormats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
+ }
+ else if (UTTypeConformsTo(flavorType, kUTTypeBMP))
+ {
+ Log(("BMP flavor detected.\n"));
+ *pfFormats |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
+ }
+ }
+ CFRelease(flavorTypeArray);
+ }
+ }
+
+ Log(("queryNewPasteboardFormats: rc = %02X\n", rc));
+ return rc;
+}
+
+/**
+ * Read content from the host clipboard and write it to the internal clipboard
+ * structure for further processing.
+ *
+ * @param pPasteboard Reference to the global pasteboard.
+ * @param fFormat The format type which should be read.
+ * @param pv The destination buffer.
+ * @param cb The size of the destination buffer.
+ * @param pcbActual The size which is needed to transfer the content.
+ *
+ * @returns IPRT status code.
+ */
+int readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual)
+{
+ Log(("readFromPasteboard: fFormat = %02X\n", fFormat));
+
+ OSStatus err = noErr;
+
+ /* Make sure all is in sync */
+ PasteboardSynchronize(pPasteboard);
+
+ /* Are some items in the pasteboard? */
+ ItemCount itemCount;
+ err = PasteboardGetItemCount(pPasteboard, &itemCount);
+ if (itemCount < 1)
+ return VINF_SUCCESS;
+
+ /* The id of the first element in the pasteboard */
+ int rc = VERR_NOT_SUPPORTED;
+ PasteboardItemID itemID;
+ if (!(err = PasteboardGetItemIdentifier(pPasteboard, 1, &itemID)))
+ {
+ /* The guest request unicode */
+ if (fFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ CFDataRef outData;
+ PRTUTF16 pwszTmp = NULL;
+ /* Try utf-16 first */
+ if (!(err = PasteboardCopyItemFlavorData(pPasteboard, itemID, kUTTypeUTF16PlainText, &outData)))
+ {
+ Log(("Clipboard content is utf-16\n"));
+
+ PRTUTF16 pwszString = (PRTUTF16)CFDataGetBytePtr(outData);
+ if (pwszString)
+ rc = RTUtf16DupEx(&pwszTmp, pwszString, 0);
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ /* Second try is utf-8 */
+ else
+ if (!(err = PasteboardCopyItemFlavorData(pPasteboard, itemID, kUTTypeUTF8PlainText, &outData)))
+ {
+ Log(("readFromPasteboard: clipboard content is utf-8\n"));
+ const char *pszString = (const char *)CFDataGetBytePtr(outData);
+ if (pszString)
+ rc = RTStrToUtf16(pszString, &pwszTmp);
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ if (pwszTmp)
+ {
+ /* Check how much longer will the converted text will be. */
+ size_t cwSrc = RTUtf16Len(pwszTmp);
+ size_t cwDest;
+ rc = vboxClipboardUtf16GetWinSize(pwszTmp, cwSrc, &cwDest);
+ if (RT_FAILURE(rc))
+ {
+ RTUtf16Free(pwszTmp);
+ Log(("readFromPasteboard: clipboard conversion failed. vboxClipboardUtf16GetWinSize returned %Rrc. Abandoning.\n", rc));
+ AssertRCReturn(rc, rc);
+ }
+ /* Set the actually needed data size */
+ *pcbActual = cwDest * 2;
+ /* Return success state */
+ rc = VINF_SUCCESS;
+ /* Do not copy data if the dst buffer is not big enough. */
+ if (*pcbActual <= cb)
+ {
+ rc = vboxClipboardUtf16LinToWin(pwszTmp, RTUtf16Len(pwszTmp), static_cast <PRTUTF16>(pv), cb / 2);
+ if (RT_FAILURE(rc))
+ {
+ RTUtf16Free(pwszTmp);
+ Log(("readFromPasteboard: clipboard conversion failed. vboxClipboardUtf16LinToWin() returned %Rrc. Abandoning.\n", rc));
+ AssertRCReturn(rc, rc);
+ }
+#ifdef SHOW_CLIPBOARD_CONTENT
+ Log(("readFromPasteboard: clipboard content: %ls\n", static_cast <PRTUTF16>(pv)));
+#endif
+ }
+ /* Free the temp string */
+ RTUtf16Free(pwszTmp);
+ }
+ }
+ /* The guest request BITMAP */
+ else if (fFormat & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ {
+ CFDataRef outData;
+ const void *pTmp = NULL;
+ size_t cbTmpSize;
+ /* Get the data from the pasteboard */
+ if (!(err = PasteboardCopyItemFlavorData(pPasteboard, itemID, kUTTypeBMP, &outData)))
+ {
+ Log(("Clipboard content is BMP\n"));
+ pTmp = CFDataGetBytePtr(outData);
+ cbTmpSize = CFDataGetLength(outData);
+ }
+ if (pTmp)
+ {
+ const void *pDib;
+ size_t cbDibSize;
+ rc = vboxClipboardBmpGetDib(pTmp, cbTmpSize, &pDib, &cbDibSize);
+ if (RT_FAILURE(rc))
+ {
+ rc = VERR_NOT_SUPPORTED;
+ Log(("readFromPasteboard: unknown bitmap format. vboxClipboardBmpGetDib returned %Rrc. Abandoning.\n", rc));
+ AssertRCReturn(rc, rc);
+ }
+
+ *pcbActual = cbDibSize;
+ /* Return success state */
+ rc = VINF_SUCCESS;
+ /* Do not copy data if the dst buffer is not big enough. */
+ if (*pcbActual <= cb)
+ {
+ memcpy(pv, pDib, cbDibSize);
+#ifdef SHOW_CLIPBOARD_CONTENT
+ Log(("readFromPasteboard: clipboard content bitmap %d bytes\n", cbDibSize));
+#endif
+ }
+ }
+ }
+ }
+
+ Log(("readFromPasteboard: rc = %02X\n", rc));
+ return rc;
+}
+
+/**
+ * Write clipboard content to the host clipboard from the internal clipboard
+ * structure.
+ *
+ * @param pPasteboard Reference to the global pasteboard.
+ * @param pv The source buffer.
+ * @param cb The size of the source buffer.
+ * @param fFormat The format type which should be written.
+ *
+ * @returns IPRT status code.
+ */
+int writeToPasteboard(PasteboardRef pPasteboard, void *pv, uint32_t cb, uint32_t fFormat)
+{
+ Log(("writeToPasteboard: fFormat = %02X\n", fFormat));
+
+ /* Clear the pasteboard */
+ if (PasteboardClear(pPasteboard))
+ return VERR_NOT_SUPPORTED;
+
+ /* Make sure all is in sync */
+ PasteboardSynchronize(pPasteboard);
+
+ int rc = VERR_NOT_SUPPORTED;
+ /* Handle the unicode text */
+ if (fFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ PRTUTF16 pwszSrcText = static_cast <PRTUTF16>(pv);
+ size_t cwSrc = cb / 2;
+ size_t cwDest = 0;
+ /* How long will the converted text be? */
+ rc = vboxClipboardUtf16GetLinSize(pwszSrcText, cwSrc, &cwDest);
+ if (RT_FAILURE(rc))
+ {
+ Log(("writeToPasteboard: clipboard conversion failed. vboxClipboardUtf16GetLinSize returned %Rrc. Abandoning.\n", rc));
+ AssertRCReturn(rc, rc);
+ }
+ /* Empty clipboard? Not critical */
+ if (cwDest == 0)
+ {
+ Log(("writeToPasteboard: received empty clipboard data from the guest, returning false.\n"));
+ return VINF_SUCCESS;
+ }
+ /* Allocate the necessary memory */
+ PRTUTF16 pwszDestText = static_cast <PRTUTF16>(RTMemAlloc(cwDest * 2));
+ if (pwszDestText == NULL)
+ {
+ Log(("writeToPasteboard: failed to allocate %d bytes\n", cwDest * 2));
+ return VERR_NO_MEMORY;
+ }
+ /* Convert the EOL */
+ rc = vboxClipboardUtf16WinToLin(pwszSrcText, cwSrc, pwszDestText, cwDest);
+ if (RT_FAILURE(rc))
+ {
+ Log(("writeToPasteboard: clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc));
+ RTMemFree(pwszDestText);
+ AssertRCReturn(rc, rc);
+ }
+
+ CFDataRef textData = NULL;
+ /* Item id is 1. Nothing special here. */
+ PasteboardItemID itemId = (PasteboardItemID)1;
+ /* Create a CData object which we could pass to the pasteboard */
+ if ((textData = CFDataCreate(kCFAllocatorDefault,
+ reinterpret_cast<UInt8*>(pwszDestText), cwDest * 2)))
+ {
+ /* Put the Utf-16 version to the pasteboard */
+ PasteboardPutItemFlavor(pPasteboard, itemId,
+ kUTTypeUTF16PlainText,
+ textData, 0);
+ }
+ /* Create a Utf-8 version */
+ char *pszDestText;
+ rc = RTUtf16ToUtf8(pwszDestText, &pszDestText);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create a CData object which we could pass to the pasteboard */
+ if ((textData = CFDataCreate(kCFAllocatorDefault,
+ reinterpret_cast<UInt8*>(pszDestText), strlen(pszDestText))))
+ {
+ /* Put the Utf-8 version to the pasteboard */
+ PasteboardPutItemFlavor(pPasteboard, itemId,
+ kUTTypeUTF8PlainText,
+ textData, 0);
+ }
+ RTStrFree(pszDestText);
+ }
+
+ RTMemFree(pwszDestText);
+ rc = VINF_SUCCESS;
+ }
+ /* Handle the bitmap */
+ else if (fFormat & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ {
+ /* Create a full BMP from it */
+ void *pBmp;
+ size_t cbBmpSize;
+ CFDataRef bmpData = NULL;
+ /* Item id is 1. Nothing special here. */
+ PasteboardItemID itemId = (PasteboardItemID)1;
+
+ rc = vboxClipboardDibToBmp(pv, cb, &pBmp, &cbBmpSize);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create a CData object which we could pass to the pasteboard */
+ if ((bmpData = CFDataCreate(kCFAllocatorDefault,
+ reinterpret_cast<UInt8*>(pBmp), cbBmpSize)))
+ {
+ /* Put the Utf-8 version to the pasteboard */
+ PasteboardPutItemFlavor(pPasteboard, itemId,
+ kUTTypeBMP,
+ bmpData, 0);
+ }
+ RTMemFree(pBmp);
+ }
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NOT_IMPLEMENTED;
+
+ Log(("writeToPasteboard: rc = %02X\n", rc));
+ return rc;
+}
+
diff --git a/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h
new file mode 100644
index 00000000..44606fe2
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/darwin-pasteboard.h
@@ -0,0 +1,34 @@
+/* $Id: darwin-pasteboard.h $ */
+/** @file
+ * Shared Clipboard Service - Mac OS X host implementation.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h
+#define VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+typedef struct OpaquePasteboardRef *PasteboardRef;
+
+int initPasteboard(PasteboardRef *pPasteboardRef);
+void destroyPasteboard(PasteboardRef *pPasteboardRef);
+
+int queryNewPasteboardFormats(PasteboardRef pPasteboard, uint32_t *pfFormats, bool *pfChanged);
+int readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual);
+int writeToPasteboard(PasteboardRef pPasteboard, void *pv, uint32_t cb, uint32_t fFormat);
+
+#endif /* !VBOX_INCLUDED_SRC_SharedClipboard_darwin_pasteboard_h */
+
diff --git a/src/VBox/HostServices/SharedClipboard/darwin.cpp b/src/VBox/HostServices/SharedClipboard/darwin.cpp
new file mode 100644
index 00000000..3dbafbd3
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/darwin.cpp
@@ -0,0 +1,274 @@
+/* $Id: darwin.cpp $ */
+/** @file
+ * Shared Clipboard Service - Mac OS X host.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/thread.h>
+
+#include "VBoxClipboard.h"
+#include "darwin-pasteboard.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Global clipboard context information */
+struct _VBOXCLIPBOARDCONTEXT
+{
+ /** We have a separate thread to poll for new clipboard content */
+ RTTHREAD thread;
+ bool volatile fTerminate;
+
+ /** The reference to the current pasteboard */
+ PasteboardRef pasteboard;
+
+ VBOXCLIPBOARDCLIENTDATA *pClient;
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Only one client is supported. There seems to be no need for more clients. */
+static VBOXCLIPBOARDCONTEXT g_ctx;
+
+
+/**
+ * Checks if something is present on the clipboard and calls vboxSvcClipboardReportMsg.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pCtx The context.
+ */
+static int vboxClipboardChanged (VBOXCLIPBOARDCONTEXT *pCtx)
+{
+ if (pCtx->pClient == NULL)
+ return VINF_SUCCESS;
+
+ uint32_t fFormats = 0;
+ bool fChanged = false;
+ /* Retrieve the formats currently in the clipboard and supported by vbox */
+ int rc = queryNewPasteboardFormats (pCtx->pasteboard, &fFormats, &fChanged);
+ if (RT_SUCCESS (rc) && fChanged)
+ {
+ vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, fFormats);
+ Log (("vboxClipboardChanged fFormats %02X\n", fFormats));
+ }
+
+ return rc;
+}
+
+
+/**
+ * The poller thread.
+ *
+ * This thread will check for the arrival of new data on the clipboard.
+ *
+ * @returns VINF_SUCCESS (not used).
+ * @param ThreadSelf Our thread handle.
+ * @param pvUser Pointer to the VBOXCLIPBOARDCONTEXT structure.
+ *
+ */
+static int vboxClipboardThread (RTTHREAD ThreadSelf, void *pvUser)
+{
+ Log (("vboxClipboardThread: starting clipboard thread\n"));
+
+ AssertPtrReturn (pvUser, VERR_INVALID_PARAMETER);
+ VBOXCLIPBOARDCONTEXT *pCtx = (VBOXCLIPBOARDCONTEXT *) pvUser;
+
+ while (!pCtx->fTerminate)
+ {
+ /* call this behind the lock because we don't know if the api is
+ thread safe and in any case we're calling several methods. */
+ vboxSvcClipboardLock();
+ vboxClipboardChanged (pCtx);
+ vboxSvcClipboardUnlock();
+
+ /* Sleep for 200 msecs before next poll */
+ RTThreadUserWait (ThreadSelf, 200);
+ }
+
+ Log (("vboxClipboardThread: clipboard thread terminated successfully with return code %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+/*
+ * Public platform dependent functions.
+ */
+
+/** Initialise the host side of the shared clipboard - called by the hgcm layer. */
+int vboxClipboardInit (void)
+{
+ Log (("vboxClipboardInit\n"));
+
+ g_ctx.fTerminate = false;
+
+ int rc = initPasteboard (&g_ctx.pasteboard);
+ AssertRCReturn (rc, rc);
+
+ rc = RTThreadCreate (&g_ctx.thread, vboxClipboardThread, &g_ctx, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
+ if (RT_FAILURE (rc))
+ {
+ g_ctx.thread = NIL_RTTHREAD;
+ destroyPasteboard (&g_ctx.pasteboard);
+ }
+
+ return rc;
+}
+
+/** Terminate the host side of the shared clipboard - called by the hgcm layer. */
+void vboxClipboardDestroy (void)
+{
+ Log (("vboxClipboardDestroy\n"));
+
+ /*
+ * Signal the termination of the polling thread and wait for it to respond.
+ */
+ ASMAtomicWriteBool (&g_ctx.fTerminate, true);
+ int rc = RTThreadUserSignal (g_ctx.thread);
+ AssertRC (rc);
+ rc = RTThreadWait (g_ctx.thread, RT_INDEFINITE_WAIT, NULL);
+ AssertRC (rc);
+
+ /*
+ * Destroy the pasteboard and uninitialize the global context record.
+ */
+ destroyPasteboard (&g_ctx.pasteboard);
+ g_ctx.thread = NIL_RTTHREAD;
+ g_ctx.pClient = NULL;
+}
+
+/**
+ * Enable the shared clipboard - called by the hgcm clipboard subsystem.
+ *
+ * @param pClient Structure containing context information about the guest system
+ * @param fHeadless Whether headless.
+ * @returns RT status code
+ */
+int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless)
+{
+ NOREF(fHeadless);
+ if (g_ctx.pClient != NULL)
+ {
+ /* One client only. */
+ return VERR_NOT_SUPPORTED;
+ }
+
+ vboxSvcClipboardLock();
+
+ pClient->pCtx = &g_ctx;
+ pClient->pCtx->pClient = pClient;
+
+ /* Initially sync the host clipboard content with the client. */
+ int rc = vboxClipboardSync (pClient);
+
+ vboxSvcClipboardUnlock();
+ return rc;
+}
+
+/**
+ * Synchronise the contents of the host clipboard with the guest, called by the HGCM layer
+ * after a save and restore of the guest.
+ */
+int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ /* Sync the host clipboard content with the client. */
+ vboxSvcClipboardLock();
+ int rc = vboxClipboardChanged (pClient->pCtx);
+ vboxSvcClipboardUnlock();
+
+ return rc;
+}
+
+/**
+ * Shut down the shared clipboard subsystem and "disconnect" the guest.
+ */
+void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ Log (("vboxClipboardDisconnect\n"));
+
+ vboxSvcClipboardLock();
+ pClient->pCtx->pClient = NULL;
+ vboxSvcClipboardUnlock();
+}
+
+/**
+ * The guest is taking possession of the shared clipboard. Called by the HGCM clipboard
+ * subsystem.
+ *
+ * @param pClient Context data for the guest system
+ * @param u32Formats Clipboard formats the guest is offering
+ */
+void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Formats)
+{
+ Log (("vboxClipboardFormatAnnounce u32Formats %02X\n", u32Formats));
+ if (u32Formats == 0)
+ {
+ /* This is just an automatism, not a genuine announcement */
+ return;
+ }
+
+ vboxSvcClipboardReportMsg (pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ u32Formats);
+}
+
+/**
+ * Called by the HGCM clipboard subsystem when the guest wants to read the host clipboard.
+ *
+ * @param pClient Context information about the guest VM
+ * @param u32Format The format that the guest would like to receive the data in
+ * @param pv Where to write the data to
+ * @param cb The size of the buffer to write the data to
+ * @param pcbActual Where to write the actual size of the written data
+ */
+int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Format,
+ void *pv, uint32_t cb, uint32_t * pcbActual)
+{
+ vboxSvcClipboardLock();
+
+ /* Default to no data available. */
+ *pcbActual = 0;
+ int rc = readFromPasteboard (pClient->pCtx->pasteboard, u32Format, pv, cb, pcbActual);
+
+ vboxSvcClipboardUnlock();
+ return rc;
+}
+
+/**
+ * Called by the HGCM clipboard subsystem when we have requested data and that data arrives.
+ *
+ * @param pClient Context information about the guest VM
+ * @param pv Buffer to which the data was written
+ * @param cb The size of the data written
+ * @param u32Format The format of the data written
+ */
+void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient, void *pv,
+ uint32_t cb, uint32_t u32Format)
+{
+ vboxSvcClipboardLock();
+
+ writeToPasteboard (pClient->pCtx->pasteboard, pv, cb, u32Format);
+
+ vboxSvcClipboardUnlock();
+}
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk
new file mode 100644
index 00000000..b1bf2d23
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk
@@ -0,0 +1,33 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Shared Clipboard Host Service testcases.
+#
+
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+
+ PROGRAMS += tstClipboardServiceHost
+
+ tstClipboardServiceHost_TEMPLATE = VBOXR3TSTEXE
+ tstClipboardServiceHost_DEFS = VBOX_WITH_HGCM UNIT_TEST
+ tstClipboardServiceHost_SOURCES = \
+ ../VBoxSharedClipboardSvc.cpp \
+ tstClipboardServiceHost.cpp
+
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp
new file mode 100644
index 00000000..f431886d
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp
@@ -0,0 +1,286 @@
+/* $Id: tstClipboardServiceHost.cpp $ */
+/** @file
+ * Shared Clipboard host service test case.
+ */
+
+/*
+ * 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 "../VBoxClipboard.h"
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable);
+
+static VBOXCLIPBOARDCLIENTDATA g_Client;
+static VBOXHGCMSVCHELPERS g_Helpers = { NULL };
+
+/** Simple call handle structure for the guest call completion callback */
+struct VBOXHGCMCALLHANDLE_TYPEDEF
+{
+ /** Where to store the result code */
+ int32_t rc;
+};
+
+/** Call completion callback for guest calls. */
+static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc)
+{
+ callHandle->rc = rc;
+ return VINF_SUCCESS;
+}
+
+static int setupTable(VBOXHGCMSVCFNTABLE *pTable)
+{
+ pTable->cbSize = sizeof(*pTable);
+ pTable->u32Version = VBOX_HGCM_SVC_VERSION;
+ g_Helpers.pfnCallComplete = callComplete;
+ pTable->pHelpers = &g_Helpers;
+ return VBoxHGCMSvcLoad(pTable);
+}
+
+static void testSetMode(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ uint32_t u32Mode;
+ int rc;
+
+ RTTestISub("Testing HOST_FN_SET_MODE");
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+ /* Reset global variable which doesn't reset itself. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHARED_CLIPBOARD_MODE_OFF);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = TestClipSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHARED_CLIPBOARD_MODE_OFF,
+ ("u32Mode=%u\n", (unsigned) u32Mode));
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU32(&parms[0], VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = TestClipSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST,
+ ("u32Mode=%u\n", (unsigned) u32Mode));
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = TestClipSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHARED_CLIPBOARD_MODE_OFF,
+ ("u32Mode=%u\n", (unsigned) u32Mode));
+ table.pfnUnload(NULL);
+}
+
+static void testGetHostMsg(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ VBOXHGCMCALLHANDLE_TYPEDEF call;
+ int rc;
+
+ RTTestISub("Setting up VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG test");
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+ /* Unless we are bidirectional the host message requests will be dropped. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+
+ RTTestISub("Testing FN_GET_HOST_MSG, one format, waiting guest call.");
+ RT_ZERO(g_Client);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_TRY_AGAIN); /* This should get updated only when the guest call completes. */
+ vboxSvcClipboardReportMsg (&g_Client, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_TRY_AGAIN); /* This call should not complete yet. */
+
+ RTTestISub("Testing FN_GET_HOST_MSG, one format, no waiting guest calls.");
+ RT_ZERO(g_Client);
+ vboxSvcClipboardReportMsg (&g_Client, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ VBOX_SHARED_CLIPBOARD_FMT_HTML);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHARED_CLIPBOARD_FMT_HTML);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_TRY_AGAIN); /* This call should not complete yet. */
+
+ RTTestISub("Testing FN_GET_HOST_MSG, two formats, waiting guest call.");
+ RT_ZERO(g_Client);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_TRY_AGAIN); /* This should get updated only when the guest call completes. */
+ vboxSvcClipboardReportMsg (&g_Client, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT | VBOX_SHARED_CLIPBOARD_FMT_HTML);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHARED_CLIPBOARD_FMT_HTML);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_TRY_AGAIN); /* This call should not complete yet. */
+
+ RTTestISub("Testing FN_GET_HOST_MSG, two formats, no waiting guest calls.");
+ RT_ZERO(g_Client);
+ vboxSvcClipboardReportMsg (&g_Client, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT | VBOX_SHARED_CLIPBOARD_FMT_HTML);
+ HGCMSvcSetU32(&parms[0], 0);
+ HGCMSvcSetU32(&parms[1], 0);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHARED_CLIPBOARD_FMT_HTML);
+ RTTESTI_CHECK_RC_OK(call.rc);
+ call.rc = VERR_TRY_AGAIN;
+ table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG,
+ 2, parms, 0);
+ RTTESTI_CHECK_RC(call.rc, VERR_TRY_AGAIN); /* This call should not complete yet. */
+ table.pfnUnload(NULL);
+}
+
+static void testSetHeadless(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ VBOXHGCMSVCFNTABLE table;
+ bool fHeadless;
+ int rc;
+
+ RTTestISub("Testing HOST_FN_SET_HEADLESS");
+ rc = setupTable(&table);
+ RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc));
+ /* Reset global variable which doesn't reset itself. */
+ HGCMSvcSetU32(&parms[0], false);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = vboxSvcClipboardGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless));
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS,
+ 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS,
+ 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU32(&parms[0], true);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = vboxSvcClipboardGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = table.pfnHostCall(NULL, VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS,
+ 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = vboxSvcClipboardGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+ table.pfnUnload(NULL);
+}
+
+static void testHostCall(void)
+{
+ testSetMode();
+ testSetHeadless();
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ const char *pcszExecName;
+ NOREF(argc);
+ pcszExecName = strrchr(argv[0], '/');
+ pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0];
+ RTTEST hTest;
+ RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the tests.
+ */
+ testHostCall();
+ testGetHostMsg();
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+
+int vboxClipboardInit() { return VINF_SUCCESS; }
+void vboxClipboardDestroy() {}
+void vboxClipboardDisconnect(_VBOXCLIPBOARDCLIENTDATA*) { AssertFailed(); }
+int vboxClipboardConnect(_VBOXCLIPBOARDCLIENTDATA*, bool)
+{ AssertFailed(); return VERR_WRONG_ORDER; }
+void vboxClipboardFormatAnnounce(_VBOXCLIPBOARDCLIENTDATA*, unsigned int)
+{ AssertFailed(); }
+int vboxClipboardReadData(_VBOXCLIPBOARDCLIENTDATA*, unsigned int, void*, unsigned int, unsigned int*)
+{ AssertFailed(); return VERR_WRONG_ORDER; }
+void vboxClipboardWriteData(_VBOXCLIPBOARDCLIENTDATA*, void*, unsigned int, unsigned int) { AssertFailed(); }
+int vboxClipboardSync(_VBOXCLIPBOARDCLIENTDATA*)
+{ AssertFailed(); return VERR_WRONG_ORDER; }
diff --git a/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp b/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp
new file mode 100644
index 00000000..88339e02
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp
@@ -0,0 +1,615 @@
+/* $Id: x11-clipboard.cpp $ */
+/** @file
+ * Shared Clipboard Service - Linux 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.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/env.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/err.h>
+
+#include "VBoxClipboard.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct _VBOXCLIPBOARDREQFROMVBOX;
+typedef struct _VBOXCLIPBOARDREQFROMVBOX VBOXCLIPBOARDREQFROMVBOX;
+
+/** Global context information used by the host glue for the X11 clipboard
+ * backend */
+struct _VBOXCLIPBOARDCONTEXT
+{
+ /** This mutex is grabbed during any critical operations on the clipboard
+ * which might clash with others. */
+ RTCRITSECT clipboardMutex;
+ /** The currently pending request for data from VBox. NULL if there is
+ * no request pending. The protocol for completing a request is to grab
+ * the critical section, check that @a pReq is not NULL, fill in the data
+ * fields and set @a pReq to NULL. The protocol for cancelling a pending
+ * request is to grab the critical section and set pReq to NULL.
+ * It is an error if a request arrives while another one is pending, and
+ * the backend is responsible for ensuring that this does not happen. */
+ VBOXCLIPBOARDREQFROMVBOX *pReq;
+
+ /** Pointer to the opaque X11 backend structure */
+ CLIPBACKEND *pBackend;
+ /** Pointer to the VBox host client data structure. */
+ VBOXCLIPBOARDCLIENTDATA *pClient;
+ /** We set this when we start shutting down as a hint not to post any new
+ * requests. */
+ bool fShuttingDown;
+};
+
+
+
+/**
+ * Report formats available in the X11 clipboard to VBox.
+ * @param pCtx Opaque context pointer for the glue code
+ * @param u32Formats The formats available
+ * @note Host glue code
+ */
+void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx,
+ uint32_t u32Formats)
+{
+ LogRelFlowFunc(("called. pCtx=%p, u32Formats=%02X\n", pCtx, u32Formats));
+ vboxSvcClipboardReportMsg(pCtx->pClient,
+ VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS,
+ u32Formats);
+}
+
+/**
+ * Initialise the host side of the shared clipboard.
+ * @note Host glue code
+ */
+int vboxClipboardInit (void)
+{
+ return VINF_SUCCESS;
+}
+
+/**
+ * Terminate the host side of the shared clipboard.
+ * @note host glue code
+ */
+void vboxClipboardDestroy (void)
+{
+
+}
+
+/**
+ * Connect a guest to the shared clipboard.
+ * @note host glue code
+ * @note on the host, we assume that some other application already owns
+ * the clipboard and leave ownership to X11.
+ */
+int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless)
+{
+ int rc = VINF_SUCCESS;
+ CLIPBACKEND *pBackend = NULL;
+
+ LogRel(("Starting host clipboard service\n"));
+ VBOXCLIPBOARDCONTEXT *pCtx =
+ (VBOXCLIPBOARDCONTEXT *) RTMemAllocZ(sizeof(VBOXCLIPBOARDCONTEXT));
+ if (!pCtx)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ RTCritSectInit(&pCtx->clipboardMutex);
+ pBackend = ClipConstructX11(pCtx, fHeadless);
+ if (pBackend == NULL)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ pCtx->pBackend = pBackend;
+ pClient->pCtx = pCtx;
+ pCtx->pClient = pClient;
+ rc = ClipStartX11(pBackend, true /* grab shared clipboard */);
+ }
+ if (RT_FAILURE(rc))
+ RTCritSectDelete(&pCtx->clipboardMutex);
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ LogRel(("Failed to initialise the shared clipboard\n"));
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Synchronise the contents of the host clipboard with the guest, called
+ * after a save and restore of the guest.
+ * @note Host glue code
+ */
+int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ /* Tell the guest we have no data in case X11 is not available. If
+ * there is data in the host clipboard it will automatically be sent to
+ * the guest when the clipboard starts up. */
+ vboxSvcClipboardReportMsg (pClient,
+ VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, 0);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Shut down the shared clipboard service and "disconnect" the guest.
+ * @note Host glue code
+ */
+void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ LogRelFlow(("vboxClipboardDisconnect\n"));
+
+ LogRel(("Stopping the host clipboard service\n"));
+ VBOXCLIPBOARDCONTEXT *pCtx = pClient->pCtx;
+ /* Drop the reference to the client, in case it is still there. This
+ * will cause any outstanding clipboard data requests from X11 to fail
+ * immediately. */
+ pCtx->fShuttingDown = true;
+ /* If there is a currently pending request, release it immediately. */
+ vboxClipboardWriteData(pClient, NULL, 0, 0);
+ int rc = ClipStopX11(pCtx->pBackend);
+ /** @todo handle this slightly more reasonably, or be really sure
+ * it won't go wrong. */
+ AssertRC(rc);
+ if (RT_SUCCESS(rc)) /* And if not? */
+ {
+ ClipDestructX11(pCtx->pBackend);
+ RTCritSectDelete(&pCtx->clipboardMutex);
+ RTMemFree(pCtx);
+ }
+}
+
+/**
+ * VBox is taking possession of the shared clipboard.
+ *
+ * @param pClient Context data for the guest system
+ * @param u32Formats Clipboard formats the guest is offering
+ * @note Host glue code
+ */
+void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient,
+ uint32_t u32Formats)
+{
+ LogRelFlowFunc(("called. pClient=%p, u32Formats=%02X\n", pClient,
+ u32Formats));
+ ClipAnnounceFormatToX11 (pClient->pCtx->pBackend, u32Formats);
+}
+
+/** Structure describing a request for clipoard data from the guest. */
+struct _CLIPREADCBREQ
+{
+ /** Where to write the returned data to. */
+ void *pv;
+ /** The size of the buffer in pv */
+ uint32_t cb;
+ /** The actual size of the data written */
+ uint32_t *pcbActual;
+};
+
+/**
+ * Called when VBox wants to read the X11 clipboard.
+ *
+ * @returns VINF_SUCCESS on successful completion
+ * @returns VINF_HGCM_ASYNC_EXECUTE if the operation will complete
+ * asynchronously
+ * @returns iprt status code on failure
+ * @param pClient Context information about the guest VM
+ * @param u32Format The format that the guest would like to receive the data in
+ * @param pv Where to write the data to
+ * @param cb The size of the buffer to write the data to
+ * @param pcbActual Where to write the actual size of the written data
+ * @note We always fail or complete asynchronously
+ * @note On success allocates a CLIPREADCBREQ structure which must be
+ * freed in ClipCompleteDataRequestFromX11 when it is called back from
+ * the backend code.
+ *
+ */
+int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient,
+ uint32_t u32Format, void *pv, uint32_t cb,
+ uint32_t *pcbActual)
+{
+ LogRelFlowFunc(("pClient=%p, u32Format=%02X, pv=%p, cb=%u, pcbActual=%p",
+ pClient, u32Format, pv, cb, pcbActual));
+
+ int rc = VINF_SUCCESS;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *) RTMemAlloc(sizeof(CLIPREADCBREQ));
+ if (!pReq)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ pReq->pv = pv;
+ pReq->cb = cb;
+ pReq->pcbActual = pcbActual;
+ rc = ClipRequestDataFromX11(pClient->pCtx->pBackend, u32Format, pReq);
+ if (RT_SUCCESS(rc))
+ rc = VINF_HGCM_ASYNC_EXECUTE;
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Complete a request from VBox for the X11 clipboard data. The data should
+ * be written to the buffer provided in the initial request.
+ * @param pCtx request context information
+ * @param rc the completion status of the request
+ * @param pReq request
+ * @param pv address
+ * @param cb size
+ *
+ * @todo change this to deal with the buffer issues rather than offloading
+ * them onto the caller
+ */
+void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc,
+ CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+ if (cb <= pReq->cb && cb != 0)
+ memcpy(pReq->pv, pv, cb);
+ RTMemFree(pReq);
+ vboxSvcClipboardCompleteReadData(pCtx->pClient, rc, cb);
+}
+
+/** A request for clipboard data from VBox */
+struct _VBOXCLIPBOARDREQFROMVBOX
+{
+ /** Data received */
+ void *pv;
+ /** The size of the data */
+ uint32_t cb;
+ /** Format of the data */
+ uint32_t format;
+ /** A semaphore for waiting for the data */
+ RTSEMEVENT finished;
+};
+
+/** Wait for clipboard data requested from VBox to arrive. */
+static int clipWaitForDataFromVBox(VBOXCLIPBOARDCONTEXT *pCtx,
+ VBOXCLIPBOARDREQFROMVBOX *pReq,
+ uint32_t u32Format)
+{
+ int rc = VINF_SUCCESS;
+ LogRelFlowFunc(("pCtx=%p, pReq=%p, u32Format=%02X\n", pCtx, pReq, u32Format));
+ /* Request data from VBox */
+ vboxSvcClipboardReportMsg(pCtx->pClient,
+ VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ u32Format);
+ /* Which will signal us when it is ready. We use a timeout here
+ * because we can't be sure that the guest will behave correctly.
+ */
+ rc = RTSemEventWait(pReq->finished, CLIPBOARD_TIMEOUT);
+ /* If the request hasn't yet completed then we cancel it. We use
+ * the critical section to prevent these operations colliding. */
+ RTCritSectEnter(&pCtx->clipboardMutex);
+ /* The data may have arrived between the semaphore timing out and
+ * our grabbing the mutex. */
+ if (rc == VERR_TIMEOUT && pReq->pv != NULL)
+ rc = VINF_SUCCESS;
+ if (pCtx->pReq == pReq)
+ pCtx->pReq = NULL;
+ Assert(pCtx->pReq == NULL);
+ RTCritSectLeave(&pCtx->clipboardMutex);
+ if (RT_SUCCESS(rc) && (pReq->pv == NULL))
+ rc = VERR_NO_DATA;
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/** Post a request for clipboard data to VBox/the guest and wait for it to be
+ * completed. */
+static int clipRequestDataFromVBox(VBOXCLIPBOARDCONTEXT *pCtx,
+ VBOXCLIPBOARDREQFROMVBOX *pReq,
+ uint32_t u32Format)
+{
+ int rc = VINF_SUCCESS;
+ LogRelFlowFunc(("pCtx=%p, pReq=%p, u32Format=%02X\n", pCtx, pReq,
+ u32Format));
+ /* Start by "posting" the request for the next invocation of
+ * vboxClipboardWriteData. */
+ RTCritSectEnter(&pCtx->clipboardMutex);
+ if (pCtx->pReq != NULL)
+ {
+ /* This would be a violation of the protocol, see the comments in the
+ * context structure definition. */
+ Assert(false);
+ rc = VERR_WRONG_ORDER;
+ }
+ else
+ pCtx->pReq = pReq;
+ RTCritSectLeave(&pCtx->clipboardMutex);
+ if (RT_SUCCESS(rc))
+ rc = clipWaitForDataFromVBox(pCtx, pReq, u32Format);
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Send a request to VBox to transfer the contents of its clipboard to X11.
+ *
+ * @param pCtx Pointer to the host clipboard structure
+ * @param u32Format The format in which the data should be transferred
+ * @param ppv On success and if pcb > 0, this will point to a buffer
+ * to be freed with RTMemFree containing the data read.
+ * @param pcb On success, this contains the number of bytes of data
+ * returned
+ * @note Host glue code.
+ */
+int ClipRequestDataForX11 (VBOXCLIPBOARDCONTEXT *pCtx,
+ uint32_t u32Format, void **ppv,
+ uint32_t *pcb)
+{
+ VBOXCLIPBOARDREQFROMVBOX request = { NULL, 0, 0, NIL_RTSEMEVENT };
+
+ LogRelFlowFunc(("pCtx=%p, u32Format=%02X, ppv=%p, pcb=%p\n", pCtx,
+ u32Format, ppv, pcb));
+ if (pCtx->fShuttingDown)
+ {
+ /* The shared clipboard is disconnecting. */
+ LogRelFunc(("host requested guest clipboard data after guest had disconnected.\n"));
+ return VERR_WRONG_ORDER;
+ }
+ int rc = RTSemEventCreate(&request.finished);
+ if (RT_SUCCESS(rc))
+ {
+ rc = clipRequestDataFromVBox(pCtx, &request, u32Format);
+ RTSemEventDestroy(request.finished);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *ppv = request.pv;
+ *pcb = request.cb;
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ LogRelFlowFunc(("*ppv=%.*ls, *pcb=%u\n", *pcb / 2, *ppv, *pcb));
+ return rc;
+}
+
+/**
+ * Called when we have requested data from VBox and that data has arrived.
+ *
+ * @param pClient Context information about the guest VM
+ * @param pv Buffer to which the data was written
+ * @param cb The size of the data written
+ * @param u32Format The format of the data written
+ * @note Host glue code
+ */
+void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient,
+ void *pv, uint32_t cb, uint32_t u32Format)
+{
+ LogRelFlowFunc (("called. pClient=%p, pv=%p (%.*ls), cb=%u, u32Format=%02X\n",
+ pClient, pv, cb / 2, pv, cb, u32Format));
+
+ VBOXCLIPBOARDCONTEXT *pCtx = pClient->pCtx;
+ /* Grab the mutex and check whether there is a pending request for data.
+ */
+ RTCritSectEnter(&pCtx->clipboardMutex);
+ VBOXCLIPBOARDREQFROMVBOX *pReq = pCtx->pReq;
+ if (pReq != NULL)
+ {
+ if (cb > 0)
+ {
+ pReq->pv = RTMemDup(pv, cb);
+ if (pReq->pv != NULL) /* NULL may also mean no memory... */
+ {
+ pReq->cb = cb;
+ pReq->format = u32Format;
+ }
+ }
+ /* Signal that the request has been completed. */
+ RTSemEventSignal(pReq->finished);
+ pCtx->pReq = NULL;
+ }
+ RTCritSectLeave(&pCtx->clipboardMutex);
+}
+
+#ifdef TESTCASE
+#include <iprt/initterm.h>
+#include <iprt/stream.h>
+
+#define TEST_NAME "tstClipboardX11-2"
+
+struct _CLIPBACKEND
+{
+ uint32_t formats;
+ struct _READDATA
+ {
+ uint32_t format;
+ int rc;
+ CLIPREADCBREQ *pReq;
+ } readData;
+ struct _COMPLETEREAD
+ {
+ int rc;
+ uint32_t cbActual;
+ } completeRead;
+ struct _WRITEDATA
+ {
+ void *pv;
+ uint32_t cb;
+ uint32_t format;
+ bool timeout;
+ } writeData;
+ struct _REPORTDATA
+ {
+ uint32_t format;
+ } reportData;
+};
+
+void vboxSvcClipboardReportMsg (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Msg, uint32_t u32Formats)
+{
+ RT_NOREF1(u32Formats);
+ CLIPBACKEND *pBackend = pClient->pCtx->pBackend;
+ if ( (u32Msg == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA)
+ && !pBackend->writeData.timeout)
+ vboxClipboardWriteData(pClient, pBackend->writeData.pv,
+ pBackend->writeData.cb,
+ pBackend->writeData.format);
+ else
+ return;
+}
+
+void vboxSvcClipboardCompleteReadData(VBOXCLIPBOARDCLIENTDATA *pClient, int rc, uint32_t cbActual)
+{
+ CLIPBACKEND *pBackend = pClient->pCtx->pBackend;
+ pBackend->completeRead.rc = rc;
+ pBackend->completeRead.cbActual = cbActual;
+}
+
+CLIPBACKEND *ClipConstructX11(VBOXCLIPBOARDCONTEXT *pFrontend, bool)
+{
+ RT_NOREF1(pFrontend);
+ return (CLIPBACKEND *)RTMemAllocZ(sizeof(CLIPBACKEND));
+}
+
+void ClipDestructX11(CLIPBACKEND *pBackend)
+{
+ RTMemFree(pBackend);
+}
+
+int ClipStartX11(CLIPBACKEND *pBackend, bool)
+{
+ RT_NOREF1(pBackend);
+ return VINF_SUCCESS;
+}
+
+int ClipStopX11(CLIPBACKEND *pBackend)
+{
+ RT_NOREF1(pBackend);
+ return VINF_SUCCESS;
+}
+
+void ClipAnnounceFormatToX11(CLIPBACKEND *pBackend,
+ uint32_t u32Formats)
+{
+ pBackend->formats = u32Formats;
+}
+
+extern int ClipRequestDataFromX11(CLIPBACKEND *pBackend, uint32_t u32Format,
+ CLIPREADCBREQ *pReq)
+{
+ pBackend->readData.format = u32Format;
+ pBackend->readData.pReq = pReq;
+ return pBackend->readData.rc;
+}
+
+int main()
+{
+ VBOXCLIPBOARDCLIENTDATA client;
+ unsigned cErrors = 0;
+ int rc = RTR3InitExeNoArguments(0);
+ RTPrintf(TEST_NAME ": TESTING\n");
+ AssertRCReturn(rc, 1);
+ rc = vboxClipboardConnect(&client, false);
+ CLIPBACKEND *pBackend = client.pCtx->pBackend;
+ AssertRCReturn(rc, 1);
+ vboxClipboardFormatAnnounce(&client,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ if (pBackend->formats != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ RTPrintf(TEST_NAME ": vboxClipboardFormatAnnounce failed with VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT\n");
+ ++cErrors;
+ }
+ pBackend->readData.rc = VINF_SUCCESS;
+ client.asyncRead.callHandle = (VBOXHGCMCALLHANDLE)pBackend;
+ client.asyncRead.paParms = (VBOXHGCMSVCPARM *)&client;
+ uint32_t u32Dummy;
+ rc = vboxClipboardReadData(&client, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ &u32Dummy, 42, &u32Dummy);
+ if (rc != VINF_HGCM_ASYNC_EXECUTE)
+ {
+ RTPrintf(TEST_NAME ": vboxClipboardReadData returned %Rrc\n", rc);
+ ++cErrors;
+ }
+ else
+ {
+ if ( pBackend->readData.format !=
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT
+ || pBackend->readData.pReq->pv != &u32Dummy
+ || pBackend->readData.pReq->cb != 42
+ || pBackend->readData.pReq->pcbActual != &u32Dummy)
+ {
+ RTPrintf(TEST_NAME ": format=%u, pReq->pv=%p, pReq->cb=%u, pReq->pcbActual=%p\n",
+ pBackend->readData.format, pBackend->readData.pReq->pv,
+ pBackend->readData.pReq->cb,
+ pBackend->readData.pReq->pcbActual);
+ ++cErrors;
+ }
+ else
+ {
+ ClipCompleteDataRequestFromX11(client.pCtx, VERR_NO_DATA,
+ pBackend->readData.pReq, NULL, 43);
+ if ( pBackend->completeRead.rc != VERR_NO_DATA
+ || pBackend->completeRead.cbActual != 43)
+ {
+ RTPrintf(TEST_NAME ": rc=%Rrc, cbActual=%u\n",
+ pBackend->completeRead.rc,
+ pBackend->completeRead.cbActual);
+ ++cErrors;
+ }
+ }
+ }
+ void *pv;
+ uint32_t cb;
+ pBackend->writeData.pv = (void *)"testing";
+ pBackend->writeData.cb = sizeof("testing");
+ pBackend->writeData.format = 1234;
+ pBackend->reportData.format = 4321; /* XX this should be handled! */
+ rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
+ if ( rc != VINF_SUCCESS
+ || strcmp((const char *)pv, "testing") != 0
+ || cb != sizeof("testing"))
+ {
+ RTPrintf("rc=%Rrc, pv=%p, cb=%u\n", rc, pv, cb);
+ ++cErrors;
+ }
+ else
+ RTMemFree(pv);
+ pBackend->writeData.timeout = true;
+ rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
+ if (rc != VERR_TIMEOUT)
+ {
+ RTPrintf("rc=%Rrc, expected VERR_TIMEOUT\n", rc);
+ ++cErrors;
+ }
+ pBackend->writeData.pv = NULL;
+ pBackend->writeData.cb = 0;
+ pBackend->writeData.timeout = false;
+ rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
+ if (rc != VERR_NO_DATA)
+ {
+ RTPrintf("rc=%Rrc, expected VERR_NO_DATA\n", rc);
+ ++cErrors;
+ }
+ /* Data arriving after a timeout should *not* cause any segfaults or
+ * memory leaks. Check with Valgrind! */
+ vboxClipboardWriteData(&client, (void *)"tested", sizeof("tested"), 999);
+ vboxClipboardDisconnect(&client);
+ if (cErrors > 0)
+ RTPrintf(TEST_NAME ": errors: %u\n", cErrors);
+ return cErrors > 0 ? 1 : 0;
+}
+#endif /* TESTCASE */
diff --git a/src/VBox/HostServices/SharedClipboard/x11-stub.cpp b/src/VBox/HostServices/SharedClipboard/x11-stub.cpp
new file mode 100644
index 00000000..756afdda
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/x11-stub.cpp
@@ -0,0 +1,137 @@
+/* $Id: x11-stub.cpp $*/
+/** @file
+ * Shared Clipboard Service - Linux host, a stub version with no functionality for use on headless hosts.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/alloc.h>
+#include <iprt/asm.h> /* For atomic operations */
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include "VBoxClipboard.h"
+
+
+
+/** Initialise the host side of the shared clipboard - called by the hgcm layer. */
+int vboxClipboardInit (void)
+{
+ LogFlowFunc(("called, returning VINF_SUCCESS.\n"));
+ return VINF_SUCCESS;
+}
+
+/** Terminate the host side of the shared clipboard - called by the hgcm layer. */
+void vboxClipboardDestroy (void)
+{
+ LogFlowFunc(("called, returning.\n"));
+}
+
+/**
+ * Enable the shared clipboard - called by the hgcm clipboard subsystem.
+ *
+ * @param pClient Structure containing context information about the guest system
+ * @param fHeadless Whether headless.
+ * @returns RT status code
+ */
+int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient,
+ bool fHeadless)
+{
+ RT_NOREF(pClient, fHeadless);
+ LogFlowFunc(("called, returning VINF_SUCCESS.\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Synchronise the contents of the host clipboard with the guest, called by the HGCM layer
+ * after a save and restore of the guest.
+ */
+int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA * /* pClient */)
+{
+ LogFlowFunc(("called, returning VINF_SUCCESS.\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Shut down the shared clipboard subsystem and "disconnect" the guest.
+ *
+ * @param pClient Structure containing context information about the guest system
+ */
+void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ RT_NOREF(pClient);
+ LogFlowFunc(("called, returning.\n"));
+}
+
+/**
+ * The guest is taking possession of the shared clipboard. Called by the HGCM clipboard
+ * subsystem.
+ *
+ * @param pClient Context data for the guest system
+ * @param u32Formats Clipboard formats the guest is offering
+ */
+void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient,
+ uint32_t u32Formats)
+{
+ RT_NOREF(pClient, u32Formats);
+ LogFlowFunc(("called, returning.\n"));
+}
+
+/**
+ * Called by the HGCM clipboard subsystem when the guest wants to read the host clipboard.
+ *
+ * @param pClient Context information about the guest VM
+ * @param u32Format The format that the guest would like to receive the data in
+ * @param pv Where to write the data to
+ * @param cb The size of the buffer to write the data to
+ * @param pcbActual Where to write the actual size of the written data
+ */
+int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Format,
+ void *pv, uint32_t cb, uint32_t *pcbActual)
+{
+ RT_NOREF(pClient, u32Format, pv, cb);
+ LogFlowFunc(("called, returning VINF_SUCCESS.\n"));
+ /* No data available. */
+ *pcbActual = 0;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Called by the HGCM clipboard subsystem when we have requested data and that data arrives.
+ *
+ * @param pClient Context information about the guest VM
+ * @param pv Buffer to which the data was written
+ * @param cb The size of the data written
+ * @param u32Format The format of the data written
+ */
+void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient, void *pv,
+ uint32_t cb, uint32_t u32Format)
+{
+ RT_NOREF(pClient, pv, cb, u32Format);
+ LogFlowFunc(("called, returning.\n"));
+}
+