VirtualBox

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

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

Shared Clipboard: Condensed the X11 clipboard reading code even more (also removes duplicate code); added ShClX11ReadDataFromX11Ex() for that.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 17.7 KB
Line 
1/** $Id: clipboard-x11.cpp 103323 2024-02-12 18:21:23Z 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, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
209{
210 RT_NOREF(pvUser);
211
212 LogFlowFuncEnter();
213
214 int rc = VERR_NO_DATA; /* Play safe. */
215
216 uint32_t cbRead = 0;
217
218 uint32_t cbData = _4K; /** @todo Make this dynamic. */
219 void *pvData = RTMemAlloc(cbData);
220 if (pvData)
221 {
222 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
223 }
224 else
225 rc = VERR_NO_MEMORY;
226
227 /*
228 * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
229 * larger buffer. The size of the buffer needed is placed in *pcb.
230 * So we start all over again.
231 */
232 if (rc == VINF_BUFFER_OVERFLOW)
233 {
234 /* cbRead contains the size required. */
235
236 cbData = cbRead;
237 pvData = RTMemRealloc(pvData, cbRead);
238 if (pvData)
239 {
240 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
241 if (rc == VINF_BUFFER_OVERFLOW)
242 rc = VERR_BUFFER_OVERFLOW;
243 }
244 else
245 rc = VERR_NO_MEMORY;
246 }
247
248 if (!cbRead)
249 rc = VERR_NO_DATA;
250
251 if (RT_SUCCESS(rc))
252 {
253 if (ppv)
254 *ppv = pvData;
255 if (pcb)
256 *pcb = cbRead; /* Actual bytes read. */
257 }
258 else
259 {
260 /*
261 * Catch other errors. This also catches the case in which the buffer was
262 * too small a second time, possibly because the clipboard contents
263 * changed half-way through the operation. Since we can't say whether or
264 * not this is actually an error, we just return size 0.
265 */
266 RTMemFree(pvData);
267 }
268
269 LogFlowFuncLeaveRC(rc);
270 return rc;
271}
272
273/**
274 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
275 *
276 * Requests data from the host.
277 *
278 * For transfers: This requests a transfer from the host. Most of the handling will be done VbglR3 then.
279 *
280 * @thread X11 event thread.
281 */
282static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
283 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
284{
285 RT_NOREF(pvUser);
286
287 LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
288
289 int rc;
290
291#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
292 if (uFmt == VBOX_SHCL_FMT_URI_LIST)
293 {
294 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
295 if (RT_SUCCESS(rc))
296 {
297 /* Request a new H->G transfer from the host.
298 * This is needed in order to get a transfer ID from the host we can initialize our own local transfer with.
299 * Transfer creation and set up will be done in VbglR3. */
300 rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx);
301 if (RT_SUCCESS(rc))
302 {
303 PSHCLHTTPSERVER pSrv = &pCtx->X11.HttpCtx.HttpServer;
304
305 /* Wait until the HTTP server got the transfer registered, so that we have something to work with. */
306 rc = ShClTransferHttpServerWaitForStatusChange(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED, SHCL_TIMEOUT_DEFAULT_MS);
307 if (RT_SUCCESS(rc))
308 {
309 PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferLast(pSrv);
310 if (pTransfer)
311 {
312 rc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED);
313 if (RT_SUCCESS(rc))
314 {
315 char *pszData;
316 size_t cbData;
317 rc = ShClTransferHttpConvertToStringList(pSrv, pTransfer, &pszData, &cbData);
318 if (RT_SUCCESS(rc))
319 {
320 *ppv = pszData;
321 *pcb = cbData;
322 /* ppv has ownership of pszData now. */
323 }
324 }
325 }
326 else
327 AssertMsgFailed(("No registered transfer found for HTTP server\n"));
328 }
329 else
330 LogRel(("Shared Clipboard: Could not start transfer, as no new HTTP transfer was registered in time\n"));
331 }
332 }
333 }
334 else /* Anything else */
335#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
336 {
337 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
338 }
339
340 if (RT_FAILURE(rc))
341 LogRel(("Shared Clipboard: Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
342
343 LogFlowFuncLeaveRC(rc);
344 return rc;
345}
346
347/**
348 * @copydoc SHCLCALLBACKS::pfnReportFormats
349 *
350 * Reports clipboard formats to the host.
351 *
352 * @thread X11 event thread.
353 */
354static DECLCALLBACK(int) vbclX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
355{
356 RT_NOREF(pvUser);
357
358 LogFlowFunc(("fFormats=%#x\n", fFormats));
359
360 int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
361
362 LogFlowFuncLeaveRC(rc);
363 return rc;
364}
365
366/**
367 * Initializes the X11-specifc Shared Clipboard code.
368 *
369 * @returns VBox status code.
370 */
371int VBClX11ClipboardInit(void)
372{
373 LogFlowFuncEnter();
374
375 int rc = ShClEventSourceCreate(&g_Ctx.EventSrc, 0 /* uID */);
376 AssertRCReturn(rc, rc);
377
378 SHCLCALLBACKS Callbacks;
379 RT_ZERO(Callbacks);
380 Callbacks.pfnReportFormats = vbclX11ReportFormatsCallback;
381 Callbacks.pfnOnRequestDataFromSource = vbclX11OnRequestDataFromSourceCallback;
382
383 rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */);
384 if (RT_SUCCESS(rc))
385 {
386 rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
387 if (RT_SUCCESS(rc))
388 {
389 rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
390 if (RT_FAILURE(rc))
391 ShClX11ThreadStop(&g_Ctx.X11);
392 }
393 }
394 else
395 VBClLogError("Initializing clipboard failed with %Rrc\n", rc);
396
397 if (RT_FAILURE(rc))
398 {
399 VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
400 ShClX11Destroy(&g_Ctx.X11);
401 }
402
403 LogFlowFuncLeaveRC(rc);
404 return rc;
405}
406
407/**
408 * Destroys the X11-specifc Shared Clipboard code.
409 *
410 * @returns VBox status code.
411 */
412int VBClX11ClipboardDestroy(void)
413{
414 return ShClEventSourceDestroy(&g_Ctx.EventSrc);
415}
416
417/**
418 * The main loop of the X11-specifc Shared Clipboard code.
419 *
420 * @returns VBox status code.
421 *
422 * @thread Clipboard service worker thread.
423 */
424int VBClX11ClipboardMain(void)
425{
426 PSHCLCONTEXT pCtx = &g_Ctx;
427
428 bool fShutdown = false;
429
430#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
431# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
432 /*
433 * Set callbacks.
434 * Those will be registered within VbglR3 when a new transfer gets initialized.
435 *
436 * Used for starting / stopping the HTTP server.
437 */
438 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
439
440 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
441 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
442
443 pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbclX11OnTransferInitializedCallback;
444 pCtx->CmdCtx.Transfers.Callbacks.pfnOnRegistered = vbclX11OnTransferRegisteredCallback;
445 pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbclX11OnTransferUnregisteredCallback;
446 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbclX11OnTransferCompletedCallback;
447 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbclX11OnTransferErrorCallback;
448# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
449#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
450
451 LogFlowFunc(("fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64 ...\n",
452 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
453
454 int rc;
455
456 /* The thread waits for incoming messages from the host. */
457 for (;;)
458 {
459 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
460 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
461
462 uint32_t idMsg = 0;
463 uint32_t cParms = 0;
464 rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
465 if (RT_SUCCESS(rc))
466 {
467#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
468 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
469#else
470 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
471#endif
472 }
473
474 if (RT_FAILURE(rc))
475 {
476 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
477
478 VbglR3ClipboardEventFree(pEvent);
479 pEvent = NULL;
480
481 if (fShutdown)
482 break;
483
484 /* Wait a bit before retrying. */
485 RTThreadSleep(RT_MS_1SEC);
486 continue;
487 }
488 else
489 {
490 AssertPtr(pEvent);
491 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
492
493 switch (pEvent->enmType)
494 {
495 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
496 {
497 ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats);
498 break;
499 }
500
501 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
502 {
503 void *pvData;
504 uint32_t cbData;
505 rc = ShClX11ReadDataFromX11Ex(&g_Ctx.X11, &pCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, pEvent->u.fReadData,
506 &pvData, &cbData);
507 if (RT_SUCCESS(rc))
508 {
509 rc = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData, pvData, cbData);
510 RTMemFree(pvData);
511 }
512
513 if (RT_FAILURE(rc))
514 VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData, NULL, 0);
515
516 break;
517 }
518
519 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
520 {
521 VBClLogVerbose(2, "Host requested termination\n");
522 fShutdown = true;
523 break;
524 }
525
526#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
527 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
528 {
529 if (pEvent->u.TransferStatus.Report.uStatus == SHCLTRANSFERSTATUS_STARTED)
530 {
531
532 }
533 rc = VINF_SUCCESS;
534 break;
535 }
536#endif
537 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
538 {
539 /* Nothing to do here. */
540 rc = VINF_SUCCESS;
541 break;
542 }
543
544 default:
545 {
546 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
547 }
548 }
549
550 if (pEvent)
551 {
552 VbglR3ClipboardEventFree(pEvent);
553 pEvent = NULL;
554 }
555 }
556
557 if (fShutdown)
558 break;
559 }
560
561 LogFlowFuncLeaveRC(rc);
562 return rc;
563}
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