VirtualBox

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

Last change on this file since 100285 was 100285, checked in by vboxsync, 20 months ago

Shared Clipboard/X11: Emphasize logging group. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 17.0 KB
Line 
1/** $Id: clipboard-x11.cpp 100285 2023-06-26 07:50:42Z 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 * Worker for waiting for a transfer status change.
60 */
61static DECLCALLBACK(int) vbclX11TransferWaitForStatusWorker(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmSts,
62 RTMSINTERVAL msTimeout)
63{
64 RT_NOREF(pCtx);
65
66 LogFlowFuncEnter();
67
68 int rc = VERR_TIMEOUT;
69
70 ShClTransferAcquire(pTransfer);
71
72 uint64_t const tsStartMs = RTTimeMilliTS();
73
74 while (RTTimeMilliTS() - tsStartMs <= msTimeout)
75 {
76 if (ShClTransferGetStatus(pTransfer) == enmSts) /* Currently we only have busy waiting here. */
77 {
78 rc = VINF_SUCCESS;
79 break;
80 }
81 RTThreadSleep(100);
82 }
83
84 ShClTransferRelease(pTransfer);
85
86 return rc;
87}
88
89/**
90 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnRegistered
91 *
92 * This starts the HTTP server if not done yet and registers the transfer with it.
93 *
94 * @thread Clipboard main thread.
95 */
96static DECLCALLBACK(void) vbclX11OnHttpTransferRegisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx)
97{
98 RT_NOREF(pTransferCtx);
99
100 LogFlowFuncEnter();
101
102 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
103 AssertPtr(pCtx);
104
105 PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
106 AssertPtr(pTransfer);
107
108 ShClTransferAcquire(pTransfer);
109
110 /* We only need to start the HTTP server when we actually receive data from the remote (host). */
111 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE)
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 below. */
115 int rc2 = ShClTransferRootListRead(pTransfer);
116 if (RT_SUCCESS(rc2))
117 {
118 ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx);
119 rc2 = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer);
120 }
121
122 if (RT_FAILURE(rc2))
123 LogRel(("Shared Clipboard: Registering HTTP transfer failed: %Rrc\n", rc2));
124 }
125
126 LogFlowFuncLeave();
127}
128
129/**
130 * Unregisters a transfer from a HTTP server.
131 *
132 * This also stops the HTTP server if no active transfers are found anymore.
133 *
134 * @param pCtx Shared clipboard context to unregister transfer for.
135 * @param pTransfer Transfer to unregister.
136 *
137 * @thread Clipboard main thread.
138 */
139static void vbclX11HttpTransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer)
140{
141 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE)
142 {
143 ShClTransferHttpServerUnregisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer);
144 ShClTransferHttpServerMaybeStop(&pCtx->X11.HttpCtx);
145 }
146
147 ShClTransferRelease(pTransfer);
148}
149
150/**
151 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered
152 *
153 * Unregisters a (now) unregistered transfer from the HTTP server.
154 *
155 * @thread Clipboard main thread.
156 */
157static DECLCALLBACK(void) vbclX11OnHttpTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx)
158{
159 RT_NOREF(pTransferCtx);
160 vbclX11HttpTransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer);
161}
162
163/**
164 * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCompleted
165 *
166 * Unregisters a complete transfer from the HTTP server.
167 *
168 * @thread Clipboard main thread.
169 */
170static DECLCALLBACK(void) vbclX11OnHttpTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc)
171{
172 RT_NOREF(rc);
173 vbclX11HttpTransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer);
174}
175
176/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnError
177 *
178 * Unregisters a failed transfer from the HTTP server.
179 *
180 * @thread Clipboard main thread.
181 */
182static DECLCALLBACK(void) vbclX11OnHttpTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCtx, int rc)
183{
184 return vbclX11OnHttpTransferCompletedCallback(pCtx, rc);
185}
186#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
187
188/**
189 * Worker for a reading clipboard from the host.
190 */
191static DECLCALLBACK(int) vbclX11ReadDataWorker(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
192{
193 RT_NOREF(pvUser);
194
195 LogFlowFuncEnter();
196
197 int rc = VERR_NO_DATA; /* Play safe. */
198
199 uint32_t cbRead = 0;
200
201 uint32_t cbData = _4K; /** @todo Make this dynamic. */
202 void *pvData = RTMemAlloc(cbData);
203 if (pvData)
204 {
205 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
206 }
207 else
208 rc = VERR_NO_MEMORY;
209
210 /*
211 * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
212 * larger buffer. The size of the buffer needed is placed in *pcb.
213 * So we start all over again.
214 */
215 if (rc == VINF_BUFFER_OVERFLOW)
216 {
217 /* cbRead contains the size required. */
218
219 cbData = cbRead;
220 pvData = RTMemRealloc(pvData, cbRead);
221 if (pvData)
222 {
223 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
224 if (rc == VINF_BUFFER_OVERFLOW)
225 rc = VERR_BUFFER_OVERFLOW;
226 }
227 else
228 rc = VERR_NO_MEMORY;
229 }
230
231 if (!cbRead)
232 rc = VERR_NO_DATA;
233
234 if (RT_SUCCESS(rc))
235 {
236 if (ppv)
237 *ppv = pvData;
238 if (pcb)
239 *pcb = cbRead; /* Actual bytes read. */
240 }
241 else
242 {
243 /*
244 * Catch other errors. This also catches the case in which the buffer was
245 * too small a second time, possibly because the clipboard contents
246 * changed half-way through the operation. Since we can't say whether or
247 * not this is actually an error, we just return size 0.
248 */
249 RTMemFree(pvData);
250 }
251
252 LogFlowFuncLeaveRC(rc);
253 return rc;
254}
255
256/**
257 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
258 *
259 * Requests URI data from the host.
260 * This initiates a transfer on the host. Most of the handling will be done VbglR3 then.
261 *
262 * @thread X11 event thread.
263 */
264static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
265 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
266{
267 RT_NOREF(pvUser);
268
269 LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
270
271 int rc;
272
273#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
274 if (uFmt == VBOX_SHCL_FMT_URI_LIST)
275 {
276 PSHCLHTTPSERVER pSrv = &pCtx->X11.HttpCtx.HttpServer;
277
278 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
279 if (RT_SUCCESS(rc))
280 rc = ShClTransferHttpServerWaitForStatusChange(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED, 5000 /* SHCL_TIMEOUT_DEFAULT_MS */);
281 if (RT_SUCCESS(rc))
282 {
283 PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferLast(pSrv);
284 if (pTransfer)
285 {
286 rc = vbclX11TransferWaitForStatusWorker(pCtx, pTransfer, SHCLTRANSFERSTATUS_STARTED, SHCL_TIMEOUT_DEFAULT_MS);
287 if (RT_SUCCESS(rc))
288 {
289 char *pszURL = ShClTransferHttpServerGetUrlA(pSrv, pTransfer->State.uID);
290 char *pszData = NULL;
291 RTStrAPrintf(&pszData, "copy\n%s", pszURL);
292
293 *ppv = pszData;
294 *pcb = strlen(pszData) + 1 /* Include terminator */;
295
296 LogFlowFunc(("pszURL=%s\n", pszURL));
297
298 RTStrFree(pszURL);
299
300 rc = VINF_SUCCESS;
301 }
302 }
303 else
304 AssertMsgFailed(("No registered transfer found for HTTP server\n"));
305 }
306 else
307 LogRel(("Shared Clipboard: Could not start transfer, as the HTTP server is not running\n"));
308 }
309 else /* Anything else */
310#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
311 {
312 rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser);
313 }
314
315 if (RT_FAILURE(rc))
316 LogRel(("Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
317
318 LogFlowFuncLeaveRC(rc);
319 return rc;
320}
321
322/**
323 * @copydoc SHCLCALLBACKS::pfnReportFormats
324 *
325 * Reports clipboard formats to the host.
326 *
327 * @thread X11 event thread.
328 */
329static DECLCALLBACK(int) vbclX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
330{
331 RT_NOREF(pvUser);
332
333 LogFlowFunc(("fFormats=%#x\n", fFormats));
334
335 int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
336
337 LogFlowFuncLeaveRC(rc);
338 return rc;
339}
340
341/**
342 * Initializes the X11-specifc Shared Clipboard code.
343 *
344 * @returns VBox status code.
345 */
346int VBClX11ClipboardInit(void)
347{
348 LogFlowFuncEnter();
349
350 SHCLCALLBACKS Callbacks;
351 RT_ZERO(Callbacks);
352 Callbacks.pfnReportFormats = vbclX11ReportFormatsCallback;
353 Callbacks.pfnOnRequestDataFromSource = vbclX11OnRequestDataFromSourceCallback;
354
355 int rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */);
356 if (RT_SUCCESS(rc))
357 {
358 rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
359 if (RT_SUCCESS(rc))
360 {
361 rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
362 if (RT_FAILURE(rc))
363 ShClX11ThreadStop(&g_Ctx.X11);
364 }
365 }
366 else
367 rc = VERR_NO_MEMORY;
368
369 if (RT_FAILURE(rc))
370 {
371 VBClLogError("Error connecting to host service, rc=%Rrc\n", rc);
372
373 VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
374 ShClX11Destroy(&g_Ctx.X11);
375 }
376
377 LogFlowFuncLeaveRC(rc);
378 return rc;
379}
380
381/**
382 * Destroys the X11-specifc Shared Clipboard code.
383 *
384 * @returns VBox status code.
385 */
386int VBClX11ClipboardDestroy(void)
387{
388 /* Nothing to do here currently. */
389 return VINF_SUCCESS;
390}
391
392/**
393 * The main loop of the X11-specifc Shared Clipboard code.
394 *
395 * @returns VBox status code.
396 *
397 * @thread Clipboard service worker thread.
398 */
399int VBClX11ClipboardMain(void)
400{
401 PSHCLCONTEXT pCtx = &g_Ctx;
402
403 bool fShutdown = false;
404
405#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
406# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
407 /*
408 * Set callbacks.
409 * Those will be registered within VbglR3 when a new transfer gets initialized.
410 *
411 * Used for starting / stopping the HTTP server.
412 */
413 RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
414
415 pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
416 pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
417
418 pCtx->CmdCtx.Transfers.Callbacks.pfnOnRegistered = vbclX11OnHttpTransferRegisteredCallback;
419 pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbclX11OnHttpTransferUnregisteredCallback;
420 pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbclX11OnHttpTransferCompletedCallback;
421 pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbclX11OnHttpTransferErrorCallback;
422# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */
423#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
424
425 LogFlowFunc(("fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64 ...\n",
426 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
427
428 int rc;
429
430 /* The thread waits for incoming messages from the host. */
431 for (;;)
432 {
433 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
434 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
435
436 uint32_t idMsg = 0;
437 uint32_t cParms = 0;
438 rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
439 if (RT_SUCCESS(rc))
440 {
441#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
442 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
443#else
444 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
445#endif
446 }
447
448 if (RT_FAILURE(rc))
449 {
450 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
451
452 VbglR3ClipboardEventFree(pEvent);
453 pEvent = NULL;
454
455 if (fShutdown)
456 break;
457
458 /* Wait a bit before retrying. */
459 RTThreadSleep(RT_MS_1SEC);
460 continue;
461 }
462 else
463 {
464 AssertPtr(pEvent);
465 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
466
467 switch (pEvent->enmType)
468 {
469 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
470 {
471 ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats);
472 break;
473 }
474
475 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
476 {
477 PSHCLEVENT pReadDataEvent;
478 rc = ShClEventSourceGenerateAndRegisterEvent(&pCtx->EventSrc, &pReadDataEvent);
479 if (RT_SUCCESS(rc))
480 {
481 rc = ShClX11ReadDataFromX11Async(&g_Ctx.X11, pEvent->u.fReadData, UINT32_MAX, pReadDataEvent);
482 if (RT_SUCCESS(rc))
483 {
484 PSHCLEVENTPAYLOAD pPayload;
485 rc = ShClEventWait(pReadDataEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
486 if (RT_SUCCESS(rc))
487 {
488 if (pPayload)
489 {
490 Assert(pPayload->cbData == sizeof(SHCLX11RESPONSE));
491 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData;
492
493 rc = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData,
494 pResp->Read.pvData, pResp->Read.cbData);
495
496 RTMemFree(pResp->Read.pvData);
497 pResp->Read.cbData = 0;
498
499 ShClPayloadFree(pPayload);
500 }
501 }
502 }
503
504 ShClEventRelease(pReadDataEvent);
505 pReadDataEvent = NULL;
506 }
507
508 if (RT_FAILURE(rc))
509 VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pEvent->u.fReadData, NULL, 0);
510
511 break;
512 }
513
514 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
515 {
516 VBClLogVerbose(2, "Host requested termination\n");
517 fShutdown = true;
518 break;
519 }
520
521#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
522 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
523 {
524 if (pEvent->u.TransferStatus.Report.uStatus == SHCLTRANSFERSTATUS_STARTED)
525 {
526
527 }
528 rc = VINF_SUCCESS;
529 break;
530 }
531#endif
532 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
533 {
534 /* Nothing to do here. */
535 rc = VINF_SUCCESS;
536 break;
537 }
538
539 default:
540 {
541 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
542 }
543 }
544
545 if (pEvent)
546 {
547 VbglR3ClipboardEventFree(pEvent);
548 pEvent = NULL;
549 }
550 }
551
552 if (fShutdown)
553 break;
554 }
555
556 LogFlowFuncLeaveRC(rc);
557 return rc;
558}
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