VirtualBox

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

Last change on this file since 100347 was 100205, checked in by vboxsync, 21 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 [build fix]. ​bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 44.9 KB
Line 
1/* $Id: VBoxClipboard.cpp 100205 2023-06-19 10:25:09Z 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 SHCLTRANSFERCALLBACKS::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 SHCLTRANSFERCALLBACKS::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 SHCLTRANSFERCALLBACKS::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#else
650 RT_NOREF(rc);
651#endif
652 }
653
654 LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: fFormats=0x%x, lastErr=%ld\n", fFormats, GetLastError()));
655 break;
656 }
657
658 case SHCL_WIN_WM_READ_DATA: /* Host wants to read clipboard data from the guest. */
659 {
660 /* Send data in the specified format to the host. */
661 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)lParam;
662 AssertPtr(pEvent);
663 Assert(pEvent->enmType == VBGLR3CLIPBOARDEVENTTYPE_READ_DATA);
664
665 const SHCLFORMAT fFormat = (uint32_t)pEvent->u.fReadData;
666
667 LogFlowFunc(("SHCL_WIN_WM_READ_DATA: fFormat=%#x\n", fFormat));
668#ifdef LOG_ENABLED
669 char *pszFmts = ShClFormatsToStrA(fFormat);
670 AssertPtrReturn(pszFmts, 0);
671 LogRel(("Shared Clipboard: Sending data to host as '%s'\n", pszFmts));
672 RTStrFree(pszFmts);
673#endif
674 int rc = SharedClipboardWinOpen(hwnd);
675 HANDLE hClip = NULL;
676 if (RT_SUCCESS(rc))
677 {
678 if (fFormat & VBOX_SHCL_FMT_BITMAP)
679 {
680 hClip = GetClipboardData(CF_DIB);
681 if (hClip != NULL)
682 {
683 LPVOID lp = GlobalLock(hClip);
684 if (lp != NULL)
685 {
686 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, lp, (uint32_t)GlobalSize(hClip));
687
688 GlobalUnlock(hClip);
689 }
690 else
691 hClip = NULL;
692 }
693 }
694 else if (fFormat & VBOX_SHCL_FMT_UNICODETEXT)
695 {
696 hClip = GetClipboardData(CF_UNICODETEXT);
697 if (hClip != NULL)
698 {
699 LPWSTR uniString = (LPWSTR)GlobalLock(hClip);
700 if (uniString != NULL)
701 {
702 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx,
703 fFormat, uniString, ((uint32_t)lstrlenW(uniString) + 1) * 2);
704
705 GlobalUnlock(hClip);
706 }
707 else
708 hClip = NULL;
709 }
710 }
711 else if (fFormat & VBOX_SHCL_FMT_HTML)
712 {
713 UINT format = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML);
714 if (format != 0)
715 {
716 hClip = GetClipboardData(format);
717 if (hClip != NULL)
718 {
719 LPVOID const pvClip = GlobalLock(hClip);
720 if (pvClip != NULL)
721 {
722 uint32_t const cbClip = (uint32_t)GlobalSize(hClip);
723
724 /* Unwrap clipboard content from CF_HTML format if needed. */
725 if (SharedClipboardWinIsCFHTML((const char *)pvClip))
726 {
727 char *pszBuf = NULL;
728 uint32_t cbBuf = 0;
729 rc = SharedClipboardWinConvertCFHTMLToMIME((const char *)pvClip, cbClip, &pszBuf, &cbBuf);
730 if (RT_SUCCESS(rc))
731 {
732 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pszBuf, cbBuf);
733 RTMemFree(pszBuf);
734 }
735 else
736 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pvClip, cbClip);
737 }
738 else
739 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pvClip, cbClip);
740
741 GlobalUnlock(hClip);
742 }
743 else
744 hClip = NULL;
745 }
746 }
747 }
748
749 if (hClip == NULL)
750 LogFunc(("SHCL_WIN_WM_READ_DATA: hClip=NULL, lastError=%ld\n", GetLastError()));
751
752 SharedClipboardWinClose();
753 }
754
755 /* If the requested clipboard format is not available, we must send empty data. */
756 if (hClip == NULL)
757 VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, VBOX_SHCL_FMT_NONE, NULL, 0);
758 break;
759 }
760
761 case WM_DESTROY:
762 {
763 LogFunc(("WM_DESTROY\n"));
764
765 int rc = SharedClipboardWinHandleWMDestroy(pWinCtx);
766 AssertRC(rc);
767
768 /*
769 * Don't need to call PostQuitMessage cause
770 * the VBoxTray already finished a message loop.
771 */
772
773 break;
774 }
775
776 default:
777 {
778 LogFunc(("WM_ %p\n", msg));
779 lresultRc = DefWindowProc(hwnd, msg, wParam, lParam);
780 break;
781 }
782 }
783
784 LogFunc(("WM_ rc %d\n", lresultRc));
785 return lresultRc;
786}
787
788static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
789
790static int vbtrShClCreateWindow(PSHCLCONTEXT pCtx)
791{
792 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
793
794 int rc = VINF_SUCCESS;
795
796 AssertPtr(pCtx->pEnv);
797 HINSTANCE hInstance = pCtx->pEnv->hInstance;
798 Assert(hInstance != 0);
799
800 /* Register the Window Class. */
801 WNDCLASSEX wc;
802 RT_ZERO(wc);
803
804 wc.cbSize = sizeof(WNDCLASSEX);
805
806 if (!GetClassInfoEx(hInstance, s_szClipWndClassName, &wc))
807 {
808 wc.style = CS_NOCLOSE;
809 wc.lpfnWndProc = vbtrShClWndProc;
810 wc.hInstance = pCtx->pEnv->hInstance;
811 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
812 wc.lpszClassName = s_szClipWndClassName;
813
814 ATOM wndClass = RegisterClassEx(&wc);
815 if (wndClass == 0)
816 rc = RTErrConvertFromWin32(GetLastError());
817 }
818
819 if (RT_SUCCESS(rc))
820 {
821 const PSHCLWINCTX pWinCtx = &pCtx->Win;
822
823 /* Create the window. */
824 pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
825 s_szClipWndClassName, s_szClipWndClassName,
826 WS_POPUPWINDOW,
827 -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
828 if (pWinCtx->hWnd == NULL)
829 {
830 rc = VERR_NOT_SUPPORTED;
831 }
832 else
833 {
834 SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0,
835 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
836
837 rc = SharedClipboardWinChainAdd(pWinCtx);
838 if (RT_SUCCESS(rc))
839 {
840 if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI))
841 pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000 /* 10s */, NULL);
842 }
843 }
844 }
845
846 LogFlowFuncLeaveRC(rc);
847 return rc;
848}
849
850static DECLCALLBACK(int) vbtrShClWindowThread(RTTHREAD hThread, void *pvUser)
851{
852 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser;
853 AssertPtr(pCtx);
854
855#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
856 HRESULT hr = OleInitialize(NULL);
857 if (FAILED(hr))
858 {
859 LogRel(("Shared Clipboard: Initializing OLE in window thread failed (%Rhrc) -- file transfers unavailable\n", hr));
860 /* Not critical, the rest of the clipboard might work. */
861 }
862 else
863 LogRel(("Shared Clipboard: Initialized OLE in window thread\n"));
864#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
865
866 int rc = vbtrShClCreateWindow(pCtx);
867 if (RT_FAILURE(rc))
868 {
869 LogRel(("Shared Clipboard: Unable to create window, rc=%Rrc\n", rc));
870 return rc;
871 }
872
873 pCtx->fStarted = true; /* Set started indicator. */
874
875 int rc2 = RTThreadUserSignal(hThread);
876 bool fSignalled = RT_SUCCESS(rc2);
877
878 LogRel2(("Shared Clipboard: Window thread running\n"));
879
880 if (RT_SUCCESS(rc))
881 {
882 for (;;)
883 {
884 MSG uMsg;
885 BOOL fRet;
886 while ((fRet = GetMessage(&uMsg, 0, 0, 0)) > 0)
887 {
888 TranslateMessage(&uMsg);
889 DispatchMessage(&uMsg);
890 }
891 Assert(fRet >= 0);
892
893 if (ASMAtomicReadBool(&pCtx->fShutdown))
894 break;
895
896 /** @todo Immediately drop on failure? */
897 }
898 }
899
900 if (!fSignalled)
901 {
902 rc2 = RTThreadUserSignal(hThread);
903 AssertRC(rc2);
904 }
905
906#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
907 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
908 OleUninitialize();
909#endif
910
911 LogRel(("Shared Clipboard: Window thread ended\n"));
912
913 LogFlowFuncLeaveRC(rc);
914 return rc;
915}
916
917static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
918{
919 PSHCLCONTEXT pCtx = &g_Ctx; /** @todo r=andy Make pCtx available through SetWindowLongPtr() / GWL_USERDATA. */
920 AssertPtr(pCtx);
921
922 /* Forward with proper context. */
923 return vbtrShClWndProcWorker(pCtx, hWnd, uMsg, wParam, lParam);
924}
925
926DECLCALLBACK(int) vbtrShClInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
927{
928 LogFlowFuncEnter();
929
930 PSHCLCONTEXT pCtx = &g_Ctx; /* Only one instance for now. */
931 AssertPtr(pCtx);
932
933 if (pCtx->pEnv)
934 {
935 /* Clipboard was already initialized. 2 or more instances are not supported. */
936 return VERR_NOT_SUPPORTED;
937 }
938
939 if (VbglR3AutoLogonIsRemoteSession())
940 {
941 /* Do not use clipboard for remote sessions. */
942 LogRel(("Shared Clipboard: Clipboard has been disabled for a remote session\n"));
943 return VERR_NOT_SUPPORTED;
944 }
945
946 pCtx->pEnv = pEnv;
947 pCtx->hThread = NIL_RTTHREAD;
948 pCtx->fStarted = false;
949 pCtx->fShutdown = false;
950
951 int rc = RTReqQueueCreate(&pCtx->Win.hReqQ);
952 AssertRCReturn(rc, rc);
953
954 rc = SharedClipboardWinCtxInit(&pCtx->Win);
955 if (RT_SUCCESS(rc))
956 rc = VbglR3ClipboardConnectEx(&pCtx->CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
957 if (RT_SUCCESS(rc))
958 {
959#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
960 rc = ShClTransferCtxInit(&pCtx->TransferCtx);
961#endif
962 if (RT_SUCCESS(rc))
963 {
964 /* Message pump thread for our proxy window. */
965 rc = RTThreadCreate(&pCtx->hThread, vbtrShClWindowThread, pCtx /* pvUser */,
966 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
967 "shclwnd");
968 if (RT_SUCCESS(rc))
969 {
970 int rc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */);
971 AssertRC(rc2);
972
973 if (!pCtx->fStarted) /* Did the thread fail to start? */
974 rc = VERR_NOT_SUPPORTED; /* Report back Shared Clipboard as not being supported. */
975 }
976 }
977
978 if (RT_SUCCESS(rc))
979 {
980 *ppInstance = pCtx;
981 }
982 else
983 VbglR3ClipboardDisconnectEx(&pCtx->CmdCtx);
984 }
985
986 if (RT_FAILURE(rc))
987 LogRel(("Shared Clipboard: Unable to initialize, rc=%Rrc\n", rc));
988
989 LogFlowFuncLeaveRC(rc);
990 return rc;
991}
992
993DECLCALLBACK(int) vbtrShClWorker(void *pInstance, bool volatile *pfShutdown)
994{
995 AssertPtr(pInstance);
996 LogFlowFunc(("pInstance=%p\n", pInstance));
997
998 /*
999 * Tell the control thread that it can continue
1000 * spawning services.
1001 */
1002 RTThreadUserSignal(RTThreadSelf());
1003
1004 const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1005 AssertPtr(pCtx);
1006
1007 const PSHCLWINCTX pWinCtx = &pCtx->Win;
1008
1009 LogRel2(("Shared Clipboard: Worker loop running\n"));
1010
1011#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1012 HRESULT hr = OleInitialize(NULL);
1013 if (FAILED(hr))
1014 {
1015 LogRel(("Shared Clipboard: Initializing OLE in worker thread failed (%Rhrc) -- file transfers unavailable\n", hr));
1016 /* Not critical, the rest of the clipboard might work. */
1017 }
1018 else
1019 LogRel(("Shared Clipboard: Initialized OLE in worker thread\n"));
1020
1021 /*
1022 * Init callbacks.
1023 * Those will be registered within VbglR3 when a new transfer gets initialized.
1024 */
1025 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
1026
1027 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
1028 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
1029
1030 pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback;
1031 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback;
1032 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback;
1033#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
1034
1035 int rc;
1036
1037 /* The thread waits for incoming messages from the host. */
1038 PVBGLR3CLIPBOARDEVENT pEvent = NULL;
1039 for (;;)
1040 {
1041 LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
1042 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
1043
1044 if (!pEvent)
1045 pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
1046 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
1047
1048 uint32_t idMsg = 0;
1049 uint32_t cParms = 0;
1050 rc = VbglR3ClipboardMsgPeek(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
1051 if (RT_SUCCESS(rc))
1052 {
1053#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1054 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
1055#else
1056 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
1057#endif
1058 }
1059 else if (rc == VERR_TRY_AGAIN) /* No new message (yet). */
1060 {
1061 RTReqQueueProcess(pCtx->Win.hReqQ, RT_MS_1SEC);
1062 continue;
1063 }
1064
1065 if (RT_FAILURE(rc))
1066 {
1067 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
1068
1069 VbglR3ClipboardEventFree(pEvent);
1070 pEvent = NULL;
1071
1072 if (*pfShutdown)
1073 break;
1074
1075 /* Wait a bit before retrying. */
1076 RTThreadSleep(1000);
1077 continue;
1078 }
1079 else
1080 {
1081 AssertPtr(pEvent);
1082 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
1083
1084 switch (pEvent->enmType)
1085 {
1086 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
1087 {
1088 /* The host has announced available clipboard formats.
1089 * Forward the information to the window, so it can later
1090 * respond to WM_RENDERFORMAT message. */
1091 ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_REPORT_FORMATS,
1092 0 /* wParam */, (LPARAM)pEvent /* lParam */);
1093
1094 pEvent = NULL; /* Consume pointer. */
1095 break;
1096 }
1097
1098 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
1099 {
1100 /* The host needs data in the specified format. */
1101 ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_READ_DATA,
1102 0 /* wParam */, (LPARAM)pEvent /* lParam */);
1103
1104 pEvent = NULL; /* Consume pointer. */
1105 break;
1106 }
1107
1108 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
1109 {
1110 LogRel2(("Shared Clipboard: Host requested termination\n"));
1111 ASMAtomicXchgBool(pfShutdown, true);
1112 break;
1113 }
1114
1115#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1116 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
1117 {
1118 /* Nothing to do here. */
1119 rc = VINF_SUCCESS;
1120 break;
1121 }
1122#endif
1123 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
1124 {
1125 /* Nothing to do here. */
1126 rc = VINF_SUCCESS;
1127 break;
1128 }
1129
1130 default:
1131 {
1132 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
1133 }
1134 }
1135
1136 if (pEvent)
1137 {
1138 VbglR3ClipboardEventFree(pEvent);
1139 pEvent = NULL;
1140 }
1141 }
1142
1143 if (*pfShutdown)
1144 break;
1145 }
1146
1147 LogRel2(("Shared Clipboard: Worker loop ended\n"));
1148
1149#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1150 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
1151 OleUninitialize();
1152#endif
1153
1154 LogFlowFuncLeaveRC(rc);
1155 return rc;
1156}
1157
1158DECLCALLBACK(int) vbtrShClStop(void *pInstance)
1159{
1160 AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
1161
1162 LogFunc(("Stopping pInstance=%p\n", pInstance));
1163
1164 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1165 AssertPtr(pCtx);
1166
1167 /* Set shutdown indicator. */
1168 ASMAtomicWriteBool(&pCtx->fShutdown, true);
1169
1170 /* Let our clipboard know that we're going to shut down. */
1171 PostMessage(pCtx->Win.hWnd, WM_QUIT, 0, 0);
1172
1173 /* Disconnect from the host service.
1174 * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
1175 VbglR3ClipboardDisconnect(pCtx->CmdCtx.idClient);
1176 pCtx->CmdCtx.idClient = 0;
1177
1178 LogFlowFuncLeaveRC(VINF_SUCCESS);
1179 return VINF_SUCCESS;
1180}
1181
1182DECLCALLBACK(void) vbtrShClDestroy(void *pInstance)
1183{
1184 AssertPtrReturnVoid(pInstance);
1185
1186 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1187 AssertPtrReturnVoid(pCtx);
1188
1189 /* Make sure that we are disconnected. */
1190 Assert(pCtx->CmdCtx.idClient == 0);
1191
1192 LogFlowFunc(("pCtx=%p\n", pCtx));
1193
1194 LogRel2(("Shared Clipboard: Destroying ...\n"));
1195
1196 const PSHCLWINCTX pWinCtx = &pCtx->Win;
1197
1198 if (pCtx->hThread != NIL_RTTHREAD)
1199 {
1200 int rcThread = VERR_WRONG_ORDER;
1201 int rc = RTThreadWait(pCtx->hThread, 60 * 1000 /* Timeout in ms */, &rcThread);
1202 LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n",
1203 rc, rcThread));
1204 RT_NOREF(rc);
1205 }
1206
1207 if (pWinCtx->hWnd)
1208 {
1209 DestroyWindow(pWinCtx->hWnd);
1210 pWinCtx->hWnd = NULL;
1211 }
1212
1213 UnregisterClass(s_szClipWndClassName, pCtx->pEnv->hInstance);
1214
1215 SharedClipboardWinCtxDestroy(&pCtx->Win);
1216
1217#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1218 ShClTransferCtxDestroy(&pCtx->TransferCtx);
1219#endif
1220
1221 RTReqQueueDestroy(pCtx->Win.hReqQ);
1222
1223 LogRel2(("Shared Clipboard: Destroyed\n"));
1224
1225 return;
1226}
1227
1228/**
1229 * The service description.
1230 */
1231VBOXSERVICEDESC g_SvcDescClipboard =
1232{
1233 /* pszName. */
1234 "clipboard",
1235 /* pszDescription. */
1236 "Shared Clipboard",
1237 /* methods */
1238 vbtrShClInit,
1239 vbtrShClWorker,
1240 vbtrShClStop,
1241 vbtrShClDestroy
1242};
Note: See TracBrowser for help on using the repository browser.

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