VirtualBox

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

Last change on this file since 103442 was 103442, checked in by vboxsync, 10 months ago

Shared Clipboard: Condensed more code by adding a SHCLTRANSFERCALLBACKS::pfnOnInitialize() callback function. bugref:9437

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