VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp@ 101678

最後變更 在這個檔案從101678是 100676,由 vboxsync 提交於 20 月 前

Shared Clipboard: HTTP transfers with multiple files are also working now. bugref:9437

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Id Revision
檔案大小: 20.2 KB
 
1/** $Id: clipboard-x11.cpp 100676 2023-07-21 11:26:04Z vboxsync $ */
2/** @file
3 * Guest Additions - X11 Shared Clipboard implementation.
4 */
5
6/*
7 * Copyright (C) 2007-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.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#include <iprt/alloc.h>
33#include <iprt/asm.h>
34#include <iprt/assert.h>
35#include <iprt/initterm.h>
36#include <iprt/mem.h>
37#include <iprt/string.h>
38#include <iprt/path.h>
39#include <iprt/process.h>
40#include <iprt/semaphore.h>
41
42#include <VBox/VBoxGuestLib.h>
43#include <VBox/HostServices/VBoxClipboardSvc.h>
44#include <VBox/GuestHost/SharedClipboard.h>
45#include <VBox/GuestHost/SharedClipboard-x11.h>
46
47#include "VBoxClient.h"
48#include "clipboard.h"
49
50#ifdef LOG_GROUP
51# undef LOG_GROUP
52#endif
53#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
54#include <iprt/log.h>
55
56
57#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
58/**
59 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized
60 *
61 * @thread Clipboard main thread.
62 */
63static DECLCALLBACK(void) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
64{
65 LogFlowFuncEnter();
66
67 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
68 AssertPtr(pCtx);
69
70 PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
71 AssertPtr(pTransfer);
72
73 int rc = VINF_SUCCESS;
74
75 /* If this is a G->H transfer, we need to set the root list entries here, as the host
76 * will start reading those as soon as we report the INITIALIZED status. */
77 switch (ShClTransferGetDir(pTransfer))
78 {
79 case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */
80 {
81 PSHCLEVENT pEvent;
82 rc = ShClEventSourceGenerateAndRegisterEvent(&pCtx->EventSrc, &pEvent);
83 if (RT_SUCCESS(rc))
84 {
85 rc = ShClX11ReadDataFromX11Async(&g_Ctx.X11, VBOX_SHCL_FMT_URI_LIST, UINT32_MAX, pEvent);
86 if (RT_SUCCESS(rc))
87 {
88 PSHCLEVENTPAYLOAD pPayload;
89 rc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
90 if (RT_SUCCESS(rc))
91 {
92 if (pPayload)
93 {
94 Assert(pPayload->cbData == sizeof(SHCLX11RESPONSE));
95 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData;
96
97 rc = ShClTransferRootsInitFromStringListEx(pTransfer, (const char *)pResp->Read.pvData, pResp->Read.cbData,
98 "\n" /* X11-based Desktop environments separate entries with "\n" */);
99
100 RTMemFree(pResp->Read.pvData);
101 pResp->Read.cbData = 0;
102
103 ShClPayloadFree(pPayload);
104 }
105 }
106 }
107 }
108 break;
109 }
110
111 case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */
112 {
113 /* Retrieve the root entries as a first action, so that the transfer is ready to go
114 * once it gets registered to HTTP server. */
115 int rc2 = ShClTransferRootListRead(pTransfer);
116 if ( RT_SUCCESS(rc2)
117 /* As soon as we register the transfer with the HTTP server, the transfer needs to have its roots set. */
118 && ShClTransferRootsCount(pTransfer))
119 {
120 rc2 = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer);
121 }
122 break;
123 }
124
125 default:
126 break;
127 }
128
129 LogFlowFuncLeaveRC(rc);
130}
131
132/**
133 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnRegistered
134 *
135 * This starts the HTTP server if not done yet and registers the transfer with it.
136 *
137 * @thread Clipboard main thread.
138 */
139static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx)
140{
141 RT_NOREF(pTransferCtx);
142
143 LogFlowFuncEnter();
144
145 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
146 AssertPtr(pCtx);
147
148 PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
149 AssertPtr(pTransfer);
150
151 /* We only need to start the HTTP server when we actually receive data from the remote (host). */
152 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */
153 {
154 int rc2 = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx);
155 if (RT_FAILURE(rc2))
156 LogRel(("Shared Clipboard: Registering HTTP transfer failed: %Rrc\n", rc2));
157 }
158
159 LogFlowFuncLeave();
160}
161
162/**
163 * Unregisters a transfer from a HTTP server.
164 *
165 * This also stops the HTTP server if no active transfers are found anymore.
166 *
167 * @param pCtx Shared clipboard context to unregister transfer for.
168 * @param pTransfer Transfer to unregister.
169 *
170 * @thread Clipboard main thread.
171 */
172static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer)
173{
174 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE)
175 {
176 if (ShClTransferHttpServerIsInitialized(&pCtx->X11.HttpCtx.HttpServer))
177 {
178 ShClTransferHttpServerUnregisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer);
179 ShClTransferHttpServerMaybeStop(&pCtx->X11.HttpCtx);
180 }
181 }
182}
183
184/**
185 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered
186 *
187 * Unregisters a (now) unregistered transfer from the HTTP server.
188 *
189 * @thread Clipboard main thread.
190 */
191static DECLCALLBACK(void) vbclX11OnTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx)
192{
193 RT_NOREF(pTransferCtx);
194 vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer);
195}
196
197/**
198 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCompleted
199 *
200 * Unregisters a complete transfer from the HTTP server.
201 *
202 * @thread Clipboard main thread.
203 */
204static DECLCALLBACK(void) vbclX11OnTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc)
205{
206 RT_NOREF(rc);
207 vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer);
208}
209
210/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnError
211 *
212 * Unregisters a failed transfer from the HTTP server.
213 *
214 * @thread Clipboard main thread.
215 */
216static DECLCALLBACK(void) vbclX11OnTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCtx, int rc)
217{
218 return vbclX11OnTransferCompletedCallback(pCtx, rc);
219}
220#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
221
222/**
223 * Worker for a reading clipboard from the host.
224 */
225static DECLCALLBACK(int) vbclX11ReadDataWorker(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
226{
227 RT_NOREF(pvUser);
228
229 LogFlowFuncEnter();
230
231 int rc = VERR_NO_DATA; /* Play safe. */
232
233 uint32_t cbRead = 0;
234
235 uint32_t cbData = _4K; /** @todo Make this dynamic. */
236 void *pvData = RTMemAlloc(cbData);
237 if (pvData)
238 {
239 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
240 }
241 else
242 rc = VERR_NO_MEMORY;
243
244 /*
245 * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
246 * larger buffer. The size of the buffer needed is placed in *pcb.
247 * So we start all over again.
248 */
249 if (rc == VINF_BUFFER_OVERFLOW)
250 {
251 /* cbRead contains the size required. */
252
253 cbData = cbRead;
254 pvData = RTMemRealloc(pvData, cbRead);
255 if (pvData)
256 {
257 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
258 if (rc == VINF_BUFFER_OVERFLOW)
259 rc = VERR_BUFFER_OVERFLOW;
260 }
261 else
262 rc = VERR_NO_MEMORY;
263 }
264
265 if (!cbRead)
266 rc = VERR_NO_DATA;
267
268 if (RT_SUCCESS(rc))
269 {
270 if (ppv)
271 *ppv = pvData;
272 if (pcb)
273 *pcb = cbRead; /* Actual bytes read. */
274 }
275 else
276 {
277 /*
278 * Catch other errors. This also catches the case in which the buffer was
279 * too small a second time, possibly because the clipboard contents
280 * changed half-way through the operation. Since we can't say whether or
281 * not this is actually an error, we just return size 0.
282 */
283 RTMemFree(pvData);
284 }
285
286 LogFlowFuncLeaveRC(rc);
287 return rc;
288}
289
290/**
291 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
292 *
293 * Requests data from the host.
294 *
295 * For transfers: This requests a transfer from the host. Most of the handling will be done VbglR3 then.
296 *
297 * @thread X11 event thread.
298 */
299static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
300 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
301{
302 RT_NOREF(pvUser);
303
304 LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
305
306 int rc;
307
308#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
309 if (uFmt == VBOX_SHCL_FMT_URI_LIST)
310 {
311 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
312 if (RT_SUCCESS(rc))
313 {
314 /* Request a new H->G transfer from the host.
315 * This is needed in order to get a transfer ID from the host we can initialize our own local transfer with.
316 * Transfer creation and set up will be done in VbglR3. */
317 rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx);
318 if (RT_SUCCESS(rc))
319 {
320 PSHCLHTTPSERVER pSrv = &pCtx->X11.HttpCtx.HttpServer;
321
322 /* Wait until the HTTP server got the transfer registered, so that we have something to work with. */
323 rc = ShClTransferHttpServerWaitForStatusChange(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED, SHCL_TIMEOUT_DEFAULT_MS);
324 if (RT_SUCCESS(rc))
325 {
326 PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferLast(pSrv);
327 if (pTransfer)
328 {
329 rc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED);
330 if (RT_SUCCESS(rc))
331 {
332 char *pszURL = NULL;
333
334 uint64_t const cRoots = ShClTransferRootsCount(pTransfer);
335 for (uint32_t i = 0; i < cRoots; i++)
336 {
337 char *pszEntry = ShClTransferHttpServerGetUrlA(pSrv, ShClTransferGetID(pTransfer), i /* Entry index */);
338 AssertPtrBreakStmt(pszEntry, rc = VERR_NO_MEMORY);
339
340 if (i > 0)
341 {
342 rc = RTStrAAppend(&pszURL, "\n"); /* Separate entries with a newline. */
343 AssertRCBreak(rc);
344 }
345
346 rc = RTStrAAppend(&pszURL, pszEntry);
347 AssertRCBreak(rc);
348
349 RTStrFree(pszEntry);
350 }
351
352 if (RT_SUCCESS(rc))
353 {
354 *ppv = pszURL;
355 *pcb = strlen(pszURL) + 1 /* Include terminator */;
356
357 LogFlowFunc(("URL is '%s'\n", pszURL));
358
359 /* ppv has ownership of pszURL. */
360 }
361 }
362 }
363 else
364 AssertMsgFailed(("No registered transfer found for HTTP server\n"));
365 }
366 else
367 LogRel(("Shared Clipboard: Could not start transfer, as no new HTTP transfer was registered in time\n"));
368 }
369 }
370 }
371 else /* Anything else */
372#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
373 {
374 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
375 }
376
377 if (RT_FAILURE(rc))
378 LogRel(("Shared Clipboard: Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
379
380 LogFlowFuncLeaveRC(rc);
381 return rc;
382}
383
384/**
385 * @copydoc SHCLCALLBACKS::pfnReportFormats
386 *
387 * Reports clipboard formats to the host.
388 *
389 * @thread X11 event thread.
390 */
391static DECLCALLBACK(int) vbclX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
392{
393 RT_NOREF(pvUser);
394
395 LogFlowFunc(("fFormats=%#x\n", fFormats));
396
397 int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
398
399 LogFlowFuncLeaveRC(rc);
400 return rc;
401}
402
403/**
404 * Initializes the X11-specifc Shared Clipboard code.
405 *
406 * @returns VBox status code.
407 */
408int VBClX11ClipboardInit(void)
409{
410 LogFlowFuncEnter();
411
412 int rc = ShClEventSourceCreate(&g_Ctx.EventSrc, 0 /* uID */);
413 AssertRCReturn(rc, rc);
414
415 SHCLCALLBACKS Callbacks;
416 RT_ZERO(Callbacks);
417 Callbacks.pfnReportFormats = vbclX11ReportFormatsCallback;
418 Callbacks.pfnOnRequestDataFromSource = vbclX11OnRequestDataFromSourceCallback;
419
420 rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */);
421 if (RT_SUCCESS(rc))
422 {
423 rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
424 if (RT_SUCCESS(rc))
425 {
426 rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
427 if (RT_FAILURE(rc))
428 ShClX11ThreadStop(&g_Ctx.X11);
429 }
430 }
431 else
432 rc = VERR_NO_MEMORY;
433
434 if (RT_FAILURE(rc))
435 {
436 VBClLogError("Error connecting to host service, rc=%Rrc\n", rc);
437
438 VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
439 ShClX11Destroy(&g_Ctx.X11);
440 }
441
442 LogFlowFuncLeaveRC(rc);
443 return rc;
444}
445
446/**
447 * Destroys the X11-specifc Shared Clipboard code.
448 *
449 * @returns VBox status code.
450 */
451int VBClX11ClipboardDestroy(void)
452{
453 return ShClEventSourceDestroy(&g_Ctx.EventSrc);
454}
455
456/**
457 * The main loop of the X11-specifc Shared Clipboard code.
458 *
459 * @returns VBox status code.
460 *
461 * @thread Clipboard service worker thread.
462 */
463int VBClX11ClipboardMain(void)
464{
465 PSHCLCONTEXT pCtx = &g_Ctx;
466
467 bool fShutdown = false;
468
469#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
470# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
471 /*
472 * Set callbacks.
473 * Those will be registered within VbglR3 when a new transfer gets initialized.
474 *
475 * Used for starting / stopping the HTTP server.
476 */
477 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
478
479 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
480 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
481
482 pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbclX11OnTransferInitializedCallback;
483 pCtx->CmdCtx.Transfers.Callbacks.pfnOnRegistered = vbclX11OnTransferRegisteredCallback;
484 pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbclX11OnTransferUnregisteredCallback;
485 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbclX11OnTransferCompletedCallback;
486 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbclX11OnTransferErrorCallback;
487# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
488#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
489
490 LogFlowFunc(("fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64 ...\n",
491 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
492
493 int rc;
494
495 /* The thread waits for incoming messages from the host. */
496 for (;;)
497 {
498 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
499 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
500
501 uint32_t idMsg = 0;
502 uint32_t cParms = 0;
503 rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
504 if (RT_SUCCESS(rc))
505 {
506#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
507 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
508#else
509 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
510#endif
511 }
512
513 if (RT_FAILURE(rc))
514 {
515 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
516
517 VbglR3ClipboardEventFree(pEvent);
518 pEvent = NULL;
519
520 if (fShutdown)
521 break;
522
523 /* Wait a bit before retrying. */
524 RTThreadSleep(RT_MS_1SEC);
525 continue;
526 }
527 else
528 {
529 AssertPtr(pEvent);
530 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
531
532 switch (pEvent->enmType)
533 {
534 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
535 {
536 ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats);
537 break;
538 }
539
540 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
541 {
542 PSHCLEVENT pReadDataEvent;
543 rc = ShClEventSourceGenerateAndRegisterEvent(&pCtx->EventSrc, &pReadDataEvent);
544 if (RT_SUCCESS(rc))
545 {
546 rc = ShClX11ReadDataFromX11Async(&g_Ctx.X11, pEvent->u.fReadData, UINT32_MAX, pReadDataEvent);
547 if (RT_SUCCESS(rc))
548 {
549 PSHCLEVENTPAYLOAD pPayload;
550 rc = ShClEventWait(pReadDataEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
551 if (RT_SUCCESS(rc))
552 {
553 if (pPayload)
554 {
555 Assert(pPayload->cbData == sizeof(SHCLX11RESPONSE));
556 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData;
557
558 rc = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData,
559 pResp->Read.pvData, pResp->Read.cbData);
560
561 RTMemFree(pResp->Read.pvData);
562 pResp->Read.cbData = 0;
563
564 ShClPayloadFree(pPayload);
565 }
566 }
567 }
568
569 ShClEventRelease(pReadDataEvent);
570 pReadDataEvent = NULL;
571 }
572
573 if (RT_FAILURE(rc))
574 VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData, NULL, 0);
575
576 break;
577 }
578
579 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
580 {
581 VBClLogVerbose(2, "Host requested termination\n");
582 fShutdown = true;
583 break;
584 }
585
586#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
587 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
588 {
589 if (pEvent->u.TransferStatus.Report.uStatus == SHCLTRANSFERSTATUS_STARTED)
590 {
591
592 }
593 rc = VINF_SUCCESS;
594 break;
595 }
596#endif
597 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
598 {
599 /* Nothing to do here. */
600 rc = VINF_SUCCESS;
601 break;
602 }
603
604 default:
605 {
606 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
607 }
608 }
609
610 if (pEvent)
611 {
612 VbglR3ClipboardEventFree(pEvent);
613 pEvent = NULL;
614 }
615 }
616
617 if (fShutdown)
618 break;
619 }
620
621 LogFlowFuncLeaveRC(rc);
622 return rc;
623}
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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