VirtualBox

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

Last change on this file since 79702 was 79702, checked in by vboxsync, 5 years ago

Shared Clipboard/URI: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 43.3 KB
Line 
1/* $Id: VBoxClipboard.cpp 79702 2019-07-11 19:34:05Z vboxsync $ */
2/** @file
3 * VBoxClipboard - Shared clipboard, Windows Guest Implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
23#include <VBox/log.h>
24
25#include "VBoxTray.h"
26#include "VBoxHelpers.h"
27
28#include <iprt/asm.h>
29#include <iprt/ldr.h>
30
31#include <iprt/errcore.h>
32
33#include <VBox/GuestHost/SharedClipboard.h>
34#include <VBox/HostServices/VBoxClipboardSvc.h> /* Temp, remove. */
35#include <VBox/GuestHost/SharedClipboard-win.h>
36#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
37# include <VBox/GuestHost/SharedClipboard-uri.h>
38#endif
39
40#include <strsafe.h>
41
42#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
43/** !!! HACK ALERT !!! Dynamically resolve functions! */
44# ifdef _WIN32_IE
45# undef _WIN32_IE
46# define _WIN32_IE 0x0501
47# endif
48# include <iprt/win/shlobj.h>
49# include <iprt/win/shlwapi.h>
50#endif
51
52
53/*********************************************************************************************************************************
54* Structures and Typedefs *
55*********************************************************************************************************************************/
56
57typedef struct _VBOXCLIPBOARDCONTEXT
58{
59 /** Pointer to the VBoxClient service environment. */
60 const VBOXSERVICEENV *pEnv;
61 /** Client ID the service is connected to the HGCM service with. */
62 uint32_t u32ClientID;
63 /** Windows-specific context data. */
64 VBOXCLIPBOARDWINCTX Win;
65#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
66 /** URI transfer data. */
67 SHAREDCLIPBOARDURICTX URI;
68#endif
69} VBOXCLIPBOARDCONTEXT, *PVBOXCLIPBOARDCONTEXT;
70
71#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
72typedef struct _VBOXCLIPBOARDURIREADTHREADCTX
73{
74 PVBOXCLIPBOARDCONTEXT pClipboardCtx;
75 PSHAREDCLIPBOARDURITRANSFER pTransfer;
76} VBOXCLIPBOARDURIREADTHREADCTX, *PVBOXCLIPBOARDURIREADTHREADCTX;
77
78typedef struct _VBOXCLIPBOARDURIWRITETHREADCTX
79{
80 PVBOXCLIPBOARDCONTEXT pClipboardCtx;
81 PSHAREDCLIPBOARDURITRANSFER pTransfer;
82} VBOXCLIPBOARDURIWRITETHREADCTX, *PVBOXCLIPBOARDURIWRITETHREADCTX;
83#endif /* VBOX_WITH_SHARED_CLIPBOARD_URI_LIST */
84
85
86/*********************************************************************************************************************************
87* Static variables *
88*********************************************************************************************************************************/
89/** Static clipboard context (since it is the single instance). Directly used in the windows proc. */
90static VBOXCLIPBOARDCONTEXT g_Ctx = { NULL };
91/** Static window class name. */
92static char s_szClipWndClassName[] = VBOX_CLIPBOARD_WNDCLASS_NAME;
93
94
95/*********************************************************************************************************************************
96* Prototypes *
97*********************************************************************************************************************************/
98#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
99static DECLCALLBACK(void) vboxClipboardURITransferCompleteCallback(PSHAREDCLIPBOARDURITRANSFERCALLBACKDATA pData, int rc);
100static DECLCALLBACK(void) vboxClipboardURITransferErrorCallback(PSHAREDCLIPBOARDURITRANSFERCALLBACKDATA pData, int rc);
101#endif
102
103
104#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
105static DECLCALLBACK(int) vboxClipboardURIWriteThread(RTTHREAD ThreadSelf, void *pvUser)
106{
107 RT_NOREF(ThreadSelf);
108
109 LogFlowFuncEnter();
110
111 PVBOXCLIPBOARDURIWRITETHREADCTX pCtx = (PVBOXCLIPBOARDURIWRITETHREADCTX)pvUser;
112 AssertPtr(pCtx);
113
114 PSHAREDCLIPBOARDURITRANSFER pTransfer = pCtx->pTransfer;
115 AssertPtr(pTransfer);
116
117 pTransfer->Thread.fStarted = true;
118
119 RTThreadUserSignal(RTThreadSelf());
120
121 uint32_t uClientID;
122 int rc = VbglR3ClipboardConnect(&uClientID);
123 if (RT_SUCCESS(rc))
124 {
125 rc = VbglR3ClipboardTransferSendStatus(uClientID, SHAREDCLIPBOARDURITRANSFERSTATUS_RUNNING);
126 if (RT_SUCCESS(rc))
127 {
128 bool fTerminate = false;
129 unsigned cErrors = 0;
130
131 for (;;)
132 {
133 PVBGLR3CLIPBOARDEVENT pEvent = NULL;
134 rc = VbglR3ClipboardEventGetNext(uClientID, pTransfer, &pEvent);
135 if (RT_SUCCESS(rc))
136 {
137 /* Nothing to do in here right now. */
138
139 VbglR3ClipboardEventFree(pEvent);
140 }
141
142 if (fTerminate)
143 break;
144
145 if (RT_FAILURE(rc))
146 {
147 if (cErrors++ >= 3)
148 break;
149 RTThreadSleep(1000);
150 }
151 }
152 }
153
154 VbglR3ClipboardDisconnect(uClientID);
155 }
156
157 RTMemFree(pCtx);
158
159 LogFlowFuncLeaveRC(rc);
160 return rc;
161}
162
163static DECLCALLBACK(void) vboxClipboardURITransferCompleteCallback(PSHAREDCLIPBOARDURITRANSFERCALLBACKDATA pData, int rc)
164{
165 RT_NOREF(rc);
166
167 LogFlowFunc(("pData=%p, rc=%Rrc\n", pData, rc));
168
169 LogRel2(("Shared Clipboard: Transfer to destination complete\n"));
170
171 PSHAREDCLIPBOARDURICTX pCtx = (PSHAREDCLIPBOARDURICTX)pData->pvUser;
172 AssertPtr(pCtx);
173
174 PSHAREDCLIPBOARDURITRANSFER pTransfer = pData->pTransfer;
175 AssertPtr(pTransfer);
176
177 if (pTransfer->pvUser) /* SharedClipboardWinURITransferCtx */
178 {
179 delete pTransfer->pvUser;
180 pTransfer->pvUser = NULL;
181 }
182
183 int rc2 = SharedClipboardURICtxTransferRemove(pCtx, pTransfer);
184 AssertRC(rc2);
185}
186
187static DECLCALLBACK(void) vboxClipboardURITransferErrorCallback(PSHAREDCLIPBOARDURITRANSFERCALLBACKDATA pData, int rc)
188{
189 RT_NOREF(rc);
190
191 LogFlowFunc(("pData=%p, rc=%Rrc\n", pData, rc));
192
193 LogRel(("Shared Clipboard: Transfer to destination failed with %Rrc\n", rc));
194
195 PSHAREDCLIPBOARDURICTX pCtx = (PSHAREDCLIPBOARDURICTX)pData->pvUser;
196 AssertPtr(pCtx);
197
198 PSHAREDCLIPBOARDURITRANSFER pTransfer = pData->pTransfer;
199 AssertPtr(pTransfer);
200
201 if (pTransfer->pvUser) /* SharedClipboardWinURITransferCtx */
202 {
203 delete pTransfer->pvUser;
204 pTransfer->pvUser = NULL;
205 }
206
207 int rc2 = SharedClipboardURICtxTransferRemove(pCtx, pTransfer);
208 AssertRC(rc2);
209}
210
211static int vboxClipboardURITransferOpen(PSHAREDCLIPBOARDPROVIDERCTX pCtx)
212{
213 RT_NOREF(pCtx);
214
215 LogFlowFuncLeave();
216 return VINF_SUCCESS;
217}
218
219static int vboxClipboardURITransferClose(PSHAREDCLIPBOARDPROVIDERCTX pCtx)
220{
221 RT_NOREF(pCtx);
222
223 LogFlowFuncLeave();
224 return VINF_SUCCESS;
225}
226
227static int vboxClipboardURIListOpen(PSHAREDCLIPBOARDPROVIDERCTX pCtx, PVBOXCLIPBOARDLISTHDR pListHdr,
228 PSHAREDCLIPBOARDLISTHANDLE phList)
229{
230 RT_NOREF(pCtx, pListHdr, phList);
231
232 LogFlowFuncEnter();
233
234 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
235 AssertPtr(pThisCtx);
236
237 int rc = 0; // VbglR3ClipboardRecvListOpen(pThisCtx->u32ClientID, pListHdr, phList);
238
239 LogFlowFuncLeaveRC(rc);
240 return rc;
241}
242
243static int vboxClipboardURIListHdrRead(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDLISTHANDLE hList,
244 PVBOXCLIPBOARDLISTHDR pListHdr)
245{
246 RT_NOREF(hList);
247
248 LogFlowFuncEnter();
249
250 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
251 AssertPtr(pThisCtx);
252
253 LogFlowFunc(("c=%RU32\n", pThisCtx->u32ClientID));
254
255 int rc = SharedClipboardURIListHdrInit(pListHdr);
256 if (RT_SUCCESS(rc))
257 {
258 if (RT_SUCCESS(rc))
259 {
260 //rc = VbglR3ClipboardListHdrReadRecv(pThisCtx->u32ClientID, hList, pListHdr);
261 }
262 else
263 SharedClipboardURIListHdrDestroy(pListHdr);
264 }
265
266 LogFlowFuncLeaveRC(rc);
267 return rc;
268}
269
270/*
271static int vboxClipboardURIListHdrWrite(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDLISTHANDLE hList,
272 PVBOXCLIPBOARDLISTHDR pListHdr)
273{
274 LogFlowFuncEnter();
275
276 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
277 AssertPtr(pThisCtx);
278
279 int rc = VbglR3ClipboardListHdrWrite(pThisCtx->u32ClientID, hList, pListHdr);
280
281 LogFlowFuncLeaveRC(rc);
282 return rc;
283}*/
284
285static int vboxClipboardURIListEntryRead(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDLISTHANDLE hList,
286 PVBOXCLIPBOARDLISTENTRY pListEntry)
287{
288 RT_NOREF(hList);
289
290 LogFlowFuncEnter();
291
292 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
293 AssertPtr(pThisCtx);
294
295 RT_NOREF(pListEntry);
296 int rc = 0; // VbglR3ClipboardListEntryRead(pThisCtx->u32ClientID, pListEntry);
297
298 LogFlowFuncLeaveRC(rc);
299 return rc;
300}
301
302/*
303static int vboxClipboardURIListEntryWrite(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDLISTHANDLE hList,
304 PVBOXCLIPBOARDLISTENTRY pListEntry)
305{
306 LogFlowFuncEnter();
307
308 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
309 AssertPtr(pThisCtx);
310
311 int rc = VbglR3ClipboardListEntryWrite(pThisCtx->u32ClientID, hList, pListEntry);
312
313 LogFlowFuncLeaveRC(rc);
314 return rc;
315}
316*/
317
318static int vboxClipboardURIObjOpen(PSHAREDCLIPBOARDPROVIDERCTX pCtx, const char *pszPath,
319 PVBOXCLIPBOARDCREATEPARMS pCreateParms, PSHAREDCLIPBOARDOBJHANDLE phObj)
320{
321 LogFlowFuncEnter();
322
323 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
324 AssertPtr(pThisCtx);
325
326 int rc = VbglR3ClipboardObjOpen(pThisCtx->u32ClientID, pszPath, pCreateParms, phObj);
327
328 LogFlowFuncLeaveRC(rc);
329 return rc;
330}
331
332static int vboxClipboardURIObjClose(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDOBJHANDLE hObj)
333{
334 LogFlowFuncEnter();
335
336 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
337 AssertPtr(pThisCtx);
338
339 int rc = VbglR3ClipboardObjClose(pThisCtx->u32ClientID, hObj);
340
341 LogFlowFuncLeaveRC(rc);
342 return rc;
343}
344
345static int vboxClipboardURIObjRead(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDOBJHANDLE hObj,
346 void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead)
347{
348 RT_NOREF(fFlags);
349
350 LogFlowFuncEnter();
351
352 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
353 AssertPtr(pThisCtx);
354
355 int rc = VbglR3ClipboardObjRead(pThisCtx->u32ClientID, hObj, pvData, cbData, pcbRead);
356
357 LogFlowFuncLeaveRC(rc);
358 return rc;
359}
360
361static int vboxClipboardURIObjWrite(PSHAREDCLIPBOARDPROVIDERCTX pCtx, SHAREDCLIPBOARDOBJHANDLE hObj,
362 void *pvData, uint32_t cbData, uint32_t fFlags, uint32_t *pcbWritten)
363{
364 RT_NOREF(fFlags);
365
366 LogFlowFuncEnter();
367
368 PVBOXCLIPBOARDCONTEXT pThisCtx = (PVBOXCLIPBOARDCONTEXT)pCtx->pvUser;
369 AssertPtr(pThisCtx);
370
371 int rc = VbglR3ClipboardObjWrite(pThisCtx->u32ClientID, hObj, pvData, cbData, pcbWritten);
372
373 LogFlowFuncLeaveRC(rc);
374 return rc;
375}
376#endif /* VBOX_WITH_SHARED_CLIPBOARD_URI_LIST */
377
378static LRESULT vboxClipboardWinProcessMsg(PVBOXCLIPBOARDCONTEXT pCtx, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
379{
380 AssertPtr(pCtx);
381
382 const PVBOXCLIPBOARDWINCTX pWinCtx = &pCtx->Win;
383
384 LRESULT lresultRc = 0;
385
386 switch (msg)
387 {
388 case WM_CLIPBOARDUPDATE:
389 {
390 const HWND hWndClipboardOwner = GetClipboardOwner();
391 if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner)
392 {
393 LogFunc(("WM_CLIPBOARDUPDATE: hWndOldClipboardOwner=%p, hWndNewClipboardOwner=%p\n",
394 pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner));
395
396 /* Clipboard was updated by another application.
397 * Report available formats to the host. */
398 VBOXCLIPBOARDFORMATS fFormats;
399 int rc = VBoxClipboardWinGetFormats(&pCtx->Win, &fFormats);
400 if (RT_SUCCESS(rc))
401 {
402 LogFunc(("WM_CLIPBOARDUPDATE: Reporting formats 0x%x\n", fFormats));
403 rc = VbglR3ClipboardReportFormats(pCtx->u32ClientID, fFormats);
404 }
405 }
406 }
407 break;
408
409 case WM_CHANGECBCHAIN:
410 {
411 LogFunc(("WM_CHANGECBCHAIN\n"));
412 lresultRc = VBoxClipboardWinHandleWMChangeCBChain(pWinCtx, hwnd, msg, wParam, lParam);
413 }
414 break;
415
416 case WM_DRAWCLIPBOARD:
417 {
418 LogFlowFunc(("WM_DRAWCLIPBOARD, hwnd %p\n", pWinCtx->hWnd));
419
420 if (GetClipboardOwner() != hwnd)
421 {
422 /* Clipboard was updated by another application. */
423 /* WM_DRAWCLIPBOARD always expects a return code of 0, so don't change "rc" here. */
424 VBOXCLIPBOARDFORMATS fFormats;
425 int rc = VBoxClipboardWinGetFormats(pWinCtx, &fFormats);
426 if (RT_SUCCESS(rc))
427 rc = VbglR3ClipboardReportFormats(pCtx->u32ClientID, fFormats);
428 }
429
430 lresultRc = VBoxClipboardWinChainPassToNext(pWinCtx, msg, wParam, lParam);
431 }
432 break;
433
434 case WM_TIMER:
435 {
436 int rc = VBoxClipboardWinHandleWMTimer(pWinCtx);
437 AssertRC(rc);
438 }
439 break;
440
441 case WM_CLOSE:
442 {
443 /* Do nothing. Ignore the message. */
444 }
445 break;
446
447 case WM_RENDERFORMAT:
448 {
449 LogFunc(("WM_RENDERFORMAT\n"));
450
451 /* Insert the requested clipboard format data into the clipboard. */
452 const UINT cfFormat = (UINT)wParam;
453
454 const VBOXCLIPBOARDFORMAT fFormat = VBoxClipboardWinClipboardFormatToVBox(cfFormat);
455
456 LogFunc(("WM_RENDERFORMAT: cfFormat=%u -> fFormat=0x%x\n", cfFormat, fFormat));
457
458 if (fFormat == VBOX_SHARED_CLIPBOARD_FMT_NONE)
459 {
460 LogFunc(("WM_RENDERFORMAT: Unsupported format requested\n"));
461 VBoxClipboardWinClear();
462 }
463 else
464 {
465 const uint32_t cbPrealloc = _4K;
466 uint32_t cb = 0;
467
468 /* Preallocate a buffer, most of small text transfers will fit into it. */
469 HANDLE hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbPrealloc);
470 LogFlowFunc(("Preallocated handle hMem = %p\n", hMem));
471
472 if (hMem)
473 {
474 void *pMem = GlobalLock(hMem);
475 LogFlowFunc(("Locked pMem = %p, GlobalSize = %ld\n", pMem, GlobalSize(hMem)));
476
477 if (pMem)
478 {
479 /* Read the host data to the preallocated buffer. */
480 int rc = VbglR3ClipboardReadData(pCtx->u32ClientID, fFormat, pMem, cbPrealloc, &cb);
481 LogFlowFunc(("VbglR3ClipboardReadData returned with rc = %Rrc\n", rc));
482
483 if (RT_SUCCESS(rc))
484 {
485 if (cb == 0)
486 {
487 /* 0 bytes returned means the clipboard is empty.
488 * Deallocate the memory and set hMem to NULL to get to
489 * the clipboard empty code path. */
490 GlobalUnlock(hMem);
491 GlobalFree(hMem);
492 hMem = NULL;
493 }
494 else if (cb > cbPrealloc)
495 {
496 GlobalUnlock(hMem);
497
498 /* The preallocated buffer is too small, adjust the size. */
499 hMem = GlobalReAlloc(hMem, cb, 0);
500 LogFlowFunc(("Reallocated hMem = %p\n", hMem));
501
502 if (hMem)
503 {
504 pMem = GlobalLock(hMem);
505 LogFlowFunc(("Locked pMem = %p, GlobalSize = %ld\n", pMem, GlobalSize(hMem)));
506
507 if (pMem)
508 {
509 /* Read the host data to the preallocated buffer. */
510 uint32_t cbNew = 0;
511 rc = VbglR3ClipboardReadData(pCtx->u32ClientID, fFormat, pMem, cb, &cbNew);
512 LogFlowFunc(("VbglR3ClipboardReadData returned with rc = %Rrc, cb = %d, cbNew = %d\n",
513 rc, cb, cbNew));
514
515 if (RT_SUCCESS(rc)
516 && cbNew <= cb)
517 {
518 cb = cbNew;
519 }
520 else
521 {
522 GlobalUnlock(hMem);
523 GlobalFree(hMem);
524 hMem = NULL;
525 }
526 }
527 else
528 {
529 GlobalFree(hMem);
530 hMem = NULL;
531 }
532 }
533 }
534
535 if (hMem)
536 {
537 /* pMem is the address of the data. cb is the size of returned data. */
538 /* Verify the size of returned text, the memory block for clipboard
539 * must have the exact string size.
540 */
541 if (fFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
542 {
543 size_t cbActual = 0;
544 HRESULT hrc = StringCbLengthW((LPWSTR)pMem, cb, &cbActual);
545 if (FAILED(hrc))
546 {
547 /* Discard invalid data. */
548 GlobalUnlock(hMem);
549 GlobalFree(hMem);
550 hMem = NULL;
551 }
552 else
553 {
554 /* cbActual is the number of bytes, excluding those used
555 * for the terminating null character.
556 */
557 cb = (uint32_t)(cbActual + 2);
558 }
559 }
560 }
561
562 if (hMem)
563 {
564 GlobalUnlock(hMem);
565
566 hMem = GlobalReAlloc(hMem, cb, 0);
567 LogFlowFunc(("Reallocated hMem = %p\n", hMem));
568
569 if (hMem)
570 {
571 /* 'hMem' contains the host clipboard data.
572 * size is 'cb' and format is 'format'. */
573 HANDLE hClip = SetClipboardData(cfFormat, hMem);
574 LogFlowFunc(("WM_RENDERFORMAT hClip = %p\n", hClip));
575
576 if (hClip)
577 {
578 /* The hMem ownership has gone to the system. Finish the processing. */
579 break;
580 }
581
582 /* Cleanup follows. */
583 }
584 }
585 }
586 if (hMem)
587 GlobalUnlock(hMem);
588 }
589 if (hMem)
590 GlobalFree(hMem);
591 }
592 }
593 }
594 break;
595
596 case WM_RENDERALLFORMATS:
597 {
598 LogFunc(("WM_RENDERALLFORMATS\n"));
599
600 int rc = VBoxClipboardWinHandleWMRenderAllFormats(pWinCtx, hwnd);
601 AssertRC(rc);
602 }
603 break;
604
605 case VBOX_CLIPBOARD_WM_SET_FORMATS:
606 {
607 LogFunc(("VBOX_CLIPBOARD_WM_SET_FORMATS\n"));
608
609 /* Announce available formats. Do not insert data -- will be inserted in WM_RENDERFORMAT. */
610 VBOXCLIPBOARDFORMATS fFormats = (uint32_t)lParam;
611 if (fFormats != VBOX_SHARED_CLIPBOARD_FMT_NONE) /* Could arrive with some older GA versions. */
612 {
613 int rc = VBoxClipboardWinOpen(hwnd);
614 if (RT_SUCCESS(rc))
615 {
616 VBoxClipboardWinClear();
617
618#if 0
619 if (fFormats & VBOX_SHARED_CLIPBOARD_FMT_URI_LIST)
620 {
621 LogFunc(("VBOX_SHARED_CLIPBOARD_FMT_URI_LIST\n"));
622
623 PSHAREDCLIPBOARDURITRANSFER pTransfer;
624 rc = SharedClipboardURITransferCreate(SHAREDCLIPBOARDURITRANSFERDIR_READ,
625 SHAREDCLIPBOARDSOURCE_REMOTE,
626 &pTransfer);
627 if (RT_SUCCESS(rc))
628 {
629 SHAREDCLIPBOARDURITRANSFERCALLBACKS TransferCallbacks;
630 RT_ZERO(TransferCallbacks);
631
632 TransferCallbacks.pvUser = &pCtx->URI;
633 TransferCallbacks.pfnTransferComplete = vboxClipboardURITransferCompleteCallback;
634 TransferCallbacks.pfnTransferError = vboxClipboardURITransferErrorCallback;
635
636 SharedClipboardURITransferSetCallbacks(pTransfer, &TransferCallbacks);
637
638 SHAREDCLIPBOARDPROVIDERCREATIONCTX creationCtx;
639 RT_ZERO(creationCtx);
640 creationCtx.enmSource = SHAREDCLIPBOARDSOURCE_REMOTE;
641
642 RT_ZERO(creationCtx.Interface);
643 creationCtx.Interface.pfnTransferOpen = vboxClipboardURITransferOpen;
644 creationCtx.Interface.pfnTransferClose = vboxClipboardURITransferClose;
645 creationCtx.Interface.pfnListHdrRead = vboxClipboardURIListHdrRead;
646 creationCtx.Interface.pfnListEntryRead = vboxClipboardURIListEntryRead;
647 creationCtx.Interface.pfnObjOpen = vboxClipboardURIObjOpen;
648 creationCtx.Interface.pfnObjClose = vboxClipboardURIObjClose;
649 creationCtx.Interface.pfnObjRead = vboxClipboardURIObjRead;
650
651 creationCtx.pvUser = pCtx;
652
653 rc = SharedClipboardURITransferSetInterface(pTransfer, &creationCtx);
654 if (RT_SUCCESS(rc))
655 {
656 rc = SharedClipboardURICtxTransferAdd(&pCtx->URI, pTransfer);
657 if (RT_SUCCESS(rc))
658 rc = VBoxClipboardWinURITransferCreate(pWinCtx, pTransfer);
659 }
660
661 /* Note: VBoxClipboardWinURITransferCreate() takes care of closing the clipboard. */
662
663 LogFunc(("VBOX_SHARED_CLIPBOARD_FMT_URI_LIST: rc=%Rrc\n", rc));
664 }
665 }
666 else
667 {
668#endif
669 rc = VBoxClipboardWinAnnounceFormats(pWinCtx, fFormats);
670
671 VBoxClipboardWinClose();
672#if 0
673 }
674#endif
675 }
676 }
677 LogFunc(("VBOX_CLIPBOARD_WM_SET_FORMATS: fFormats=0x%x, lastErr=%ld\n", fFormats, GetLastError()));
678 }
679 break;
680
681 case VBOX_CLIPBOARD_WM_READ_DATA:
682 {
683 /* Send data in the specified format to the host. */
684 VBOXCLIPBOARDFORMAT uFormat = (uint32_t)lParam;
685 HANDLE hClip = NULL;
686
687 LogFlowFunc(("VBOX_WM_SHCLPB_READ_DATA: uFormat=0x%x\n", uFormat));
688
689 int rc = VBoxClipboardWinOpen(hwnd);
690 if (RT_SUCCESS(rc))
691 {
692 if (uFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
693 {
694 hClip = GetClipboardData(CF_DIB);
695 if (hClip != NULL)
696 {
697 LPVOID lp = GlobalLock(hClip);
698 if (lp != NULL)
699 {
700 rc = VbglR3ClipboardWriteData(pCtx->u32ClientID, VBOX_SHARED_CLIPBOARD_FMT_BITMAP,
701 lp, GlobalSize(hClip));
702 GlobalUnlock(hClip);
703 }
704 else
705 {
706 hClip = NULL;
707 }
708 }
709 }
710 else if (uFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
711 {
712 hClip = GetClipboardData(CF_UNICODETEXT);
713 if (hClip != NULL)
714 {
715 LPWSTR uniString = (LPWSTR)GlobalLock(hClip);
716 if (uniString != NULL)
717 {
718 rc = VbglR3ClipboardWriteData(pCtx->u32ClientID, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
719 uniString, (lstrlenW(uniString) + 1) * 2);
720 GlobalUnlock(hClip);
721 }
722 else
723 {
724 hClip = NULL;
725 }
726 }
727 }
728 else if (uFormat == VBOX_SHARED_CLIPBOARD_FMT_HTML)
729 {
730 UINT format = RegisterClipboardFormat(VBOX_CLIPBOARD_WIN_REGFMT_HTML);
731 if (format != 0)
732 {
733 hClip = GetClipboardData(format);
734 if (hClip != NULL)
735 {
736 LPVOID lp = GlobalLock(hClip);
737
738 if (lp != NULL)
739 {
740 rc = VbglR3ClipboardWriteData(pCtx->u32ClientID, VBOX_SHARED_CLIPBOARD_FMT_HTML,
741 lp, GlobalSize(hClip));
742 GlobalUnlock(hClip);
743 }
744 else
745 {
746 hClip = NULL;
747 }
748 }
749 }
750 }
751
752 if (hClip == NULL)
753 {
754 LogFunc(("VBOX_WM_SHCLPB_READ_DATA: hClip=NULL, lastError=%ld\n", GetLastError()));
755
756 /* Requested clipboard format is not available, send empty data. */
757 VbglR3ClipboardWriteData(pCtx->u32ClientID, VBOX_SHARED_CLIPBOARD_FMT_NONE, NULL, 0);
758#ifdef DEBUG_andy
759 AssertFailed();
760#endif
761 }
762
763 VBoxClipboardWinClose();
764 }
765 break;
766 }
767
768#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
769 /* The host wants to read URI data. */
770 case VBOX_CLIPBOARD_WM_URI_START_READ:
771 {
772 LogFunc(("VBOX_CLIPBOARD_WM_URI_START_READ: cTransfersRunning=%RU32\n",
773 SharedClipboardURICtxGetRunningTransfers(&pCtx->URI)));
774
775 int rc = VBoxClipboardWinOpen(hwnd);
776 if (RT_SUCCESS(rc))
777 {
778 PSHAREDCLIPBOARDURITRANSFER pTransfer;
779 rc = SharedClipboardURITransferCreate(SHAREDCLIPBOARDURITRANSFERDIR_WRITE,
780 SHAREDCLIPBOARDSOURCE_LOCAL,
781 &pTransfer);
782 if (RT_SUCCESS(rc))
783 {
784 rc = SharedClipboardURICtxTransferAdd(&pCtx->URI, pTransfer);
785 if (RT_SUCCESS(rc))
786 {
787 /* The data data in CF_HDROP format, as the files are locally present and don't need to be
788 * presented as a IDataObject or IStream. */
789 HANDLE hClip = hClip = GetClipboardData(CF_HDROP);
790 if (hClip)
791 {
792 HDROP hDrop = (HDROP)GlobalLock(hClip);
793 if (hDrop)
794 {
795 char *papszList;
796 uint32_t cbList;
797 rc = VBoxClipboardWinDropFilesToStringList((DROPFILES *)hDrop, &papszList, &cbList);
798
799 GlobalUnlock(hClip);
800
801 if (RT_SUCCESS(rc))
802 {
803 rc = SharedClipboardURILTransferSetRoots(pTransfer,
804 papszList, cbList + 1 /* Include termination */);
805 if (RT_SUCCESS(rc))
806 {
807 PVBOXCLIPBOARDURIWRITETHREADCTX pThreadCtx
808 = (PVBOXCLIPBOARDURIWRITETHREADCTX)RTMemAllocZ(sizeof(VBOXCLIPBOARDURIWRITETHREADCTX));
809 if (pThreadCtx)
810 {
811 pThreadCtx->pClipboardCtx = pCtx;
812 pThreadCtx->pTransfer = pTransfer;
813
814 rc = SharedClipboardURITransferPrepare(pTransfer);
815 if (RT_SUCCESS(rc))
816 {
817 rc = SharedClipboardURITransferRun(pTransfer, vboxClipboardURIWriteThread,
818 pThreadCtx /* pvUser */);
819 /* pThreadCtx now is owned by vboxClipboardURIWriteThread(). */
820 }
821 }
822 else
823 rc = VERR_NO_MEMORY;
824 }
825
826 if (papszList)
827 RTStrFree(papszList);
828 }
829 }
830 else
831 {
832 hClip = NULL;
833 }
834 }
835 }
836 }
837
838 VBoxClipboardWinClose();
839 }
840
841 if (RT_FAILURE(rc))
842 LogFunc(("VBOX_CLIPBOARD_WM_URI_START_READ: Failed with rc=%Rrc\n", rc));
843 break;
844 }
845
846 /* The host wants to write URI data. */
847 case VBOX_CLIPBOARD_WM_URI_START_WRITE:
848 {
849 LogFunc(("VBOX_CLIPBOARD_WM_URI_START_WRITE: cTransfersRunning=%RU32\n",
850 SharedClipboardURICtxGetRunningTransfers(&pCtx->URI)));
851
852 PSHAREDCLIPBOARDURITRANSFER pTransfer;
853 int rc = SharedClipboardURITransferCreate(SHAREDCLIPBOARDURITRANSFERDIR_READ,
854 SHAREDCLIPBOARDSOURCE_LOCAL,
855 &pTransfer);
856 if (RT_SUCCESS(rc))
857 {
858 SHAREDCLIPBOARDURITRANSFERCALLBACKS TransferCallbacks;
859 RT_ZERO(TransferCallbacks);
860
861 TransferCallbacks.pvUser = &pCtx->URI;
862 TransferCallbacks.pfnTransferComplete = vboxClipboardURITransferCompleteCallback;
863 TransferCallbacks.pfnTransferError = vboxClipboardURITransferErrorCallback;
864
865 SharedClipboardURITransferSetCallbacks(pTransfer, &TransferCallbacks);
866
867 SHAREDCLIPBOARDPROVIDERCREATIONCTX creationCtx;
868 RT_ZERO(creationCtx);
869 creationCtx.enmSource = SHAREDCLIPBOARDSOURCE_REMOTE;
870
871 RT_ZERO(creationCtx.Interface);
872 creationCtx.Interface.pfnListHdrRead = vboxClipboardURIListHdrRead;
873 creationCtx.Interface.pfnListEntryRead = vboxClipboardURIListEntryRead;
874 creationCtx.Interface.pfnObjOpen = vboxClipboardURIObjOpen;
875 creationCtx.Interface.pfnObjClose = vboxClipboardURIObjClose;
876 creationCtx.Interface.pfnObjRead = vboxClipboardURIObjRead;
877
878 creationCtx.pvUser = pCtx;
879
880 rc = SharedClipboardURITransferSetInterface(pTransfer, &creationCtx);
881 if (RT_SUCCESS(rc))
882 {
883 rc = SharedClipboardURICtxTransferAdd(&pCtx->URI, pTransfer);
884 if (RT_SUCCESS(rc))
885 {
886 if (RT_SUCCESS(rc))
887 {
888 rc = SharedClipboardURITransferPrepare(pTransfer);
889 if (RT_SUCCESS(rc))
890 {
891 rc = VBoxClipboardWinURITransferCreate(pWinCtx, pTransfer);
892 }
893 }
894 }
895 }
896 }
897
898 break;
899 }
900#endif /* VBOX_WITH_SHARED_CLIPBOARD_URI_LIST */
901
902 case WM_DESTROY:
903 {
904 LogFunc(("WM_DESTROY\n"));
905
906 int rc = VBoxClipboardWinHandleWMDestroy(pWinCtx);
907 AssertRC(rc);
908
909 /*
910 * Don't need to call PostQuitMessage cause
911 * the VBoxTray already finished a message loop.
912 */
913 }
914 break;
915
916 default:
917 {
918 LogFunc(("WM_ %p\n", msg));
919 lresultRc = DefWindowProc(hwnd, msg, wParam, lParam);
920 }
921 break;
922 }
923
924 LogFunc(("WM_ rc %d\n", lresultRc));
925 return lresultRc;
926}
927
928static LRESULT CALLBACK vboxClipboardWinWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
929
930static int vboxClipboardCreateWindow(PVBOXCLIPBOARDCONTEXT pCtx)
931{
932 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
933
934 int rc = VINF_SUCCESS;
935
936 AssertPtr(pCtx->pEnv);
937 HINSTANCE hInstance = pCtx->pEnv->hInstance;
938 Assert(hInstance != 0);
939
940 /* Register the Window Class. */
941 WNDCLASSEX wc = { 0 };
942 wc.cbSize = sizeof(WNDCLASSEX);
943
944 if (!GetClassInfoEx(hInstance, s_szClipWndClassName, &wc))
945 {
946 wc.style = CS_NOCLOSE;
947 wc.lpfnWndProc = vboxClipboardWinWndProc;
948 wc.hInstance = pCtx->pEnv->hInstance;
949 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
950 wc.lpszClassName = s_szClipWndClassName;
951
952 ATOM wndClass = RegisterClassEx(&wc);
953 if (wndClass == 0)
954 rc = RTErrConvertFromWin32(GetLastError());
955 }
956
957 if (RT_SUCCESS(rc))
958 {
959 const PVBOXCLIPBOARDWINCTX pWinCtx = &pCtx->Win;
960
961 /* Create the window. */
962 pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
963 s_szClipWndClassName, s_szClipWndClassName,
964 WS_POPUPWINDOW,
965 -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
966 if (pWinCtx->hWnd == NULL)
967 {
968 rc = VERR_NOT_SUPPORTED;
969 }
970 else
971 {
972 SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0,
973 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
974
975 VBoxClipboardWinChainAdd(pWinCtx);
976 if (!VBoxClipboardWinIsNewAPI(&pWinCtx->newAPI))
977 pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000 /* 10s */, NULL);
978 }
979 }
980
981 LogFlowFuncLeaveRC(rc);
982 return rc;
983}
984
985static void vboxClipboardDestroy(PVBOXCLIPBOARDCONTEXT pCtx)
986{
987 AssertPtrReturnVoid(pCtx);
988
989 const PVBOXCLIPBOARDWINCTX pWinCtx = &pCtx->Win;
990
991 if (pWinCtx->hWnd)
992 {
993 DestroyWindow(pWinCtx->hWnd);
994 pWinCtx->hWnd = NULL;
995 }
996
997 UnregisterClass(s_szClipWndClassName, pCtx->pEnv->hInstance);
998}
999
1000static LRESULT CALLBACK vboxClipboardWinWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1001{
1002 PVBOXCLIPBOARDCONTEXT pCtx = &g_Ctx; /** @todo r=andy Make pCtx available through SetWindowLongPtr() / GWL_USERDATA. */
1003 AssertPtr(pCtx);
1004
1005 /* Forward with proper context. */
1006 return vboxClipboardWinProcessMsg(pCtx, hWnd, uMsg, wParam, lParam);
1007}
1008
1009DECLCALLBACK(int) VBoxClipboardInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
1010{
1011 LogFlowFuncEnter();
1012
1013 PVBOXCLIPBOARDCONTEXT pCtx = &g_Ctx; /* Only one instance for now. */
1014 AssertPtr(pCtx);
1015
1016 if (pCtx->pEnv)
1017 {
1018 /* Clipboard was already initialized. 2 or more instances are not supported. */
1019 return VERR_NOT_SUPPORTED;
1020 }
1021
1022 if (VbglR3AutoLogonIsRemoteSession())
1023 {
1024 /* Do not use clipboard for remote sessions. */
1025 LogRel(("Clipboard: Clipboard has been disabled for a remote session\n"));
1026 return VERR_NOT_SUPPORTED;
1027 }
1028
1029 pCtx->pEnv = pEnv;
1030
1031 int rc = VINF_SUCCESS;
1032
1033#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1034 HRESULT hr = OleInitialize(NULL);
1035 if (FAILED(hr))
1036 {
1037 LogRel(("Clipboard: Initializing OLE failed (%Rhrc) -- file transfers unavailable\n"));
1038 /* Not critical, the rest of the clipboard might work. */
1039 }
1040 else
1041 LogRel(("Clipboard: Initialized OLE\n"));
1042#endif
1043
1044 if (RT_SUCCESS(rc))
1045 {
1046 /* Check if new Clipboard API is available. */
1047 /* ignore rc */ VBoxClipboardWinCheckAndInitNewAPI(&pCtx->Win.newAPI);
1048
1049 rc = VbglR3ClipboardConnect(&pCtx->u32ClientID);
1050 if (RT_SUCCESS(rc))
1051 {
1052 rc = vboxClipboardCreateWindow(pCtx);
1053 if (RT_SUCCESS(rc))
1054 {
1055#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1056 rc = SharedClipboardURICtxInit(&pCtx->URI);
1057 if (RT_SUCCESS(rc))
1058#endif
1059 *ppInstance = pCtx;
1060 }
1061 else
1062 {
1063 VbglR3ClipboardDisconnect(pCtx->u32ClientID);
1064 }
1065 }
1066 }
1067
1068 LogFlowFuncLeaveRC(rc);
1069 return rc;
1070}
1071
1072DECLCALLBACK(int) VBoxClipboardWorker(void *pInstance, bool volatile *pfShutdown)
1073{
1074 AssertPtr(pInstance);
1075 LogFlowFunc(("pInstance=%p\n", pInstance));
1076
1077 /*
1078 * Tell the control thread that it can continue
1079 * spawning services.
1080 */
1081 RTThreadUserSignal(RTThreadSelf());
1082
1083 const PVBOXCLIPBOARDCONTEXT pCtx = (PVBOXCLIPBOARDCONTEXT)pInstance;
1084 AssertPtr(pCtx);
1085
1086 const PVBOXCLIPBOARDWINCTX pWinCtx = &pCtx->Win;
1087
1088 int rc;
1089
1090 /* The thread waits for incoming messages from the host. */
1091 for (;;)
1092 {
1093 LogFlowFunc(("Waiting for host message ...\n"));
1094
1095 uint32_t u32Msg;
1096 uint32_t u32Formats;
1097 rc = VbglR3ClipboardGetHostMsgOld(pCtx->u32ClientID, &u32Msg, &u32Formats);
1098 if (RT_FAILURE(rc))
1099 {
1100 if (rc == VERR_INTERRUPTED)
1101 break;
1102
1103 LogFunc(("Error getting host message, rc=%Rrc\n", rc));
1104
1105 if (*pfShutdown)
1106 break;
1107
1108 /* Wait a bit before retrying. */
1109 RTThreadSleep(1000);
1110 continue;
1111 }
1112 else
1113 {
1114 LogFlowFunc(("u32Msg=%RU32, u32Formats=0x%x\n", u32Msg, u32Formats));
1115 switch (u32Msg)
1116 {
1117 case VBOX_SHARED_CLIPBOARD_HOST_MSG_REPORT_FORMATS:
1118 {
1119 LogFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_REPORT_FORMATS\n"));
1120
1121 /* The host has announced available clipboard formats.
1122 * Forward the information to the window, so it can later
1123 * respond to WM_RENDERFORMAT message. */
1124 ::PostMessage(pWinCtx->hWnd, VBOX_CLIPBOARD_WM_SET_FORMATS, 0, u32Formats);
1125 break;
1126 }
1127
1128 case VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
1129 {
1130 LogFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA\n"));
1131
1132 /* The host needs data in the specified format. */
1133 ::PostMessage(pWinCtx->hWnd, VBOX_CLIPBOARD_WM_READ_DATA, 0, u32Formats);
1134 break;
1135 }
1136
1137#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1138 case VBOX_SHARED_CLIPBOARD_HOST_MSG_URI_TRANSFER_START:
1139 {
1140 LogFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_URI_TRANSFER_START\n"));
1141
1142 const UINT uMsg = u32Formats == 0 ?
1143 VBOX_CLIPBOARD_WM_URI_START_READ : VBOX_CLIPBOARD_WM_URI_START_WRITE;
1144
1145 ::PostMessage(pWinCtx->hWnd, uMsg, 0 /* wParm */, 0 /* lParm */);
1146 break;
1147 }
1148#endif
1149 case VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT:
1150 {
1151 LogFlowFunc(("VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT\n"));
1152
1153 /* The host is terminating. */
1154 LogRel(("Clipboard: Terminating ...\n"));
1155 ASMAtomicXchgBool(pfShutdown, true);
1156 break;
1157 }
1158
1159 default:
1160 {
1161 LogFlowFunc(("Unsupported message from host, message=%RU32\n", u32Msg));
1162
1163 /* Wait a bit before retrying. */
1164 RTThreadSleep(1000);
1165 break;
1166 }
1167 }
1168 }
1169
1170 if (*pfShutdown)
1171 break;
1172 }
1173
1174 LogFlowFuncLeaveRC(rc);
1175 return rc;
1176}
1177
1178DECLCALLBACK(int) VBoxClipboardStop(void *pInstance)
1179{
1180 AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
1181
1182 LogFunc(("Stopping pInstance=%p\n", pInstance));
1183
1184 PVBOXCLIPBOARDCONTEXT pCtx = (PVBOXCLIPBOARDCONTEXT)pInstance;
1185 AssertPtr(pCtx);
1186
1187 VbglR3ClipboardDisconnect(pCtx->u32ClientID);
1188 pCtx->u32ClientID = 0;
1189
1190 LogFlowFuncLeaveRC(VINF_SUCCESS);
1191 return VINF_SUCCESS;
1192}
1193
1194DECLCALLBACK(void) VBoxClipboardDestroy(void *pInstance)
1195{
1196 AssertPtrReturnVoid(pInstance);
1197
1198 PVBOXCLIPBOARDCONTEXT pCtx = (PVBOXCLIPBOARDCONTEXT)pInstance;
1199 AssertPtr(pCtx);
1200
1201 /* Make sure that we are disconnected. */
1202 Assert(pCtx->u32ClientID == 0);
1203
1204 vboxClipboardDestroy(pCtx);
1205
1206#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1207 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
1208 OleUninitialize();
1209
1210 SharedClipboardURICtxDestroy(&pCtx->URI);
1211#endif
1212
1213 return;
1214}
1215
1216/**
1217 * The service description.
1218 */
1219VBOXSERVICEDESC g_SvcDescClipboard =
1220{
1221 /* pszName. */
1222 "clipboard",
1223 /* pszDescription. */
1224 "Shared Clipboard",
1225 /* methods */
1226 VBoxClipboardInit,
1227 VBoxClipboardWorker,
1228 VBoxClipboardStop,
1229 VBoxClipboardDestroy
1230};
1231
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