VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/wayland-helper-dcp.cpp@ 101883

Last change on this file since 101883 was 101883, checked in by vboxsync, 13 months ago

Additions: X11/Wayland: Fix warnings related to documantation generation, bugref:10194.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 54.2 KB
Line 
1/* $Id: wayland-helper-dcp.cpp 101883 2023-11-06 17:37:54Z vboxsync $ */
2/** @file
3 * Guest Additions - Data Control Protocol (DCP) helper for Wayland.
4 *
5 * This module implements Shared Clipboard support for Wayland guests
6 * using Data Control Protocol interface.
7 */
8
9/*
10 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
11 *
12 * This file is part of VirtualBox base platform packages, as
13 * available from https://www.virtualbox.org.
14 *
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation, in version 3 of the
18 * License.
19 *
20 * This program is distributed in the hope that it will be useful, but
21 * WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, see <https://www.gnu.org/licenses>.
27 *
28 * SPDX-License-Identifier: GPL-3.0-only
29 */
30
31#include <iprt/env.h>
32#include <iprt/assert.h>
33#include <iprt/string.h>
34#include <iprt/thread.h>
35
36#include <VBox/GuestHost/mime-type-converter.h>
37
38#include "VBoxClient.h"
39#include "clipboard.h"
40#include "wayland-helper.h"
41#include "wayland-helper-ipc.h"
42
43#include "wayland-client-protocol.h"
44#include "wlr-data-control-unstable-v1.h"
45
46/** Environment variable which points to which Wayland compositor we should connect.
47 * Must always be checked. */
48#define VBCL_ENV_WAYLAND_DISPLAY "WAYLAND_DISPLAY"
49
50/* Maximum length of Wayland interface name. */
51#define VBCL_WAYLAND_INTERFACE_NAME_MAX (64)
52/* Maximum waiting time interval for Wayland socket I/O to start. */
53#define VBCL_WAYLAND_IO_TIMEOUT_MS (500)
54
55/* Data chunk size when reading clipboard data from Wayland. */
56#define VBOX_WAYLAND_BUFFER_CHUNK_SIZE (_1M)
57/* Data chunk increment size to grow local buffer when it is not big enough. */
58#define VBOX_WAYLAND_BUFFER_CHUNK_INC_SIZE (_4M)
59/* Maximum length of clipboard buffer. */
60#define VBOX_WAYLAND_BUFFER_MAX (_16M)
61
62/** Minimum version numbers of Wayland interfaces we expect a compositor to provide. */
63#define VBCL_WAYLAND_DATA_DEVICE_MANAGER_VERSION_MIN (3)
64#define VBCL_WAYLAND_SEAT_VERSION_MIN (5)
65#define VBCL_WAYLAND_ZWLR_DATA_CONTROL_MANAGER_VERSION_MIN (1)
66
67/* A helper for matching interface and bind to it in registry callback.*/
68#define VBCL_WAYLAND_REGISTRY_ADD_MATCH(_pRegistry, _sIfaceName, _uIface, _iface_to_bind_to, _ctx_member, _ctx_member_type, _uVersion) \
69 if (RTStrNCmp(_sIfaceName, _iface_to_bind_to.name, VBCL_WAYLAND_INTERFACE_NAME_MAX) == 0) \
70 { \
71 if (! _ctx_member) \
72 { \
73 _ctx_member = \
74 (_ctx_member_type)wl_registry_bind(_pRegistry, _uIface, &_iface_to_bind_to, _uVersion); \
75 VBClLogVerbose(4, "binding to Wayland interface '%s' (%u) v%u\n", _iface_to_bind_to.name, _uIface, wl_proxy_get_version((struct wl_proxy *) _ctx_member)); \
76 } \
77 AssertPtrReturnVoid(_ctx_member); \
78 }
79
80/* Node of mime-types list. */
81typedef struct
82{
83 /** IPRT list node. */
84 RTLISTNODE Node;
85 /** Data mime-type in string representation. */
86 char *pszMimeType;
87} vbox_wl_dcp_mime_t;
88
89/**
90 * DCP session data.
91 *
92 * A structure which accumulates all the necessary data required to
93 * maintain session between host and Wayland for clipboard sharing. */
94typedef struct
95{
96 /** Generic VBoxClient Wayland session data (synchronization point). */
97 vbcl_wl_session_t Base;
98
99 /** Session data for clipboard sharing.
100 *
101 * This data will be filled sequentially piece by piece by both
102 * sides - host event loop and Wayland event loop until clipboard
103 * buffer is obtained.
104 */
105 struct
106 {
107 /** List of mime-types which are being advertised by guest. */
108 vbox_wl_dcp_mime_t mimeTypesList;
109
110 /** Bitmask which represents list of clipboard formats which
111 * are being advertised either by host or guest depending
112 * on session type. */
113 vbcl::Waitable<volatile SHCLFORMATS> fFmts;
114
115 /** Clipboard format which either host or guest wants to
116 * obtain depending on session type. */
117 vbcl::Waitable<volatile SHCLFORMAT> uFmt;
118
119 /** Clipboard buffer which contains requested data. */
120 vbcl::Waitable<volatile uint64_t> pvClipboardBuf;
121
122 /** Size of clipboard buffer. */
123 vbcl::Waitable<volatile uint32_t> cbClipboardBuf;
124 } clip;
125} vbox_wl_dcp_session_t;
126
127/**
128 * A set of objects required to handle clipboard sharing over
129 * Data Control Protocol. */
130typedef struct
131{
132 /** Wayland event loop thread. */
133 RTTHREAD Thread;
134
135 /** A flag which indicates that Wayland event loop should terminate. */
136 volatile bool fShutdown;
137
138 /** Communication session between host event loop and Wayland. */
139 vbox_wl_dcp_session_t Session;
140
141 /** When set, incoming clipboard announcements will
142 * be ignored. This flag is used in order to prevent a feedback
143 * loop when host advertises clipboard data to Wayland. In this case,
144 * Wayland will send the same advertisements back to us. */
145 bool fIngnoreWlClipIn;
146
147 /** A flag which indicates that host has announced new clipboard content
148 * and now Wayland event loop thread should pass this information to
149 * other Wayland clients. */
150 vbcl::Waitable<volatile bool> fSendToGuest;
151
152 /** Connection handle to the host clipboard service. */
153 PVBGLR3SHCLCMDCTX pClipboardCtx;
154
155 /** Wayland compositor connection object. */
156 struct wl_display *pDisplay;
157
158 /** Wayland registry object. */
159 struct wl_registry *pRegistry;
160
161 /** Wayland Seat object. */
162 struct wl_seat *pSeat;
163
164 /** Wayland Data Device object. */
165 struct zwlr_data_control_device_v1 *pDataDevice;
166
167 /** Wayland Data Control Manager object. */
168 struct zwlr_data_control_manager_v1 *pDataControlManager;
169} vbox_wl_dcp_ctx_t;
170
171/** Data required to write clipboard content to Wayland. */
172struct vbcl_wl_dcp_write_ctx
173{
174 /** Content mime-type in string representation. */
175 const char *sMimeType;
176 /** Active file descriptor to write data into. */
177 int32_t fd;
178};
179
180/** Helper context. */
181static vbox_wl_dcp_ctx_t g_DcpCtx;
182
183static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb(vbcl_wl_session_type_t enmSessionType, void *pvUser);
184static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb(vbcl_wl_session_type_t enmSessionType, void *pvUser);
185
186
187/**********************************************************************************************************************************
188 * Wayland low level operations.
189 *********************************************************************************************************************************/
190
191
192/**
193 * A helper function which reallocates buffer to bigger size.
194 *
195 * This function will attempt to re-allocate specified buffer by cbChunk bytes.
196 * If failed, caller is responsible for freeing input buffer. On success, output
197 * buffer must be freed by caller.
198 *
199 * @returns IPRT status code.
200 * @param pvBufIn Previously allocated buffer which size needs to be increased.
201 * @param cbBufIn Size of input buffer.
202 * @param cbChunk Amount of bytes by which caller wants to increase buffer size.
203 * @param cbMax Maximum size of output buffer.
204 * @param ppBufOut Output buffer (must be freed by caller).
205 * @param pcbBufOut Size of output buffer.
206 */
207RTDECL(int) vbcl_wayland_hlp_dcp_grow_buffer(void *pvBufIn, size_t cbBufIn, size_t cbChunk, size_t cbMax,
208 void **ppBufOut, size_t *pcbBufOut)
209{
210 int rc = VERR_NO_MEMORY;
211
212 /* How many chunks were already added to the buffer. */
213 int cChunks = cbBufIn / cbChunk;
214 /* Size of a chunk to be added to already allocated buffer. */
215 size_t cbCurrentChunk = 0;
216
217 if (cbBufIn < cbMax)
218 {
219 void *pvBuf;
220
221 /* Calculate size of a chunk which can be added to already allocated memory
222 * in a way that resulting buffer size will not exceed cbMax. Always add
223 * the extra '\0' byte to the end of allocated area for safety reasons. */
224 cbCurrentChunk = RT_MIN(cbMax, cbChunk * (cChunks + 1)) - cbBufIn + 1;
225 pvBuf = RTMemReallocZ(pvBufIn, cbBufIn, cbBufIn + cbCurrentChunk);
226 if (RT_VALID_PTR(pvBuf))
227 {
228 LogRel(("Wayland: buffer size increased from %u to %u bytes\n", cbBufIn, cbBufIn + cbCurrentChunk));
229 *ppBufOut = pvBuf;
230 *pcbBufOut = cbBufIn + cbCurrentChunk;
231 rc = VINF_SUCCESS;
232 }
233 else
234 {
235 LogRel(("Wayland: unable to allocate buffer of size of %u bytes: no memory\n", cbBufIn + cbCurrentChunk));
236 rc = VERR_NO_MEMORY;
237 }
238 }
239 else
240 {
241 LogRel(("Shared Clipboard: unable to re-allocate buffer: size of %u bytes exceeded\n", cbMax));
242 rc = VERR_BUFFER_OVERFLOW;
243 }
244
245 return rc;
246}
247
248/**
249 * A helper function for reading from file descriptor until EOF.
250 *
251 * Reads clipboard data from Wayland via file descriptor.
252 *
253 * @returns IPRT status code.
254 * @param fd A file descriptor to read data from.
255 * @param ppvBuf Newly allocated output buffer (must be freed by caller).
256 * @param pcbBuf Size of output buffer.
257 */
258RTDECL(int) vbcl_wayland_hlp_dcp_read_wl_fd(int fd, void **ppvBuf, size_t *pcbBuf)
259{
260 int rc = VERR_NO_MEMORY;
261
262 struct timeval tv;
263 fd_set rfds;
264
265 /* Amount of payload actually read from Wayland fd in bytes. */
266 size_t cbDst = 0;
267 /* Dynamically growing buffer to store Wayland clipboard. */
268 void *pvDst = NULL;
269 /* Number of bytes currently allocated to read entire
270 * Wayland buffer content (actual size of pvDst). */
271 size_t cbGrowingBuffer = 0;
272 /* Number of bytes read from Wayland fd per attempt. */
273 size_t cbRead = 0;
274
275 /* Start with allocating one chunk and grow buffer later if needed. */
276 cbGrowingBuffer = VBOX_WAYLAND_BUFFER_CHUNK_INC_SIZE + 1 /* '\0' */;
277 pvDst = RTMemAllocZ(cbGrowingBuffer);
278 if (RT_VALID_PTR(pvDst))
279 {
280 /* Read everything from given fd. */
281 while (1)
282 {
283 tv.tv_sec = 0;
284 tv.tv_usec = VBCL_WAYLAND_IO_TIMEOUT_MS * 1000;
285
286 FD_ZERO(&rfds);
287 FD_SET(fd, &rfds);
288
289 /* Wait until data is available. */
290 if (select(fd + 1, &rfds, NULL, NULL, &tv) > 0)
291 {
292 /* Check if backing buffer size is big enough to store one more data chunk
293 * read from fd. If not, try to increase buffer by size of chunk x 2. */
294 if (cbDst + VBOX_WAYLAND_BUFFER_CHUNK_SIZE > cbGrowingBuffer)
295 {
296 void *pBufTmp = NULL;
297
298 rc = vbcl_wayland_hlp_dcp_grow_buffer(
299 pvDst, cbGrowingBuffer, VBOX_WAYLAND_BUFFER_CHUNK_INC_SIZE,
300 VBOX_WAYLAND_BUFFER_MAX, &pBufTmp, &cbGrowingBuffer);
301
302 if (RT_FAILURE(rc))
303 {
304 RTMemFree(pvDst);
305 break;
306 }
307 else
308 pvDst = pBufTmp;
309 }
310
311 /* Read all data from fd until EOF. */
312 cbRead = read(fd, (void *)((uint8_t *)pvDst + cbDst), VBOX_WAYLAND_BUFFER_CHUNK_SIZE);
313 if (cbRead > 0)
314 {
315 LogRel(("Wayland: read chunk of %u bytes from Wayland\n", cbRead));
316 cbDst += cbRead;
317 }
318 else
319 {
320 /* EOF has been reached. */
321 LogRel(("Wayland: read %u bytes from Wayland\n", cbDst));
322
323 if (cbDst > 0)
324 {
325 rc = VINF_SUCCESS;
326 *ppvBuf = pvDst;
327 *pcbBuf = cbDst;
328 }
329 else
330 {
331 rc = VERR_NO_DATA;
332 RTMemFree(pvDst);
333 }
334
335 break;
336 }
337 }
338 else
339 {
340 rc = VERR_TIMEOUT;
341 break;
342 }
343 }
344 }
345
346 return rc;
347}
348
349/**
350 * A helper function for writing to a file descriptor provided by Wayland.
351 *
352 * @returns IPRT status code.
353 * @param fd A file descriptor to write data to.
354 * @param pvBuf Data buffer.
355 * @param cbBuf Size of data buffer.
356 */
357RTDECL(int) vbcl_wayland_hlp_dcp_write_wl_fd(int fd, void *pvBuf, size_t cbBuf)
358{
359 struct timeval tv;
360 fd_set wfds;
361
362 int rc = VINF_SUCCESS;
363
364 tv.tv_sec = 0;
365 tv.tv_usec = VBCL_WAYLAND_IO_TIMEOUT_MS * 1000;
366
367 FD_ZERO(&wfds);
368 FD_SET(fd, &wfds);
369
370 /* Wait until data is available. */
371 if (select(fd + 1, NULL, &wfds, NULL, &tv) > 0)
372 {
373 if (FD_ISSET(fd, &wfds))
374 {
375 ssize_t cbWritten = write(fd, pvBuf, cbBuf);
376 if (cbWritten != (ssize_t)cbBuf)
377 {
378 VBClLogError("cannot write clipboard data, written %d out of %d bytes\n",
379 cbWritten, cbBuf);
380 rc = VERR_PIPE_NOT_CONNECTED;
381 }
382 else
383 VBClLogVerbose(5, "written %u bytes to Wayland clipboard\n", cbWritten);
384 }
385 else
386 {
387 VBClLogError("cannot write fd\n");
388 rc = VERR_TIMEOUT;
389 }
390 }
391 else
392 rc = VERR_TIMEOUT;
393
394 return rc;
395}
396
397/**
398 * Read the next event from Wayland compositor.
399 *
400 * Implements custom reader function which can be interrupted
401 * on service termination request.
402 *
403 * @returns IPRT status code.
404 * @param pCtx Context data.
405 */
406static int vbcl_wayland_hlp_dcp_next_event(vbox_wl_dcp_ctx_t *pCtx)
407{
408 int rc = VINF_SUCCESS;
409
410 struct timeval tv;
411 fd_set rfds, efds;
412 int fd;
413
414 /* Instead of using wl_display_dispatch() directly, implement
415 * custom event loop handling as recommended in Wayland documentation.
416 * Thus, we can have a control over Wayland fd polling and in turn
417 * can request event loop thread to shutdown when needed. */
418
419 tv.tv_sec = 0;
420 tv.tv_usec = VBCL_WAYLAND_IO_TIMEOUT_MS * 1000;
421
422 fd = wl_display_get_fd(pCtx->pDisplay);
423
424 FD_ZERO(&rfds);
425 FD_SET(fd, &rfds);
426
427 FD_ZERO(&efds);
428 FD_SET(fd, &efds);
429
430 while (wl_display_prepare_read(pCtx->pDisplay) != 0)
431 wl_display_dispatch(pCtx->pDisplay);
432
433 wl_display_flush(pCtx->pDisplay);
434
435 if (select(fd + 1, &rfds, NULL, &efds, &tv) > 0)
436 wl_display_read_events(pCtx->pDisplay);
437 else
438 {
439 wl_display_cancel_read(pCtx->pDisplay);
440 rc = VERR_TIMEOUT;
441 }
442
443 wl_display_dispatch_pending(pCtx->pDisplay);
444
445 return rc;
446}
447
448
449/**********************************************************************************************************************************
450 * Host Clipboard service callbacks.
451 *********************************************************************************************************************************/
452
453
454/**
455 * Release session resources.
456 *
457 * @param pSession Session data.
458 */
459static void vbcl_wayland_hlp_dcp_session_release(vbox_wl_dcp_session_t *pSession)
460{
461 void *pvData;
462
463 if (!RTListIsEmpty(&pSession->clip.mimeTypesList.Node))
464 {
465 vbox_wl_dcp_mime_t *pEntry, *pNextEntry;
466
467 RTListForEachSafe(&pSession->clip.mimeTypesList.Node, pEntry, pNextEntry, vbox_wl_dcp_mime_t, Node)
468 {
469 RTListNodeRemove(&pEntry->Node);
470 RTStrFree(pEntry->pszMimeType);
471 RTMemFree(pEntry);
472 }
473 }
474
475 pvData = (void *)pSession->clip.pvClipboardBuf.reset();
476 if (RT_VALID_PTR(pvData))
477 RTMemFree(pvData);
478}
479
480/**
481 * Initialize session.
482 *
483 * @param pSession Session data.
484 */
485static void vbcl_wayland_hlp_dcp_session_init(vbox_wl_dcp_session_t *pSession)
486{
487 RTListInit(&pSession->clip.mimeTypesList.Node);
488
489 pSession->clip.fFmts.init(VBOX_SHCL_FMT_NONE, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
490 pSession->clip.uFmt.init(VBOX_SHCL_FMT_NONE, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
491 pSession->clip.pvClipboardBuf.init(0, VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS);
492 pSession->clip.cbClipboardBuf.init(0, VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS);
493}
494
495/**
496 * Reset previously initialized session.
497 *
498 * @param pSession Session data.
499 */
500static void vbcl_wayland_hlp_dcp_session_prepare(vbox_wl_dcp_session_t *pSession)
501{
502 vbcl_wayland_hlp_dcp_session_release(pSession);
503 vbcl_wayland_hlp_dcp_session_init(pSession);
504}
505
506/**
507 * Session callback: Generic session initializer.
508 *
509 * This callback starts new session.
510 *
511 * @returns IPRT status code.
512 * @param enmSessionType Session type (unused).
513 * @param pvUser User data (unused).
514 */
515static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_session_start_generic_cb(
516 vbcl_wl_session_type_t enmSessionType, void *pvUser)
517{
518 RT_NOREF(enmSessionType, pvUser);
519
520 VBCL_LOG_CALLBACK;
521
522 vbcl_wayland_hlp_dcp_session_prepare(&g_DcpCtx.Session);
523
524 return VINF_SUCCESS;
525}
526
527/**
528 * Wayland registry global handler.
529 *
530 * This callback is triggered when Wayland Registry listener is registered.
531 * Wayland client library will trigger it individually for each available global
532 * object.
533 *
534 * @param pvUser Context data.
535 * @param pRegistry Wayland Registry object.
536 * @param uName Numeric name of the global object.
537 * @param sIface Name of interface implemented by the object.
538 * @param uVersion Interface version.
539 */
540static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_registry_global_handler(
541 void *pvUser, struct wl_registry *pRegistry, uint32_t uName, const char *sIface, uint32_t uVersion)
542{
543 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
544
545 RT_NOREF(pRegistry);
546 RT_NOREF(uVersion);
547
548 AssertPtrReturnVoid(pCtx);
549 AssertPtrReturnVoid(sIface);
550
551 /* Wrappers around 'if' statement. */
552 VBCL_WAYLAND_REGISTRY_ADD_MATCH(pRegistry, sIface, uName, wl_seat_interface, pCtx->pSeat, struct wl_seat *, VBCL_WAYLAND_SEAT_VERSION_MIN)
553 else VBCL_WAYLAND_REGISTRY_ADD_MATCH(pRegistry, sIface, uName, zwlr_data_control_manager_v1_interface, pCtx->pDataControlManager, struct zwlr_data_control_manager_v1 *, VBCL_WAYLAND_ZWLR_DATA_CONTROL_MANAGER_VERSION_MIN)
554 else
555 VBClLogVerbose(5, "ignoring Wayland interface %s\n", sIface);
556}
557
558/**
559 * Wayland registry global remove handler.
560 *
561 * Triggered when global object is removed from Wayland registry.
562 *
563 * @param pvUser Context data.
564 * @param pRegistry Wayland Registry object.
565 * @param uName Numeric name of the global object.
566 */
567static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_registry_global_remove_handler(
568 void *pvUser, struct wl_registry *pRegistry, uint32_t uName)
569{
570 RT_NOREF(pvUser);
571 RT_NOREF(pRegistry);
572 RT_NOREF(uName);
573}
574
575/** Wayland global registry callbacks. */
576static const struct wl_registry_listener g_vbcl_wayland_hlp_registry_cb =
577{
578 vbcl_wayland_hlp_dcp_registry_global_handler, /* .global */
579 vbcl_wayland_hlp_dcp_registry_global_remove_handler /* .global_remove */
580};
581
582
583
584/**********************************************************************************************************************************
585 * Wayland Data Control Offer callbacks.
586 *********************************************************************************************************************************/
587
588
589/**
590 * Session callback: Collect clipboard format advertised by guest.
591 *
592 * This callback must be executed in context of Wayland event thread
593 * in order to be able to access Wayland clipboard content.
594 *
595 * This callback adds mime-type just advertised by Wayland into a list
596 * of mime-types which in turn later will be advertised to the host.
597 *
598 * @returns IPRT status code.
599 * @param enmSessionType Session type, must be verified as
600 * a consistency check.
601 * @param pvUser User data (Wayland mime-type).
602 */
603static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_add_fmt_cb(
604 vbcl_wl_session_type_t enmSessionType, void *pvUser)
605{
606 const char *sMimeType = (const char *)pvUser;
607 AssertPtrReturn(sMimeType, VERR_INVALID_PARAMETER);
608
609 SHCLFORMAT uFmt = VBoxMimeConvGetIdByMime(sMimeType);
610
611 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
612 ? VINF_SUCCESS : VERR_WRONG_ORDER;
613
614 VBCL_LOG_CALLBACK;
615
616 if (RT_SUCCESS(rc))
617 {
618 if (uFmt != VBOX_SHCL_FMT_NONE)
619 {
620 vbox_wl_dcp_mime_t *pNode = (vbox_wl_dcp_mime_t *)RTMemAllocZ(sizeof(vbox_wl_dcp_mime_t));
621 if (RT_VALID_PTR(pNode))
622 {
623 pNode->pszMimeType = RTStrDup((char *)sMimeType);
624 if (RT_VALID_PTR(pNode->pszMimeType))
625 RTListAppend(&g_DcpCtx.Session.clip.mimeTypesList.Node, &pNode->Node);
626 else
627 RTMemFree(pNode);
628 }
629
630 if ( !RT_VALID_PTR(pNode)
631 || !RT_VALID_PTR(pNode->pszMimeType))
632 {
633 rc = VERR_NO_MEMORY;
634 }
635 }
636 else
637 rc = VERR_NO_DATA;
638 }
639
640 return rc;
641}
642
643
644/**
645 * Data Control Offer advertise callback.
646 *
647 * Triggered when other Wayland client advertises new clipboard content.
648 *
649 * @param pvUser Context data.
650 * @param pOffer Wayland Data Control Offer object.
651 * @param sMimeType Mime-type of newly available clipboard data.
652 */
653static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_control_offer_offer(
654 void *pvUser, struct zwlr_data_control_offer_v1 *pOffer, const char *sMimeType)
655{
656 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
657 int rc;
658
659 RT_NOREF(pOffer);
660
661 VBCL_LOG_CALLBACK;
662
663 rc = vbcl_wayland_session_join(&pCtx->Session.Base,
664 &vbcl_wayland_hlp_dcp_gh_add_fmt_cb,
665 (void *)sMimeType);
666 if (RT_FAILURE(rc))
667 VBClLogError("cannot save formats announced by the guest, rc=%Rrc\n", rc);
668}
669
670/** Wayland Data Control Offer interface callbacks. */
671static const struct zwlr_data_control_offer_v1_listener g_data_control_offer_listener =
672{
673 vbcl_wayland_hlp_dcp_data_control_offer_offer,
674};
675
676
677/**********************************************************************************************************************************
678 * Wayland Data Control Device callbacks.
679 *********************************************************************************************************************************/
680
681
682/**
683 * Convert list of mime-types in string representation into bitmask of VBox formats.
684 *
685 * @returns Formats bitmask.
686 * @param pList List of mime-types in string representation.
687 */
688static SHCLFORMATS vbcl_wayland_hlp_dcp_match_formats(vbox_wl_dcp_mime_t *pList)
689{
690 SHCLFORMATS fFmts = VBOX_SHCL_FMT_NONE;
691
692 if (!RTListIsEmpty(&pList->Node))
693 {
694 vbox_wl_dcp_mime_t *pEntry;
695 RTListForEach(&pList->Node, pEntry, vbox_wl_dcp_mime_t, Node)
696 {
697 AssertPtrReturn(pEntry, VERR_INVALID_PARAMETER);
698 AssertPtrReturn(pEntry->pszMimeType, VERR_INVALID_PARAMETER);
699
700 fFmts |= VBoxMimeConvGetIdByMime(pEntry->pszMimeType);
701 }
702 }
703
704 return fFmts;
705}
706
707/**
708 * Find first matching clipboard mime-type for given format ID.
709 *
710 * @returns Matching mime-type in string representation or NULL if not found.
711 * @param uFmt Format in VBox representation to match.
712 * @param pList List of Wayland mime-types in string representation.
713 */
714static char *vbcl_wayland_hlp_dcp_match_mime_type(SHCLFORMAT uFmt, vbox_wl_dcp_mime_t *pList)
715{
716 char *pszMimeType = NULL;
717
718 if (!RTListIsEmpty(&pList->Node))
719 {
720 vbox_wl_dcp_mime_t *pEntry;
721 RTListForEach(&pList->Node, pEntry, vbox_wl_dcp_mime_t, Node)
722 {
723 AssertPtrReturn(pEntry, NULL);
724 AssertPtrReturn(pEntry->pszMimeType, NULL);
725
726 if (uFmt == VBoxMimeConvGetIdByMime(pEntry->pszMimeType))
727 {
728 pszMimeType = pEntry->pszMimeType;
729 break;
730 }
731 }
732 }
733
734 return pszMimeType;
735}
736
737/**
738 * Read clipboard buffer from Wayland in specified format.
739 *
740 * @returns IPRT status code.
741 * @param pCtx DCP context data.
742 * @param pOffer Data offer object.
743 * @param uFmt Clipboard format in VBox representation.
744 * @param pszMimeType Requested mime-type in string representation.
745 */
746static int vbcl_wayland_hlp_dcp_receive_offer(
747 vbox_wl_dcp_ctx_t *pCtx, zwlr_data_control_offer_v1 *pOffer, SHCLFORMAT uFmt, char *pszMimeType)
748{
749 int rc = VERR_PIPE_NOT_CONNECTED;
750
751 int aFds[2];
752 void *pvBuf = NULL;
753 size_t cbBuf = 0;
754
755 RT_NOREF(uFmt);
756
757 if (pipe(aFds) == 0)
758 {
759 zwlr_data_control_offer_v1_receive(
760 (struct zwlr_data_control_offer_v1 *)pOffer, pszMimeType, aFds[1]);
761
762 close(aFds[1]);
763 wl_display_flush(pCtx->pDisplay);
764
765 rc = vbcl_wayland_hlp_dcp_read_wl_fd(aFds[0], &pvBuf, &cbBuf);
766 if (RT_SUCCESS(rc))
767 {
768 void *pvBufOut = NULL;
769 size_t cbBufOut = 0;
770
771 rc = VBoxMimeConvNativeToVBox(pszMimeType, pvBuf, cbBuf, &pvBufOut, &cbBufOut);
772 if (RT_SUCCESS(rc))
773 {
774 pCtx->Session.clip.pvClipboardBuf.set((uint64_t)pvBufOut);
775 pCtx->Session.clip.cbClipboardBuf.set((uint64_t)cbBufOut);
776 }
777
778 RTMemFree(pvBuf);
779 }
780 }
781 else
782 VBClLogError("cannot read mime-type '%s' from Wayland, rc=%Rrc\n", pszMimeType, rc);
783
784 return rc;
785}
786
787/**
788 * Session callback: Advertise clipboard to the host.
789 *
790 * This callback must be executed in context of Wayland event thread
791 * in order to be able to access Wayland clipboard content.
792 *
793 * This callback (1) coverts Wayland clipboard formats into VBox
794 * representation, (2) sets formats to the session, (3) waits for
795 * host to request clipboard data in certain format, and (4)
796 * receives Wayland clipboard in requested format.
797 *
798 * @returns IPRT status code.
799 * @param enmSessionType Session type, must be verified as
800 * a consistency check.
801 * @param pvUser User data (data offer object).
802 */
803static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_clip_report_cb(
804 vbcl_wl_session_type_t enmSessionType, void *pvUser)
805{
806 struct zwlr_data_control_offer_v1 *pOffer = (struct zwlr_data_control_offer_v1 *)pvUser;
807 SHCLFORMATS fFmts = VBOX_SHCL_FMT_NONE;
808
809 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
810 ? VINF_SUCCESS : VERR_WRONG_ORDER;
811
812 AssertPtrReturn(pOffer, VERR_INVALID_PARAMETER);
813
814 VBCL_LOG_CALLBACK;
815
816 if (RT_SUCCESS(rc))
817 {
818 fFmts = vbcl_wayland_hlp_dcp_match_formats(&g_DcpCtx.Session.clip.mimeTypesList);
819 if (fFmts != VBOX_SHCL_FMT_NONE)
820 {
821 SHCLFORMAT uFmt;
822
823 g_DcpCtx.Session.clip.fFmts.set(fFmts);
824
825 if (RT_VALID_PTR(g_DcpCtx.pClipboardCtx))
826 {
827 rc = VbglR3ClipboardReportFormats(g_DcpCtx.pClipboardCtx->idClient, fFmts);
828 if (RT_SUCCESS(rc))
829 {
830 uFmt = g_DcpCtx.Session.clip.uFmt.wait();
831 if (uFmt != g_DcpCtx.Session.clip.uFmt.defaults())
832 {
833 char *pszMimeType =
834 vbcl_wayland_hlp_dcp_match_mime_type(uFmt, &g_DcpCtx.Session.clip.mimeTypesList);
835
836 if (RT_VALID_PTR(pszMimeType))
837 {
838 rc = vbcl_wayland_hlp_dcp_receive_offer(&g_DcpCtx, pOffer, uFmt, pszMimeType);
839
840 VBClLogVerbose(5, "will send fmt=0x%x (%s) to the host\n", uFmt, pszMimeType);
841 }
842 else
843 rc = VERR_NO_DATA;
844 }
845 else
846 rc = VERR_TIMEOUT;
847 }
848 else
849 VBClLogError("cannot report formats to host, rc=%Rrc\n", rc);
850 }
851 else
852 {
853 VBClLogVerbose(2, "cannot announce to guest, no host service connection yet\n");
854 rc = VERR_TRY_AGAIN;
855 }
856 }
857 else
858 rc = VERR_NO_DATA;
859
860 zwlr_data_control_offer_v1_destroy((struct zwlr_data_control_offer_v1 *)pOffer);
861
862 VBClLogVerbose(5, "announcing fFmts=0x%x to host, rc=%Rrc\n", fFmts, rc);
863 }
864
865 return rc;
866}
867
868/**
869 * Data Control Device offer callback.
870 *
871 * Triggered when other Wayland client advertises new clipboard content.
872 * When this callback is triggered, a new zwlr_data_control_offer_v1 object
873 * is created. This callback should setup listener callbacks for this object.
874 *
875 * @param pvUser Context data.
876 * @param pDevice Wayland Data Control Device object.
877 * @param pOffer Wayland Data Control Offer object.
878 */
879static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_device_data_offer(
880 void *pvUser, struct zwlr_data_control_device_v1 *pDevice, struct zwlr_data_control_offer_v1 *pOffer)
881{
882 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
883 int rc;
884
885 RT_NOREF(pDevice);
886
887 VBCL_LOG_CALLBACK;
888
889 if (pCtx->fIngnoreWlClipIn)
890 {
891 VBClLogVerbose(5, "ignoring Wayland clipboard data offer, we advertising new clipboard ourselves\n");
892 return;
893 }
894
895 rc = vbcl_wayland_session_end(&pCtx->Session.Base, NULL, NULL);
896 if (RT_SUCCESS(rc))
897 {
898 rc = vbcl_wayland_session_start(&pCtx->Session.Base,
899 VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST,
900 &vbcl_wayland_hlp_dcp_session_start_generic_cb,
901 &pCtx->Session);
902 if (RT_SUCCESS(rc))
903 {
904 zwlr_data_control_offer_v1_add_listener(pOffer, &g_data_control_offer_listener, pvUser);
905
906 /* Receive all the advertised mime types. */
907 wl_display_roundtrip(pCtx->pDisplay);
908
909 /* Try to send an announcement to the host. */
910 rc = vbcl_wayland_session_join(&pCtx->Session.Base,
911 &vbcl_wayland_hlp_dcp_gh_clip_report_cb,
912 pOffer);
913 }
914 else
915 VBClLogError("unable to start session, rc=%Rrc\n", rc);
916 }
917 else
918 VBClLogError("unable to start session, previous session is still running, rc=%Rrc\n", rc);
919}
920
921/**
922 * Data Control Device selection callback.
923 *
924 * Triggered when Wayland client advertises new clipboard content.
925 * In this callback, actual clipboard data is received from Wayland client.
926 *
927 * @param pvUser Context data.
928 * @param pDevice Wayland Data Control Device object.
929 * @param pOffer Wayland Data Control Offer object.
930 */
931static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_device_selection(
932 void *pvUser, struct zwlr_data_control_device_v1 *pDevice, struct zwlr_data_control_offer_v1 *pOffer)
933{
934 RT_NOREF(pDevice, pvUser, pOffer);
935
936 VBCL_LOG_CALLBACK;
937}
938
939/**
940 * Data Control Device finished callback.
941 *
942 * Triggered when Data Control Device object is no longer valid and
943 * needs to be destroyed.
944 *
945 * @param pvUser Context data.
946 * @param pDevice Wayland Data Control Device object.
947 */
948static void vbcl_wayland_hlp_dcp_data_device_finished(
949 void *pvUser, struct zwlr_data_control_device_v1 *pDevice)
950{
951 RT_NOREF(pvUser);
952
953 VBCL_LOG_CALLBACK;
954
955 zwlr_data_control_device_v1_destroy(pDevice);
956}
957
958/**
959 * Data Control Device primary selection callback.
960 *
961 * Same as shcl_wl_data_control_device_selection, but triggered for
962 * primary selection case.
963 *
964 * @param pvUser Context data.
965 * @param pDevice Wayland Data Control Device object.
966 * @param pOffer Wayland Data Control Offer object.
967 */
968static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_device_primary_selection(
969 void *pvUser, struct zwlr_data_control_device_v1 *pDevice, struct zwlr_data_control_offer_v1 *pOffer)
970{
971 RT_NOREF(pDevice, pvUser, pOffer);
972
973 VBCL_LOG_CALLBACK;
974}
975
976
977/** Data Control Device interface callbacks. */
978static const struct zwlr_data_control_device_v1_listener g_data_device_listener =
979{
980 vbcl_wayland_hlp_dcp_data_device_data_offer,
981 vbcl_wayland_hlp_dcp_data_device_selection,
982 vbcl_wayland_hlp_dcp_data_device_finished,
983 vbcl_wayland_hlp_dcp_data_device_primary_selection,
984};
985
986
987/**********************************************************************************************************************************
988 * Wayland Data Control Source callbacks.
989 *********************************************************************************************************************************/
990
991/**
992 * Wayland data send callback.
993 *
994 * Triggered when other Wayland client wants to read clipboard
995 * data from us.
996 *
997 * @param pvUser VBox private data.
998 * @param pDataSource, Wayland Data Control Source object.
999 * @param sMimeType A mime-type of requested data.
1000 * @param fd A file descriptor to write clipboard content into.
1001 */
1002static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_source_send(
1003 void *pvUser, struct zwlr_data_control_source_v1 *pDataSource,
1004 const char *sMimeType, int32_t fd)
1005{
1006 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
1007 int rc;
1008
1009 struct vbcl_wl_dcp_write_ctx priv;
1010
1011 RT_NOREF(pDataSource);
1012
1013 VBCL_LOG_CALLBACK;
1014
1015 RT_ZERO(priv);
1016
1017 priv.sMimeType = sMimeType;
1018 priv.fd = fd;
1019
1020 rc = vbcl_wayland_session_join(&pCtx->Session.Base,
1021 &vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb,
1022 &priv);
1023
1024 VBClLogVerbose(5, "vbcl_wayland_hlp_dcp_data_source_send, rc=%Rrc\n", rc);
1025 close(fd);
1026}
1027
1028/**
1029 * Wayland data canceled callback.
1030 *
1031 * Triggered when data source was replaced by another data source
1032 * and no longer valid.
1033 *
1034 * @param pvData VBox private data.
1035 * @param pDataSource Wayland Data Control Source object.
1036 */
1037static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_source_cancelled(
1038 void *pvData, struct zwlr_data_control_source_v1 *pDataSource)
1039{
1040 RT_NOREF(pvData);
1041
1042 VBCL_LOG_CALLBACK;
1043
1044 zwlr_data_control_source_v1_destroy(pDataSource);
1045}
1046
1047
1048/** Wayland Data Control Source interface callbacks. */
1049static const struct zwlr_data_control_source_v1_listener g_data_source_listener =
1050{
1051 vbcl_wayland_hlp_dcp_data_source_send,
1052 vbcl_wayland_hlp_dcp_data_source_cancelled,
1053};
1054
1055
1056/**********************************************************************************************************************************
1057 * Helper specific code and session callbacks.
1058 *********************************************************************************************************************************/
1059
1060
1061/**
1062 * Setup or reset helper context.
1063 *
1064 * This function is used on helper init and termination. In case of
1065 * init, memory is not initialized yet, so it needs to be zeroed.
1066 * In case of shutdown, memory is already initialized and previously
1067 * allocated resources must be freed.
1068 *
1069 * @param pCtx Context data.
1070 * @param fShutdown A flag to indicate if session resources
1071 * need to be deallocated.
1072 */
1073static void vbcl_wayland_hlp_dcp_reset_ctx(vbox_wl_dcp_ctx_t *pCtx, bool fShutdown)
1074{
1075 pCtx->Thread = NIL_RTTHREAD;
1076 pCtx->fShutdown = false;
1077 pCtx->fIngnoreWlClipIn = false;
1078 pCtx->fSendToGuest.init(false, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
1079 pCtx->pClipboardCtx = NULL;
1080 pCtx->pDisplay = NULL;
1081 pCtx->pRegistry = NULL;
1082 pCtx->pSeat = NULL;
1083 pCtx->pDataDevice = NULL;
1084 pCtx->pDataControlManager = NULL;
1085
1086 if (fShutdown)
1087 vbcl_wayland_hlp_dcp_session_release(&pCtx->Session);
1088
1089 vbcl_wayland_hlp_dcp_session_init(&pCtx->Session);
1090}
1091
1092/**
1093 * Disconnect from Wayland compositor.
1094 *
1095 * Close connection, release resources and reset context data.
1096 *
1097 * @param pCtx Context data.
1098 */
1099static void vbcl_wayland_hlp_dcp_disconnect(vbox_wl_dcp_ctx_t *pCtx)
1100{
1101 if (RT_VALID_PTR(pCtx->pDataControlManager))
1102 zwlr_data_control_manager_v1_destroy(pCtx->pDataControlManager);
1103
1104 if (RT_VALID_PTR(pCtx->pDataDevice))
1105 zwlr_data_control_device_v1_destroy(pCtx->pDataDevice);
1106
1107 if (RT_VALID_PTR(pCtx->pSeat))
1108 wl_seat_destroy(pCtx->pSeat);
1109
1110 if (RT_VALID_PTR(pCtx->pRegistry))
1111 wl_registry_destroy(pCtx->pRegistry);
1112
1113 if (RT_VALID_PTR(pCtx->pDisplay))
1114 wl_display_disconnect(pCtx->pDisplay);
1115
1116 vbcl_wayland_hlp_dcp_reset_ctx(pCtx, true);
1117}
1118
1119/**
1120 * Connect to Wayland compositor.
1121 *
1122 * Establish connection, bind to all required interfaces.
1123 *
1124 * @returns TRUE on success, FALSE otherwise.
1125 * @param pCtx Context data.
1126 */
1127static bool vbcl_wayland_hlp_dcp_connect(vbox_wl_dcp_ctx_t *pCtx)
1128{
1129 const char *csWaylandDisplay = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY);
1130 bool fConnected = false;
1131
1132 if (RT_VALID_PTR(csWaylandDisplay))
1133 pCtx->pDisplay = wl_display_connect(csWaylandDisplay);
1134 else
1135 VBClLogError("cannot connect to Wayland compositor "
1136 VBCL_ENV_WAYLAND_DISPLAY " environment variable not set\n");
1137
1138 if (RT_VALID_PTR(pCtx->pDisplay))
1139 {
1140 pCtx->pRegistry = wl_display_get_registry(pCtx->pDisplay);
1141 if (RT_VALID_PTR(pCtx->pRegistry))
1142 {
1143 wl_registry_add_listener(pCtx->pRegistry, &g_vbcl_wayland_hlp_registry_cb, (void *)pCtx);
1144 wl_display_roundtrip(pCtx->pDisplay);
1145
1146 if (RT_VALID_PTR(pCtx->pDataControlManager))
1147 {
1148 if (RT_VALID_PTR(pCtx->pSeat))
1149 {
1150 pCtx->pDataDevice = zwlr_data_control_manager_v1_get_data_device(pCtx->pDataControlManager, pCtx->pSeat);
1151 if (RT_VALID_PTR(pCtx->pDataDevice))
1152 {
1153 if (RT_VALID_PTR(pCtx->pDataControlManager))
1154 fConnected = true;
1155 else
1156 VBClLogError("cannot get Wayland data control manager interface\n");
1157 }
1158 else
1159 VBClLogError("cannot get Wayland data device interface\n");
1160 }
1161 else
1162 VBClLogError("cannot get Wayland seat interface\n");
1163 }
1164 else
1165 VBClLogError("cannot get Wayland device manager interface\n");
1166 }
1167 else
1168 VBClLogError("cannot connect to Wayland registry\n");
1169 }
1170 else
1171 VBClLogError("cannot connect to Wayland compositor\n");
1172
1173 if (!fConnected)
1174 vbcl_wayland_hlp_dcp_disconnect(pCtx);
1175
1176 return fConnected;
1177}
1178
1179
1180/**
1181 * Main loop for Wayland compositor events.
1182 *
1183 * All requests to Wayland compositor must be performed in context
1184 * of this thread.
1185 *
1186 * @returns IPRT status code.
1187 * @param hThreadSelf IPRT thread object.
1188 * @param pvUser Context data.
1189 */
1190static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_event_loop(RTTHREAD hThreadSelf, void *pvUser)
1191{
1192 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
1193 int rc = VERR_TRY_AGAIN;
1194
1195 if (vbcl_wayland_hlp_dcp_connect(pCtx))
1196 {
1197 /* Start listening Data Control Device interface. */
1198 if (zwlr_data_control_device_v1_add_listener(pCtx->pDataDevice, &g_data_device_listener, (void *)pCtx) == 0)
1199 {
1200 /* Tell parent thread we are ready. */
1201 RTThreadUserSignal(hThreadSelf);
1202
1203 while (1)
1204 {
1205 rc = vbcl_wayland_hlp_dcp_next_event(pCtx);
1206 if ( rc != VERR_TIMEOUT
1207 && RT_FAILURE(rc))
1208 {
1209 VBClLogError("cannot read event from Wayland, rc=%Rrc\n", rc);
1210 }
1211
1212 if (pCtx->fSendToGuest.reset())
1213 {
1214 rc = vbcl_wayland_session_join(&pCtx->Session.Base,
1215 &vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb,
1216 NULL);
1217 }
1218
1219 /* Handle graceful thread termination. */
1220 if (pCtx->fShutdown)
1221 {
1222 rc = VINF_SUCCESS;
1223 break;
1224 }
1225 }
1226 }
1227 else
1228 {
1229 rc = VERR_NOT_SUPPORTED;
1230 VBClLogError("cannot subscribe to Data Control Device events\n");
1231 }
1232
1233 vbcl_wayland_hlp_dcp_disconnect(pCtx);
1234 }
1235
1236 /* Notify parent thread if we failed to start, so it won't be
1237 * waiting 30 sec to figure this out. */
1238 if (RT_FAILURE(rc))
1239 RTThreadUserSignal(hThreadSelf);
1240
1241 return rc;
1242}
1243
1244/**
1245 * @interface_method_impl{VBCLWAYLANDHELPER,pfnProbe}
1246 */
1247static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_probe(void)
1248{
1249 vbox_wl_dcp_ctx_t probeCtx;
1250 int fCaps = VBOX_WAYLAND_HELPER_CAP_NONE;
1251 VBGHDISPLAYSERVERTYPE enmDisplayServerType = VBGHDisplayServerTypeDetect();
1252
1253 vbcl_wayland_hlp_dcp_reset_ctx(&probeCtx, false /* fShutdown */);
1254 vbcl_wayland_session_init(&probeCtx.Session.Base);
1255
1256 if (VBGHDisplayServerTypeIsWaylandAvailable(enmDisplayServerType))
1257 {
1258 if (vbcl_wayland_hlp_dcp_connect(&probeCtx))
1259 {
1260 fCaps |= VBOX_WAYLAND_HELPER_CAP_CLIPBOARD;
1261 vbcl_wayland_hlp_dcp_disconnect(&probeCtx);
1262 }
1263 }
1264
1265 return fCaps;
1266}
1267
1268/**
1269 * @interface_method_impl{VBCLWAYLANDHELPER,pfnInit}
1270 */
1271RTDECL(int) vbcl_wayland_hlp_dcp_init(void)
1272{
1273 vbcl_wayland_hlp_dcp_reset_ctx(&g_DcpCtx, false /* fShutdown */);
1274 vbcl_wayland_session_init(&g_DcpCtx.Session.Base);
1275
1276 return VBClClipboardThreadStart(&g_DcpCtx.Thread, vbcl_wayland_hlp_dcp_event_loop, "wl-dcp", &g_DcpCtx);
1277}
1278
1279/**
1280 * @interface_method_impl{VBCLWAYLANDHELPER,pfnTerm}
1281 */
1282RTDECL(int) vbcl_wayland_hlp_dcp_term(void)
1283{
1284 int rc;
1285 int rcThread = 0;
1286
1287 /* Set termination flag. Wayland event loop should pick it up
1288 * on the next iteration. */
1289 g_DcpCtx.fShutdown = true;
1290
1291 /* Wait for Wayland event loop thread to shutdown. */
1292 rc = RTThreadWait(g_DcpCtx.Thread, RT_MS_30SEC, &rcThread);
1293 if (RT_SUCCESS(rc))
1294 VBClLogInfo("Wayland event thread exited with status, rc=%Rrc\n", rcThread);
1295 else
1296 VBClLogError("unable to stop Wayland event thread, rc=%Rrc\n", rc);
1297
1298 return rc;
1299}
1300
1301/**
1302 * @interface_method_impl{VBCLWAYLANDHELPER,pfnSetClipboardCtx}
1303 */
1304static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_set_clipboard_ctx(PVBGLR3SHCLCMDCTX pCtx)
1305{
1306 g_DcpCtx.pClipboardCtx = pCtx;
1307}
1308
1309/**
1310 * @interface_method_impl{VBCLWAYLANDHELPER,pfnPopup}
1311 */
1312static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_popup(void)
1313{
1314 return VINF_SUCCESS;
1315}
1316
1317/**
1318 * Session callback: Copy clipboard to the guest.
1319 *
1320 * This callback must be executed in context of Wayland event thread
1321 * in order to be able to inject clipboard content into Wayland. It is
1322 * triggered when Wayland client already decided data in which format
1323 * it wants to request.
1324 *
1325 * This callback (1) sets requested clipboard format to the session,
1326 * (2) waits for clipboard data to be copied from the host, (3) converts
1327 * host clipboard data into guest representation, and (4) sends clipboard
1328 * to the guest by writing given file descriptor.
1329 *
1330 * @returns IPRT status code.
1331 * @param enmSessionType Session type, must be verified as
1332 * a consistency check.
1333 * @param pvUser User data (Wayland I/O context).
1334 */
1335static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb(
1336 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1337{
1338 struct vbcl_wl_dcp_write_ctx *pPriv = (struct vbcl_wl_dcp_write_ctx *)pvUser;
1339 AssertPtrReturn(pPriv, VERR_INVALID_PARAMETER);
1340
1341 void *pvBuf;
1342 uint32_t cbBuf;
1343
1344 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
1345 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1346
1347 VBCL_LOG_CALLBACK;
1348
1349 if (RT_SUCCESS(rc))
1350 {
1351 if (RT_VALID_PTR(g_DcpCtx.pClipboardCtx))
1352 {
1353 /* Set requested format to the session. */
1354 g_DcpCtx.Session.clip.uFmt.set(VBoxMimeConvGetIdByMime(pPriv->sMimeType));
1355
1356 /* Wait for data in requested format. */
1357 pvBuf = (void *)g_DcpCtx.Session.clip.pvClipboardBuf.wait();
1358 cbBuf = g_DcpCtx.Session.clip.cbClipboardBuf.wait();
1359 if ( cbBuf != g_DcpCtx.Session.clip.cbClipboardBuf.defaults()
1360 && pvBuf != (void *)g_DcpCtx.Session.clip.pvClipboardBuf.defaults())
1361 {
1362 void *pvBufOut;
1363 size_t cbOut;
1364
1365 /* Convert clipboard data from VBox representation into guest format. */
1366 rc = VBoxMimeConvVBoxToNative(pPriv->sMimeType, pvBuf, cbBuf, &pvBufOut, &cbOut);
1367 if (RT_SUCCESS(rc))
1368 {
1369 rc = vbcl_wayland_hlp_dcp_write_wl_fd(pPriv->fd, pvBufOut, cbOut);
1370 RTMemFree(pvBufOut);
1371 }
1372 else
1373 VBClLogError("cannot convert '%s' to native format, rc=%Rrc\n", rc);
1374 }
1375 else
1376 rc = VERR_TIMEOUT;
1377 }
1378 else
1379 {
1380 VBClLogVerbose(2, "cannot send to guest, no host service connection yet\n");
1381 rc = VERR_TRY_AGAIN;
1382 }
1383
1384 g_DcpCtx.fIngnoreWlClipIn = false;
1385 }
1386
1387 return rc;
1388}
1389
1390/**
1391 * Enumeration callback used for sending clipboard offers to Wayland client.
1392 *
1393 * When host announces its clipboard content, this call back is used in order
1394 * to send corresponding offers to other Wayland clients.
1395 *
1396 * Callback must be executed in context of Wayland event thread.
1397 *
1398 * @param pcszMimeType Mime-type to advertise.
1399 * @param pvUser User data (DCP data source object).
1400 */
1401static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_send_offers(const char *pcszMimeType, void *pvUser)
1402{
1403 zwlr_data_control_source_v1 *pDataSource = (zwlr_data_control_source_v1 *)pvUser;
1404 zwlr_data_control_source_v1_offer(pDataSource, pcszMimeType);
1405}
1406
1407/**
1408 * Session callback: Advertise clipboard to the guest.
1409 *
1410 * This callback must be executed in context of Wayland event thread
1411 * in order to be able to inject clipboard content into Wayland.
1412 *
1413 * This callback (1) prevents Wayland event loop from processing
1414 * incoming clipboard advertisements before sending any data to
1415 * other Wayland clients (this is needed in order to avoid feedback
1416 * loop from our own advertisements), (2) waits for the list of clipboard
1417 * formats available on the host side (set by vbcl_wayland_hlp_dcp_hg_clip_report_join_cb),
1418 * and (3) sends data offers for available host clipboard to other clients.
1419 *
1420 * @returns IPRT status code.
1421 * @param enmSessionType Session type, must be verified as
1422 * a consistency check.
1423 * @param pvUser User data (unused).
1424 */
1425static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb(
1426 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1427{
1428 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
1429 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1430
1431 RT_NOREF(pvUser);
1432
1433 VBCL_LOG_CALLBACK;
1434
1435 if (RT_SUCCESS(rc))
1436 {
1437 g_DcpCtx.fIngnoreWlClipIn = true;
1438
1439 SHCLFORMATS fFmts = g_DcpCtx.Session.clip.fFmts.wait();
1440 if (fFmts != g_DcpCtx.Session.clip.fFmts.defaults())
1441 {
1442 zwlr_data_control_source_v1 *pDataSource =
1443 zwlr_data_control_manager_v1_create_data_source(g_DcpCtx.pDataControlManager);
1444
1445 if (RT_VALID_PTR(pDataSource))
1446 {
1447 zwlr_data_control_source_v1_add_listener(
1448 (struct zwlr_data_control_source_v1 *)pDataSource, &g_data_source_listener, &g_DcpCtx);
1449
1450 VBoxMimeConvEnumerateMimeById(fFmts,
1451 vbcl_wayland_hlp_dcp_send_offers,
1452 pDataSource);
1453
1454 zwlr_data_control_device_v1_set_selection(g_DcpCtx.pDataDevice, pDataSource);
1455 }
1456 else
1457 rc = VERR_NO_MEMORY;
1458 }
1459 else
1460 rc = VERR_NO_DATA;
1461 }
1462
1463 return rc;
1464}
1465
1466/**
1467 * Session callback: Copy clipboard from the host.
1468 *
1469 * This callback (1) sets host clipboard formats list to the session,
1470 * (2) asks Wayland event thread to advertise these formats to the guest,
1471 * (3) waits for guest to request clipboard in specific format, (4) read
1472 * host clipboard in this format, and (5) sets clipboard data to the session,
1473 * so Wayland events thread can inject it into the guest.
1474 *
1475 * This callback should not return until clipboard data is read from
1476 * the host or error occurred. It must block host events loop until
1477 * current host event is fully processed.
1478 *
1479 * @returns IPRT status code.
1480 * @param enmSessionType Session type, must be verified as
1481 * a consistency check.
1482 * @param pvUser User data (host clipboard formats).
1483 */
1484static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join_cb(
1485 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1486{
1487 SHCLFORMATS *pfFmts = (SHCLFORMATS *)pvUser;
1488 AssertPtrReturn(pfFmts, VERR_INVALID_PARAMETER);
1489
1490 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
1491 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1492
1493 VBCL_LOG_CALLBACK;
1494
1495 if (RT_SUCCESS(rc))
1496 {
1497 SHCLFORMAT uFmt;
1498 void *pvData;
1499 uint32_t cbData;
1500
1501 /* Set list of host clipboard formats to the session. */
1502 g_DcpCtx.Session.clip.fFmts.set(*pfFmts);
1503
1504 /* Ask Wayland event thread to advertise formats to the guest. */
1505 g_DcpCtx.fSendToGuest.set(true);
1506 RTThreadPoke(g_DcpCtx.Thread);
1507
1508 /* Wait for the guest to request certain clipboard format. */
1509 uFmt = g_DcpCtx.Session.clip.uFmt.wait();
1510 if (uFmt != g_DcpCtx.Session.clip.uFmt.defaults())
1511 {
1512 /* Read host clipboard in specified format. */
1513 rc = VBClClipboardReadHostClipboard(g_DcpCtx.pClipboardCtx, uFmt, &pvData, &cbData);
1514 if (RT_SUCCESS(rc))
1515 {
1516 /* Set clipboard data to the session. */
1517 g_DcpCtx.Session.clip.pvClipboardBuf.set((uint64_t)pvData);
1518 g_DcpCtx.Session.clip.cbClipboardBuf.set((uint64_t)cbData);
1519 }
1520 }
1521 else
1522 rc = VERR_TIMEOUT;
1523
1524 }
1525
1526 return rc;
1527}
1528
1529/**
1530 * @interface_method_impl{VBCLWAYLANDHELPER,pfnHGClipReport}
1531 */
1532static int vbcl_wayland_hlp_dcp_hg_clip_report(SHCLFORMATS fFormats)
1533{
1534 int rc = VERR_NO_DATA;
1535
1536 VBCL_LOG_CALLBACK;
1537
1538 if (fFormats != VBOX_SHCL_FMT_NONE)
1539 {
1540 rc = vbcl_wayland_session_end(&g_DcpCtx.Session.Base, NULL, NULL);
1541 if (RT_SUCCESS(rc))
1542 {
1543 rc = vbcl_wayland_session_start(&g_DcpCtx.Session.Base,
1544 VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST,
1545 &vbcl_wayland_hlp_dcp_session_start_generic_cb,
1546 NULL);
1547
1548 if (RT_SUCCESS(rc))
1549 rc = vbcl_wayland_session_join(&g_DcpCtx.Session.Base,
1550 vbcl_wayland_hlp_dcp_hg_clip_report_join_cb,
1551 &fFormats);
1552 }
1553 else
1554 VBClLogError("unable to start session, previous session is still running rc=%Rrc\n", rc);
1555 }
1556
1557 return rc;
1558}
1559
1560/**
1561 * Session callback: Copy clipboard to the host.
1562 *
1563 * This callback sets clipboard format to the session as requested
1564 * by host, waits for guest clipboard data in requested format and
1565 * sends data to the host.
1566 *
1567 * This callback should not return until clipboard data is sent to
1568 * the host or error occurred. It must block host events loop until
1569 * current host event is fully processed.
1570 *
1571 * @returns IPRT status code.
1572 * @param enmSessionType Session type, must be verified as
1573 * a consistency check.
1574 * @param pvUser User data (requested format).
1575 */
1576static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_clip_read_join_cb(
1577 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1578{
1579 SHCLFORMAT *puFmt = (SHCLFORMAT *)pvUser;
1580 AssertPtrReturn(puFmt, VERR_INVALID_PARAMETER);
1581
1582 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
1583 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1584
1585 VBCL_LOG_CALLBACK;
1586
1587 if (RT_SUCCESS(rc))
1588 {
1589 void *pvData;
1590 size_t cbData;
1591
1592 /* Store requested clipboard format to the session. */
1593 g_DcpCtx.Session.clip.uFmt.set(*puFmt);
1594
1595 /* Wait for data in requested format. */
1596 pvData = (void *)g_DcpCtx.Session.clip.pvClipboardBuf.wait();
1597 cbData = g_DcpCtx.Session.clip.cbClipboardBuf.wait();
1598 if ( cbData != g_DcpCtx.Session.clip.cbClipboardBuf.defaults()
1599 && pvData != (void *)g_DcpCtx.Session.clip.pvClipboardBuf.defaults())
1600 {
1601 /* Send clipboard data to the host. */
1602 rc = VbglR3ClipboardWriteDataEx(g_DcpCtx.pClipboardCtx, *puFmt, pvData, cbData);
1603 }
1604 else
1605 rc = VERR_TIMEOUT;
1606 }
1607
1608 return rc;
1609}
1610
1611/**
1612 * @interface_method_impl{VBCLWAYLANDHELPER,pfnGHClipRead}
1613 */
1614static int vbcl_wayland_hlp_dcp_gh_clip_read(SHCLFORMAT uFmt)
1615{
1616 int rc;
1617
1618 VBCL_LOG_CALLBACK;
1619
1620 rc = vbcl_wayland_session_join(&g_DcpCtx.Session.Base,
1621 &vbcl_wayland_hlp_dcp_gh_clip_read_join_cb,
1622 &uFmt);
1623 return rc;
1624}
1625
1626/* Helper callbacks. */
1627const VBCLWAYLANDHELPER g_WaylandHelperDcp =
1628{
1629 "wayland-dcp", /* .pszName */
1630 vbcl_wayland_hlp_dcp_probe, /* .pfnProbe */
1631 vbcl_wayland_hlp_dcp_init, /* .pfnInit */
1632 vbcl_wayland_hlp_dcp_term, /* .pfnTerm */
1633 vbcl_wayland_hlp_dcp_set_clipboard_ctx, /* .pfnSetClipboardCtx */
1634 vbcl_wayland_hlp_dcp_popup, /* .pfnPopup */
1635 vbcl_wayland_hlp_dcp_hg_clip_report, /* .pfnHGClipReport */
1636 vbcl_wayland_hlp_dcp_gh_clip_read, /* .pfnGHClipRead */
1637};
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