/* $Id: VBoxSharedClipboardSvc-win.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */ /** @file * Shared Clipboard Service - Win32 host. */ /* * Copyright (C) 2006-2024 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD #include #include #include #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include #endif #include #include #include #include #include #include #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include #endif #include #include /* Needed for shell objects. */ #include "VBoxSharedClipboardSvc-internal.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include "VBoxSharedClipboardSvc-transfers.h" #endif /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Global context information used by the host glue for the X11 clipboard backend. */ struct SHCLCONTEXT { /** Handle for window message handling thread. */ RTTHREAD hThread; /** Structure for keeping and communicating with service client. */ PSHCLCLIENT pClient; /** Windows-specific context data. */ SHCLWINCTX Win; }; /********************************************************************************************************************************* * Prototypes * *********************************************************************************************************************************/ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx); #endif /** * Copy clipboard data into the guest buffer. * * At first attempt, guest will provide a buffer of default size. * Usually 1K or 4K (see platform specific Guest Additions code around * VbglR3ClipboardReadData calls). If this buffer is not big enough * to fit host clipboard content, this function will return VINF_BUFFER_OVERFLOW * and provide guest with host's clipboard buffer actual size. This will be a * signal for the guest to re-read host clipboard data providing bigger buffer * to store it. * * @returns IPRT status code. * @returns VINF_BUFFER_OVERFLOW returned when guest buffer size if not big * enough to store host clipboard data. This is a signal to the guest * to re-issue host clipboard read request with bigger buffer size * (specified in @a pcbActualDst output parameter). * @param u32Format VBox clipboard format (VBOX_SHCL_FMT_XXX) of copied data. * VBOX_SHCL_FMT_NONE returns 0 data. * @param pvSrc Pointer to host clipboard data. * @param cbSrc Size (in bytes) of actual clipboard data to copy. * @param pvDst Pointer to guest buffer to store clipboard data. * @param cbDst Size (in bytes) of guest buffer. * @param pcbActualDst Actual size (in bytes) of host clipboard data. * Only set if guest buffer size if not big enough * to store host clipboard content. When set, * function returns VINF_BUFFER_OVERFLOW. */ static int vboxClipboardSvcWinDataGet(SHCLFORMAT u32Format, const void *pvSrc, uint32_t cbSrc, void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst) { AssertPtrReturn(pcbActualDst, VERR_INVALID_POINTER); if (u32Format == VBOX_SHCL_FMT_NONE) { *pcbActualDst = 0; return VINF_SUCCESS; } AssertPtrReturn(pvSrc, VERR_INVALID_POINTER); AssertReturn (cbSrc, VERR_INVALID_PARAMETER); AssertPtrReturn(pvDst, VERR_INVALID_POINTER); AssertReturn (cbDst, VERR_INVALID_PARAMETER); LogFlowFunc(("cbSrc = %d, cbDst = %d\n", cbSrc, cbDst)); if ( u32Format == VBOX_SHCL_FMT_HTML && ShClWinIsCFHTML((const char *)pvSrc)) { /** @todo r=bird: Why the double conversion? */ char *pszBuf = NULL; uint32_t cbBuf = 0; int rc = ShClWinConvertCFHTMLToMIME((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 VINF_BUFFER_OVERFLOW; } memcpy(pvDst, pszBuf, cbBuf); RTMemFree(pszBuf); } else *pcbActualDst = 0; } else { *pcbActualDst = cbSrc; /* Tell the caller how much space we need. */ if (cbSrc > cbDst) return VINF_BUFFER_OVERFLOW; memcpy(pvDst, pvSrc, cbSrc); } #ifdef LOG_ENABLED ShClDbgDumpData(pvDst, cbSrc, u32Format); #endif return VINF_SUCCESS; } /** * Worker for a reading clipboard from the guest. */ static int vboxClipboardSvcWinReadDataFromGuestWorker(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppvData, uint32_t *pcbData) { return ShClSvcReadDataFromGuest(pCtx->pClient, uFmt, ppvData, pcbData); } static int vboxClipboardSvcWinReadDataFromGuest(PSHCLCONTEXT pCtx, UINT uWinFormat, void **ppvData, uint32_t *pcbData) { SHCLFORMAT uVBoxFmt = ShClWinClipboardFormatToVBox(uWinFormat); if (uVBoxFmt == VBOX_SHCL_FMT_NONE) { LogRel2(("Shared Clipboard: Windows format %u not supported, ignoring\n", uWinFormat)); return VERR_NOT_SUPPORTED; } int rc = vboxClipboardSvcWinReadDataFromGuestWorker(pCtx, uVBoxFmt, ppvData, pcbData); LogFlowFuncLeaveRC(rc); return rc; } /** * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource * * Called from the IDataObject implementation to request data from the guest. * * @thread Windows event thread. */ static DECLCALLBACK(int) vboxClipboardSvcWinRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) { RT_NOREF(pvUser); LogFlowFuncEnter(); int rc = vboxClipboardSvcWinReadDataFromGuestWorker(pCtx, uFmt, ppv, pcb); LogFlowFuncLeaveRC(rc); return rc; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated * * @thread Service main thread. */ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); PSHCLCLIENT const pClient = pCtx->pClient; AssertPtr(pClient); /* * Set transfer provider. * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. */ /* Set the interface to the local provider by default first. */ RT_ZERO(pClient->Transfers.Provider); ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); PSHCLTXPROVIDERIFACE pIface = &pClient->Transfers.Provider.Interface; pClient->Transfers.Provider.enmSource = pClient->State.enmSource; pClient->Transfers.Provider.pvUser = pClient; int rc = VINF_SUCCESS; switch (ShClTransferGetDir(pTransfer)) { case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ { pIface->pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; pIface->pfnListOpen = ShClSvcTransferIfaceGHListOpen; pIface->pfnListClose = ShClSvcTransferIfaceGHListClose; pIface->pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; pIface->pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; pIface->pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; pIface->pfnObjClose = ShClSvcTransferIfaceGHObjClose; pIface->pfnObjRead = ShClSvcTransferIfaceGHObjRead; break; } case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ { pIface->pfnRootListRead = shClSvcWinTransferIfaceHGRootListRead; break; } default: AssertFailedStmt(rc = VERR_NOT_SUPPORTED); break; } if (RT_SUCCESS(rc)) { rc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); if (RT_SUCCESS(rc)) rc = ShClWinTransferCreate(&pCtx->Win, pTransfer); } LogFlowFuncLeaveRC(rc); } /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialize * * For G->H: Called on transfer intialization to notify the "in-flight" IDataObject about a data transfer. * For H->G: Called on transfer intialization to populate the transfer's root list. * * @thread Service main thread. */ static DECLCALLBACK(int) shClSvcWinTransferOnInitializeCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); int rc = VINF_SUCCESS; switch (ShClTransferGetDir(pTransfer)) { case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ { rc = ShClWinTransferInitialize(&pCtx->Win, pTransfer); break; } case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ { rc = ShClTransferRootListRead(pTransfer); /* Calls shClSvcWinTransferIfaceHGRootListRead(). */ break; } default: AssertFailedStmt(rc = VERR_NOT_SUPPORTED); break; } LogFlowFuncLeaveRC(rc); return rc; } /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized * * Called by ShClTransferInit via VbglR3. * For H->G: Called on transfer intialization to start the data transfer for the "in-flight" IDataObject. * For G->H: Nothing to do here. * * @thread Clipboard main thread. */ static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); int rc = VINF_SUCCESS; PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); switch(ShClTransferGetDir(pTransfer)) { case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ { rc = ShClWinTransferStart(&pCtx->Win, pTransfer); break; } case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ break; default: break; } LogFlowFuncLeaveRC(rc); } /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnDestroy * * @thread Service main thread. */ static DECLCALLBACK(void) shClSvcWinTransferOnDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); ShClWinTransferDestroy(&pCtx->Win, pTransfer); } /** * @copydoc ShClWinDataObject::CALLBACKS::pfnTransferBegin * * Called by ShClWinDataObject::GetData() when the user wants to paste data. * This then creates and initializes a new transfer on the host + lets the guest know about that new transfer. * * @thread Service main thread. */ static DECLCALLBACK(int) shClSvcWinDataObjectTransferBeginCallback(ShClWinDataObject::PCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); PSHCLTRANSFER pTransfer; int rc = ShClSvcTransferCreate(pCtx->pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); if (RT_SUCCESS(rc)) { /* Initialize the transfer on the host side. */ rc = ShClSvcTransferInit(pCtx->pClient, pTransfer); if (RT_FAILURE(rc)) ShClSvcTransferDestroy(pCtx->pClient, pTransfer); } LogFlowFuncLeaveRC(rc); return rc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF { AssertPtr(pCtx); LRESULT lresultRc = 0; const PSHCLWINCTX pWinCtx = &pCtx->Win; switch (uMsg) { case WM_CLIPBOARDUPDATE: { LogFunc(("WM_CLIPBOARDUPDATE\n")); int rc = RTCritSectEnter(&pWinCtx->CritSect); if (RT_SUCCESS(rc)) { const HWND hWndClipboardOwner = GetClipboardOwner(); LogFunc(("WM_CLIPBOARDUPDATE: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n", pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner)); if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner) { int rc2 = RTCritSectLeave(&pWinCtx->CritSect); AssertRC(rc2); /* Clipboard was updated by another application, retrieve formats and report back. */ rc = vboxClipboardSvcWinSyncInternal(pCtx); } else { int rc2 = RTCritSectLeave(&pWinCtx->CritSect); AssertRC(rc2); } } if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: WM_CLIPBOARDUPDATE failed with %Rrc\n", rc)); break; } case WM_CHANGECBCHAIN: { LogFunc(("WM_CHANGECBCHAIN\n")); lresultRc = ShClWinHandleWMChangeCBChain(pWinCtx, hWnd, uMsg, wParam, lParam); break; } case WM_DRAWCLIPBOARD: { LogFunc(("WM_DRAWCLIPBOARD\n")); int rc = RTCritSectEnter(&pWinCtx->CritSect); if (RT_SUCCESS(rc)) { const HWND hWndClipboardOwner = GetClipboardOwner(); LogFunc(("WM_DRAWCLIPBOARD: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n", pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner)); if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner) { int rc2 = RTCritSectLeave(&pWinCtx->CritSect); AssertRC(rc2); /* Clipboard was updated by another application, retrieve formats and report back. */ rc = vboxClipboardSvcWinSyncInternal(pCtx); } else { int rc2 = RTCritSectLeave(&pWinCtx->CritSect); AssertRC(rc2); } } lresultRc = ShClWinChainPassToNext(pWinCtx, uMsg, wParam, lParam); break; } case WM_TIMER: { int rc = ShClWinHandleWMTimer(pWinCtx); AssertRC(rc); break; } case WM_RENDERFORMAT: { /* Insert the requested clipboard format data into the clipboard. */ const UINT uFmtWin = (UINT)wParam; const SHCLFORMAT uFmtVBox = ShClWinClipboardFormatToVBox(uFmtWin); LogFunc(("WM_RENDERFORMAT: uFmtWin=%u -> uFmtVBox=0x%x\n", uFmtWin, uFmtVBox)); #ifdef LOG_ENABLED char *pszFmts = ShClFormatsToStrA(uFmtVBox); AssertPtrReturn(pszFmts, 0); LogRel(("Shared Clipboard: Rendering Windows format %#x as VBox format '%s'\n", uFmtWin, pszFmts)); RTStrFree(pszFmts); #endif if ( uFmtVBox == VBOX_SHCL_FMT_NONE || pCtx->pClient == NULL) { /* Unsupported clipboard format is requested. */ LogFunc(("WM_RENDERFORMAT unsupported format requested or client is not active\n")); ShClWinClear(); } else { void *pvData = NULL; uint32_t cbData = 0; int rc = ShClSvcReadDataFromGuest(pCtx->pClient, uFmtVBox, &pvData, &cbData); if (RT_SUCCESS(rc)) { /* Wrap HTML clipboard content info CF_HTML format if needed. */ if (uFmtVBox == VBOX_SHCL_FMT_HTML && !ShClWinIsCFHTML((char *)pvData)) { char *pszWrapped = NULL; uint32_t cbWrapped = 0; rc = ShClWinConvertMIMEToCFHTML((char *)pvData, cbData, &pszWrapped, &cbWrapped); if (RT_SUCCESS(rc)) { /* Replace buffer with wrapped data content. */ RTMemFree(pvData); pvData = (void *)pszWrapped; cbData = cbWrapped; } else LogRel(("Shared Clipboard: cannot convert HTML clipboard into CF_HTML format, rc=%Rrc\n", rc)); } rc = ShClWinDataWrite(uFmtWin, pvData, cbData); if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Setting clipboard data for Windows host failed with %Rrc\n", rc)); RTMemFree(pvData); cbData = 0; } if (RT_FAILURE(rc)) ShClWinClear(); } break; } case WM_RENDERALLFORMATS: { LogFunc(("WM_RENDERALLFORMATS\n")); int rc = ShClWinHandleWMRenderAllFormats(pWinCtx, hWnd); AssertRC(rc); break; } case SHCL_WIN_WM_REPORT_FORMATS: /* Guest reported clipboard formats. */ { /* Announce available formats. Do not insert data -- will be inserted in WM_RENDERFORMAT (or via IDataObject). */ SHCLFORMATS fFormats = (uint32_t)lParam; LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: fFormats=%#xn", fFormats)); int rc = ShClWinClearAndAnnounceFormats(pWinCtx, fFormats, hWnd); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if ( RT_SUCCESS(rc) && fFormats & VBOX_SHCL_FMT_URI_LIST) { /* * Create our IDataObject implementation and push it to the Windows clibpoard. * That way Windows will recognize that there is a data transfer available. */ ShClWinDataObject::CALLBACKS Callbacks; RT_ZERO(Callbacks); Callbacks.pfnTransferBegin = shClSvcWinDataObjectTransferBeginCallback; rc = ShClWinTransferCreateAndSetDataObject(pWinCtx, pCtx, &Callbacks); } #else RT_NOREF(rc); #endif LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: lastErr=%ld\n", GetLastError())); break; } case WM_DESTROY: { LogFunc(("WM_DESTROY\n")); int rc = ShClWinHandleWMDestroy(pWinCtx); AssertRC(rc); PostQuitMessage(0); break; } default: lresultRc = DefWindowProc(hWnd, uMsg, wParam, lParam); break; } LogFlowFunc(("LEAVE hWnd=%p, WM_ %u -> %#zx\n", hWnd, uMsg, lresultRc)); return lresultRc; } /** * Static helper function for having a per-client proxy window instances. */ static LRESULT CALLBACK vboxClipboardSvcWinWndProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF { LONG_PTR pUserData = GetWindowLongPtr(hWnd, GWLP_USERDATA); AssertPtrReturn(pUserData, 0); PSHCLCONTEXT pCtx = reinterpret_cast(pUserData); if (pCtx) return vboxClipboardSvcWinWndProcMain(pCtx, hWnd, uMsg, wParam, lParam); return 0; } /** * Static helper function for routing Windows messages to a specific * proxy window instance. */ static LRESULT CALLBACK vboxClipboardSvcWinWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF { /* Note: WM_NCCREATE is not the first ever message which arrives, but * early enough for us. */ if (uMsg == WM_NCCREATE) { LogFlowFunc(("WM_NCCREATE\n")); LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam; AssertPtr(pCS); SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams); SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)vboxClipboardSvcWinWndProcInstance); return vboxClipboardSvcWinWndProcInstance(hWnd, uMsg, wParam, lParam); } /* No window associated yet. */ return DefWindowProc(hWnd, uMsg, wParam, lParam); } DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) { LogFlowFuncEnter(); bool fThreadSignalled = false; const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; AssertPtr(pCtx); const PSHCLWINCTX pWinCtx = &pCtx->Win; HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); /* Register the Window Class. */ WNDCLASS wc; RT_ZERO(wc); wc.style = CS_NOCLOSE; wc.lpfnWndProc = vboxClipboardSvcWinWndProc; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); /* Register an unique wnd class name. */ char szWndClassName[32]; RTStrPrintf2(szWndClassName, sizeof(szWndClassName), "%s-%RU64", SHCL_WIN_WNDCLASS_NAME, RTThreadGetNative(hThreadSelf)); wc.lpszClassName = szWndClassName; int rc; ATOM atomWindowClass = RegisterClass(&wc); if (atomWindowClass == 0) { LogFunc(("Failed to register window class\n")); rc = VERR_NOT_SUPPORTED; } else { /* Create a window and make it a clipboard viewer. */ pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST, szWndClassName, szWndClassName, WS_POPUPWINDOW, -200, -200, 100, 100, NULL, NULL, hInstance, pCtx /* lpParam */); if (pWinCtx->hWnd == NULL) { LogFunc(("Failed to create window\n")); rc = VERR_NOT_SUPPORTED; } else { SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0, SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE); rc = ShClWinChainAdd(&pCtx->Win); if (RT_SUCCESS(rc)) { if (!ShClWinIsNewAPI(&pWinCtx->newAPI)) pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000, NULL); } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (RT_SUCCESS(rc)) { HRESULT hr = OleInitialize(NULL); if (FAILED(hr)) { LogRel(("Shared Clipboard: Initializing window thread OLE failed (%Rhrc) -- file transfers unavailable\n", hr)); /* Not critical, the rest of the clipboard might work. */ } else LogRel(("Shared Clipboard: Initialized window thread OLE\n")); } #endif int rc2 = RTThreadUserSignal(hThreadSelf); AssertRC(rc2); fThreadSignalled = true; 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); LogFunc(("Message loop finished. GetMessage returned %d, message id: %d \n", msgret, msg.message)); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ OleUninitialize(); #endif } } pWinCtx->hWnd = NULL; if (atomWindowClass != 0) { UnregisterClass(szWndClassName, hInstance); atomWindowClass = 0; } if (!fThreadSignalled) { int rc2 = RTThreadUserSignal(hThreadSelf); AssertRC(rc2); } LogFlowFuncLeaveRC(rc); return rc; } /** * Synchronizes the host and the guest clipboard formats by sending all supported host clipboard * formats to the guest. * * @returns VBox status code, VINF_NO_CHANGE if no synchronization was required. * @param pCtx Clipboard context to synchronize. */ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); LogFlowFuncEnter(); int rc; if (pCtx->pClient) { SHCLFORMATS fFormats = 0; rc = ShClWinGetFormats(&pCtx->Win, &fFormats); if (RT_SUCCESS(rc)) rc = ShClSvcReportFormats(pCtx->pClient, fFormats); } else /* If we don't have any client data (yet), bail out. */ rc = VINF_NO_CHANGE; LogFlowFuncLeaveRC(rc); return rc; } /********************************************************************************************************************************* * Backend implementation * *********************************************************************************************************************************/ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { RT_NOREF(pBackend, pTable); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS HRESULT hr = OleInitialize(NULL); if (FAILED(hr)) { LogRel(("Shared Clipboard: Initializing OLE failed (%Rhrc) -- file transfers unavailable\n", hr)); /* Not critical, the rest of the clipboard might work. */ } else LogRel(("Shared Clipboard: Initialized OLE\n")); #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ return VINF_SUCCESS; } void ShClBackendDestroy(PSHCLBACKEND pBackend) { RT_NOREF(pBackend); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ OleUninitialize(); #endif } int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) { RT_NOREF(pBackend, fHeadless); LogFlowFuncEnter(); int rc; PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); if (pCtx) { rc = ShClWinCtxInit(&pCtx->Win); if (RT_SUCCESS(rc)) { rc = RTThreadCreate(&pCtx->hThread, vboxClipboardSvcWinThread, pCtx /* pvUser */, _64K /* Stack size */, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ShClWin"); if (RT_SUCCESS(rc)) { int rc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */); AssertRC(rc2); } } pClient->State.pCtx = pCtx; pClient->State.pCtx->pClient = pClient; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /* * Set callbacks. * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. */ RT_ZERO(pClient->Transfers.Callbacks); pClient->Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ pClient->Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); pClient->Transfers.Callbacks.pfnOnCreated = shClSvcWinTransferOnCreatedCallback; pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; pClient->Transfers.Callbacks.pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } else rc = VERR_NO_MEMORY; LogFlowFuncLeaveRC(rc); return rc; } int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) { RT_NOREF(pBackend); /* Sync the host clipboard content with the client. */ return vboxClipboardSvcWinSyncInternal(pClient->State.pCtx); } int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) { RT_NOREF(pBackend); AssertPtrReturn(pClient, VERR_INVALID_POINTER); LogFlowFuncEnter(); int rc = VINF_SUCCESS; PSHCLCONTEXT pCtx = pClient->State.pCtx; if (pCtx) { if (pCtx->Win.hWnd) PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */); if (pCtx->hThread != NIL_RTTHREAD) { LogFunc(("Waiting for thread to terminate ...\n")); /* Wait for the window thread to terminate. */ rc = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */, NULL); if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Waiting for window thread termination failed with rc=%Rrc\n", rc)); pCtx->hThread = NIL_RTTHREAD; } ShClWinCtxDestroy(&pCtx->Win); if (RT_SUCCESS(rc)) { RTMemFree(pCtx); pCtx = NULL; pClient->State.pCtx = NULL; } } LogFlowFuncLeaveRC(rc); return rc; } int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) { RT_NOREF(pBackend); AssertPtrReturn(pClient, VERR_INVALID_POINTER); PSHCLCONTEXT pCtx = pClient->State.pCtx; AssertPtrReturn(pCtx, VERR_INVALID_POINTER); LogFlowFunc(("fFormats=0x%x, hWnd=%p\n", fFormats, pCtx->Win.hWnd)); /* * The guest announced formats. Forward to the window thread. */ PostMessage(pCtx->Win.hWnd, SHCL_WIN_WM_REPORT_FORMATS, 0 /* wParam */, fFormats /* lParam */); LogFlowFuncLeaveRC(VINF_SUCCESS); return VINF_SUCCESS; } int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); RT_NOREF(pBackend, pCmdCtx); AssertPtrReturn(pClient->State.pCtx, VERR_INVALID_POINTER); LogFlowFunc(("uFmt=%#x\n", uFmt)); HANDLE hClip = NULL; const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; /* * The guest wants to read data in the given format. */ int rc = ShClWinOpen(pWinCtx->hWnd); if (RT_SUCCESS(rc)) { if (uFmt & VBOX_SHCL_FMT_BITMAP) { LogFunc(("CF_DIB\n")); hClip = GetClipboardData(CF_DIB); if (hClip != NULL) { LPVOID lp = GlobalLock(hClip); if (lp != NULL) { rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_BITMAP, lp, GlobalSize(hClip), pvData, cbData, pcbActual); GlobalUnlock(hClip); } else { hClip = NULL; } } } else if (uFmt & VBOX_SHCL_FMT_UNICODETEXT) { LogFunc(("CF_UNICODETEXT\n")); hClip = GetClipboardData(CF_UNICODETEXT); if (hClip != NULL) { LPWSTR uniString = (LPWSTR)GlobalLock(hClip); if (uniString != NULL) { rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_UNICODETEXT, uniString, (lstrlenW(uniString) + 1) * 2, pvData, cbData, pcbActual); GlobalUnlock(hClip); } else { hClip = NULL; } } } else if (uFmt & VBOX_SHCL_FMT_HTML) { LogFunc(("SHCL_WIN_REGFMT_HTML\n")); UINT uRegFmt = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML); if (uRegFmt != 0) { hClip = GetClipboardData(uRegFmt); if (hClip != NULL) { LPVOID lp = GlobalLock(hClip); if (lp != NULL) { rc = vboxClipboardSvcWinDataGet(VBOX_SHCL_FMT_HTML, lp, GlobalSize(hClip), pvData, cbData, pcbActual); #ifdef LOG_ENABLED if (RT_SUCCESS(rc)) { LogFlowFunc(("Raw HTML clipboard data from host:\n")); ShClDbgDumpHtml((char *)pvData, cbData); } #endif GlobalUnlock(hClip); } else { hClip = NULL; } } } } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS else if (uFmt & VBOX_SHCL_FMT_URI_LIST) { hClip = hClip = GetClipboardData(CF_HDROP); if (hClip) { HDROP hDrop = (HDROP)GlobalLock(hClip); if (hDrop) { char *pszList = NULL; uint32_t cbList; rc = ShClWinTransferDropFilesToStringList((DROPFILES *)hDrop, &pszList, &cbList); GlobalUnlock(hClip); if (RT_SUCCESS(rc)) { if (cbList <= cbData) { memcpy(pvData, pszList, cbList); *pcbActual = cbList; } RTStrFree(pszList); } } else LogRel(("Shared Clipboard: Unable to lock clipboard data, last error: %ld\n", GetLastError())); } else LogRel(("Shared Clipboard: Unable to retrieve clipboard data from clipboard (CF_HDROP), last error: %ld\n", GetLastError())); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ ShClWinClose(); } if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Error reading host clipboard data in format %#x from Windows, rc=%Rrc\n", uFmt, rc)); LogFlowFuncLeaveRC(rc); return rc; } int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) { RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); LogFlowFuncEnter(); /* Nothing to do here yet. */ LogFlowFuncLeave(); return VINF_SUCCESS; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** * Handles transfer status replies from the guest. */ int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) { RT_NOREF(pBackend, pClient, pTransfer, enmSource, enmStatus, rcStatus); return VINF_SUCCESS; } /********************************************************************************************************************************* * Provider interface implementation * *********************************************************************************************************************************/ /** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx) { LogFlowFuncEnter(); PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; AssertPtr(pClient); AssertPtr(pClient->State.pCtx); PSHCLWINCTX pWin = &pClient->State.pCtx->Win; int rc = ShClWinTransferGetRootsFromClipboard(pWin, pCtx->pTransfer); LogFlowFuncLeaveRC(rc); return rc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */