VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp@ 100347

Last change on this file since 100347 was 100237, checked in by vboxsync, 21 months ago

Shared Clipboard: Fixed reading data from X11 host clipboard. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.4 KB
Line 
1/* $Id: VBoxSharedClipboardSvc-x11.cpp 100237 2023-06-21 11:35:44Z vboxsync $ */
2/** @file
3 * Shared Clipboard Service - Linux host.
4 */
5
6/*
7 * Copyright (C) 2006-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#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
33#include <iprt/assert.h>
34#include <iprt/critsect.h>
35#include <iprt/env.h>
36#include <iprt/mem.h>
37#include <iprt/semaphore.h>
38#include <iprt/string.h>
39#include <iprt/asm.h>
40
41#include <VBox/GuestHost/SharedClipboard.h>
42#include <VBox/GuestHost/SharedClipboard-x11.h>
43#include <VBox/HostServices/VBoxClipboardSvc.h>
44#include <iprt/errcore.h>
45
46#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
47# include <VBox/GuestHost/SharedClipboard-transfers.h>
48#endif
49
50#include "VBoxSharedClipboardSvc-internal.h"
51
52/* Number of currently extablished connections. */
53static volatile uint32_t g_cShClConnections;
54
55
56/*********************************************************************************************************************************
57* Structures and Typedefs *
58*********************************************************************************************************************************/
59/**
60 * Global context information used by the host glue for the X11 clipboard backend.
61 */
62struct SHCLCONTEXT
63{
64 /** This mutex is grabbed during any critical operations on the clipboard
65 * which might clash with others. */
66 RTCRITSECT CritSect;
67 /** X11 context data. */
68 SHCLX11CTX X11;
69 /** Pointer to the VBox host client data structure. */
70 PSHCLCLIENT pClient;
71 /** We set this when we start shutting down as a hint not to post any new
72 * requests. */
73 bool fShuttingDown;
74};
75
76
77/*********************************************************************************************************************************
78* Prototypes *
79*********************************************************************************************************************************/
80static DECLCALLBACK(int) shClSvcX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser);
81static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser);
82
83
84int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable)
85{
86 RT_NOREF(pBackend);
87
88 LogFlowFuncEnter();
89
90 /* Override the connection limit. */
91 for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++)
92 pTable->acMaxClients[i] = RT_MIN(VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX, pTable->acMaxClients[i]);
93
94 RT_ZERO(pBackend->Callbacks);
95 /* Use internal callbacks by default. */
96 pBackend->Callbacks.pfnReportFormats = shClSvcX11ReportFormatsCallback;
97 pBackend->Callbacks.pfnOnRequestDataFromSource = shClSvcX11RequestDataFromSourceCallback;
98
99 return VINF_SUCCESS;
100}
101
102void ShClBackendDestroy(PSHCLBACKEND pBackend)
103{
104 RT_NOREF(pBackend);
105
106 LogFlowFuncEnter();
107}
108
109void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks)
110{
111#define SET_FN_IF_NOT_NULL(a_Fn) \
112 if (pCallbacks->pfn##a_Fn) \
113 pBackend->Callbacks.pfn##a_Fn = pCallbacks->pfn##a_Fn;
114
115 SET_FN_IF_NOT_NULL(ReportFormats);
116 SET_FN_IF_NOT_NULL(OnRequestDataFromSource);
117
118#undef SET_FN_IF_NOT_NULL
119}
120
121/**
122 * @note On the host, we assume that some other application already owns
123 * the clipboard and leave ownership to X11.
124 */
125int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless)
126{
127 int rc;
128
129 /* Check if maximum allowed connections count has reached. */
130 if (ASMAtomicIncU32(&g_cShClConnections) > VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX)
131 {
132 ASMAtomicDecU32(&g_cShClConnections);
133 LogRel(("Shared Clipboard: maximum amount for client connections reached\n"));
134 return VERR_OUT_OF_RESOURCES;
135 }
136
137 PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT));
138 if (pCtx)
139 {
140 rc = RTCritSectInit(&pCtx->CritSect);
141 if (RT_SUCCESS(rc))
142 {
143 rc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx, fHeadless);
144 if (RT_SUCCESS(rc))
145 {
146 pClient->State.pCtx = pCtx;
147 pCtx->pClient = pClient;
148
149 rc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */);
150 if (RT_FAILURE(rc))
151 ShClX11Destroy(&pCtx->X11);
152 }
153
154 if (RT_FAILURE(rc))
155 RTCritSectDelete(&pCtx->CritSect);
156 }
157
158 if (RT_FAILURE(rc))
159 {
160 pClient->State.pCtx = NULL;
161 RTMemFree(pCtx);
162 }
163 }
164 else
165 rc = VERR_NO_MEMORY;
166
167 if (RT_FAILURE(rc))
168 {
169 /* Restore active connections count. */
170 ASMAtomicDecU32(&g_cShClConnections);
171 }
172
173 LogFlowFuncLeaveRC(rc);
174 return rc;
175}
176
177int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient)
178{
179 RT_NOREF(pBackend);
180
181 LogFlowFuncEnter();
182
183 /* Tell the guest we have no data in case X11 is not available. If
184 * there is data in the host clipboard it will automatically be sent to
185 * the guest when the clipboard starts up. */
186 if (ShClSvcIsBackendActive())
187 return ShClSvcHostReportFormats(pClient, VBOX_SHCL_FMT_NONE);
188 return VINF_SUCCESS;
189}
190
191/**
192 * Shuts down the shared clipboard service and "disconnect" the guest.
193 * Note! Host glue code
194 */
195int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient)
196{
197 RT_NOREF(pBackend);
198
199 LogFlowFuncEnter();
200
201 PSHCLCONTEXT pCtx = pClient->State.pCtx;
202 AssertPtr(pCtx);
203
204 /* Drop the reference to the client, in case it is still there. This
205 * will cause any outstanding clipboard data requests from X11 to fail
206 * immediately. */
207 pCtx->fShuttingDown = true;
208
209 int rc = ShClX11ThreadStop(&pCtx->X11);
210 /** @todo handle this slightly more reasonably, or be really sure
211 * it won't go wrong. */
212 AssertRC(rc);
213
214 ShClX11Destroy(&pCtx->X11);
215 RTCritSectDelete(&pCtx->CritSect);
216
217 RTMemFree(pCtx);
218
219 /* Decrease active connections count. */
220 ASMAtomicDecU32(&g_cShClConnections);
221
222 LogFlowFuncLeaveRC(rc);
223 return rc;
224}
225
226/**
227 * Reports clipboard formats to the host clipboard.
228 */
229int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats)
230{
231 RT_NOREF(pBackend);
232
233 int rc = ShClX11ReportFormatsToX11Async(&pClient->State.pCtx->X11, fFormats);
234
235 LogFlowFuncLeaveRC(rc);
236 return rc;
237}
238
239/**
240 * Reads data from the host clipboard.
241 *
242 * Schedules a request to the X11 event thread.
243 *
244 * @note We always fail or complete asynchronously.
245 */
246int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat,
247 void *pvData, uint32_t cbData, uint32_t *pcbActual)
248{
249 RT_NOREF(pBackend);
250
251 AssertPtrReturn(pClient, VERR_INVALID_POINTER);
252 AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER);
253 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
254 AssertPtrReturn(pcbActual, VERR_INVALID_POINTER);
255
256 RT_NOREF(pCmdCtx);
257
258 LogFlowFunc(("pClient=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n",
259 pClient, uFormat, pvData, cbData, pcbActual));
260
261 PSHCLEVENT pEvent;
262 int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent);
263 if (RT_SUCCESS(rc))
264 {
265 rc = ShClX11ReadDataFromX11Async(&pClient->State.pCtx->X11, uFormat, cbData, pEvent);
266 if (RT_SUCCESS(rc))
267 {
268 PSHCLEVENTPAYLOAD pPayload;
269 rc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
270 if (RT_SUCCESS(rc))
271 {
272 if (pPayload)
273 {
274 Assert(pPayload->cbData == sizeof(SHCLX11RESPONSE));
275 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData;
276
277 uint32_t const cbRead = pResp->Read.cbData;
278
279 size_t const cbToCopy = RT_MIN(cbData, cbRead);
280 if (cbToCopy) /* memcpy doesn't like 0 byte inputs. */
281 memcpy(pvData, pResp->Read.pvData, RT_MIN(cbData, cbRead));
282
283 LogRel2(("Shared Clipboard: Read %RU32 bytes host X11 clipboard data\n", cbRead));
284
285 *pcbActual = cbRead;
286
287 RTMemFree(pResp->Read.pvData);
288 pResp->Read.cbData = 0;
289
290 ShClPayloadFree(pPayload);
291 }
292 else /* No payload given; could happen on invalid / not-expected formats. */
293 *pcbActual = 0;
294 }
295 }
296
297 ShClEventRelease(pEvent);
298 }
299
300 if (RT_FAILURE(rc))
301 LogRel(("Shared Clipboard: Error reading host clipboard data from X11, rc=%Rrc\n", rc));
302
303 LogFlowFuncLeaveRC(rc);
304 return rc;
305}
306
307int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx,
308 SHCLFORMAT uFormat, void *pvData, uint32_t cbData)
309{
310 RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData);
311
312 LogFlowFuncEnter();
313
314 /* Nothing to do here yet. */
315
316 LogFlowFuncLeave();
317 return VINF_SUCCESS;
318}
319
320/**
321 * @copydoc SHCLCALLBACKS::pfnReportFormats
322 *
323 * Reports clipboard formats to the guest.
324 */
325static DECLCALLBACK(int) shClSvcX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
326{
327 RT_NOREF(pvUser);
328
329 LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats));
330
331 int rc = VINF_SUCCESS;
332 PSHCLCLIENT pClient = pCtx->pClient;
333 AssertPtr(pClient);
334
335 rc = RTCritSectEnter(&pClient->CritSect);
336 if (RT_SUCCESS(rc))
337 {
338 if (ShClSvcIsBackendActive())
339 {
340 /** @todo r=bird: BUGBUG: Revisit this */
341 if (fFormats != VBOX_SHCL_FMT_NONE) /* No formats to report? */
342 {
343 rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats);
344 }
345 }
346
347 RTCritSectLeave(&pClient->CritSect);
348 }
349
350 LogFlowFuncLeaveRC(rc);
351 return rc;
352}
353
354/**
355 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
356 *
357 * Requests clipboard data from the guest.
358 *
359 * @thread Called from X11 event thread.
360 */
361static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
362 SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
363{
364 RT_NOREF(pvUser);
365
366 LogFlowFunc(("pCtx=%p, uFmt=0x%x\n", pCtx, uFmt));
367
368 if (pCtx->fShuttingDown)
369 {
370 /* The shared clipboard is disconnecting. */
371 LogRel(("Shared Clipboard: Host requested guest clipboard data after guest had disconnected\n"));
372 return VERR_WRONG_ORDER;
373 }
374
375 int rc = VINF_SUCCESS;
376
377#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
378 /*
379 * Note: We always return a generic URI list here.
380 * As we don't know which Atom target format was requested by the caller, the X11 clipboard codes needs
381 * to decide & transform the list into the actual clipboard Atom target format the caller wanted.
382 */
383 if (uFmt == VBOX_SHCL_FMT_URI_LIST)
384 {
385 PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferFirst(&pCtx->X11.HttpCtx.HttpServer);
386 if (pTransfer)
387 {
388 if (RT_SUCCESS(rc))
389 rc = ShClTransferRootListRead(pTransfer);
390 }
391
392 /** @todo BUGBUG IMPLEMENT THIS! */
393
394 *ppv = NULL;
395 *pcb = 0;
396
397 rc = VERR_NO_DATA;
398 }
399#endif
400
401 if (RT_SUCCESS(rc))
402 {
403 /* Request data from the guest and for data to arrive. */
404 PSHCLEVENT pEvent;
405 rc = ShClSvcReadDataFromGuestAsync(pCtx->pClient, uFmt, &pEvent);
406 if (RT_SUCCESS(rc))
407 {
408 PSHCLEVENTPAYLOAD pPayload;
409 rc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
410 if (RT_SUCCESS(rc))
411 {
412 if ( !pPayload
413 || !pPayload->cbData)
414 {
415 rc = VERR_NO_DATA;
416 }
417 else
418 {
419 *ppv = pPayload->pvData;
420 *pcb = pPayload->cbData;
421 }
422 }
423
424 ShClEventRelease(pEvent);
425 }
426 }
427
428 if (RT_FAILURE(rc))
429 LogRel(("Shared Clipboard: Requesting data in format %#x for X11 host failed with %Rrc\n", uFmt, rc));
430
431 LogFlowFuncLeaveRC(rc);
432 return rc;
433}
434
435#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
436
437int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer)
438{
439 RT_NOREF(pBackend);
440#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
441 /* We only need to start the HTTP server (and register the transfer to it) when we actually receive data from the guest. */
442 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE)
443 return ShClTransferHttpServerMaybeStart(&pClient->State.pCtx->X11.HttpCtx);
444#else
445 RT_NOREF(pClient, pTransfer);
446#endif
447 return VINF_SUCCESS;
448}
449
450int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer)
451{
452 RT_NOREF(pBackend);
453#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
454 /* See comment in ShClBackendTransferCreate(). */
455 if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE)
456 return ShClTransferHttpServerMaybeStop(&pClient->State.pCtx->X11.HttpCtx);
457#else
458 RT_NOREF(pClient, pTransfer);
459#endif
460 return VINF_SUCCESS;
461}
462
463int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer)
464{
465 RT_NOREF(pBackend);
466
467 LogFlowFuncEnter();
468
469 PSHCLEVENT pEvent;
470 int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent);
471 if (RT_SUCCESS(rc))
472 {
473 rc = ShClX11ReadDataFromX11Async(&pClient->State.pCtx->X11, VBOX_SHCL_FMT_URI_LIST, UINT32_MAX, pEvent);
474 if (RT_SUCCESS(rc))
475 {
476 /* X supplies the data asynchronously, so we need to wait for data to arrive first. */
477 PSHCLEVENTPAYLOAD pPayload;
478 rc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
479 if (RT_SUCCESS(rc))
480 {
481 if (pPayload)
482 {
483 Assert(pPayload->cbData == sizeof(SHCLX11RESPONSE));
484 AssertPtr(pPayload->pvData);
485 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData;
486
487 rc = ShClTransferRootsInitFromStringList(pTransfer,
488 (char *)pResp->Read.pvData, pResp->Read.cbData + 1 /* Include zero terminator */);
489 if (RT_SUCCESS(rc))
490 rc = ShClTransferRootListRead(pTransfer);
491
492 if (RT_SUCCESS(rc))
493 LogRel2(("Shared Clipboard: Host reported %RU64 X11 root entries for transfer to guest\n", ShClTransferRootsCount(pTransfer)));
494
495 RTMemFree(pResp->Read.pvData);
496 pResp->Read.cbData = 0;
497
498 ShClPayloadFree(pPayload);
499 pPayload = NULL;
500 }
501 else
502 rc = VERR_NO_DATA; /* No payload. */
503 }
504 }
505
506 ShClEventRelease(pEvent);
507 }
508 else
509 rc = VERR_SHCLPB_MAX_EVENTS_REACHED;
510
511 LogFlowFuncLeaveRC(rc);
512 return rc;
513}
514#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
515
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