VirtualBox

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

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

Shared Clipboard/Transfers: Initial working PoC for host -> guest transfers. bugref:9437

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