diff options
Diffstat (limited to 'src/VBox/HostServices/SharedClipboard')
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")); +} + |