VirtualBox

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

Last change on this file since 103365 was 103365, checked in by vboxsync, 4 months ago

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

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 16.3 KB
Line 
1/** $Id: clipboard-x11.cpp 103365 2024-02-14 18:13:38Z 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.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#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 void *pvData;
82 uint32_t cbData;
83 rc = ShClX11ReadDataFromX11Ex(&g_Ctx.X11, &pCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, VBOX_SHCL_FMT_URI_LIST,
84 &pvData, &cbData);
85 if (RT_SUCCESS(rc))
86 {
87 rc = ShClTransferRootsInitFromStringListEx(pTransfer, (const char *)pvData, cbData,
88 "\n" /* X11-based Desktop environments separate entries with "\n" */);
89 RTMemFree(pvData);
90 }
91 break;
92 }
93
94 case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */
95 {
96 /* Retrieve the root entries as a first action, so that the transfer is ready to go
97 * once it gets registered to HTTP server. */
98 int rc2 = ShClTransferRootListRead(pTransfer);
99 if ( RT_SUCCESS(rc2)
100 /* As soon as we register the transfer with the HTTP server, the transfer needs to have its roots set. */
101 && ShClTransferRootsCount(pTransfer))
102 {
103 rc2 = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer);
104 }
105 break;
106 }
107
108 default:
109 break;
110 }
111
112 LogFlowFuncLeaveRC(rc);
113}
114
115/**
116 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnRegistered
117 *
118 * This starts the HTTP server if not done yet and registers the transfer with it.
119 *
120 * @thread Clipboard main thread.
121 */
122static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx)
123{
124 RT_NOREF(pTransferCtx);
125
126 LogFlowFuncEnter();
127
128 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
129 AssertPtr(pCtx);
130
131 PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
132 AssertPtr(pTransfer);
133
134 /* We only need to start the HTTP server when we actually receive data from the remote (host). */
135 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */
136 {
137 int rc2 = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx);
138 if (RT_FAILURE(rc2))
139 LogRel(("Shared Clipboard: Registering HTTP transfer failed: %Rrc\n", rc2));
140 }
141
142 LogFlowFuncLeave();
143}
144
145/**
146 * Unregisters a transfer from a HTTP server.
147 *
148 * This also stops the HTTP server if no active transfers are found anymore.
149 *
150 * @param pCtx Shared clipboard context to unregister transfer for.
151 * @param pTransfer Transfer to unregister.
152 *
153 * @thread Clipboard main thread.
154 */
155static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer)
156{
157 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE)
158 {
159 if (ShClTransferHttpServerIsInitialized(&pCtx->X11.HttpCtx.HttpServer))
160 {
161 ShClTransferHttpServerUnregisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer);
162 ShClTransferHttpServerMaybeStop(&pCtx->X11.HttpCtx);
163 }
164 }
165}
166
167/**
168 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered
169 *
170 * Unregisters a (now) unregistered transfer from the HTTP server.
171 *
172 * @thread Clipboard main thread.
173 */
174static DECLCALLBACK(void) vbclX11OnTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx)
175{
176 RT_NOREF(pTransferCtx);
177 vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer);
178}
179
180/**
181 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCompleted
182 *
183 * Unregisters a complete transfer from the HTTP server.
184 *
185 * @thread Clipboard main thread.
186 */
187static DECLCALLBACK(void) vbclX11OnTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc)
188{
189 RT_NOREF(rc);
190 vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer);
191}
192
193/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnError
194 *
195 * Unregisters a failed transfer from the HTTP server.
196 *
197 * @thread Clipboard main thread.
198 */
199static DECLCALLBACK(void) vbclX11OnTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCtx, int rc)
200{
201 return vbclX11OnTransferCompletedCallback(pCtx, rc);
202}
203#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
204
205/**
206 * Worker for a reading clipboard from the host.
207 */
208static DECLCALLBACK(int) vbclX11ReadDataWorker(PSHCLCONTEXT pCtx,
209 SHCLFORMAT uFmt, void **ppvData, uint32_t *pcbData, void *pvUser)
210{
211 RT_NOREF(pvUser);
212
213 return VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, ppvData, pcbData);
214}
215
216/**
217 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
218 *
219 * Requests data from the host.
220 *
221 * For transfers: This requests a transfer from the host. Most of the handling will be done VbglR3 then.
222 *
223 * @thread X11 event thread.
224 */
225static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
226 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
227{
228 RT_NOREF(pvUser);
229
230 LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
231
232 int rc;
233
234#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
235 if (uFmt == VBOX_SHCL_FMT_URI_LIST)
236 {
237 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
238 if (RT_SUCCESS(rc))
239 {
240 /* Request a new H->G transfer from the host.
241 * This is needed in order to get a transfer ID from the host we can initialize our own local transfer with.
242 * Transfer creation and set up will be done in VbglR3. */
243 rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx);
244 if (RT_SUCCESS(rc))
245 {
246 PSHCLHTTPSERVER pSrv = &pCtx->X11.HttpCtx.HttpServer;
247
248 /* Wait until the HTTP server got the transfer registered, so that we have something to work with. */
249 rc = ShClTransferHttpServerWaitForStatusChange(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED, SHCL_TIMEOUT_DEFAULT_MS);
250 if (RT_SUCCESS(rc))
251 {
252 PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferLast(pSrv);
253 if (pTransfer)
254 {
255 rc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED);
256 if (RT_SUCCESS(rc))
257 {
258 char *pszData;
259 size_t cbData;
260 rc = ShClTransferHttpConvertToStringList(pSrv, pTransfer, &pszData, &cbData);
261 if (RT_SUCCESS(rc))
262 {
263 *ppv = pszData;
264 *pcb = cbData;
265 /* ppv has ownership of pszData now. */
266 }
267 }
268 }
269 else
270 AssertMsgFailed(("No registered transfer found for HTTP server\n"));
271 }
272 else
273 LogRel(("Shared Clipboard: Could not start transfer, as no new HTTP transfer was registered in time\n"));
274 }
275 }
276 }
277 else /* Anything else */
278#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
279 {
280 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
281 }
282
283 if (RT_FAILURE(rc))
284 LogRel(("Shared Clipboard: Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
285
286 LogFlowFuncLeaveRC(rc);
287 return rc;
288}
289
290/**
291 * @copydoc SHCLCALLBACKS::pfnReportFormats
292 *
293 * Reports clipboard formats to the host.
294 *
295 * @thread X11 event thread.
296 */
297static DECLCALLBACK(int) vbclX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
298{
299 RT_NOREF(pvUser);
300
301 LogFlowFunc(("fFormats=%#x\n", fFormats));
302
303 int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
304
305 LogFlowFuncLeaveRC(rc);
306 return rc;
307}
308
309/**
310 * Initializes the X11-specifc Shared Clipboard code.
311 *
312 * @returns VBox status code.
313 */
314int VBClX11ClipboardInit(void)
315{
316 LogFlowFuncEnter();
317
318 int rc = ShClEventSourceCreate(&g_Ctx.EventSrc, 0 /* uID */);
319 AssertRCReturn(rc, rc);
320
321 SHCLCALLBACKS Callbacks;
322 RT_ZERO(Callbacks);
323 Callbacks.pfnReportFormats = vbclX11ReportFormatsCallback;
324 Callbacks.pfnOnRequestDataFromSource = vbclX11OnRequestDataFromSourceCallback;
325
326 rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */);
327 if (RT_SUCCESS(rc))
328 {
329 rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
330 if (RT_SUCCESS(rc))
331 {
332 rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
333 if (RT_FAILURE(rc))
334 ShClX11ThreadStop(&g_Ctx.X11);
335 }
336 }
337 else
338 VBClLogError("Initializing clipboard failed with %Rrc\n", rc);
339
340 if (RT_FAILURE(rc))
341 {
342 VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
343 ShClX11Destroy(&g_Ctx.X11);
344 }
345
346 LogFlowFuncLeaveRC(rc);
347 return rc;
348}
349
350/**
351 * Destroys the X11-specifc Shared Clipboard code.
352 *
353 * @returns VBox status code.
354 */
355int VBClX11ClipboardDestroy(void)
356{
357 return ShClEventSourceDestroy(&g_Ctx.EventSrc);
358}
359
360/**
361 * The main loop of the X11-specifc Shared Clipboard code.
362 *
363 * @returns VBox status code.
364 *
365 * @thread Clipboard service worker thread.
366 */
367int VBClX11ClipboardMain(void)
368{
369 PSHCLCONTEXT pCtx = &g_Ctx;
370
371 bool fShutdown = false;
372
373#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
374# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
375 /*
376 * Set callbacks.
377 * Those will be registered within VbglR3 when a new transfer gets initialized.
378 *
379 * Used for starting / stopping the HTTP server.
380 */
381 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
382
383 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
384 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
385
386 pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbclX11OnTransferInitializedCallback;
387 pCtx->CmdCtx.Transfers.Callbacks.pfnOnRegistered = vbclX11OnTransferRegisteredCallback;
388 pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbclX11OnTransferUnregisteredCallback;
389 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbclX11OnTransferCompletedCallback;
390 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbclX11OnTransferErrorCallback;
391# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
392#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
393
394 LogFlowFunc(("fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64 ...\n",
395 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
396
397 int rc;
398
399 /* The thread waits for incoming messages from the host. */
400 for (;;)
401 {
402 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
403 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
404
405 uint32_t idMsg = 0;
406 uint32_t cParms = 0;
407 rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
408 if (RT_SUCCESS(rc))
409 {
410#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
411 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
412#else
413 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
414#endif
415 }
416
417 if (RT_FAILURE(rc))
418 {
419 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
420
421 VbglR3ClipboardEventFree(pEvent);
422 pEvent = NULL;
423
424 if (fShutdown)
425 break;
426
427 /* Wait a bit before retrying. */
428 RTThreadSleep(RT_MS_1SEC);
429 continue;
430 }
431 else
432 {
433 AssertPtr(pEvent);
434 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
435
436 switch (pEvent->enmType)
437 {
438 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
439 {
440 ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats);
441 break;
442 }
443
444 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
445 {
446 void *pvData;
447 uint32_t cbData;
448 rc = ShClX11ReadDataFromX11Ex(&g_Ctx.X11, &pCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, pEvent->u.fReadData,
449 &pvData, &cbData);
450 if (RT_SUCCESS(rc))
451 {
452 rc = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData, pvData, cbData);
453 RTMemFree(pvData);
454 }
455
456 if (RT_FAILURE(rc))
457 VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData, NULL, 0);
458
459 break;
460 }
461
462 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
463 {
464 VBClLogVerbose(2, "Host requested termination\n");
465 fShutdown = true;
466 break;
467 }
468
469#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
470 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
471 {
472 if (pEvent->u.TransferStatus.Report.uStatus == SHCLTRANSFERSTATUS_STARTED)
473 {
474
475 }
476 rc = VINF_SUCCESS;
477 break;
478 }
479#endif
480 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
481 {
482 /* Nothing to do here. */
483 rc = VINF_SUCCESS;
484 break;
485 }
486
487 default:
488 {
489 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
490 }
491 }
492
493 if (pEvent)
494 {
495 VbglR3ClipboardEventFree(pEvent);
496 pEvent = NULL;
497 }
498 }
499
500 if (fShutdown)
501 break;
502 }
503
504 LogFlowFuncLeaveRC(rc);
505 return rc;
506}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use