VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxClipboard.cpp@ 100204

Last change on this file since 100204 was 100204, checked in by vboxsync, 18 months ago

Shared Clipboard: Unified root list entry code to also use the generic list entry code, a lot of updates for the cross OS transfer handling code, more updates for HTTP server transfer handling.

This also changed the handling of how that transfers are being initiated, as we needed to have this for X11: Before, transfers were initiated as soon as on side announced the URI list format -- now we postpone initiating the transfer until the receiving side requests the data as URI list.

bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 44.8 KB
Line 
1/* $Id: VBoxClipboard.cpp 100204 2023-06-19 09:11:37Z vboxsync $ */
2/** @file
3 * VBoxClipboard - Shared clipboard, Windows Guest Implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
33#include <VBox/log.h>
34
35#include "VBoxTray.h"
36#include "VBoxHelpers.h"
37
38#include <iprt/asm.h>
39#include <iprt/errcore.h>
40#include <iprt/ldr.h>
41#include <iprt/mem.h>
42#include <iprt/utf16.h>
43
44#include <VBox/GuestHost/SharedClipboard.h>
45#include <VBox/GuestHost/SharedClipboard-win.h>
46#include <VBox/GuestHost/clipboard-helper.h>
47#include <VBox/HostServices/VBoxClipboardSvc.h> /* Temp, remove. */
48#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
49# include <VBox/GuestHost/SharedClipboard-transfers.h>
50#endif
51
52#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
53# include <iprt/win/shlobj.h>
54# include <iprt/win/shlwapi.h>
55#endif
56
57
58/*********************************************************************************************************************************
59* Structures and Typedefs *
60*********************************************************************************************************************************/
61struct SHCLCONTEXT
62{
63 /** Pointer to the VBoxClient service environment. */
64 const VBOXSERVICEENV *pEnv;
65 /** Command context. */
66 VBGLR3SHCLCMDCTX CmdCtx;
67 /** Windows-specific context data. */
68 SHCLWINCTX Win;
69 /** Thread handle for window thread. */
70 RTTHREAD hThread;
71 /** Start indicator flag. */
72 bool fStarted;
73 /** Shutdown indicator flag. */
74 bool fShutdown;
75#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
76 /** Associated transfer data. */
77 SHCLTRANSFERCTX TransferCtx;
78#endif
79};
80
81
82/*********************************************************************************************************************************
83* Static variables *
84*********************************************************************************************************************************/
85/** Static clipboard context (since it is the single instance). Directly used in the windows proc. */
86static SHCLCONTEXT g_Ctx = { NULL };
87/** Static window class name. */
88static char s_szClipWndClassName[] = SHCL_WIN_WNDCLASS_NAME;
89
90
91/*********************************************************************************************************************************
92* Prototypes *
93*********************************************************************************************************************************/
94#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
95static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTXPROVIDERCTX pCtx);
96static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTXPROVIDERCTX pCtx);
97static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTXPROVIDERCTX pCtx, int rc);
98#endif
99
100
101#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
102
103/**
104 * Cleanup helper function for transfer callbacks.
105 *
106 * @param pTransferCtx Pointer to transfer context that the transfer contains.
107 * @param pTransfer Pointer to transfer to cleanup.
108 */
109static void vbtrShClTransferCallbackCleanup(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer)
110{
111 LogFlowFuncEnter();
112
113 if (!pTransferCtx || !pTransfer)
114 return;
115
116 if (pTransfer->pvUser) /* SharedClipboardWinTransferCtx */
117 {
118 delete pTransfer->pvUser;
119 pTransfer->pvUser = NULL;
120 }
121
122 int rc2 = ShClTransferCtxTransferUnregister(pTransferCtx, pTransfer->State.uID);
123 AssertRC(rc2);
124
125 ShClTransferDestroy(pTransfer);
126
127 RTMemFree(pTransfer);
128 pTransfer = NULL;
129}
130
131/**
132 * Worker for a reading clipboard from the host.
133 *
134 * @thread Main clipboard thread.
135 */
136static DECLCALLBACK(int) vbtrShClRequestDataFromSourceCallbackWorker(PSHCLCONTEXT pCtx,
137 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
138{
139 RT_NOREF(pvUser);
140
141 LogFlowFuncEnter();
142
143 int rc = VERR_NO_DATA; /* Play safe. */
144
145 uint32_t cbRead = 0;
146
147 uint32_t cbData = _4K; /** @todo Make this dynamic. */
148 void *pvData = RTMemAlloc(cbData);
149 if (pvData)
150 {
151 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
152 }
153 else
154 rc = VERR_NO_MEMORY;
155
156 /*
157 * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
158 * larger buffer. The size of the buffer needed is placed in *pcb.
159 * So we start all over again.
160 */
161 if (rc == VINF_BUFFER_OVERFLOW)
162 {
163 /* cbRead contains the size required. */
164
165 cbData = cbRead;
166 pvData = RTMemRealloc(pvData, cbRead);
167 if (pvData)
168 {
169 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
170 if (rc == VINF_BUFFER_OVERFLOW)
171 rc = VERR_BUFFER_OVERFLOW;
172 }
173 else
174 rc = VERR_NO_MEMORY;
175 }
176
177 if (!cbRead)
178 rc = VERR_NO_DATA;
179
180 if (RT_SUCCESS(rc))
181 {
182 if (ppv)
183 *ppv = pvData;
184 if (pcb)
185 *pcb = cbRead; /* Actual bytes read. */
186 }
187 else
188 {
189 /*
190 * Catch other errors. This also catches the case in which the buffer was
191 * too small a second time, possibly because the clipboard contents
192 * changed half-way through the operation. Since we can't say whether or
193 * not this is actually an error, we just return size 0.
194 */
195 RTMemFree(pvData);
196 }
197
198 LogFlowFuncLeaveRC(rc);
199 return rc;
200}
201
202/**
203 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
204 *
205 * Called from the IDataObject implementation to request data from the host.
206 *
207 * @thread shclwnd thread.
208 */
209DECLCALLBACK(int) vbtrShClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
210 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
211{
212 PRTREQ pReq = NULL;
213 int rc = RTReqQueueCallEx(pCtx->Win.hReqQ, &pReq, SHCL_TIMEOUT_DEFAULT_MS, RTREQFLAGS_IPRT_STATUS,
214 (PFNRT)vbtrShClRequestDataFromSourceCallbackWorker, 5, pCtx, uFmt, ppv, pcb, pvUser);
215 RTReqRelease(pReq);
216 return rc;
217}
218
219/**
220 * @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnStart
221 *
222 * Called from VbglR3 (main thread) to notify the IDataObject.
223 *
224 * @thread Clipboard main thread.
225 */
226static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
227{
228 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
229 AssertPtr(pCtx);
230
231 PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
232 AssertPtr(pTransfer);
233
234 const SHCLTRANSFERDIR enmDir = ShClTransferGetDir(pTransfer);
235
236 LogFlowFunc(("pCtx=%p, idTransfer=%RU32, enmDir=%RU32\n", pCtx, ShClTransferGetID(pTransfer), enmDir));
237
238 int rc;
239
240 /* The guest wants to transfer data to the host. */
241 if (enmDir == SHCLTRANSFERDIR_TO_REMOTE)
242 {
243 rc = SharedClipboardWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer);
244 }
245 /* The guest wants to transfer data from the host. */
246 else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE)
247 {
248 rc = RTCritSectEnter(&pCtx->Win.CritSect);
249 if (RT_SUCCESS(rc))
250 {
251 SharedClipboardWinDataObject *pObj = pCtx->Win.pDataObjInFlight;
252 AssertPtrReturnVoid(pObj);
253 rc = pObj->SetAndStartTransfer(pTransfer);
254
255 pCtx->Win.pDataObjInFlight = NULL; /* Hand off to Windows. */
256
257 int rc2 = RTCritSectLeave(&pCtx->Win.CritSect);
258 AssertRC(rc2);
259 }
260 }
261 else
262 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
263
264 if (RT_FAILURE(rc))
265 LogRel(("Shared Clipboard: Starting transfer failed, rc=%Rrc\n", rc));
266
267 LogFlowFunc(("LEAVE: idTransfer=%RU32, rc=%Rrc\n", ShClTransferGetID(pTransfer), rc));
268}
269
270/** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnCompleted */
271static DECLCALLBACK(void) vbtrShClTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcCompletion)
272{
273 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
274 AssertPtr(pCtx);
275
276 LogRel2(("Shared Clipboard: Transfer to destination %s\n",
277 rcCompletion == VERR_CANCELLED ? "canceled" : "complete"));
278
279 vbtrShClTransferCallbackCleanup(&pCtx->TransferCtx, pCbCtx->pTransfer);
280}
281
282/** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnError */
283static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcError)
284{
285 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
286 AssertPtr(pCtx);
287
288 LogRel(("Shared Clipboard: Transfer to destination failed with %Rrc\n", rcError));
289
290 vbtrShClTransferCallbackCleanup(&pCtx->TransferCtx, pCbCtx->pTransfer);
291}
292
293#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
294
295static LRESULT vbtrShClWndProcWorker(PSHCLCONTEXT pCtx, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
296{
297 AssertPtr(pCtx);
298
299 const PSHCLWINCTX pWinCtx = &pCtx->Win;
300
301 LRESULT lresultRc = 0;
302
303 switch (msg)
304 {
305 case WM_CLIPBOARDUPDATE:
306 {
307 LogFunc(("WM_CLIPBOARDUPDATE: pWinCtx=%p\n", pWinCtx));
308
309 if (pCtx->fShutdown) /* If we're about to shut down, skip handling stuff here. */
310 break;
311
312 int rc = RTCritSectEnter(&pWinCtx->CritSect);
313 if (RT_SUCCESS(rc))
314 {
315 const HWND hWndClipboardOwner = GetClipboardOwner();
316
317 LogFunc(("WM_CLIPBOARDUPDATE: hWndOldClipboardOwner=%p, hWndNewClipboardOwner=%p\n",
318 pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner));
319
320 if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner)
321 {
322 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
323 AssertRC(rc2);
324
325 /* Clipboard was updated by another application.
326 * Report available formats to the host. */
327 SHCLFORMATS fFormats;
328 rc = SharedClipboardWinGetFormats(pWinCtx, &fFormats);
329 if (RT_SUCCESS(rc))
330 {
331 LogFunc(("WM_CLIPBOARDUPDATE: Reporting formats %#x\n", fFormats));
332 rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
333 }
334 }
335 else
336 {
337 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
338 AssertRC(rc2);
339 }
340 }
341
342 if (RT_FAILURE(rc))
343 LogRel(("Shared Clipboard: WM_CLIPBOARDUPDATE failed with %Rrc\n", rc));
344
345 break;
346 }
347
348 case WM_CHANGECBCHAIN:
349 {
350 LogFunc(("WM_CHANGECBCHAIN\n"));
351 lresultRc = SharedClipboardWinHandleWMChangeCBChain(pWinCtx, hwnd, msg, wParam, lParam);
352 break;
353 }
354
355 case WM_DRAWCLIPBOARD:
356 {
357 LogFlowFunc(("WM_DRAWCLIPBOARD: pWinCtx=%p\n", pWinCtx));
358
359 int rc = RTCritSectEnter(&pWinCtx->CritSect);
360 if (RT_SUCCESS(rc))
361 {
362 const HWND hWndClipboardOwner = GetClipboardOwner();
363
364 LogFunc(("WM_DRAWCLIPBOARD: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n",
365 pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner));
366
367 if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner)
368 {
369 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
370 AssertRC(rc2);
371
372 /* Clipboard was updated by another application. */
373 /* WM_DRAWCLIPBOARD always expects a return code of 0, so don't change "rc" here. */
374 SHCLFORMATS fFormats;
375 rc = SharedClipboardWinGetFormats(pWinCtx, &fFormats);
376 if ( RT_SUCCESS(rc)
377 && fFormats != VBOX_SHCL_FMT_NONE)
378 rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
379 }
380 else
381 {
382 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
383 AssertRC(rc2);
384 }
385 }
386
387 lresultRc = SharedClipboardWinChainPassToNext(pWinCtx, msg, wParam, lParam);
388 break;
389 }
390
391 case WM_TIMER:
392 {
393 int rc = SharedClipboardWinHandleWMTimer(pWinCtx);
394 AssertRC(rc);
395
396 break;
397 }
398
399 case WM_CLOSE:
400 {
401 /* Do nothing. Ignore the message. */
402 break;
403 }
404
405 case WM_RENDERFORMAT: /* Guest wants to render the clipboard data. */
406 {
407 LogFunc(("WM_RENDERFORMAT\n"));
408
409 /* Insert the requested clipboard format data into the clipboard. */
410 const UINT cfFormat = (UINT)wParam;
411
412 const SHCLFORMAT fFormat = SharedClipboardWinClipboardFormatToVBox(cfFormat);
413
414 LogFunc(("WM_RENDERFORMAT: cfFormat=%u -> fFormat=0x%x\n", cfFormat, fFormat));
415
416#ifdef LOG_ENABLED
417 char *pszFmts = ShClFormatsToStrA(fFormat);
418 AssertPtrReturn(pszFmts, 0);
419 LogRel(("Shared Clipboard: Rendering Windows format %#x as VBox format '%s'\n", cfFormat, pszFmts));
420 RTStrFree(pszFmts);
421#endif
422 if (fFormat == VBOX_SHCL_FMT_NONE)
423 {
424 LogRel(("Shared Clipboard: Unsupported format (%#x) requested\n", cfFormat));
425 SharedClipboardWinClear();
426 }
427 else
428 {
429 uint32_t const cbPrealloc = _4K;
430 uint32_t cb = 0;
431
432 /* Preallocate a buffer, most of small text transfers will fit into it. */
433 HANDLE hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbPrealloc);
434 if (hMem)
435 {
436 void *pvMem = GlobalLock(hMem);
437 if (pvMem)
438 {
439 /* Read the host data to the preallocated buffer. */
440 int rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, fFormat, pvMem, cbPrealloc, &cb);
441 if (RT_SUCCESS(rc))
442 {
443 if (cb == 0)
444 {
445 /* 0 bytes returned means the clipboard is empty.
446 * Deallocate the memory and set hMem to NULL to get to
447 * the clipboard empty code path. */
448 GlobalUnlock(hMem);
449 GlobalFree(hMem);
450 hMem = NULL;
451 }
452 else if (cb > cbPrealloc)
453 {
454 GlobalUnlock(hMem);
455
456 LogRel2(("Shared Clipboard: Buffer too small (%RU32), needs %RU32 bytes\n", cbPrealloc, cb));
457
458 /* The preallocated buffer is too small, adjust the size. */
459 hMem = GlobalReAlloc(hMem, cb, 0);
460 if (hMem)
461 {
462 pvMem = GlobalLock(hMem);
463 if (pvMem)
464 {
465 /* Read the host data to the preallocated buffer. */
466 uint32_t cbNew = 0;
467 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, fFormat, pvMem, cb, &cbNew);
468 if ( RT_SUCCESS(rc)
469 && cbNew <= cb)
470 {
471 cb = cbNew;
472 }
473 else
474 {
475 LogRel(("Shared Clipboard: Receiving host data failed with %Rrc\n", rc));
476
477 GlobalUnlock(hMem);
478 GlobalFree(hMem);
479 hMem = NULL;
480 }
481 }
482 else
483 {
484 LogRel(("Shared Clipboard: Error locking reallocated host data buffer\n"));
485
486 GlobalFree(hMem);
487 hMem = NULL;
488 }
489 }
490 else
491 LogRel(("Shared Clipboard: No memory for reallocating host data buffer\n"));
492 }
493
494 if (hMem)
495 {
496 /* pvMem is the address of the data. cb is the size of returned data. */
497 /* Verify the size of returned text, the memory block for clipboard
498 * must have the exact string size.
499 */
500 if (fFormat == VBOX_SHCL_FMT_UNICODETEXT)
501 {
502 size_t cwcActual = 0;
503 rc = RTUtf16NLenEx((PCRTUTF16)pvMem, cb / sizeof(RTUTF16), &cwcActual);
504 if (RT_SUCCESS(rc))
505 cb = (uint32_t)((cwcActual + 1 /* '\0' */) * sizeof(RTUTF16));
506 else
507 {
508 LogRel(("Shared Clipboard: Invalid UTF16 string from host: cb=%RU32, cwcActual=%zu, rc=%Rrc\n",
509 cb, cwcActual, rc));
510
511 /* Discard invalid data. */
512 GlobalUnlock(hMem);
513 GlobalFree(hMem);
514 hMem = NULL;
515 }
516 }
517 else if (fFormat == VBOX_SHCL_FMT_HTML)
518 {
519 /* Wrap content into CF_HTML clipboard format if needed. */
520 if (!SharedClipboardWinIsCFHTML((const char *)pvMem))
521 {
522 char *pszWrapped = NULL;
523 uint32_t cbWrapped = 0;
524 rc = SharedClipboardWinConvertMIMEToCFHTML((const char *)pvMem, cb,
525 &pszWrapped, &cbWrapped);
526 if (RT_SUCCESS(rc))
527 {
528 if (GlobalUnlock(hMem) == 0)
529 {
530 hMem = GlobalReAlloc(hMem, cbWrapped, 0);
531 if (hMem)
532 {
533 pvMem = GlobalLock(hMem);
534 if (pvMem)
535 {
536 /* Copy wrapped content back to memory passed to system clipboard. */
537 memcpy(pvMem, pszWrapped, cbWrapped);
538 cb = cbWrapped;
539 }
540 else
541 {
542 LogRel(("Shared Clipboard: Failed to lock memory (%u), HTML clipboard data won't be converted into CF_HTML clipboard format\n", GetLastError()));
543 GlobalFree(hMem);
544 hMem = NULL;
545 }
546 }
547 else
548 LogRel(("Shared Clipboard: Failed to re-allocate memory (%u), HTML clipboard data won't be converted into CF_HTML clipboard format\n", GetLastError()));
549 }
550 else
551 LogRel(("Shared Clipboard: Failed to unlock memory (%u), HTML clipboard data won't be converted into CF_HTML clipboard format\n", GetLastError()));
552
553 RTMemFree(pszWrapped);
554 }
555 else
556 LogRel(("Shared Clipboard: Cannot convert HTML clipboard data into CF_HTML clipboard format, rc=%Rrc\n", rc));
557 }
558 }
559 }
560
561 if (hMem)
562 {
563 GlobalUnlock(hMem);
564
565 hMem = GlobalReAlloc(hMem, cb, 0);
566 if (hMem)
567 {
568 /* 'hMem' contains the host clipboard data.
569 * size is 'cb' and format is 'format'. */
570 HANDLE hClip = SetClipboardData(cfFormat, hMem);
571 if (hClip)
572 {
573 /* The hMem ownership has gone to the system. Finish the processing. */
574 break;
575 }
576 else
577 LogRel(("Shared Clipboard: Setting host data buffer to clipboard failed with %ld\n",
578 GetLastError()));
579
580 /* Cleanup follows. */
581 }
582 else
583 LogRel(("Shared Clipboard: No memory for allocating final host data buffer\n"));
584 }
585 }
586
587 if (hMem)
588 GlobalUnlock(hMem);
589 }
590 else
591 LogRel(("Shared Clipboard: No memory for allocating host data buffer\n"));
592
593 if (hMem)
594 GlobalFree(hMem);
595 }
596 }
597
598 break;
599 }
600
601 case WM_RENDERALLFORMATS:
602 {
603 LogFunc(("WM_RENDERALLFORMATS\n"));
604
605 int rc = SharedClipboardWinHandleWMRenderAllFormats(pWinCtx, hwnd);
606 AssertRC(rc);
607
608 break;
609 }
610
611 case SHCL_WIN_WM_REPORT_FORMATS: /* Host reported clipboard formats. */
612 {
613 LogFunc(("SHCL_WIN_WM_REPORT_FORMATS\n"));
614
615 /* Announce available formats. Do not insert data -- will be inserted in WM_RENDERFORMAT. */
616 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)lParam;
617 AssertPtr(pEvent);
618 Assert(pEvent->enmType == VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS);
619
620 const SHCLFORMATS fFormats = pEvent->u.fReportedFormats;
621
622#ifdef LOG_ENABLED
623 char *pszFmts = ShClFormatsToStrA(fFormats);
624 AssertPtrReturn(pszFmts, 0);
625 LogRel(("Shared Clipboard: Host reported formats '%s'\n", pszFmts));
626 RTStrFree(pszFmts);
627#endif
628 if (fFormats != VBOX_SHCL_FMT_NONE) /* Could arrive with some older GA versions. */
629 {
630 int rc = SharedClipboardWinClearAndAnnounceFormats(pWinCtx, fFormats, hwnd);
631#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
632 if ( RT_SUCCESS(rc)
633 && fFormats & VBOX_SHCL_FMT_URI_LIST)
634 {
635 /*
636 * Creating and starting the actual transfer will be done in vbglR3ClipboardTransferStart() as soon
637 * as the host announces the start of the transfer via a VBOX_SHCL_HOST_MSG_TRANSFER_STATUS message.
638 * Transfers always are controlled and initiated on the host side!
639 *
640 * What we need to do here, however, is, that we create our IDataObject implementation and push it to the
641 * Windows clibpoard. That way Windows will recognize that there is a data transfer "in flight".
642 */
643 SHCLCALLBACKS Callbacks;
644 RT_ZERO(Callbacks);
645 Callbacks.pfnOnRequestDataFromSource = vbtrShClRequestDataFromSourceCallback;
646
647 rc = SharedClipboardWinTransferCreateAndSetDataObject(pWinCtx, pCtx, &Callbacks);
648 }
649#endif
650 }
651
652 LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: fFormats=0x%x, lastErr=%ld\n", fFormats, GetLastError()));
653 break;
654 }
655
656 case SHCL_WIN_WM_READ_DATA: /* Host wants to read clipboard data from the guest. */
657 {
658 /* Send data in the specified format to the host. */
659 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)lParam;
660 AssertPtr(pEvent);
661 Assert(pEvent->enmType == VBGLR3CLIPBOARDEVENTTYPE_READ_DATA);
662
663 const SHCLFORMAT fFormat = (uint32_t)pEvent->u.fReadData;
664
665 LogFlowFunc(("SHCL_WIN_WM_READ_DATA: fFormat=%#x\n", fFormat));
666#ifdef LOG_ENABLED
667 char *pszFmts = ShClFormatsToStrA(fFormat);
668 AssertPtrReturn(pszFmts, 0);
669 LogRel(("Shared Clipboard: Sending data to host as '%s'\n", pszFmts));
670 RTStrFree(pszFmts);
671#endif
672 int rc = SharedClipboardWinOpen(hwnd);
673 HANDLE hClip = NULL;
674 if (RT_SUCCESS(rc))
675 {
676 if (fFormat & VBOX_SHCL_FMT_BITMAP)
677 {
678 hClip = GetClipboardData(CF_DIB);
679 if (hClip != NULL)
680 {
681 LPVOID lp = GlobalLock(hClip);
682 if (lp != NULL)
683 {
684 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, lp, (uint32_t)GlobalSize(hClip));
685
686 GlobalUnlock(hClip);
687 }
688 else
689 hClip = NULL;
690 }
691 }
692 else if (fFormat & VBOX_SHCL_FMT_UNICODETEXT)
693 {
694 hClip = GetClipboardData(CF_UNICODETEXT);
695 if (hClip != NULL)
696 {
697 LPWSTR uniString = (LPWSTR)GlobalLock(hClip);
698 if (uniString != NULL)
699 {
700 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx,
701 fFormat, uniString, ((uint32_t)lstrlenW(uniString) + 1) * 2);
702
703 GlobalUnlock(hClip);
704 }
705 else
706 hClip = NULL;
707 }
708 }
709 else if (fFormat & VBOX_SHCL_FMT_HTML)
710 {
711 UINT format = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML);
712 if (format != 0)
713 {
714 hClip = GetClipboardData(format);
715 if (hClip != NULL)
716 {
717 LPVOID const pvClip = GlobalLock(hClip);
718 if (pvClip != NULL)
719 {
720 uint32_t const cbClip = (uint32_t)GlobalSize(hClip);
721
722 /* Unwrap clipboard content from CF_HTML format if needed. */
723 if (SharedClipboardWinIsCFHTML((const char *)pvClip))
724 {
725 char *pszBuf = NULL;
726 uint32_t cbBuf = 0;
727 rc = SharedClipboardWinConvertCFHTMLToMIME((const char *)pvClip, cbClip, &pszBuf, &cbBuf);
728 if (RT_SUCCESS(rc))
729 {
730 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pszBuf, cbBuf);
731 RTMemFree(pszBuf);
732 }
733 else
734 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pvClip, cbClip);
735 }
736 else
737 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pvClip, cbClip);
738
739 GlobalUnlock(hClip);
740 }
741 else
742 hClip = NULL;
743 }
744 }
745 }
746
747 if (hClip == NULL)
748 LogFunc(("SHCL_WIN_WM_READ_DATA: hClip=NULL, lastError=%ld\n", GetLastError()));
749
750 SharedClipboardWinClose();
751 }
752
753 /* If the requested clipboard format is not available, we must send empty data. */
754 if (hClip == NULL)
755 VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, VBOX_SHCL_FMT_NONE, NULL, 0);
756 break;
757 }
758
759 case WM_DESTROY:
760 {
761 LogFunc(("WM_DESTROY\n"));
762
763 int rc = SharedClipboardWinHandleWMDestroy(pWinCtx);
764 AssertRC(rc);
765
766 /*
767 * Don't need to call PostQuitMessage cause
768 * the VBoxTray already finished a message loop.
769 */
770
771 break;
772 }
773
774 default:
775 {
776 LogFunc(("WM_ %p\n", msg));
777 lresultRc = DefWindowProc(hwnd, msg, wParam, lParam);
778 break;
779 }
780 }
781
782 LogFunc(("WM_ rc %d\n", lresultRc));
783 return lresultRc;
784}
785
786static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
787
788static int vbtrShClCreateWindow(PSHCLCONTEXT pCtx)
789{
790 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
791
792 int rc = VINF_SUCCESS;
793
794 AssertPtr(pCtx->pEnv);
795 HINSTANCE hInstance = pCtx->pEnv->hInstance;
796 Assert(hInstance != 0);
797
798 /* Register the Window Class. */
799 WNDCLASSEX wc;
800 RT_ZERO(wc);
801
802 wc.cbSize = sizeof(WNDCLASSEX);
803
804 if (!GetClassInfoEx(hInstance, s_szClipWndClassName, &wc))
805 {
806 wc.style = CS_NOCLOSE;
807 wc.lpfnWndProc = vbtrShClWndProc;
808 wc.hInstance = pCtx->pEnv->hInstance;
809 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
810 wc.lpszClassName = s_szClipWndClassName;
811
812 ATOM wndClass = RegisterClassEx(&wc);
813 if (wndClass == 0)
814 rc = RTErrConvertFromWin32(GetLastError());
815 }
816
817 if (RT_SUCCESS(rc))
818 {
819 const PSHCLWINCTX pWinCtx = &pCtx->Win;
820
821 /* Create the window. */
822 pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
823 s_szClipWndClassName, s_szClipWndClassName,
824 WS_POPUPWINDOW,
825 -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
826 if (pWinCtx->hWnd == NULL)
827 {
828 rc = VERR_NOT_SUPPORTED;
829 }
830 else
831 {
832 SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0,
833 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
834
835 rc = SharedClipboardWinChainAdd(pWinCtx);
836 if (RT_SUCCESS(rc))
837 {
838 if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI))
839 pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000 /* 10s */, NULL);
840 }
841 }
842 }
843
844 LogFlowFuncLeaveRC(rc);
845 return rc;
846}
847
848static DECLCALLBACK(int) vbtrShClWindowThread(RTTHREAD hThread, void *pvUser)
849{
850 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser;
851 AssertPtr(pCtx);
852
853#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
854 HRESULT hr = OleInitialize(NULL);
855 if (FAILED(hr))
856 {
857 LogRel(("Shared Clipboard: Initializing OLE in window thread failed (%Rhrc) -- file transfers unavailable\n", hr));
858 /* Not critical, the rest of the clipboard might work. */
859 }
860 else
861 LogRel(("Shared Clipboard: Initialized OLE in window thread\n"));
862#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
863
864 int rc = vbtrShClCreateWindow(pCtx);
865 if (RT_FAILURE(rc))
866 {
867 LogRel(("Shared Clipboard: Unable to create window, rc=%Rrc\n", rc));
868 return rc;
869 }
870
871 pCtx->fStarted = true; /* Set started indicator. */
872
873 int rc2 = RTThreadUserSignal(hThread);
874 bool fSignalled = RT_SUCCESS(rc2);
875
876 LogRel2(("Shared Clipboard: Window thread running\n"));
877
878 if (RT_SUCCESS(rc))
879 {
880 for (;;)
881 {
882 MSG uMsg;
883 BOOL fRet;
884 while ((fRet = GetMessage(&uMsg, 0, 0, 0)) > 0)
885 {
886 TranslateMessage(&uMsg);
887 DispatchMessage(&uMsg);
888 }
889 Assert(fRet >= 0);
890
891 if (ASMAtomicReadBool(&pCtx->fShutdown))
892 break;
893
894 /** @todo Immediately drop on failure? */
895 }
896 }
897
898 if (!fSignalled)
899 {
900 rc2 = RTThreadUserSignal(hThread);
901 AssertRC(rc2);
902 }
903
904#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
905 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
906 OleUninitialize();
907#endif
908
909 LogRel(("Shared Clipboard: Window thread ended\n"));
910
911 LogFlowFuncLeaveRC(rc);
912 return rc;
913}
914
915static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
916{
917 PSHCLCONTEXT pCtx = &g_Ctx; /** @todo r=andy Make pCtx available through SetWindowLongPtr() / GWL_USERDATA. */
918 AssertPtr(pCtx);
919
920 /* Forward with proper context. */
921 return vbtrShClWndProcWorker(pCtx, hWnd, uMsg, wParam, lParam);
922}
923
924DECLCALLBACK(int) vbtrShClInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
925{
926 LogFlowFuncEnter();
927
928 PSHCLCONTEXT pCtx = &g_Ctx; /* Only one instance for now. */
929 AssertPtr(pCtx);
930
931 if (pCtx->pEnv)
932 {
933 /* Clipboard was already initialized. 2 or more instances are not supported. */
934 return VERR_NOT_SUPPORTED;
935 }
936
937 if (VbglR3AutoLogonIsRemoteSession())
938 {
939 /* Do not use clipboard for remote sessions. */
940 LogRel(("Shared Clipboard: Clipboard has been disabled for a remote session\n"));
941 return VERR_NOT_SUPPORTED;
942 }
943
944 pCtx->pEnv = pEnv;
945 pCtx->hThread = NIL_RTTHREAD;
946 pCtx->fStarted = false;
947 pCtx->fShutdown = false;
948
949 int rc = RTReqQueueCreate(&pCtx->Win.hReqQ);
950 AssertRCReturn(rc, rc);
951
952 rc = SharedClipboardWinCtxInit(&pCtx->Win);
953 if (RT_SUCCESS(rc))
954 rc = VbglR3ClipboardConnectEx(&pCtx->CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
955 if (RT_SUCCESS(rc))
956 {
957#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
958 rc = ShClTransferCtxInit(&pCtx->TransferCtx);
959#endif
960 if (RT_SUCCESS(rc))
961 {
962 /* Message pump thread for our proxy window. */
963 rc = RTThreadCreate(&pCtx->hThread, vbtrShClWindowThread, pCtx /* pvUser */,
964 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
965 "shclwnd");
966 if (RT_SUCCESS(rc))
967 {
968 int rc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */);
969 AssertRC(rc2);
970
971 if (!pCtx->fStarted) /* Did the thread fail to start? */
972 rc = VERR_NOT_SUPPORTED; /* Report back Shared Clipboard as not being supported. */
973 }
974 }
975
976 if (RT_SUCCESS(rc))
977 {
978 *ppInstance = pCtx;
979 }
980 else
981 VbglR3ClipboardDisconnectEx(&pCtx->CmdCtx);
982 }
983
984 if (RT_FAILURE(rc))
985 LogRel(("Shared Clipboard: Unable to initialize, rc=%Rrc\n", rc));
986
987 LogFlowFuncLeaveRC(rc);
988 return rc;
989}
990
991DECLCALLBACK(int) vbtrShClWorker(void *pInstance, bool volatile *pfShutdown)
992{
993 AssertPtr(pInstance);
994 LogFlowFunc(("pInstance=%p\n", pInstance));
995
996 /*
997 * Tell the control thread that it can continue
998 * spawning services.
999 */
1000 RTThreadUserSignal(RTThreadSelf());
1001
1002 const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1003 AssertPtr(pCtx);
1004
1005 const PSHCLWINCTX pWinCtx = &pCtx->Win;
1006
1007 LogRel2(("Shared Clipboard: Worker loop running\n"));
1008
1009#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1010 HRESULT hr = OleInitialize(NULL);
1011 if (FAILED(hr))
1012 {
1013 LogRel(("Shared Clipboard: Initializing OLE in worker thread failed (%Rhrc) -- file transfers unavailable\n", hr));
1014 /* Not critical, the rest of the clipboard might work. */
1015 }
1016 else
1017 LogRel(("Shared Clipboard: Initialized OLE in worker thread\n"));
1018
1019 /*
1020 * Init callbacks.
1021 * Those will be registered within VbglR3 when a new transfer gets initialized.
1022 */
1023 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
1024
1025 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
1026 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
1027
1028 pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback;
1029 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback;
1030 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback;
1031#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
1032
1033 int rc;
1034
1035 /* The thread waits for incoming messages from the host. */
1036 PVBGLR3CLIPBOARDEVENT pEvent = NULL;
1037 for (;;)
1038 {
1039 LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
1040 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
1041
1042 if (!pEvent)
1043 pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
1044 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
1045
1046 uint32_t idMsg = 0;
1047 uint32_t cParms = 0;
1048 rc = VbglR3ClipboardMsgPeek(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
1049 if (RT_SUCCESS(rc))
1050 {
1051#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1052 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
1053#else
1054 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
1055#endif
1056 }
1057 else if (rc == VERR_TRY_AGAIN) /* No new message (yet). */
1058 {
1059 RTReqQueueProcess(pCtx->Win.hReqQ, RT_MS_1SEC);
1060 continue;
1061 }
1062
1063 if (RT_FAILURE(rc))
1064 {
1065 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
1066
1067 VbglR3ClipboardEventFree(pEvent);
1068 pEvent = NULL;
1069
1070 if (*pfShutdown)
1071 break;
1072
1073 /* Wait a bit before retrying. */
1074 RTThreadSleep(1000);
1075 continue;
1076 }
1077 else
1078 {
1079 AssertPtr(pEvent);
1080 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
1081
1082 switch (pEvent->enmType)
1083 {
1084 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
1085 {
1086 /* The host has announced available clipboard formats.
1087 * Forward the information to the window, so it can later
1088 * respond to WM_RENDERFORMAT message. */
1089 ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_REPORT_FORMATS,
1090 0 /* wParam */, (LPARAM)pEvent /* lParam */);
1091
1092 pEvent = NULL; /* Consume pointer. */
1093 break;
1094 }
1095
1096 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
1097 {
1098 /* The host needs data in the specified format. */
1099 ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_READ_DATA,
1100 0 /* wParam */, (LPARAM)pEvent /* lParam */);
1101
1102 pEvent = NULL; /* Consume pointer. */
1103 break;
1104 }
1105
1106 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
1107 {
1108 LogRel2(("Shared Clipboard: Host requested termination\n"));
1109 ASMAtomicXchgBool(pfShutdown, true);
1110 break;
1111 }
1112
1113#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1114 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
1115 {
1116 /* Nothing to do here. */
1117 rc = VINF_SUCCESS;
1118 break;
1119 }
1120#endif
1121 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
1122 {
1123 /* Nothing to do here. */
1124 rc = VINF_SUCCESS;
1125 break;
1126 }
1127
1128 default:
1129 {
1130 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
1131 }
1132 }
1133
1134 if (pEvent)
1135 {
1136 VbglR3ClipboardEventFree(pEvent);
1137 pEvent = NULL;
1138 }
1139 }
1140
1141 if (*pfShutdown)
1142 break;
1143 }
1144
1145 LogRel2(("Shared Clipboard: Worker loop ended\n"));
1146
1147#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1148 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
1149 OleUninitialize();
1150#endif
1151
1152 LogFlowFuncLeaveRC(rc);
1153 return rc;
1154}
1155
1156DECLCALLBACK(int) vbtrShClStop(void *pInstance)
1157{
1158 AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
1159
1160 LogFunc(("Stopping pInstance=%p\n", pInstance));
1161
1162 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1163 AssertPtr(pCtx);
1164
1165 /* Set shutdown indicator. */
1166 ASMAtomicWriteBool(&pCtx->fShutdown, true);
1167
1168 /* Let our clipboard know that we're going to shut down. */
1169 PostMessage(pCtx->Win.hWnd, WM_QUIT, 0, 0);
1170
1171 /* Disconnect from the host service.
1172 * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
1173 VbglR3ClipboardDisconnect(pCtx->CmdCtx.idClient);
1174 pCtx->CmdCtx.idClient = 0;
1175
1176 LogFlowFuncLeaveRC(VINF_SUCCESS);
1177 return VINF_SUCCESS;
1178}
1179
1180DECLCALLBACK(void) vbtrShClDestroy(void *pInstance)
1181{
1182 AssertPtrReturnVoid(pInstance);
1183
1184 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1185 AssertPtrReturnVoid(pCtx);
1186
1187 /* Make sure that we are disconnected. */
1188 Assert(pCtx->CmdCtx.idClient == 0);
1189
1190 LogFlowFunc(("pCtx=%p\n", pCtx));
1191
1192 LogRel2(("Shared Clipboard: Destroying ...\n"));
1193
1194 const PSHCLWINCTX pWinCtx = &pCtx->Win;
1195
1196 if (pCtx->hThread != NIL_RTTHREAD)
1197 {
1198 int rcThread = VERR_WRONG_ORDER;
1199 int rc = RTThreadWait(pCtx->hThread, 60 * 1000 /* Timeout in ms */, &rcThread);
1200 LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n",
1201 rc, rcThread));
1202 RT_NOREF(rc);
1203 }
1204
1205 if (pWinCtx->hWnd)
1206 {
1207 DestroyWindow(pWinCtx->hWnd);
1208 pWinCtx->hWnd = NULL;
1209 }
1210
1211 UnregisterClass(s_szClipWndClassName, pCtx->pEnv->hInstance);
1212
1213 SharedClipboardWinCtxDestroy(&pCtx->Win);
1214
1215#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1216 ShClTransferCtxDestroy(&pCtx->TransferCtx);
1217#endif
1218
1219 RTReqQueueDestroy(pCtx->Win.hReqQ);
1220
1221 LogRel2(("Shared Clipboard: Destroyed\n"));
1222
1223 return;
1224}
1225
1226/**
1227 * The service description.
1228 */
1229VBOXSERVICEDESC g_SvcDescClipboard =
1230{
1231 /* pszName. */
1232 "clipboard",
1233 /* pszDescription. */
1234 "Shared Clipboard",
1235 /* methods */
1236 vbtrShClInit,
1237 vbtrShClWorker,
1238 vbtrShClStop,
1239 vbtrShClDestroy
1240};
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette