win32.cpp revision dd2204401754cb451c14fddb6946ad837f63f7b5
/** @file
* Shared Clipboard: Win32 host.
*/
/*
* Copyright (C) 2006-2007 innotek GmbH
*
* 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/HostServices/VBoxClipboardSvc.h>
#include <iprt/alloc.h>
#include <iprt/string.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/thread.h>
#include <process.h>
#include "VBoxClipboard.h"
#define dprintf Log
static char gachWindowClassName[] = "VBoxSharedClipboardClass";
struct _VBOXCLIPBOARDCONTEXT
{
HWND hwnd;
HWND hwndNextInChain;
RTTHREAD thread;
bool fTerminate;
HANDLE hRenderEvent;
VBOXCLIPBOARDCLIENTDATA *pClient;
};
/* 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));
}
else
{
Log(("%p %d\n", pv, cb));
}
}
else
{
dprintf(("DUMP: invalid format %02X\n", u32Format));
}
}
#else
#define vboxClipboardDump(__pv, __cb, __format) do { NOREF(__pv); NOREF(__cb); NOREF(__format); } while (0)
#endif /* LOG_ENABLED */
static void vboxClipboardGetData (uint32_t u32Format, const void *pvSrc, uint32_t cbSrc,
void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst)
{
dprintf (("vboxClipboardGetData.\n"));
*pcbActualDst = cbSrc;
LogFlow(("vboxClipboardGetData cbSrc = %d, cbDst = %d\n", cbSrc, cbDst));
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->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);
WaitForSingleObject(pCtx->hRenderEvent, INFINITE);
LogFlow(("vboxClipboardReadDataFromClient wait completed\n"));
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. */
if (OpenClipboard (pCtx->hwnd))
{
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);
}
}
static LRESULT CALLBACK vboxClipboardWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
LRESULT rc = 0;
VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
switch (msg)
{
case WM_CHANGECBCHAIN:
{
Log(("WM_CHANGECBCHAIN\n"));
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. */
rc = SendMessage (pCtx->hwndNextInChain, WM_CHANGECBCHAIN, wParam, lParam);
}
}
} break;
case WM_DRAWCLIPBOARD:
{
Log(("WM_DRAWCLIPBOARD next %p\n", pCtx->hwndNextInChain));
if (GetClipboardOwner () != hwnd)
{
/* Clipboard was updated by another application. */
vboxClipboardChanged (pCtx);
}
if (pCtx->hwndNextInChain)
{
/* Pass the message to next windows in the clipboard chain. */
rc = SendMessage (pCtx->hwndNextInChain, msg, wParam, lParam);
}
} break;
case WM_CLOSE:
{
/* Do nothing. Ignore the message. */
} 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\n", vboxrc));
if ( VBOX_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 becames inactive.
*/
if (OpenClipboard (hwnd))
{
EmptyClipboard();
CloseClipboard();
}
} 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.
*/
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));
if (OpenClipboard (hwnd))
{
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\n"));
}
} 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 ThreadSelf, void *pInstance)
{
/* 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;
wc.style = CS_NOCLOSE;
wc.lpfnWndProc = vboxClipboardWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
wc.lpszMenuName = NULL;
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);
pCtx->hwndNextInChain = SetClipboardViewer (pCtx->hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) && !pCtx->fTerminate)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
if (pCtx->hwnd)
{
ChangeClipboardChain (pCtx->hwnd, pCtx->hwndNextInChain);
pCtx->hwndNextInChain = NULL;
DestroyWindow (pCtx->hwnd);
pCtx->hwnd = NULL;
}
if (atomWindowClass != 0)
{
UnregisterClass (gachWindowClassName, hInstance);
atomWindowClass = 0;
}
return 0;
}
/*
* Public platform dependent functions.
*/
int vboxClipboardInit (void)
{
int rc = VINF_SUCCESS;
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"));
/* Set the termination flag and ping the window thread. */
g_ctx.fTerminate = true;
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)
{
Log(("vboxClipboardConnect\n"));
if (g_ctx.pClient != NULL)
{
/* One client only. */
return VERR_NOT_SUPPORTED;
}
pClient->pCtx = &g_ctx;
pClient->pCtx->pClient = pClient;
/* Synch the host clipboard content with the client. */
vboxClipboardSync (pClient);
return VINF_SUCCESS;
}
int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
{
/* Synch the host clipboard content with the client. */
vboxClipboardChanged (pClient->pCtx);
return VINF_SUCCESS;
}
void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *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 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.
*/
if (OpenClipboard (pClient->pCtx->hwnd))
{
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);
GlobalUnlock(hClip);
}
else
{
hClip = NULL;
}
}
}
}
CloseClipboard ();
}
else
{
dprintf(("failed to open clipboard\n"));
}
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)
{
pClient->data.pv = RTMemAlloc (cb);
if (pClient->data.pv)
{
memcpy (pClient->data.pv, pv, cb);
pClient->data.cb = cb;
pClient->data.u32Format = u32Format;
}
}
SetEvent(pClient->pCtx->hRenderEvent);
}