VirtualBox

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

Last change on this file since 103367 was 103367, checked in by vboxsync, 12 months ago

Shared Clipboard/Additions: Removed lots of code duplication for reading clipboard data from the host (partly introduced by r159772) [build fix].

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 40.9 KB
Line 
1/* $Id: VBoxClipboard.cpp 103367 2024-02-14 18:47:32Z 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) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx);
96static DECLCALLBACK(void) vbtrShClTransferDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx);
97static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx);
98static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx);
99static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc);
100#endif
101
102
103#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
104/**
105 * @copydoc SharedClipboardWinDataObject::CALLBACKS::pfnTransferBegin
106 *
107 * Called by SharedClipboardWinDataObject::GetData() when the user wants to paste data.
108 * This then requests a new transfer on the host.
109 *
110 * @thread Clipboard main thread.
111 */
112static DECLCALLBACK(int) vbtrShClDataObjectTransferBeginCallback(SharedClipboardWinDataObject::PCALLBACKCTX pCbCtx)
113{
114 LogFlowFuncEnter();
115
116 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
117 AssertPtr(pCtx);
118
119 int rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx);
120
121 LogFlowFuncLeaveRC(rc);
122 return rc;
123}
124
125/**
126 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated
127 *
128 * Called by ShClTransferCreate via VbglR3.
129 *
130 * @thread Clipboard main thread.
131 */
132static DECLCALLBACK(void) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
133{
134 LogFlowFuncEnter();
135
136 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
137 AssertPtr(pCtx);
138
139 int rc = SharedClipboardWinTransferCreate(&pCtx->Win, pCbCtx->pTransfer);
140
141 LogFlowFuncLeaveRC(rc);
142}
143
144/**
145 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnDestroy
146 *
147 * Called by ShClTransferDestroy via VbglR3.
148 *
149 * @thread Clipboard main thread.
150 */
151static DECLCALLBACK(void) vbtrShClTransferDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
152{
153 LogFlowFuncEnter();
154
155 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
156 AssertPtr(pCtx);
157
158 SharedClipboardWinTransferDestroy(&pCtx->Win, pCbCtx->pTransfer);
159
160 LogFlowFuncLeave();
161}
162
163/**
164 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized
165 *
166 * Called by ShClTransferInit via VbglR3.
167 * For G->H: Called on transfer intialization to notify the "in-flight" IDataObject about a data transfer.
168 * For H->G: Called on transfer intialization to populate the transfer's root list.
169 *
170 * @thread Clipboard main thread.
171 */
172static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
173{
174 LogFlowFuncEnter();
175
176 int rc = VINF_SUCCESS;
177
178 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
179 AssertPtr(pCtx);
180
181 switch(ShClTransferGetDir(pCbCtx->pTransfer))
182 {
183 case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */
184 {
185 SharedClipboardWinDataObject *pObj = pCtx->Win.pDataObjInFlight;
186 if (pObj)
187 {
188 rc = pObj->SetTransfer(pCbCtx->pTransfer);
189 if (RT_SUCCESS(rc))
190 rc = pObj->SetStatus(SharedClipboardWinDataObject::Running);
191
192 pCtx->Win.pDataObjInFlight = NULL; /* Hand off to Windows. */
193 }
194 else
195 AssertMsgFailed(("No data object in flight!\n"));
196
197 break;
198 }
199
200 case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */
201 {
202 rc = SharedClipboardWinTransferGetRootsFromClipboard(&pCtx->Win, pCbCtx->pTransfer);
203 break;
204 }
205
206 default:
207 break;
208 }
209
210 LogFlowFuncLeaveRC(rc);
211}
212
213/**
214 * Worker for a reading clipboard from the host.
215 *
216 * @returns VBox status code.
217 * @retval VERR_SHCLPB_NO_DATA if no clipboard data is available.
218 * @param pCtx Shared Clipbaord context to use.
219 * @param uFmt The format to read clipboard data in.
220 * @param ppvData Where to return the allocated data read.
221 * Must be free'd by the caller.
222 * @param pcbData Where to return number of bytes read.
223 * @param pvUser User-supplied context.
224 *
225 * @thread Clipboard main thread.
226 *
227 */
228static DECLCALLBACK(int) vbtrShClRequestDataFromSourceCallbackWorker(PSHCLCONTEXT pCtx,
229 SHCLFORMAT uFmt, void **ppvData, uint32_t *pcbData, void *pvUser)
230{
231 RT_NOREF(pvUser);
232
233 return VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, ppvData, pcbData);
234}
235
236/**
237 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
238 *
239 * Called from the IDataObject implementation to request data from the host.
240 *
241 * @thread shclwnd thread.
242 */
243DECLCALLBACK(int) vbtrShClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
244 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
245{
246 PRTREQ pReq = NULL;
247 int rc = RTReqQueueCallEx(pCtx->Win.hReqQ, &pReq, SHCL_TIMEOUT_DEFAULT_MS, RTREQFLAGS_IPRT_STATUS,
248 (PFNRT)vbtrShClRequestDataFromSourceCallbackWorker, 5, pCtx, uFmt, ppv, pcb, pvUser);
249 RTReqRelease(pReq);
250 return rc;
251}
252
253/**
254 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnStart
255 *
256 * Called from VbglR3 (main thread) to notify the IDataObject.
257 *
258 * @thread Clipboard main thread.
259 */
260static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
261{
262 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
263 AssertPtr(pCtx);
264
265 PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
266 AssertPtr(pTransfer);
267
268 SHCLTRANSFERDIR const enmDir = ShClTransferGetDir(pTransfer);
269
270 int rc = VINF_SUCCESS;
271
272 /* The guest wants to transfer data to the host. */
273 if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* G->H */
274 {
275 rc = SharedClipboardWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer);
276 }
277 else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */
278 {
279 /* Nothing to do here. */
280 }
281 else
282 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
283
284 if (RT_FAILURE(rc))
285 LogRel(("Shared Clipboard: Starting transfer failed, rc=%Rrc\n", rc));
286}
287
288/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnCompleted */
289static DECLCALLBACK(void) vbtrShClTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcCompletion)
290{
291 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
292 AssertPtr(pCtx);
293
294 LogFlowFunc(("rcCompletion=%Rrc\n", rcCompletion));
295
296 LogRel2(("Shared Clipboard: Transfer %RU16 %s\n",
297 ShClTransferGetID(pCbCtx->pTransfer), rcCompletion == VERR_CANCELLED ? "canceled" : "complete"));
298
299 SHCLTRANSFERSTATUS enmSts;
300
301 switch (rcCompletion)
302 {
303 case VERR_CANCELLED:
304 enmSts = SHCLTRANSFERSTATUS_CANCELED;
305 break;
306
307 case VINF_SUCCESS:
308 enmSts = SHCLTRANSFERSTATUS_COMPLETED;
309 break;
310
311 default:
312 AssertFailedStmt(enmSts = SHCLTRANSFERSTATUS_ERROR);
313 break;
314 }
315
316 int rc = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pCbCtx->pTransfer, enmSts, rcCompletion);
317 LogFlowFuncLeaveRC(rc);
318}
319
320/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnError */
321static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcError)
322{
323 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
324 AssertPtr(pCtx);
325
326 LogRel(("Shared Clipboard: Transfer %RU16 failed with %Rrc\n", ShClTransferGetID(pCbCtx->pTransfer), rcError));
327
328 if (g_cVerbosity) /* Only show this in verbose mode. */
329 {
330 char szMsg [256]; /* Sizes according to MSDN. */
331 char szTitle[64];
332
333 /** @todo Add some translation macros here. */
334 RTStrPrintf(szTitle, sizeof(szTitle), "VirtualBox Shared Clipboard");
335 RTStrPrintf(szMsg, sizeof(szMsg),
336 "Transfer %RU16 failed with %Rrc", ShClTransferGetID(pCbCtx->pTransfer), rcError);
337
338 hlpShowBalloonTip(g_hInstance, g_hwndToolWindow, ID_TRAYICON,
339 szMsg, szTitle,
340 5000 /* Time to display in msec */, NIIF_INFO);
341 }
342
343 int rc = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pCbCtx->pTransfer, SHCLTRANSFERSTATUS_ERROR, rcError);
344 LogFlowFuncLeaveRC(rc);
345}
346#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
347
348static LRESULT vbtrShClWndProcWorker(PSHCLCONTEXT pCtx, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
349{
350 AssertPtr(pCtx);
351
352 const PSHCLWINCTX pWinCtx = &pCtx->Win;
353
354 LRESULT lresultRc = 0;
355
356 switch (msg)
357 {
358 case WM_CLIPBOARDUPDATE:
359 {
360 LogFunc(("WM_CLIPBOARDUPDATE: pWinCtx=%p\n", pWinCtx));
361
362 if (pCtx->fShutdown) /* If we're about to shut down, skip handling stuff here. */
363 break;
364
365 int rc = RTCritSectEnter(&pWinCtx->CritSect);
366 if (RT_SUCCESS(rc))
367 {
368 const HWND hWndClipboardOwner = GetClipboardOwner();
369
370 LogFunc(("WM_CLIPBOARDUPDATE: hWndOldClipboardOwner=%p, hWndNewClipboardOwner=%p\n",
371 pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner));
372
373 if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner)
374 {
375 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
376 AssertRC(rc2);
377
378 /* Clipboard was updated by another application.
379 * Report available formats to the host. */
380 SHCLFORMATS fFormats;
381 rc = SharedClipboardWinGetFormats(pWinCtx, &fFormats);
382 if (RT_SUCCESS(rc))
383 {
384 LogFunc(("WM_CLIPBOARDUPDATE: Reporting formats %#x\n", fFormats));
385 rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
386 }
387 }
388 else
389 {
390 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
391 AssertRC(rc2);
392 }
393 }
394
395 if (RT_FAILURE(rc))
396 LogRel(("Shared Clipboard: WM_CLIPBOARDUPDATE failed with %Rrc\n", rc));
397
398 break;
399 }
400
401 case WM_CHANGECBCHAIN:
402 {
403 LogFunc(("WM_CHANGECBCHAIN\n"));
404 lresultRc = SharedClipboardWinHandleWMChangeCBChain(pWinCtx, hwnd, msg, wParam, lParam);
405 break;
406 }
407
408 case WM_DRAWCLIPBOARD:
409 {
410 LogFlowFunc(("WM_DRAWCLIPBOARD: pWinCtx=%p\n", pWinCtx));
411
412 int rc = RTCritSectEnter(&pWinCtx->CritSect);
413 if (RT_SUCCESS(rc))
414 {
415 const HWND hWndClipboardOwner = GetClipboardOwner();
416
417 LogFunc(("WM_DRAWCLIPBOARD: hWndClipboardOwnerUs=%p, hWndNewClipboardOwner=%p\n",
418 pWinCtx->hWndClipboardOwnerUs, hWndClipboardOwner));
419
420 if (pWinCtx->hWndClipboardOwnerUs != hWndClipboardOwner)
421 {
422 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
423 AssertRC(rc2);
424
425 /* Clipboard was updated by another application. */
426 /* WM_DRAWCLIPBOARD always expects a return code of 0, so don't change "rc" here. */
427 SHCLFORMATS fFormats;
428 rc = SharedClipboardWinGetFormats(pWinCtx, &fFormats);
429 if ( RT_SUCCESS(rc)
430 && fFormats != VBOX_SHCL_FMT_NONE)
431 rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
432 }
433 else
434 {
435 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
436 AssertRC(rc2);
437 }
438 }
439
440 lresultRc = SharedClipboardWinChainPassToNext(pWinCtx, msg, wParam, lParam);
441 break;
442 }
443
444 case WM_TIMER:
445 {
446 int rc = SharedClipboardWinHandleWMTimer(pWinCtx);
447 AssertRC(rc);
448
449 break;
450 }
451
452 case WM_CLOSE:
453 {
454 /* Do nothing. Ignore the message. */
455 break;
456 }
457
458 case WM_RENDERFORMAT: /* Guest wants to render the clipboard data. */
459 {
460 /* Insert the requested clipboard format data into the clipboard. */
461 const UINT uFmtWin = (UINT)wParam;
462 const SHCLFORMAT uFmtVBox = SharedClipboardWinClipboardFormatToVBox(uFmtWin);
463
464 LogFunc(("WM_RENDERFORMAT: uFmtWin=%u -> uFmtVBox=0x%x\n", uFmtWin, uFmtVBox));
465#ifdef LOG_ENABLED
466 char *pszFmts = ShClFormatsToStrA(uFmtVBox);
467 AssertPtrReturn(pszFmts, 0);
468 LogRel(("Shared Clipboard: Rendering Windows format %#x as VBox format '%s'\n", uFmtWin, pszFmts));
469 RTStrFree(pszFmts);
470#endif
471 if (uFmtVBox == VBOX_SHCL_FMT_NONE)
472 {
473 LogRel(("Shared Clipboard: Unsupported format (%#x) requested\n", uFmtWin));
474 SharedClipboardWinClear();
475 }
476 else
477 {
478 void *pvData = NULL;
479 uint32_t cbData;
480
481 HANDLE hMem;
482 int rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmtVBox, &pvData, &cbData);
483 if (RT_SUCCESS(rc))
484 {
485 /* Verify the size of returned text, the memory block for clipboard must have the exact string size. */
486 if (uFmtVBox == VBOX_SHCL_FMT_UNICODETEXT)
487 {
488 size_t cwcActual = 0;
489 rc = RTUtf16NLenEx((PCRTUTF16)pvData, cbData / sizeof(RTUTF16), &cwcActual);
490 if (RT_SUCCESS(rc))
491 cbData = (uint32_t)((cwcActual + 1 /* '\0' */) * sizeof(RTUTF16));
492 else
493 LogRel(("Shared Clipboard: Invalid UTF16 string from host: cb=%RU32, cwcActual=%zu, rc=%Rrc\n",
494 cbData, cwcActual, rc));
495 }
496 else if (uFmtVBox == VBOX_SHCL_FMT_HTML)
497 {
498 /* Wrap content into CF_HTML clipboard format if needed. */
499 if (!SharedClipboardWinIsCFHTML((const char *)pvData))
500 {
501 char *pszWrapped = NULL;
502 uint32_t cbWrapped = 0;
503 rc = SharedClipboardWinConvertMIMEToCFHTML((const char *)pvData, cbData, &pszWrapped, &cbWrapped);
504 if (RT_SUCCESS(rc))
505 {
506 AssertBreakStmt(cbWrapped, rc = VERR_INVALID_PARAMETER);
507 pvData = RTMemRealloc(pvData, cbWrapped);
508 if (pvData)
509 {
510 memcpy(pvData, pszWrapped, cbWrapped);
511 cbData = cbWrapped;
512 }
513 else
514 rc = VERR_NO_MEMORY;
515 RTMemFree(pszWrapped);
516 }
517
518 if (RT_FAILURE(rc))
519 LogRel(("Shared Clipboard: Cannot convert HTML clipboard data into CF_HTML clipboard format, rc=%Rrc\n", rc));
520 }
521 }
522
523 hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbData);
524 if (hMem)
525 {
526 void *pvMem = GlobalLock(hMem);
527 if (pvMem)
528 {
529 memcpy(pvMem, pvData, cbData);
530 GlobalUnlock(hMem);
531
532 HANDLE hClip = SetClipboardData(uFmtWin, hMem);
533 if (!hClip)
534 {
535 /* The hMem ownership has gone to the system. Finish the processing. */
536 break;
537 }
538 else
539 LogRel(("Shared Clipboard: Setting host data buffer to clipboard failed with %Rrc\n",
540 RTErrConvertFromWin32(GetLastError())));
541 }
542 else
543 LogRel(("Shared Clipboard: Failed to lock memory (%Rrc)\n", RTErrConvertFromWin32(GetLastError())));
544 GlobalFree(hMem);
545 }
546 else
547 LogRel(("Shared Clipboard: No memory for allocating host data buffer\n"));
548 }
549 }
550
551 break;
552 }
553
554 case WM_RENDERALLFORMATS:
555 {
556 LogFunc(("WM_RENDERALLFORMATS\n"));
557
558 int rc = SharedClipboardWinHandleWMRenderAllFormats(pWinCtx, hwnd);
559 AssertRC(rc);
560
561 break;
562 }
563
564 case SHCL_WIN_WM_REPORT_FORMATS: /* Host reported clipboard formats. */
565 {
566 LogFunc(("SHCL_WIN_WM_REPORT_FORMATS\n"));
567
568 /* Announce available formats. Do not insert data -- will be inserted in WM_RENDERFORMAT. */
569 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)lParam;
570 AssertPtr(pEvent);
571 Assert(pEvent->enmType == VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS);
572
573 const SHCLFORMATS fFormats = pEvent->u.fReportedFormats;
574
575#ifdef LOG_ENABLED
576 char *pszFmts = ShClFormatsToStrA(fFormats);
577 AssertPtrReturn(pszFmts, 0);
578 LogRel(("Shared Clipboard: Host reported formats '%s'\n", pszFmts));
579 RTStrFree(pszFmts);
580#endif
581 if (fFormats != VBOX_SHCL_FMT_NONE) /* Could arrive with some older GA versions. */
582 {
583 int rc = SharedClipboardWinClearAndAnnounceFormats(pWinCtx, fFormats, hwnd);
584#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
585 if ( RT_SUCCESS(rc)
586 && fFormats & VBOX_SHCL_FMT_URI_LIST)
587 {
588 /*
589 * Create our IDataObject implementation and push it to the Windows clibpoard.
590 * That way Windows will recognize that there is a data transfer available.
591 */
592 SharedClipboardWinDataObject::CALLBACKS Callbacks;
593 RT_ZERO(Callbacks);
594 Callbacks.pfnTransferBegin = vbtrShClDataObjectTransferBeginCallback;
595
596 rc = SharedClipboardWinTransferCreateAndSetDataObject(pWinCtx, pCtx, &Callbacks);
597 }
598#else
599 RT_NOREF(rc);
600#endif
601 }
602
603 LogFunc(("SHCL_WIN_WM_REPORT_FORMATS: fFormats=0x%x, lastErr=%ld\n", fFormats, GetLastError()));
604 break;
605 }
606
607 case SHCL_WIN_WM_READ_DATA: /* Host wants to read clipboard data from the guest. */
608 {
609 /* Send data in the specified format to the host. */
610 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)lParam;
611 AssertPtr(pEvent);
612 Assert(pEvent->enmType == VBGLR3CLIPBOARDEVENTTYPE_READ_DATA);
613
614 const SHCLFORMAT fFormat = (uint32_t)pEvent->u.fReadData;
615
616 LogFlowFunc(("SHCL_WIN_WM_READ_DATA: fFormat=%#x\n", fFormat));
617#ifdef LOG_ENABLED
618 char *pszFmts = ShClFormatsToStrA(fFormat);
619 AssertPtrReturn(pszFmts, 0);
620 LogRel(("Shared Clipboard: Sending data to host as '%s'\n", pszFmts));
621 RTStrFree(pszFmts);
622#endif
623 int rc = SharedClipboardWinOpen(hwnd);
624 HANDLE hClip = NULL;
625 if (RT_SUCCESS(rc))
626 {
627 if (fFormat & VBOX_SHCL_FMT_BITMAP)
628 {
629 hClip = GetClipboardData(CF_DIB);
630 if (hClip != NULL)
631 {
632 LPVOID lp = GlobalLock(hClip);
633 if (lp != NULL)
634 {
635 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, lp, (uint32_t)GlobalSize(hClip));
636
637 GlobalUnlock(hClip);
638 }
639 else
640 hClip = NULL;
641 }
642 }
643 else if (fFormat & VBOX_SHCL_FMT_UNICODETEXT)
644 {
645 hClip = GetClipboardData(CF_UNICODETEXT);
646 if (hClip != NULL)
647 {
648 LPWSTR uniString = (LPWSTR)GlobalLock(hClip);
649 if (uniString != NULL)
650 {
651 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx,
652 fFormat, uniString, ((uint32_t)lstrlenW(uniString) + 1) * 2);
653
654 GlobalUnlock(hClip);
655 }
656 else
657 hClip = NULL;
658 }
659 }
660 else if (fFormat & VBOX_SHCL_FMT_HTML)
661 {
662 UINT format = RegisterClipboardFormat(SHCL_WIN_REGFMT_HTML);
663 if (format != 0)
664 {
665 hClip = GetClipboardData(format);
666 if (hClip != NULL)
667 {
668 LPVOID const pvClip = GlobalLock(hClip);
669 if (pvClip != NULL)
670 {
671 uint32_t const cbClip = (uint32_t)GlobalSize(hClip);
672
673 /* Unwrap clipboard content from CF_HTML format if needed. */
674 if (SharedClipboardWinIsCFHTML((const char *)pvClip))
675 {
676 char *pszBuf = NULL;
677 uint32_t cbBuf = 0;
678 rc = SharedClipboardWinConvertCFHTMLToMIME((const char *)pvClip, cbClip, &pszBuf, &cbBuf);
679 if (RT_SUCCESS(rc))
680 {
681 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pszBuf, cbBuf);
682 RTMemFree(pszBuf);
683 }
684 else
685 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pvClip, cbClip);
686 }
687 else
688 rc = VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, fFormat, pvClip, cbClip);
689
690 GlobalUnlock(hClip);
691 }
692 else
693 hClip = NULL;
694 }
695 }
696 }
697
698 if (hClip == NULL)
699 LogFunc(("SHCL_WIN_WM_READ_DATA: hClip=NULL, lastError=%ld\n", GetLastError()));
700
701 SharedClipboardWinClose();
702 }
703
704 /* If the requested clipboard format is not available, we must send empty data. */
705 if (hClip == NULL)
706 VbglR3ClipboardWriteDataEx(&pEvent->cmdCtx, VBOX_SHCL_FMT_NONE, NULL, 0);
707 break;
708 }
709
710 case WM_DESTROY:
711 {
712 LogFunc(("WM_DESTROY\n"));
713
714 int rc = SharedClipboardWinHandleWMDestroy(pWinCtx);
715 AssertRC(rc);
716
717 /*
718 * Don't need to call PostQuitMessage cause
719 * the VBoxTray already finished a message loop.
720 */
721
722 break;
723 }
724
725 default:
726 {
727 LogFunc(("WM_ %p\n", msg));
728 lresultRc = DefWindowProc(hwnd, msg, wParam, lParam);
729 break;
730 }
731 }
732
733 LogFunc(("WM_ rc %d\n", lresultRc));
734 return lresultRc;
735}
736
737static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
738
739static int vbtrShClCreateWindow(PSHCLCONTEXT pCtx)
740{
741 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
742
743 int rc = VINF_SUCCESS;
744
745 AssertPtr(pCtx->pEnv);
746 HINSTANCE hInstance = pCtx->pEnv->hInstance;
747 Assert(hInstance != 0);
748
749 /* Register the Window Class. */
750 WNDCLASSEX wc;
751 RT_ZERO(wc);
752
753 wc.cbSize = sizeof(WNDCLASSEX);
754
755 if (!GetClassInfoEx(hInstance, s_szClipWndClassName, &wc))
756 {
757 wc.style = CS_NOCLOSE;
758 wc.lpfnWndProc = vbtrShClWndProc;
759 wc.hInstance = pCtx->pEnv->hInstance;
760 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
761 wc.lpszClassName = s_szClipWndClassName;
762
763 ATOM wndClass = RegisterClassEx(&wc);
764 if (wndClass == 0)
765 rc = RTErrConvertFromWin32(GetLastError());
766 }
767
768 if (RT_SUCCESS(rc))
769 {
770 const PSHCLWINCTX pWinCtx = &pCtx->Win;
771
772 /* Create the window. */
773 pWinCtx->hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
774 s_szClipWndClassName, s_szClipWndClassName,
775 WS_POPUPWINDOW,
776 -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
777 if (pWinCtx->hWnd == NULL)
778 {
779 rc = VERR_NOT_SUPPORTED;
780 }
781 else
782 {
783 SetWindowPos(pWinCtx->hWnd, HWND_TOPMOST, -200, -200, 0, 0,
784 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
785
786 rc = SharedClipboardWinChainAdd(pWinCtx);
787 if (RT_SUCCESS(rc))
788 {
789 if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI))
790 pWinCtx->oldAPI.timerRefresh = SetTimer(pWinCtx->hWnd, 0, 10 * 1000 /* 10s */, NULL);
791 }
792 }
793 }
794
795 LogFlowFuncLeaveRC(rc);
796 return rc;
797}
798
799static DECLCALLBACK(int) vbtrShClWindowThread(RTTHREAD hThread, void *pvUser)
800{
801 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser;
802 AssertPtr(pCtx);
803
804#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
805 HRESULT hr = OleInitialize(NULL);
806 if (FAILED(hr))
807 {
808 LogRel(("Shared Clipboard: Initializing OLE in window thread failed (%Rhrc) -- file transfers unavailable\n", hr));
809 /* Not critical, the rest of the clipboard might work. */
810 }
811 else
812 LogRel(("Shared Clipboard: Initialized OLE in window thread\n"));
813#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
814
815 int rc = vbtrShClCreateWindow(pCtx);
816 if (RT_FAILURE(rc))
817 {
818 LogRel(("Shared Clipboard: Unable to create window, rc=%Rrc\n", rc));
819 return rc;
820 }
821
822 pCtx->fStarted = true; /* Set started indicator. */
823
824 int rc2 = RTThreadUserSignal(hThread);
825 bool fSignalled = RT_SUCCESS(rc2);
826
827 LogRel2(("Shared Clipboard: Window thread running\n"));
828
829 if (RT_SUCCESS(rc))
830 {
831 for (;;)
832 {
833 MSG uMsg;
834 BOOL fRet;
835 while ((fRet = GetMessage(&uMsg, 0, 0, 0)) > 0)
836 {
837 TranslateMessage(&uMsg);
838 DispatchMessage(&uMsg);
839 }
840 Assert(fRet >= 0);
841
842 if (ASMAtomicReadBool(&pCtx->fShutdown))
843 break;
844
845 /** @todo Immediately drop on failure? */
846 }
847 }
848
849 if (!fSignalled)
850 {
851 rc2 = RTThreadUserSignal(hThread);
852 AssertRC(rc2);
853 }
854
855#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
856 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
857 OleUninitialize();
858#endif
859
860 LogRel(("Shared Clipboard: Window thread ended\n"));
861
862 LogFlowFuncLeaveRC(rc);
863 return rc;
864}
865
866static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
867{
868 PSHCLCONTEXT pCtx = &g_Ctx; /** @todo r=andy Make pCtx available through SetWindowLongPtr() / GWL_USERDATA. */
869 AssertPtr(pCtx);
870
871 /* Forward with proper context. */
872 return vbtrShClWndProcWorker(pCtx, hWnd, uMsg, wParam, lParam);
873}
874
875DECLCALLBACK(int) vbtrShClInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
876{
877 LogFlowFuncEnter();
878
879 PSHCLCONTEXT pCtx = &g_Ctx; /* Only one instance for now. */
880 AssertPtr(pCtx);
881
882 if (pCtx->pEnv)
883 {
884 /* Clipboard was already initialized. 2 or more instances are not supported. */
885 return VERR_NOT_SUPPORTED;
886 }
887
888 if (VbglR3AutoLogonIsRemoteSession())
889 {
890 /* Do not use clipboard for remote sessions. */
891 LogRel(("Shared Clipboard: Clipboard has been disabled for a remote session\n"));
892 return VERR_NOT_SUPPORTED;
893 }
894
895 pCtx->pEnv = pEnv;
896 pCtx->hThread = NIL_RTTHREAD;
897 pCtx->fStarted = false;
898 pCtx->fShutdown = false;
899
900 int rc = RTReqQueueCreate(&pCtx->Win.hReqQ);
901 AssertRCReturn(rc, rc);
902
903 rc = SharedClipboardWinCtxInit(&pCtx->Win);
904 if (RT_SUCCESS(rc))
905 rc = VbglR3ClipboardConnectEx(&pCtx->CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
906 if (RT_SUCCESS(rc))
907 {
908#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
909 rc = ShClTransferCtxInit(&pCtx->TransferCtx);
910#endif
911 if (RT_SUCCESS(rc))
912 {
913 /* Message pump thread for our proxy window. */
914 rc = RTThreadCreate(&pCtx->hThread, vbtrShClWindowThread, pCtx /* pvUser */,
915 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
916 "shclwnd");
917 if (RT_SUCCESS(rc))
918 {
919 int rc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */);
920 AssertRC(rc2);
921
922 if (!pCtx->fStarted) /* Did the thread fail to start? */
923 rc = VERR_NOT_SUPPORTED; /* Report back Shared Clipboard as not being supported. */
924 }
925 }
926
927 if (RT_SUCCESS(rc))
928 {
929 *ppInstance = pCtx;
930 }
931 else
932 VbglR3ClipboardDisconnectEx(&pCtx->CmdCtx);
933 }
934
935 if (RT_FAILURE(rc))
936 LogRel(("Shared Clipboard: Unable to initialize, rc=%Rrc\n", rc));
937
938 LogFlowFuncLeaveRC(rc);
939 return rc;
940}
941
942DECLCALLBACK(int) vbtrShClWorker(void *pInstance, bool volatile *pfShutdown)
943{
944 AssertPtr(pInstance);
945 LogFlowFunc(("pInstance=%p\n", pInstance));
946
947 /*
948 * Tell the control thread that it can continue
949 * spawning services.
950 */
951 RTThreadUserSignal(RTThreadSelf());
952
953 const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
954 AssertPtr(pCtx);
955
956 const PSHCLWINCTX pWinCtx = &pCtx->Win;
957
958 LogRel2(("Shared Clipboard: Worker loop running\n"));
959
960#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
961 HRESULT hr = OleInitialize(NULL);
962 if (FAILED(hr))
963 {
964 LogRel(("Shared Clipboard: Initializing OLE in worker thread failed (%Rhrc) -- file transfers unavailable\n", hr));
965 /* Not critical, the rest of the clipboard might work. */
966 }
967 else
968 LogRel(("Shared Clipboard: Initialized OLE in worker thread\n"));
969
970 /*
971 * Init callbacks.
972 * Those will be registered within VbglR3 when a new transfer gets initialized.
973 */
974 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
975
976 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
977 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
978
979 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCreated = vbtrShClTransferCreatedCallback;
980 pCtx->CmdCtx.Transfers.Callbacks.pfnOnDestroy = vbtrShClTransferDestroyCallback;
981 pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbtrShClTransferInitializedCallback;
982 pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback;
983 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback;
984 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback;
985#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
986
987 int rc;
988
989 /* The thread waits for incoming messages from the host. */
990 PVBGLR3CLIPBOARDEVENT pEvent = NULL;
991 for (;;)
992 {
993 LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
994 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
995
996 if (!pEvent)
997 pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
998 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
999
1000 uint32_t idMsg = 0;
1001 uint32_t cParms = 0;
1002 rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
1003 if (RT_SUCCESS(rc))
1004 {
1005#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1006 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
1007#else
1008 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
1009#endif
1010 }
1011 else if (rc == VERR_TRY_AGAIN) /* No new message (yet). */
1012 {
1013 RTReqQueueProcess(pCtx->Win.hReqQ, RT_MS_1SEC);
1014 continue;
1015 }
1016
1017 if (RT_FAILURE(rc))
1018 {
1019 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
1020
1021 VbglR3ClipboardEventFree(pEvent);
1022 pEvent = NULL;
1023
1024 if (*pfShutdown)
1025 break;
1026
1027 /* Wait a bit before retrying. */
1028 RTThreadSleep(1000);
1029 continue;
1030 }
1031 else
1032 {
1033 AssertPtr(pEvent);
1034 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
1035
1036 switch (pEvent->enmType)
1037 {
1038 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
1039 {
1040 /* The host has announced available clipboard formats.
1041 * Forward the information to the window, so it can later
1042 * respond to WM_RENDERFORMAT message. */
1043 ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_REPORT_FORMATS,
1044 0 /* wParam */, (LPARAM)pEvent /* lParam */);
1045
1046 pEvent = NULL; /* Consume pointer. */
1047 break;
1048 }
1049
1050 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
1051 {
1052 /* The host needs data in the specified format. */
1053 ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_READ_DATA,
1054 0 /* wParam */, (LPARAM)pEvent /* lParam */);
1055
1056 pEvent = NULL; /* Consume pointer. */
1057 break;
1058 }
1059
1060 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
1061 {
1062 LogRel2(("Shared Clipboard: Host requested termination\n"));
1063 ASMAtomicXchgBool(pfShutdown, true);
1064 break;
1065 }
1066
1067#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1068 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
1069 {
1070 /* Nothing to do here. */
1071 rc = VINF_SUCCESS;
1072 break;
1073 }
1074#endif
1075 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
1076 {
1077 /* Nothing to do here. */
1078 rc = VINF_SUCCESS;
1079 break;
1080 }
1081
1082 default:
1083 {
1084 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
1085 }
1086 }
1087
1088 if (pEvent)
1089 {
1090 VbglR3ClipboardEventFree(pEvent);
1091 pEvent = NULL;
1092 }
1093 }
1094
1095 if (*pfShutdown)
1096 break;
1097 }
1098
1099 LogRel2(("Shared Clipboard: Worker loop ended\n"));
1100
1101#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1102 OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
1103 OleUninitialize();
1104#endif
1105
1106 LogFlowFuncLeaveRC(rc);
1107 return rc;
1108}
1109
1110DECLCALLBACK(int) vbtrShClStop(void *pInstance)
1111{
1112 AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
1113
1114 LogFunc(("Stopping pInstance=%p\n", pInstance));
1115
1116 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1117 AssertPtr(pCtx);
1118
1119 /* Set shutdown indicator. */
1120 ASMAtomicWriteBool(&pCtx->fShutdown, true);
1121
1122 /* Let our clipboard know that we're going to shut down. */
1123 PostMessage(pCtx->Win.hWnd, WM_QUIT, 0, 0);
1124
1125 /* Disconnect from the host service.
1126 * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
1127 VbglR3ClipboardDisconnect(pCtx->CmdCtx.idClient);
1128 pCtx->CmdCtx.idClient = 0;
1129
1130 LogFlowFuncLeaveRC(VINF_SUCCESS);
1131 return VINF_SUCCESS;
1132}
1133
1134DECLCALLBACK(void) vbtrShClDestroy(void *pInstance)
1135{
1136 AssertPtrReturnVoid(pInstance);
1137
1138 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
1139 AssertPtrReturnVoid(pCtx);
1140
1141 /* Make sure that we are disconnected. */
1142 Assert(pCtx->CmdCtx.idClient == 0);
1143
1144 LogFlowFunc(("pCtx=%p\n", pCtx));
1145
1146 LogRel2(("Shared Clipboard: Destroying ...\n"));
1147
1148 const PSHCLWINCTX pWinCtx = &pCtx->Win;
1149
1150 if (pCtx->hThread != NIL_RTTHREAD)
1151 {
1152 int rcThread = VERR_WRONG_ORDER;
1153 int rc = RTThreadWait(pCtx->hThread, 60 * 1000 /* Timeout in ms */, &rcThread);
1154 LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n",
1155 rc, rcThread));
1156 RT_NOREF(rc);
1157 }
1158
1159 if (pWinCtx->hWnd)
1160 {
1161 DestroyWindow(pWinCtx->hWnd);
1162 pWinCtx->hWnd = NULL;
1163 }
1164
1165 UnregisterClass(s_szClipWndClassName, pCtx->pEnv->hInstance);
1166
1167 SharedClipboardWinCtxDestroy(&pCtx->Win);
1168
1169#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1170 ShClTransferCtxDestroy(&pCtx->TransferCtx);
1171#endif
1172
1173 RTReqQueueDestroy(pCtx->Win.hReqQ);
1174
1175 LogRel2(("Shared Clipboard: Destroyed\n"));
1176
1177 return;
1178}
1179
1180/**
1181 * The service description.
1182 */
1183VBOXSERVICEDESC g_SvcDescClipboard =
1184{
1185 /* pszName. */
1186 "clipboard",
1187 /* pszDescription. */
1188 "Shared Clipboard",
1189 /* methods */
1190 vbtrShClInit,
1191 vbtrShClWorker,
1192 vbtrShClStop,
1193 vbtrShClDestroy
1194};
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