summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp1248
1 files changed, 1248 insertions, 0 deletions
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;
+}