VirtualBox

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

Last change on this file since 87082 was 87082, checked in by vboxsync, 4 years ago

Shared Clipboard: Simplified and cleaned up the X11 callback handling to actually do what they advertise in case no data is available. This also makes the surround code a lot easier to understand / follow.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 15.0 KB
Line 
1/** $Id: clipboard.cpp 87082 2020-12-10 10:01:11Z vboxsync $ */
2/** @file
3 * Guest Additions - X11 Shared Clipboard.
4 */
5
6/*
7 * Copyright (C) 2007-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/alloc.h>
23#include <iprt/asm.h>
24#include <iprt/assert.h>
25#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
26# include <iprt/dir.h>
27#endif
28#include <iprt/initterm.h>
29#include <iprt/mem.h>
30#include <iprt/string.h>
31#include <iprt/path.h>
32#include <iprt/process.h>
33#include <iprt/semaphore.h>
34
35#include <VBox/log.h>
36#include <VBox/VBoxGuestLib.h>
37#include <VBox/HostServices/VBoxClipboardSvc.h>
38#include <VBox/GuestHost/SharedClipboard.h>
39#include <VBox/GuestHost/SharedClipboard-x11.h>
40
41#include "VBoxClient.h"
42
43#include "clipboard.h"
44#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
45# include "clipboard-fuse.h"
46#endif
47
48
49/*********************************************************************************************************************************
50* Global Variables *
51*********************************************************************************************************************************/
52
53/** Only one context is supported at a time for now. */
54SHCLCONTEXT g_Ctx;
55#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
56SHCLFUSECTX g_FuseCtx;
57#endif
58
59
60#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
61static DECLCALLBACK(int) vbclShClOnTransferInitCallback(PSHCLTRANSFERCALLBACKDATA pData)
62{
63# ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
64 return ShClFuseOnTransferInitCallback(pData);
65# else
66 RT_NOREF(pData);
67 return VERR_NOT_IMPLEMENTED;
68# endif
69}
70
71static DECLCALLBACK(int) vbclShClOnTransferStartCallback(PSHCLTRANSFERCALLBACKDATA pData)
72{
73# ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
74 return ShClFuseOnTransferStartCallback(pData);
75# else
76 RT_NOREF(pData);
77 return VERR_NOT_IMPLEMENTED;
78# endif
79}
80
81static DECLCALLBACK(void) vbclShClOnTransferCompleteCallback(PSHCLTRANSFERCALLBACKDATA pData, int rc)
82{
83# ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
84 return ShClFuseOnTransferCompleteCallback(pData, rc);
85# else
86 RT_NOREF(pData, rc);
87# endif
88}
89
90static DECLCALLBACK(void) vbclShClOnTransferErrorCallback(PSHCLTRANSFERCALLBACKDATA pData, int rc)
91{
92# ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
93 return ShClFuseOnTransferErrorCallback(pData, rc);
94# else
95 RT_NOREF(pData, rc);
96# endif
97}
98#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
99
100/**
101 * Callback implementation for getting clipboard data from the host.
102 *
103 * @returns VBox status code. VERR_NO_DATA if no data available.
104 * @param pCtx Our context information.
105 * @param uFmt The format of the data being requested.
106 * @param ppv Returns an allocated buffer with data read from the host on success.
107 * Needs to be free'd with RTMemFree() by the caller.
108 * @param pcb Returns the amount of data read (in bytes) on success.
109 */
110DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb)
111{
112 LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
113
114 int rc = VINF_SUCCESS;
115
116#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
117 if (uFmt == VBOX_SHCL_FMT_URI_LIST)
118 {
119 //rc = VbglR3ClipboardRootListRead()
120
121 rc = VERR_NO_DATA;
122 }
123 else
124#endif
125 {
126 uint32_t cbRead = 0;
127
128 uint32_t cbData = _4K; /** @todo Make this dynamic. */
129 void *pvData = RTMemAlloc(cbData);
130 if (pvData)
131 {
132 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
133 }
134 else
135 rc = VERR_NO_MEMORY;
136
137 /*
138 * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
139 * larger buffer. The size of the buffer needed is placed in *pcb.
140 * So we start all over again.
141 */
142 if (rc == VINF_BUFFER_OVERFLOW)
143 {
144 /* cbRead contains the size required. */
145
146 cbData = cbRead;
147 pvData = RTMemRealloc(pvData, cbRead);
148 if (pvData)
149 {
150 rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
151 if (rc == VINF_BUFFER_OVERFLOW)
152 rc = VERR_BUFFER_OVERFLOW;
153 }
154 else
155 rc = VERR_NO_MEMORY;
156 }
157
158 if (!cbRead)
159 rc = VERR_NO_DATA;
160
161 if (RT_SUCCESS(rc))
162 {
163 *pcb = cbRead; /* Actual bytes read. */
164 *ppv = pvData;
165 }
166 else
167 {
168 /*
169 * Catch other errors. This also catches the case in which the buffer was
170 * too small a second time, possibly because the clipboard contents
171 * changed half-way through the operation. Since we can't say whether or
172 * not this is actually an error, we just return size 0.
173 */
174 RTMemFree(pvData);
175 }
176 }
177
178 if (RT_FAILURE(rc))
179 LogRel(("Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
180
181 LogFlowFuncLeaveRC(rc);
182 return rc;
183}
184
185/**
186 * Opaque data structure describing a request from the host for clipboard
187 * data, passed in when the request is forwarded to the X11 backend so that
188 * it can be completed correctly.
189 */
190struct CLIPREADCBREQ
191{
192 /** The data format that was requested. */
193 SHCLFORMAT Format;
194};
195
196/**
197 * Tell the host that new clipboard formats are available.
198 *
199 * @param pCtx Our context information.
200 * @param fFormats The formats to report.
201 */
202DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats)
203{
204 RT_NOREF(pCtx);
205
206 LogFlowFunc(("fFormats=%#x\n", fFormats));
207
208 int rc2 = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
209 RT_NOREF(rc2);
210 LogFlowFuncLeaveRC(rc2);
211}
212
213/**
214 * This is called by the backend to tell us that a request for data from
215 * X11 has completed.
216 *
217 * @param pCtx Our context information.
218 * @param rcCompletion The completion status of the request.
219 * @param pReq The request structure that we passed in when we started
220 * the request. We RTMemFree() this in this function.
221 * @param pv The clipboard data returned from X11 if the request succeeded (see @a rc).
222 * @param cb The size of the data in @a pv.
223 */
224DECLCALLBACK(void) ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx,
225 int rcCompletion, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
226{
227 LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", rcCompletion, pReq->Format, pv, cb));
228
229 if (RT_SUCCESS(rcCompletion)) /* Only write data if the request succeeded. */
230 {
231 AssertPtrReturnVoid(pv);
232 AssertReturnVoid(pv);
233
234 rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pReq->Format, pv, cb);
235 }
236
237 RTMemFree(pReq);
238
239 LogFlowFuncLeaveRC(rcCompletion);
240}
241
242/**
243 * Connect the guest clipboard to the host.
244 *
245 * @returns VBox status code.
246 */
247static int vboxClipboardConnect(void)
248{
249 LogFlowFuncEnter();
250
251 int rc = ShClX11Init(&g_Ctx.X11, &g_Ctx, false /* fHeadless */);
252 if (RT_SUCCESS(rc))
253 {
254 rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
255 if (RT_SUCCESS(rc))
256 {
257 rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
258 if (RT_FAILURE(rc))
259 ShClX11ThreadStop(&g_Ctx.X11);
260 }
261 }
262 else
263 rc = VERR_NO_MEMORY;
264
265 if (RT_FAILURE(rc))
266 {
267 VBClLogError("Error connecting to host service, rc=%Rrc\n", rc);
268
269 VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
270 ShClX11Destroy(&g_Ctx.X11);
271 }
272
273 LogFlowFuncLeaveRC(rc);
274 return rc;
275}
276
277/**
278 * The main loop of our clipboard reader.
279 */
280int vboxClipboardMain(void)
281{
282 int rc;
283
284 PSHCLCONTEXT pCtx = &g_Ctx;
285
286 bool fShutdown = false;
287
288 /* The thread waits for incoming messages from the host. */
289 for (;;)
290 {
291 PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
292 AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
293
294 LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
295 pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
296
297 uint32_t idMsg = 0;
298 uint32_t cParms = 0;
299 rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
300 if (RT_SUCCESS(rc))
301 {
302#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
303 rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
304#else
305 rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
306#endif
307 }
308
309 if (RT_FAILURE(rc))
310 {
311 LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
312
313 VbglR3ClipboardEventFree(pEvent);
314 pEvent = NULL;
315
316 if (fShutdown)
317 break;
318
319 /* Wait a bit before retrying. */
320 RTThreadSleep(1000);
321 continue;
322 }
323 else
324 {
325 AssertPtr(pEvent);
326 LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
327
328 switch (pEvent->enmType)
329 {
330 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
331 {
332 ShClX11ReportFormatsToX11(&g_Ctx.X11, pEvent->u.fReportedFormats);
333 break;
334 }
335
336 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
337 {
338 /* The host needs data in the specified format. */
339 CLIPREADCBREQ *pReq;
340 pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ));
341 if (pReq)
342 {
343 pReq->Format = pEvent->u.fReadData;
344 ShClX11ReadDataFromX11(&g_Ctx.X11, pReq->Format, pReq);
345 }
346 else
347 rc = VERR_NO_MEMORY;
348 break;
349 }
350
351 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
352 {
353 VBClLogVerbose(2, "Host requested termination\n");
354 fShutdown = true;
355 break;
356 }
357
358#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
359 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
360 {
361 /* Nothing to do here. */
362 rc = VINF_SUCCESS;
363 break;
364 }
365#endif
366 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
367 {
368 /* Nothing to do here. */
369 rc = VINF_SUCCESS;
370 break;
371 }
372
373 default:
374 {
375 AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
376 }
377 }
378
379 if (pEvent)
380 {
381 VbglR3ClipboardEventFree(pEvent);
382 pEvent = NULL;
383 }
384 }
385
386 if (fShutdown)
387 break;
388 }
389
390 LogFlowFuncLeaveRC(rc);
391 return rc;
392}
393
394/**
395 * @interface_method_impl{VBCLSERVICE,pfnInit}
396 */
397static DECLCALLBACK(int) vbclShClInit(void)
398{
399 int rc;
400
401#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
402 /* Install callbacks. */
403 RT_ZERO(g_Ctx.CmdCtx.Transfers.Callbacks);
404
405 g_Ctx.CmdCtx.Transfers.Callbacks.pvUser = &g_Ctx; /* Assign context as user-provided callback data. */
406 g_Ctx.CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
407
408 g_Ctx.CmdCtx.Transfers.Callbacks.pfnTransferInitialize = vbclShClOnTransferInitCallback;
409 g_Ctx.CmdCtx.Transfers.Callbacks.pfnTransferStart = vbclShClOnTransferStartCallback;
410 g_Ctx.CmdCtx.Transfers.Callbacks.pfnTransferComplete = vbclShClOnTransferCompleteCallback;
411 g_Ctx.CmdCtx.Transfers.Callbacks.pfnTransferError = vbclShClOnTransferErrorCallback;
412
413 rc = ShClTransferCtxInit(&g_Ctx.TransferCtx);
414#else
415 rc = VINF_SUCCESS;
416#endif
417
418 LogFlowFuncLeaveRC(rc);
419 return rc;
420}
421
422/**
423 * @interface_method_impl{VBCLSERVICE,pfnWorker}
424 */
425static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown)
426{
427 RT_NOREF(pfShutdown);
428
429 /* Initialise the guest library. */
430 int rc = vboxClipboardConnect();
431 if (RT_SUCCESS(rc))
432 {
433#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
434 rc = VbClShClFUSEInit(&g_FuseCtx, &g_Ctx);
435 if (RT_SUCCESS(rc))
436 {
437 rc = VbClShClFUSEStart(&g_FuseCtx);
438 if (RT_SUCCESS(rc))
439 {
440#endif
441 /* Let the main thread know that it can continue spawning services. */
442 RTThreadUserSignal(RTThreadSelf());
443
444 rc = vboxClipboardMain();
445
446#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
447 int rc2 = VbClShClFUSEStop(&g_FuseCtx);
448 if (RT_SUCCESS(rc))
449 rc = rc2;
450 }
451 }
452#endif
453 }
454
455 if (RT_FAILURE(rc))
456 VBClLogError("Service terminated abnormally with %Rrc\n", rc);
457
458 if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
459 rc = VINF_SUCCESS; /* Prevent automatic restart by daemon script if host service not available. */
460
461 return rc;
462}
463
464/**
465 * @interface_method_impl{VBCLSERVICE,pfnStop}
466 */
467static DECLCALLBACK(void) vbclShClStop(void)
468{
469 /* Disconnect from the host service.
470 * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
471 VbglR3ClipboardDisconnect(g_Ctx.CmdCtx.idClient);
472 g_Ctx.CmdCtx.idClient = 0;
473}
474
475/**
476 * @interface_method_impl{VBCLSERVICE,pfnTerm}
477 */
478static DECLCALLBACK(int) vbclShClTerm(void)
479{
480#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
481 ShClTransferCtxDestroy(&g_Ctx.TransferCtx);
482#endif
483
484 return VINF_SUCCESS;
485}
486
487VBCLSERVICE g_SvcClipboard =
488{
489 "shcl", /* szName */
490 "Shared Clipboard", /* pszDescription */
491 ".vboxclient-clipboard.pid", /* pszPidFilePath */
492 NULL, /* pszUsage */
493 NULL, /* pszOptions */
494 NULL, /* pfnOption */
495 vbclShClInit, /* pfnInit */
496 vbclShClWorker, /* pfnWorker */
497 vbclShClStop, /* pfnStop*/
498 vbclShClTerm /* pfnTerm */
499};
500
Note: See TracBrowser for help on using the repository browser.

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