VirtualBox

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

Last change on this file was 102045, checked in by vboxsync, 6 months ago

Additions: X11/Wayland: wayland-helper-dcp: Return value of proper type on assert, bugref:10194.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 54.1 KB
Line 
1/* $Id: wayland-helper-dcp.cpp 102045 2023-11-09 17:28:07Z 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 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 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 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, VBOX_SHCL_FMT_NONE);
698 AssertPtrReturn(pEntry->pszMimeType, VBOX_SHCL_FMT_NONE);
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 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 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 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/**
993 * Wayland data send callback.
994 *
995 * Triggered when other Wayland client wants to read clipboard
996 * data from us.
997 *
998 * @param pvUser VBox private data.
999 * @param pDataSource Wayland Data Control Source object.
1000 * @param sMimeType A mime-type of requested data.
1001 * @param fd A file descriptor to write clipboard content into.
1002 */
1003static void vbcl_wayland_hlp_dcp_data_source_send(
1004 void *pvUser, struct zwlr_data_control_source_v1 *pDataSource,
1005 const char *sMimeType, int32_t fd)
1006{
1007 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
1008 int rc;
1009
1010 struct vbcl_wl_dcp_write_ctx priv;
1011
1012 RT_NOREF(pDataSource);
1013
1014 VBCL_LOG_CALLBACK;
1015
1016 RT_ZERO(priv);
1017
1018 priv.sMimeType = sMimeType;
1019 priv.fd = fd;
1020
1021 rc = vbcl_wayland_session_join(&pCtx->Session.Base,
1022 &vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb,
1023 &priv);
1024
1025 VBClLogVerbose(5, "vbcl_wayland_hlp_dcp_data_source_send, rc=%Rrc\n", rc);
1026 close(fd);
1027}
1028
1029/**
1030 * Wayland data canceled callback.
1031 *
1032 * Triggered when data source was replaced by another data source
1033 * and no longer valid.
1034 *
1035 * @param pvData VBox private data.
1036 * @param pDataSource Wayland Data Control Source object.
1037 */
1038static void vbcl_wayland_hlp_dcp_data_source_cancelled(
1039 void *pvData, struct zwlr_data_control_source_v1 *pDataSource)
1040{
1041 RT_NOREF(pvData);
1042
1043 VBCL_LOG_CALLBACK;
1044
1045 zwlr_data_control_source_v1_destroy(pDataSource);
1046}
1047
1048
1049/** Wayland Data Control Source interface callbacks. */
1050static const struct zwlr_data_control_source_v1_listener g_data_source_listener =
1051{
1052 &vbcl_wayland_hlp_dcp_data_source_send,
1053 &vbcl_wayland_hlp_dcp_data_source_cancelled,
1054};
1055
1056
1057/**********************************************************************************************************************************
1058 * Helper specific code and session callbacks.
1059 *********************************************************************************************************************************/
1060
1061
1062/**
1063 * Setup or reset helper context.
1064 *
1065 * This function is used on helper init and termination. In case of
1066 * init, memory is not initialized yet, so it needs to be zeroed.
1067 * In case of shutdown, memory is already initialized and previously
1068 * allocated resources must be freed.
1069 *
1070 * @param pCtx Context data.
1071 * @param fShutdown A flag to indicate if session resources
1072 * need to be deallocated.
1073 */
1074static void vbcl_wayland_hlp_dcp_reset_ctx(vbox_wl_dcp_ctx_t *pCtx, bool fShutdown)
1075{
1076 pCtx->Thread = NIL_RTTHREAD;
1077 pCtx->fShutdown = false;
1078 pCtx->fIngnoreWlClipIn = false;
1079 pCtx->fSendToGuest.init(false, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
1080 pCtx->pClipboardCtx = NULL;
1081 pCtx->pDisplay = NULL;
1082 pCtx->pRegistry = NULL;
1083 pCtx->pSeat = NULL;
1084 pCtx->pDataDevice = NULL;
1085 pCtx->pDataControlManager = NULL;
1086
1087 if (fShutdown)
1088 vbcl_wayland_hlp_dcp_session_release(&pCtx->Session);
1089
1090 vbcl_wayland_hlp_dcp_session_init(&pCtx->Session);
1091}
1092
1093/**
1094 * Disconnect from Wayland compositor.
1095 *
1096 * Close connection, release resources and reset context data.
1097 *
1098 * @param pCtx Context data.
1099 */
1100static void vbcl_wayland_hlp_dcp_disconnect(vbox_wl_dcp_ctx_t *pCtx)
1101{
1102 if (RT_VALID_PTR(pCtx->pDataControlManager))
1103 zwlr_data_control_manager_v1_destroy(pCtx->pDataControlManager);
1104
1105 if (RT_VALID_PTR(pCtx->pDataDevice))
1106 zwlr_data_control_device_v1_destroy(pCtx->pDataDevice);
1107
1108 if (RT_VALID_PTR(pCtx->pSeat))
1109 wl_seat_destroy(pCtx->pSeat);
1110
1111 if (RT_VALID_PTR(pCtx->pRegistry))
1112 wl_registry_destroy(pCtx->pRegistry);
1113
1114 if (RT_VALID_PTR(pCtx->pDisplay))
1115 wl_display_disconnect(pCtx->pDisplay);
1116
1117 vbcl_wayland_hlp_dcp_reset_ctx(pCtx, true);
1118}
1119
1120/**
1121 * Connect to Wayland compositor.
1122 *
1123 * Establish connection, bind to all required interfaces.
1124 *
1125 * @returns TRUE on success, FALSE otherwise.
1126 * @param pCtx Context data.
1127 */
1128static bool vbcl_wayland_hlp_dcp_connect(vbox_wl_dcp_ctx_t *pCtx)
1129{
1130 const char *csWaylandDisplay = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY);
1131 bool fConnected = false;
1132
1133 if (RT_VALID_PTR(csWaylandDisplay))
1134 pCtx->pDisplay = wl_display_connect(csWaylandDisplay);
1135 else
1136 VBClLogError("cannot connect to Wayland compositor "
1137 VBCL_ENV_WAYLAND_DISPLAY " environment variable not set\n");
1138
1139 if (RT_VALID_PTR(pCtx->pDisplay))
1140 {
1141 pCtx->pRegistry = wl_display_get_registry(pCtx->pDisplay);
1142 if (RT_VALID_PTR(pCtx->pRegistry))
1143 {
1144 wl_registry_add_listener(pCtx->pRegistry, &g_vbcl_wayland_hlp_registry_cb, (void *)pCtx);
1145 wl_display_roundtrip(pCtx->pDisplay);
1146
1147 if (RT_VALID_PTR(pCtx->pDataControlManager))
1148 {
1149 if (RT_VALID_PTR(pCtx->pSeat))
1150 {
1151 pCtx->pDataDevice = zwlr_data_control_manager_v1_get_data_device(pCtx->pDataControlManager, pCtx->pSeat);
1152 if (RT_VALID_PTR(pCtx->pDataDevice))
1153 {
1154 if (RT_VALID_PTR(pCtx->pDataControlManager))
1155 fConnected = true;
1156 else
1157 VBClLogError("cannot get Wayland data control manager interface\n");
1158 }
1159 else
1160 VBClLogError("cannot get Wayland data device interface\n");
1161 }
1162 else
1163 VBClLogError("cannot get Wayland seat interface\n");
1164 }
1165 else
1166 VBClLogError("cannot get Wayland device manager interface\n");
1167 }
1168 else
1169 VBClLogError("cannot connect to Wayland registry\n");
1170 }
1171 else
1172 VBClLogError("cannot connect to Wayland compositor\n");
1173
1174 if (!fConnected)
1175 vbcl_wayland_hlp_dcp_disconnect(pCtx);
1176
1177 return fConnected;
1178}
1179
1180
1181/**
1182 * Main loop for Wayland compositor events.
1183 *
1184 * All requests to Wayland compositor must be performed in context
1185 * of this thread.
1186 *
1187 * @returns IPRT status code.
1188 * @param hThreadSelf IPRT thread object.
1189 * @param pvUser Context data.
1190 */
1191static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_event_loop(RTTHREAD hThreadSelf, void *pvUser)
1192{
1193 vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
1194 int rc = VERR_TRY_AGAIN;
1195
1196 if (vbcl_wayland_hlp_dcp_connect(pCtx))
1197 {
1198 /* Start listening Data Control Device interface. */
1199 if (zwlr_data_control_device_v1_add_listener(pCtx->pDataDevice, &g_data_device_listener, (void *)pCtx) == 0)
1200 {
1201 /* Tell parent thread we are ready. */
1202 RTThreadUserSignal(hThreadSelf);
1203
1204 while (1)
1205 {
1206 rc = vbcl_wayland_hlp_dcp_next_event(pCtx);
1207 if ( rc != VERR_TIMEOUT
1208 && RT_FAILURE(rc))
1209 {
1210 VBClLogError("cannot read event from Wayland, rc=%Rrc\n", rc);
1211 }
1212
1213 if (pCtx->fSendToGuest.reset())
1214 {
1215 rc = vbcl_wayland_session_join(&pCtx->Session.Base,
1216 &vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb,
1217 NULL);
1218 }
1219
1220 /* Handle graceful thread termination. */
1221 if (pCtx->fShutdown)
1222 {
1223 rc = VINF_SUCCESS;
1224 break;
1225 }
1226 }
1227 }
1228 else
1229 {
1230 rc = VERR_NOT_SUPPORTED;
1231 VBClLogError("cannot subscribe to Data Control Device events\n");
1232 }
1233
1234 vbcl_wayland_hlp_dcp_disconnect(pCtx);
1235 }
1236
1237 /* Notify parent thread if we failed to start, so it won't be
1238 * waiting 30 sec to figure this out. */
1239 if (RT_FAILURE(rc))
1240 RTThreadUserSignal(hThreadSelf);
1241
1242 return rc;
1243}
1244
1245/**
1246 * @interface_method_impl{VBCLWAYLANDHELPER,pfnProbe}
1247 */
1248static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_probe(void)
1249{
1250 vbox_wl_dcp_ctx_t probeCtx;
1251 int fCaps = VBOX_WAYLAND_HELPER_CAP_NONE;
1252 VBGHDISPLAYSERVERTYPE enmDisplayServerType = VBGHDisplayServerTypeDetect();
1253
1254 vbcl_wayland_hlp_dcp_reset_ctx(&probeCtx, false /* fShutdown */);
1255 vbcl_wayland_session_init(&probeCtx.Session.Base);
1256
1257 if (VBGHDisplayServerTypeIsWaylandAvailable(enmDisplayServerType))
1258 {
1259 if (vbcl_wayland_hlp_dcp_connect(&probeCtx))
1260 {
1261 fCaps |= VBOX_WAYLAND_HELPER_CAP_CLIPBOARD;
1262 vbcl_wayland_hlp_dcp_disconnect(&probeCtx);
1263 }
1264 }
1265
1266 return fCaps;
1267}
1268
1269/**
1270 * @interface_method_impl{VBCLWAYLANDHELPER,pfnInit}
1271 */
1272RTDECL(int) vbcl_wayland_hlp_dcp_init(void)
1273{
1274 vbcl_wayland_hlp_dcp_reset_ctx(&g_DcpCtx, false /* fShutdown */);
1275 vbcl_wayland_session_init(&g_DcpCtx.Session.Base);
1276
1277 return VBClClipboardThreadStart(&g_DcpCtx.Thread, vbcl_wayland_hlp_dcp_event_loop, "wl-dcp", &g_DcpCtx);
1278}
1279
1280/**
1281 * @interface_method_impl{VBCLWAYLANDHELPER,pfnTerm}
1282 */
1283RTDECL(int) vbcl_wayland_hlp_dcp_term(void)
1284{
1285 int rc;
1286 int rcThread = 0;
1287
1288 /* Set termination flag. Wayland event loop should pick it up
1289 * on the next iteration. */
1290 g_DcpCtx.fShutdown = true;
1291
1292 /* Wait for Wayland event loop thread to shutdown. */
1293 rc = RTThreadWait(g_DcpCtx.Thread, RT_MS_30SEC, &rcThread);
1294 if (RT_SUCCESS(rc))
1295 VBClLogInfo("Wayland event thread exited with status, rc=%Rrc\n", rcThread);
1296 else
1297 VBClLogError("unable to stop Wayland event thread, rc=%Rrc\n", rc);
1298
1299 return rc;
1300}
1301
1302/**
1303 * @interface_method_impl{VBCLWAYLANDHELPER,pfnSetClipboardCtx}
1304 */
1305static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_set_clipboard_ctx(PVBGLR3SHCLCMDCTX pCtx)
1306{
1307 g_DcpCtx.pClipboardCtx = pCtx;
1308}
1309
1310/**
1311 * @interface_method_impl{VBCLWAYLANDHELPER,pfnPopup}
1312 */
1313static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_popup(void)
1314{
1315 return VINF_SUCCESS;
1316}
1317
1318/**
1319 * Session callback: Copy clipboard to the guest.
1320 *
1321 * This callback must be executed in context of Wayland event thread
1322 * in order to be able to inject clipboard content into Wayland. It is
1323 * triggered when Wayland client already decided data in which format
1324 * it wants to request.
1325 *
1326 * This callback (1) sets requested clipboard format to the session,
1327 * (2) waits for clipboard data to be copied from the host, (3) converts
1328 * host clipboard data into guest representation, and (4) sends clipboard
1329 * to the guest by writing given file descriptor.
1330 *
1331 * @returns IPRT status code.
1332 * @param enmSessionType Session type, must be verified as
1333 * a consistency check.
1334 * @param pvUser User data (Wayland I/O context).
1335 */
1336static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb(
1337 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1338{
1339 struct vbcl_wl_dcp_write_ctx *pPriv = (struct vbcl_wl_dcp_write_ctx *)pvUser;
1340 AssertPtrReturn(pPriv, VERR_INVALID_PARAMETER);
1341
1342 void *pvBuf;
1343 uint32_t cbBuf;
1344
1345 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
1346 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1347
1348 VBCL_LOG_CALLBACK;
1349
1350 if (RT_SUCCESS(rc))
1351 {
1352 if (RT_VALID_PTR(g_DcpCtx.pClipboardCtx))
1353 {
1354 /* Set requested format to the session. */
1355 g_DcpCtx.Session.clip.uFmt.set(VBoxMimeConvGetIdByMime(pPriv->sMimeType));
1356
1357 /* Wait for data in requested format. */
1358 pvBuf = (void *)g_DcpCtx.Session.clip.pvClipboardBuf.wait();
1359 cbBuf = g_DcpCtx.Session.clip.cbClipboardBuf.wait();
1360 if ( cbBuf != g_DcpCtx.Session.clip.cbClipboardBuf.defaults()
1361 && pvBuf != (void *)g_DcpCtx.Session.clip.pvClipboardBuf.defaults())
1362 {
1363 void *pvBufOut;
1364 size_t cbOut;
1365
1366 /* Convert clipboard data from VBox representation into guest format. */
1367 rc = VBoxMimeConvVBoxToNative(pPriv->sMimeType, pvBuf, cbBuf, &pvBufOut, &cbOut);
1368 if (RT_SUCCESS(rc))
1369 {
1370 rc = vbcl_wayland_hlp_dcp_write_wl_fd(pPriv->fd, pvBufOut, cbOut);
1371 RTMemFree(pvBufOut);
1372 }
1373 else
1374 VBClLogError("cannot convert '%s' to native format, rc=%Rrc\n", rc);
1375 }
1376 else
1377 rc = VERR_TIMEOUT;
1378 }
1379 else
1380 {
1381 VBClLogVerbose(2, "cannot send to guest, no host service connection yet\n");
1382 rc = VERR_TRY_AGAIN;
1383 }
1384
1385 g_DcpCtx.fIngnoreWlClipIn = false;
1386 }
1387
1388 return rc;
1389}
1390
1391/**
1392 * Enumeration callback used for sending clipboard offers to Wayland client.
1393 *
1394 * When host announces its clipboard content, this call back is used in order
1395 * to send corresponding offers to other Wayland clients.
1396 *
1397 * Callback must be executed in context of Wayland event thread.
1398 *
1399 * @param pcszMimeType Mime-type to advertise.
1400 * @param pvUser User data (DCP data source object).
1401 */
1402static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_send_offers(const char *pcszMimeType, void *pvUser)
1403{
1404 zwlr_data_control_source_v1 *pDataSource = (zwlr_data_control_source_v1 *)pvUser;
1405 zwlr_data_control_source_v1_offer(pDataSource, pcszMimeType);
1406}
1407
1408/**
1409 * Session callback: Advertise clipboard to the guest.
1410 *
1411 * This callback must be executed in context of Wayland event thread
1412 * in order to be able to inject clipboard content into Wayland.
1413 *
1414 * This callback (1) prevents Wayland event loop from processing
1415 * incoming clipboard advertisements before sending any data to
1416 * other Wayland clients (this is needed in order to avoid feedback
1417 * loop from our own advertisements), (2) waits for the list of clipboard
1418 * formats available on the host side (set by vbcl_wayland_hlp_dcp_hg_clip_report_join_cb),
1419 * and (3) sends data offers for available host clipboard to other clients.
1420 *
1421 * @returns IPRT status code.
1422 * @param enmSessionType Session type, must be verified as
1423 * a consistency check.
1424 * @param pvUser User data (unused).
1425 */
1426static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb(
1427 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1428{
1429 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
1430 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1431
1432 RT_NOREF(pvUser);
1433
1434 VBCL_LOG_CALLBACK;
1435
1436 if (RT_SUCCESS(rc))
1437 {
1438 g_DcpCtx.fIngnoreWlClipIn = true;
1439
1440 SHCLFORMATS fFmts = g_DcpCtx.Session.clip.fFmts.wait();
1441 if (fFmts != g_DcpCtx.Session.clip.fFmts.defaults())
1442 {
1443 zwlr_data_control_source_v1 *pDataSource =
1444 zwlr_data_control_manager_v1_create_data_source(g_DcpCtx.pDataControlManager);
1445
1446 if (RT_VALID_PTR(pDataSource))
1447 {
1448 zwlr_data_control_source_v1_add_listener(
1449 (struct zwlr_data_control_source_v1 *)pDataSource, &g_data_source_listener, &g_DcpCtx);
1450
1451 VBoxMimeConvEnumerateMimeById(fFmts,
1452 vbcl_wayland_hlp_dcp_send_offers,
1453 pDataSource);
1454
1455 zwlr_data_control_device_v1_set_selection(g_DcpCtx.pDataDevice, pDataSource);
1456 }
1457 else
1458 rc = VERR_NO_MEMORY;
1459 }
1460 else
1461 rc = VERR_NO_DATA;
1462 }
1463
1464 return rc;
1465}
1466
1467/**
1468 * Session callback: Copy clipboard from the host.
1469 *
1470 * This callback (1) sets host clipboard formats list to the session,
1471 * (2) asks Wayland event thread to advertise these formats to the guest,
1472 * (3) waits for guest to request clipboard in specific format, (4) read
1473 * host clipboard in this format, and (5) sets clipboard data to the session,
1474 * so Wayland events thread can inject it into the guest.
1475 *
1476 * This callback should not return until clipboard data is read from
1477 * the host or error occurred. It must block host events loop until
1478 * current host event is fully processed.
1479 *
1480 * @returns IPRT status code.
1481 * @param enmSessionType Session type, must be verified as
1482 * a consistency check.
1483 * @param pvUser User data (host clipboard formats).
1484 */
1485static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join_cb(
1486 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1487{
1488 SHCLFORMATS *pfFmts = (SHCLFORMATS *)pvUser;
1489 AssertPtrReturn(pfFmts, VERR_INVALID_PARAMETER);
1490
1491 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
1492 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1493
1494 VBCL_LOG_CALLBACK;
1495
1496 if (RT_SUCCESS(rc))
1497 {
1498 SHCLFORMAT uFmt;
1499 void *pvData;
1500 uint32_t cbData;
1501
1502 /* Set list of host clipboard formats to the session. */
1503 g_DcpCtx.Session.clip.fFmts.set(*pfFmts);
1504
1505 /* Ask Wayland event thread to advertise formats to the guest. */
1506 g_DcpCtx.fSendToGuest.set(true);
1507 RTThreadPoke(g_DcpCtx.Thread);
1508
1509 /* Wait for the guest to request certain clipboard format. */
1510 uFmt = g_DcpCtx.Session.clip.uFmt.wait();
1511 if (uFmt != g_DcpCtx.Session.clip.uFmt.defaults())
1512 {
1513 /* Read host clipboard in specified format. */
1514 rc = VBClClipboardReadHostClipboard(g_DcpCtx.pClipboardCtx, uFmt, &pvData, &cbData);
1515 if (RT_SUCCESS(rc))
1516 {
1517 /* Set clipboard data to the session. */
1518 g_DcpCtx.Session.clip.pvClipboardBuf.set((uint64_t)pvData);
1519 g_DcpCtx.Session.clip.cbClipboardBuf.set((uint64_t)cbData);
1520 }
1521 }
1522 else
1523 rc = VERR_TIMEOUT;
1524
1525 }
1526
1527 return rc;
1528}
1529
1530/**
1531 * @interface_method_impl{VBCLWAYLANDHELPER,pfnHGClipReport}
1532 */
1533static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report(SHCLFORMATS fFormats)
1534{
1535 int rc = VERR_NO_DATA;
1536
1537 VBCL_LOG_CALLBACK;
1538
1539 if (fFormats != VBOX_SHCL_FMT_NONE)
1540 {
1541 rc = vbcl_wayland_session_end(&g_DcpCtx.Session.Base, NULL, NULL);
1542 if (RT_SUCCESS(rc))
1543 {
1544 rc = vbcl_wayland_session_start(&g_DcpCtx.Session.Base,
1545 VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST,
1546 &vbcl_wayland_hlp_dcp_session_start_generic_cb,
1547 NULL);
1548
1549 if (RT_SUCCESS(rc))
1550 rc = vbcl_wayland_session_join(&g_DcpCtx.Session.Base,
1551 vbcl_wayland_hlp_dcp_hg_clip_report_join_cb,
1552 &fFormats);
1553 }
1554 else
1555 VBClLogError("unable to start session, previous session is still running rc=%Rrc\n", rc);
1556 }
1557
1558 return rc;
1559}
1560
1561/**
1562 * Session callback: Copy clipboard to the host.
1563 *
1564 * This callback sets clipboard format to the session as requested
1565 * by host, waits for guest clipboard data in requested format and
1566 * sends data to the host.
1567 *
1568 * This callback should not return until clipboard data is sent to
1569 * the host or error occurred. It must block host events loop until
1570 * current host event is fully processed.
1571 *
1572 * @returns IPRT status code.
1573 * @param enmSessionType Session type, must be verified as
1574 * a consistency check.
1575 * @param pvUser User data (requested format).
1576 */
1577static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_clip_read_join_cb(
1578 vbcl_wl_session_type_t enmSessionType, void *pvUser)
1579{
1580 SHCLFORMAT *puFmt = (SHCLFORMAT *)pvUser;
1581 AssertPtrReturn(puFmt, VERR_INVALID_PARAMETER);
1582
1583 int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
1584 ? VINF_SUCCESS : VERR_WRONG_ORDER;
1585
1586 VBCL_LOG_CALLBACK;
1587
1588 if (RT_SUCCESS(rc))
1589 {
1590 void *pvData;
1591 size_t cbData;
1592
1593 /* Store requested clipboard format to the session. */
1594 g_DcpCtx.Session.clip.uFmt.set(*puFmt);
1595
1596 /* Wait for data in requested format. */
1597 pvData = (void *)g_DcpCtx.Session.clip.pvClipboardBuf.wait();
1598 cbData = g_DcpCtx.Session.clip.cbClipboardBuf.wait();
1599 if ( cbData != g_DcpCtx.Session.clip.cbClipboardBuf.defaults()
1600 && pvData != (void *)g_DcpCtx.Session.clip.pvClipboardBuf.defaults())
1601 {
1602 /* Send clipboard data to the host. */
1603 rc = VbglR3ClipboardWriteDataEx(g_DcpCtx.pClipboardCtx, *puFmt, pvData, cbData);
1604 }
1605 else
1606 rc = VERR_TIMEOUT;
1607 }
1608
1609 return rc;
1610}
1611
1612/**
1613 * @interface_method_impl{VBCLWAYLANDHELPER,pfnGHClipRead}
1614 */
1615static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_clip_read(SHCLFORMAT uFmt)
1616{
1617 int rc;
1618
1619 VBCL_LOG_CALLBACK;
1620
1621 rc = vbcl_wayland_session_join(&g_DcpCtx.Session.Base,
1622 &vbcl_wayland_hlp_dcp_gh_clip_read_join_cb,
1623 &uFmt);
1624 return rc;
1625}
1626
1627/* Helper callbacks. */
1628const VBCLWAYLANDHELPER g_WaylandHelperDcp =
1629{
1630 "wayland-dcp", /* .pszName */
1631 vbcl_wayland_hlp_dcp_probe, /* .pfnProbe */
1632 vbcl_wayland_hlp_dcp_init, /* .pfnInit */
1633 vbcl_wayland_hlp_dcp_term, /* .pfnTerm */
1634 vbcl_wayland_hlp_dcp_set_clipboard_ctx, /* .pfnSetClipboardCtx */
1635 vbcl_wayland_hlp_dcp_popup, /* .pfnPopup */
1636 vbcl_wayland_hlp_dcp_hg_clip_report, /* .pfnHGClipReport */
1637 vbcl_wayland_hlp_dcp_gh_clip_read, /* .pfnGHClipRead */
1638};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use