VirtualBox

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

Last change on this file since 95478 was 93505, checked in by vboxsync, 3 years ago

Shared Clipboard: Implemented backend callbacks and a dedicated backend context, together with a new testcase which mocks HGCM to also test the guest-side clipboard code (disabled by default for now). Work in progress, only tested on Linux so far [VBoxClient fixes].

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