VirtualBox

Changeset 101878 in vbox


Ignore:
Timestamp:
Nov 6, 2023 3:36:24 PM (15 months ago)
Author:
vboxsync
Message:

Additions: X11/Wayland: Add initial support for clipboard sharing with Gnome and Plasma Wayland guests (not yet enabled), bugref:10194.

Location:
trunk
Files:
4 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/VBox/GuestHost/clipboard-helper.h

    r98103 r101878  
    4444
    4545#include <VBox/GuestHost/SharedClipboard.h>
     46
     47/** Guest property which is set by GUI in order to notify guest about VM window focus change.  */
     48#define VBOX_GUI_FOCUS_CHANGE_GUEST_PROP_NAME   "/VirtualBox/GuestAdd/GuiOnFocus"
    4649
    4750/** Constants needed for string conversions done by the Linux/Mac clipboard code. */
  • trunk/src/VBox/Additions/x11/VBoxClient/Makefile.kmk

    r100585 r101878  
    4848#
    4949PROGRAMS.linux += VBoxDRMClient
     50
     51ifdef VBOX_WITH_WAYLAND_ADDITIONS
     52 PROGRAMS.linux += vboxwl
     53endif
    5054
    5155# Common Guest / Host sources.
     
    7579ifdef VBOX_WITH_WAYLAND_ADDITIONS
    7680 VBoxClient_DEFS += VBOX_WITH_WAYLAND_ADDITIONS
     81 VBoxClient_INCS += ../wlInclude/
    7782 VBoxClient_SOURCES += \
    7883        wayland-helper-dcp.cpp \
    7984        wayland-helper-gtk.cpp \
    80         wayland.cpp
     85        wayland-helper.cpp \
     86        wayland.cpp \
     87        wayland-helper-ipc.cpp \
     88        ../wlInclude/wayland-protocol.c \
     89        ../wlInclude/wlr-data-control-unstable-v1.c
    8190endif
    8291
     
    117126 endif
    118127endif
     128ifdef VBOX_WITH_WAYLAND_ADDITIONS
     129 VBoxClient_LIBS.linux += glib-2.0
     130 VBoxClient_LIBS.linux += gobject-2.0
     131endif
    119132
    120133# XXX: -L comes from the template, but not rpath
     
    185198        $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \
    186199        clipboard.cpp \
     200        clipboard-common.cpp \
    187201        clipboard-x11.cpp
    188202 ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     
    190204  VBoxClient_SOURCES += \
    191205        $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \
    192         $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
     206        $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp \
     207        $(PATH_ROOT)/src/VBox/GuestHost/common/mime-type-converter.cpp
    193208  ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
    194209   VBoxClient_DEFS    += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
     
    226241  VBOX_VBOXCLIENT_LIBXMU_VER = 6
    227242  VBOX_VBOXCLIENT_LIBXRANDR_VER = 2
     243  VBOX_VBOXCLIENT_LIBWAYLANDCLIENT_VER = 0
    228244 endif
    229245 ## Dynamic import no. 1: libX11.so
     
    235251 ## Dynamic import no. 4: libXrandr.so
    236252 $(evalcall2 def_libToLazyLoad,$(PATH_ROOT)/src/VBox/GuestHost/libXrandr.def,libXrandr,$(VBOX_VBOXCLIENT_LIBXRANDR_VER))
     253 ## Dynamic import no. 5: libwayland-client.so
     254 $(evalcall2 def_libToLazyLoad,$(PATH_ROOT)/src/VBox/GuestHost/libwayland-client.def,libwayland-client,$(VBOX_VBOXCLIENT_LIBWAYLANDCLIENT_VER))
     255endif
     256
     257ifdef VBOX_WITH_WAYLAND_ADDITIONS
     258 define def_libToLazyLoad2
     259  vboxwl_SOURCES       += \
     260        $$(vboxwl_0_OUTDIR)/$(2)LazyLoad.asm
     261  vboxwl_CLEAN         += \
     262        $$(vboxwl_0_OUTDIR)/$(2)LazyLoad.asm
     263  $$$$(vboxwl_0_OUTDIR)/$(2)LazyLoad.asm: $$(PATH_ROOT)/src/VBox/GuestHost/$(2).def $(VBOX_DEF_2_LAZY_LOAD) | $$$$(dir $$@)
     264        $$(call MSG_TOOL,VBoxDef2LazyLoad,$$(PATH_ROOT)/src/VBox/GuestHost/$(2).def,$$@)
     265        $$(eval VBOX_VBOXWL_LIB_TO_LAZYLOAD_SUFF=$$$$(if $(3),$(SUFF_DLL).$(3),$(SUFF_DLL)))
     266        $$(QUIET)$$(RM) -f -- "$$@"
     267        $$(VBOX_DEF_2_LAZY_LOAD) --system --library $(2)$$(VBOX_VBOXWL_LIB_TO_LAZYLOAD_SUFF) --output "$$@" $$(PATH_ROOT)/src/VBox/GuestHost/$(2).def
     268 endef
     269
     270 vboxwl_TEMPLATE = VBoxGuestR3Exe
     271 vboxwl_DEFS += VBOX_WITH_SHARED_CLIPBOARD
     272 ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
     273  vboxwl_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
     274 else
     275  vboxwl_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
     276 endif
     277 vboxwl_SOURCES = \
     278        vboxwl.cpp \
     279        logging.cpp \
     280        clipboard-common.cpp \
     281        wayland-helper-ipc.cpp \
     282        wayland-helper.cpp \
     283        $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
     284        $(PATH_ROOT)/src/VBox/GuestHost/common/mime-type-converter.cpp
     285 vboxwl_SOURCES += $(VBOX_GH_SOURCES)
     286
     287 VBOX_VBOXWL_LIBWAYLANDCLIENT_VER = 0
     288 VBOX_VBOXWL_LIBGTK3_VER = 0
     289
     290 ## Dynamic import no. 5: libwayland-client.so
     291 $(evalcall2 def_libToLazyLoad2,$(PATH_ROOT)/src/VBox/GuestHost/libwayland-client.def,libwayland-client,$(VBOX_VBOXWL_LIBWAYLANDCLIENT_VER))
     292 ## Dynamic import no. 6: libgtk-3.so
     293 $(evalcall2 def_libToLazyLoad2,$(PATH_ROOT)/src/VBox/GuestHost/libgtk-3.def,libgtk-3,$(VBOX_VBOXWL_LIBGTK3_VER))
     294
     295 vboxwl_LIBS.linux += glib-2.0
     296 vboxwl_LIBS.linux += gobject-2.0
     297 vboxwl_LIBS.linux += gio-2.0
    237298endif
    238299
  • trunk/src/VBox/Additions/x11/VBoxClient/VBoxClient.h

    r100246 r101878  
    3838
    3939#include <VBox/GuestHost/DisplayServerType.h>
     40
     41/** A shortcut to log callback entering. */
     42#define VBCL_LOG_CALLBACK VBClLogVerbose(3, "%s\n", __func__)
    4043
    4144int VBClShowNotify(const char *pszHeader, const char *pszBody);
  • trunk/src/VBox/Additions/x11/VBoxClient/main.cpp

    r100246 r101878  
    206206static void vboxClientSignalHandler(int iSignal)
    207207{
    208     int rc = RTCritSectEnter(&g_csSignalHandler);
     208    int rc;
     209
     210    /* On Wayland, SIGPIPE might be issued if compositor no longer wants
     211     * to communicate. This should not be a reason for process termination. */
     212    if (iSignal == SIGPIPE)
     213        return;
     214
     215    rc = RTCritSectEnter(&g_csSignalHandler);
    209216    if (RT_SUCCESS(rc))
    210217    {
  • trunk/src/VBox/Additions/x11/VBoxClient/wayland-helper-dcp.cpp

    r100246 r101878  
    11/* $Id$ */
    22/** @file
    3  * Guest Additions - Wayland Desktop Environment helper which uses Data Control Protocol.
     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.
    47 */
    58
     
    2629 */
    2730
     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"
    2840#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   ppvBuf              Data buffer.
     355 * @param   pcbBuf              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 * @returns IPRT status code.
     458 * @param   pSession        Session data.
     459 */
     460static void vbcl_wayland_hlp_dcp_session_release(vbox_wl_dcp_session_t *pSession)
     461{
     462    void *pvData;
     463
     464    if (!RTListIsEmpty(&pSession->clip.mimeTypesList.Node))
     465    {
     466        vbox_wl_dcp_mime_t *pEntry, *pNextEntry;
     467
     468        RTListForEachSafe(&pSession->clip.mimeTypesList.Node, pEntry, pNextEntry, vbox_wl_dcp_mime_t, Node)
     469        {
     470            RTListNodeRemove(&pEntry->Node);
     471            RTStrFree(pEntry->pszMimeType);
     472            RTMemFree(pEntry);
     473        }
     474    }
     475
     476    pvData = (void *)pSession->clip.pvClipboardBuf.reset();
     477    if (RT_VALID_PTR(pvData))
     478        RTMemFree(pvData);
     479}
     480
     481/**
     482 * Initialize session.
     483 *
     484 * @returns IPRT status code.
     485 * @param   pSession        Session data.
     486 */
     487static void vbcl_wayland_hlp_dcp_session_init(vbox_wl_dcp_session_t *pSession)
     488{
     489    RTListInit(&pSession->clip.mimeTypesList.Node);
     490
     491    pSession->clip.fFmts.init(VBOX_SHCL_FMT_NONE, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
     492    pSession->clip.uFmt.init(VBOX_SHCL_FMT_NONE, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
     493    pSession->clip.pvClipboardBuf.init(0, VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS);
     494    pSession->clip.cbClipboardBuf.init(0, VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS);
     495}
     496
     497/**
     498 * Reset previously initialized session.
     499 *
     500 * @returns IPRT status code.
     501 * @param   pSession        Session data.
     502 */
     503static void vbcl_wayland_hlp_dcp_session_prepare(vbox_wl_dcp_session_t *pSession)
     504{
     505    vbcl_wayland_hlp_dcp_session_release(pSession);
     506    vbcl_wayland_hlp_dcp_session_init(pSession);
     507#if 0
     508    void *pvData;
     509
     510    if (!RTListIsEmpty(&pSession->clip.mimeTypesList.Node))
     511    {
     512        vbox_wl_dcp_mime_t *pEntry, *pNextEntry;
     513
     514        RTListForEachSafe(&pSession->clip.mimeTypesList.Node, pEntry, pNextEntry, vbox_wl_dcp_mime_t, Node)
     515        {
     516            RTListNodeRemove(&pEntry->Node);
     517            RTStrFree(pEntry->pszMimeType);
     518            RTMemFree(pEntry);
     519        }
     520    }
     521
     522    pvData = (void *)pSession->clip.pvClipboardBuf.reset();
     523    if (RT_VALID_PTR(pvData))
     524        RTMemFree(pvData);
     525
     526
     527    RTListInit(&pSession->clip.mimeTypesList.Node);
     528
     529    pSession->clip.fFmts.init(VBOX_SHCL_FMT_NONE, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
     530    pSession->clip.uFmt.init(VBOX_SHCL_FMT_NONE, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
     531    pSession->clip.pvClipboardBuf.init(0, VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS);
     532    pSession->clip.cbClipboardBuf.init(0, VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS);
     533#endif
     534}
     535
     536/**
     537 * Session callback: Generic session initializer.
     538 *
     539 * This callback starts new session.
     540 *
     541 * @returns IPRT status code.
     542 * @param   enmSessionType      Session type (unused).
     543 * @param   pvUser              User data (unused).
     544 */
     545static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_session_start_generic_cb(
     546    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     547{
     548    RT_NOREF(enmSessionType, pvUser);
     549
     550    VBCL_LOG_CALLBACK;
     551
     552    vbcl_wayland_hlp_dcp_session_prepare(&g_DcpCtx.Session);
     553
     554    return VINF_SUCCESS;
     555}
     556
     557/**
     558 * Wayland registry global handler.
     559 *
     560 * This callback is triggered when Wayland Registry listener is registered.
     561 * Wayland client library will trigger it individually for each available global
     562 * object.
     563 *
     564 * @param pvUser            Context data.
     565 * @param pRegistry         Wayland Registry object.
     566 * @param uName             Numeric name of the global object.
     567 * @param sIface            Name of interface implemented by the object.
     568 * @param uVersion          Interface version.
     569 */
     570static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_registry_global_handler(
     571    void *pvUser, struct wl_registry *pRegistry, uint32_t uName, const char *sIface, uint32_t uVersion)
     572{
     573    vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
     574
     575    RT_NOREF(pRegistry);
     576    RT_NOREF(uVersion);
     577
     578    AssertPtrReturnVoid(pCtx);
     579    AssertPtrReturnVoid(sIface);
     580
     581    /* Wrappers around 'if' statement. */
     582         VBCL_WAYLAND_REGISTRY_ADD_MATCH(pRegistry, sIface, uName, wl_seat_interface,                       pCtx->pSeat,                struct wl_seat *,                       VBCL_WAYLAND_SEAT_VERSION_MIN)
     583    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)
     584    else
     585        VBClLogVerbose(5, "ignoring Wayland interface %s\n", sIface);
     586}
     587
     588/**
     589 * Wayland registry global remove handler.
     590 *
     591 * Triggered when global object is removed from Wayland registry.
     592 *
     593 * @param pvUser            Context data.
     594 * @param pRegistry         Wayland Registry object.
     595 * @param uName             Numeric name of the global object.
     596 */
     597static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_registry_global_remove_handler(
     598    void *pvUser, struct wl_registry *pRegistry, uint32_t uName)
     599{
     600    RT_NOREF(pvUser);
     601    RT_NOREF(pRegistry);
     602    RT_NOREF(uName);
     603}
     604
     605/** Wayland global registry callbacks. */
     606static const struct wl_registry_listener g_vbcl_wayland_hlp_registry_cb =
     607{
     608    vbcl_wayland_hlp_dcp_registry_global_handler,       /* .global */
     609    vbcl_wayland_hlp_dcp_registry_global_remove_handler /* .global_remove */
     610};
     611
     612
     613
     614/**********************************************************************************************************************************
     615 * Wayland Data Control Offer callbacks.
     616 *********************************************************************************************************************************/
     617
     618
     619/**
     620 * Session callback: Collect clipboard format advertised by guest.
     621 *
     622 * This callback must be executed in context of Wayland event thread
     623 * in order to be able to access Wayland clipboard content.
     624 *
     625 * This callback adds mime-type just advertised by Wayland into a list
     626 * of mime-types which in turn later will be advertised to the host.
     627 *
     628 * @returns IPRT status code.
     629 * @param   enmSessionType      Session type, must be verified as
     630 *                              a consistency check.
     631 * @param   pvUser              User data (Wayland mime-type).
     632 */
     633static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_add_fmt_cb(
     634    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     635{
     636    const char *sMimeType = (const char *)pvUser;
     637    AssertPtrReturn(sMimeType, VERR_INVALID_PARAMETER);
     638
     639    SHCLFORMAT uFmt = VBoxMimeConvGetIdByMime(sMimeType);
     640
     641    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
     642             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     643
     644    VBCL_LOG_CALLBACK;
     645
     646    if (RT_SUCCESS(rc))
     647    {
     648        if (uFmt != VBOX_SHCL_FMT_NONE)
     649        {
     650            vbox_wl_dcp_mime_t *pNode = (vbox_wl_dcp_mime_t *)RTMemAllocZ(sizeof(vbox_wl_dcp_mime_t));
     651            if (RT_VALID_PTR(pNode))
     652            {
     653                pNode->pszMimeType = RTStrDup((char *)sMimeType);
     654                if (RT_VALID_PTR(pNode->pszMimeType))
     655                    RTListAppend(&g_DcpCtx.Session.clip.mimeTypesList.Node, &pNode->Node);
     656                else
     657                    RTMemFree(pNode);
     658            }
     659
     660            if (   !RT_VALID_PTR(pNode)
     661                || !RT_VALID_PTR(pNode->pszMimeType))
     662            {
     663                rc = VERR_NO_MEMORY;
     664            }
     665        }
     666        else
     667            rc = VERR_NO_DATA;
     668    }
     669
     670    return rc;
     671}
     672
     673
     674/**
     675 * Data Control Offer advertise callback.
     676 *
     677 * Triggered when other Wayland client advertises new clipboard content.
     678 *
     679 * @param pvUser            Context data.
     680 * @param pOffer            Wayland Data Control Offer object.
     681 * @param sMimeType         Mime-type of newly available clipboard data.
     682 */
     683static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_control_offer_offer(
     684    void *pvUser, struct zwlr_data_control_offer_v1 *pOffer, const char *sMimeType)
     685{
     686    vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
     687    int rc;
     688
     689    RT_NOREF(pOffer);
     690
     691    VBCL_LOG_CALLBACK;
     692
     693    rc = vbcl_wayland_session_join(&pCtx->Session.Base,
     694                                   &vbcl_wayland_hlp_dcp_gh_add_fmt_cb,
     695                                   (void *)sMimeType);
     696    if (RT_FAILURE(rc))
     697        VBClLogError("cannot save formats announced by the guest, rc=%Rrc\n", rc);
     698}
     699
     700/** Wayland Data Control Offer interface callbacks. */
     701static const struct zwlr_data_control_offer_v1_listener g_data_control_offer_listener =
     702{
     703    vbcl_wayland_hlp_dcp_data_control_offer_offer,
     704};
     705
     706
     707/**********************************************************************************************************************************
     708 * Wayland Data Control Device callbacks.
     709 *********************************************************************************************************************************/
     710
     711
     712/**
     713 * Convert list of mime-types in string representation into bitmask of VBox formats.
     714 *
     715 * @returns Formats bitmask.
     716 * @param   pList   List of mime-types in string representation.
     717 */
     718static SHCLFORMATS vbcl_wayland_hlp_dcp_match_formats(vbox_wl_dcp_mime_t *pList)
     719{
     720    SHCLFORMATS fFmts = VBOX_SHCL_FMT_NONE;
     721
     722    if (!RTListIsEmpty(&pList->Node))
     723    {
     724        vbox_wl_dcp_mime_t *pEntry;
     725        RTListForEach(&pList->Node, pEntry, vbox_wl_dcp_mime_t, Node)
     726        {
     727            AssertPtrReturn(pEntry, VERR_INVALID_PARAMETER);
     728            AssertPtrReturn(pEntry->pszMimeType, VERR_INVALID_PARAMETER);
     729
     730            fFmts |= VBoxMimeConvGetIdByMime(pEntry->pszMimeType);
     731        }
     732    }
     733
     734    return fFmts;
     735}
     736
     737/**
     738 * Find first matching clipboard mime-type for given format ID.
     739 *
     740 * @returns Matching mime-type in string representation or NULL if not found.
     741 * @param   uFmt    Format in VBox representation to match.
     742 * @param   pList   List of Wayland mime-types in string representation.
     743 */
     744static char *vbcl_wayland_hlp_dcp_match_mime_type(SHCLFORMAT uFmt, vbox_wl_dcp_mime_t *pList)
     745{
     746    char *pszMimeType = NULL;
     747
     748    if (!RTListIsEmpty(&pList->Node))
     749    {
     750        vbox_wl_dcp_mime_t *pEntry;
     751        RTListForEach(&pList->Node, pEntry, vbox_wl_dcp_mime_t, Node)
     752        {
     753            AssertPtrReturn(pEntry, NULL);
     754            AssertPtrReturn(pEntry->pszMimeType, NULL);
     755
     756            if (uFmt == VBoxMimeConvGetIdByMime(pEntry->pszMimeType))
     757            {
     758                pszMimeType = pEntry->pszMimeType;
     759                break;
     760            }
     761        }
     762    }
     763
     764    return pszMimeType;
     765}
     766
     767/**
     768 * Read clipboard buffer from Wayland in specified format.
     769 *
     770 * @returns IPRT status code.
     771 * @param   pCtx            DCP context data.
     772 * @param   pOffer          Data offer object.
     773 * @param   pszMimeType     Requested mime-type in string representation.
     774 */
     775static int vbcl_wayland_hlp_dcp_receive_offer(
     776    vbox_wl_dcp_ctx_t *pCtx, zwlr_data_control_offer_v1 *pOffer, SHCLFORMAT uFmt, char *pszMimeType)
     777{
     778    int rc = VERR_PIPE_NOT_CONNECTED;
     779
     780    int aFds[2];
     781    void *pvBuf = NULL;
     782    size_t cbBuf = 0;
     783
     784    RT_NOREF(uFmt);
     785
     786    if (pipe(aFds) == 0)
     787    {
     788        zwlr_data_control_offer_v1_receive(
     789            (struct zwlr_data_control_offer_v1 *)pOffer, pszMimeType, aFds[1]);
     790
     791        close(aFds[1]);
     792        wl_display_flush(pCtx->pDisplay);
     793
     794        rc = vbcl_wayland_hlp_dcp_read_wl_fd(aFds[0], &pvBuf, &cbBuf);
     795        if (RT_SUCCESS(rc))
     796        {
     797            void *pvBufOut = NULL;
     798            size_t cbBufOut = 0;
     799
     800            rc = VBoxMimeConvNativeToVBox(pszMimeType, pvBuf, cbBuf, &pvBufOut, &cbBufOut);
     801            if (RT_SUCCESS(rc))
     802            {
     803                pCtx->Session.clip.pvClipboardBuf.set((uint64_t)pvBufOut);
     804                pCtx->Session.clip.cbClipboardBuf.set((uint64_t)cbBufOut);
     805            }
     806
     807            RTMemFree(pvBuf);
     808        }
     809    }
     810    else
     811        VBClLogError("cannot read mime-type '%s' from Wayland, rc=%Rrc\n", pszMimeType, rc);
     812
     813    return rc;
     814}
     815
     816/**
     817 * Session callback: Advertise clipboard to the host.
     818 *
     819 * This callback must be executed in context of Wayland event thread
     820 * in order to be able to access Wayland clipboard content.
     821 *
     822 * This callback (1) coverts Wayland clipboard formats into VBox
     823 * representation, (2) sets formats to the session, (3) waits for
     824 * host to request clipboard data in certain format, and (4)
     825 * receives Wayland clipboard in requested format.
     826 *
     827 * @returns IPRT status code.
     828 * @param   enmSessionType      Session type, must be verified as
     829 *                              a consistency check.
     830 * @param   pvUser              User data (data offer object).
     831 */
     832static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_clip_report_cb(
     833    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     834{
     835    struct zwlr_data_control_offer_v1 *pOffer = (struct zwlr_data_control_offer_v1 *)pvUser;
     836    SHCLFORMATS fFmts = VBOX_SHCL_FMT_NONE;
     837
     838    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
     839             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     840
     841    AssertPtrReturn(pOffer, VERR_INVALID_PARAMETER);
     842
     843    VBCL_LOG_CALLBACK;
     844
     845    if (RT_SUCCESS(rc))
     846    {
     847        fFmts = vbcl_wayland_hlp_dcp_match_formats(&g_DcpCtx.Session.clip.mimeTypesList);
     848        if (fFmts != VBOX_SHCL_FMT_NONE)
     849        {
     850            SHCLFORMAT uFmt;
     851
     852            g_DcpCtx.Session.clip.fFmts.set(fFmts);
     853
     854            if (RT_VALID_PTR(g_DcpCtx.pClipboardCtx))
     855            {
     856                rc = VbglR3ClipboardReportFormats(g_DcpCtx.pClipboardCtx->idClient, fFmts);
     857                if (RT_SUCCESS(rc))
     858                {
     859                    uFmt = g_DcpCtx.Session.clip.uFmt.wait();
     860                    if (uFmt != g_DcpCtx.Session.clip.uFmt.defaults())
     861                    {
     862                        char *pszMimeType =
     863                            vbcl_wayland_hlp_dcp_match_mime_type(uFmt, &g_DcpCtx.Session.clip.mimeTypesList);
     864
     865                        if (RT_VALID_PTR(pszMimeType))
     866                        {
     867                            rc = vbcl_wayland_hlp_dcp_receive_offer(&g_DcpCtx, pOffer, uFmt, pszMimeType);
     868
     869                            VBClLogVerbose(5, "will send fmt=0x%x (%s) to the host\n", uFmt, pszMimeType);
     870                        }
     871                        else
     872                            rc = VERR_NO_DATA;
     873                    }
     874                    else
     875                        rc = VERR_TIMEOUT;
     876                }
     877                else
     878                    VBClLogError("cannot report formats to host, rc=%Rrc\n", rc);
     879            }
     880            else
     881            {
     882                VBClLogVerbose(2, "cannot announce to guest, no host service connection yet\n");
     883                rc = VERR_TRY_AGAIN;
     884            }
     885        }
     886        else
     887            rc = VERR_NO_DATA;
     888
     889        zwlr_data_control_offer_v1_destroy((struct zwlr_data_control_offer_v1 *)pOffer);
     890
     891        VBClLogVerbose(5, "announcing fFmts=0x%x to host, rc=%Rrc\n", fFmts, rc);
     892    }
     893
     894    return rc;
     895}
     896
     897/**
     898 * Data Control Device offer callback.
     899 *
     900 * Triggered when other Wayland client advertises new clipboard content.
     901 * When this callback is triggered, a new zwlr_data_control_offer_v1 object
     902 * is created. This callback should setup listener callbacks for this object.
     903 *
     904 * @param pvUser            Context data.
     905 * @param pDevice           Wayland Data Control Device object.
     906 * @param pOffer            Wayland Data Control Offer object.
     907 */
     908static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_device_data_offer(
     909    void *pvUser, struct zwlr_data_control_device_v1 *pDevice, struct zwlr_data_control_offer_v1 *pOffer)
     910{
     911    vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
     912    int rc;
     913
     914    RT_NOREF(pDevice);
     915
     916    VBCL_LOG_CALLBACK;
     917
     918    if (pCtx->fIngnoreWlClipIn)
     919    {
     920        VBClLogVerbose(5, "ignoring Wayland clipboard data offer, we advertising new clipboard ourselves\n");
     921        return;
     922    }
     923
     924    rc = vbcl_wayland_session_end(&pCtx->Session.Base, NULL, NULL);
     925    if (RT_SUCCESS(rc))
     926    {
     927        rc = vbcl_wayland_session_start(&pCtx->Session.Base,
     928                                        VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST,
     929                                        &vbcl_wayland_hlp_dcp_session_start_generic_cb,
     930                                        &pCtx->Session);
     931        if (RT_SUCCESS(rc))
     932        {
     933            zwlr_data_control_offer_v1_add_listener(pOffer, &g_data_control_offer_listener, pvUser);
     934
     935            /* Receive all the advertised mime types. */
     936            wl_display_roundtrip(pCtx->pDisplay);
     937
     938            /* Try to send an announcement to the host. */
     939            rc = vbcl_wayland_session_join(&pCtx->Session.Base,
     940                                           &vbcl_wayland_hlp_dcp_gh_clip_report_cb,
     941                                           pOffer);
     942        }
     943        else
     944            VBClLogError("unable to start session, rc=%Rrc\n", rc);
     945    }
     946    else
     947        VBClLogError("unable to start session, previous session is still running, rc=%Rrc\n", rc);
     948}
     949
     950/**
     951 * Data Control Device selection callback.
     952 *
     953 * Triggered when Wayland client advertises new clipboard content.
     954 * In this callback, actual clipboard data is received from Wayland client.
     955 *
     956 * @param pvUser            Context data.
     957 * @param pDevice           Wayland Data Control Device object.
     958 * @param pOffer            Wayland Data Control Offer object.
     959 */
     960static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_device_selection(
     961    void *pvUser, struct zwlr_data_control_device_v1 *pDevice, struct zwlr_data_control_offer_v1 *pOffer)
     962{
     963    RT_NOREF(pDevice, pvUser, pOffer);
     964
     965    VBCL_LOG_CALLBACK;
     966}
     967
     968/**
     969 * Data Control Device finished callback.
     970 *
     971 * Triggered when Data Control Device object is no longer valid and
     972 * needs to be destroyed.
     973 *
     974 * @param pvUser            Context data.
     975 * @param pDevice           Wayland Data Control Device object.
     976 */
     977static void vbcl_wayland_hlp_dcp_data_device_finished(
     978    void *data, struct zwlr_data_control_device_v1 *pDevice)
     979{
     980    RT_NOREF(data);
     981
     982    VBCL_LOG_CALLBACK;
     983
     984    zwlr_data_control_device_v1_destroy(pDevice);
     985}
     986
     987/**
     988 * Data Control Device primary selection callback.
     989 *
     990 * Same as shcl_wl_data_control_device_selection, but triggered for
     991 * primary selection case.
     992 *
     993 * @param pvUser            Context data.
     994 * @param pDevice           Wayland Data Control Device object.
     995 * @param pOffer            Wayland Data Control Offer object.
     996 */
     997static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_device_primary_selection(
     998    void *pvUser, struct zwlr_data_control_device_v1 *pDevice, struct zwlr_data_control_offer_v1 *pOffer)
     999{
     1000    RT_NOREF(pDevice, pvUser, pOffer);
     1001
     1002    VBCL_LOG_CALLBACK;
     1003}
     1004
     1005
     1006/** Data Control Device interface callbacks. */
     1007static const struct zwlr_data_control_device_v1_listener g_data_device_listener =
     1008{
     1009    vbcl_wayland_hlp_dcp_data_device_data_offer,
     1010    vbcl_wayland_hlp_dcp_data_device_selection,
     1011    vbcl_wayland_hlp_dcp_data_device_finished,
     1012    vbcl_wayland_hlp_dcp_data_device_primary_selection,
     1013};
     1014
     1015
     1016/**********************************************************************************************************************************
     1017 * Wayland Data Control Source callbacks.
     1018 *********************************************************************************************************************************/
     1019
     1020/**
     1021 * Wayland data send callback.
     1022 *
     1023 * Triggered when other Wayland client wants to read clipboard
     1024 * data from us.
     1025 *
     1026 * @param pvUser            VBox private data.
     1027 * @param pSourceSource     Wayland Data Control Source object.
     1028 * @param sMimeType         A mime-type of requested data.
     1029 * @param fd                A file descriptor to write clipboard content into.
     1030 */
     1031static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_source_send(
     1032    void *pvUser, struct zwlr_data_control_source_v1 *pDataSource,
     1033    const char *sMimeType, int32_t fd)
     1034{
     1035    vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
     1036    int rc;
     1037
     1038    struct vbcl_wl_dcp_write_ctx priv;
     1039
     1040    RT_NOREF(pDataSource);
     1041
     1042    VBCL_LOG_CALLBACK;
     1043
     1044    RT_ZERO(priv);
     1045
     1046    priv.sMimeType = sMimeType;
     1047    priv.fd = fd;
     1048
     1049    rc = vbcl_wayland_session_join(&pCtx->Session.Base,
     1050                                   &vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb,
     1051                                   &priv);
     1052
     1053    VBClLogVerbose(5, "vbcl_wayland_hlp_dcp_data_source_send, rc=%Rrc\n", rc);
     1054    close(fd);
     1055}
     1056
     1057/**
     1058 * Wayland data canceled callback.
     1059 *
     1060 * Triggered when data source was replaced by another data source
     1061 * and no longer valid.
     1062 *
     1063 * @param pvData            VBox private data.
     1064 * @param pDataSource       Wayland Data Control Source object.
     1065 */
     1066static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_data_source_cancelled(
     1067    void *pvData, struct zwlr_data_control_source_v1 *pDataSource)
     1068{
     1069    RT_NOREF(pvData);
     1070
     1071    VBCL_LOG_CALLBACK;
     1072
     1073    zwlr_data_control_source_v1_destroy(pDataSource);
     1074}
     1075
     1076
     1077/** Wayland Data Control Source interface callbacks. */
     1078static const struct zwlr_data_control_source_v1_listener g_data_source_listener =
     1079{
     1080    vbcl_wayland_hlp_dcp_data_source_send,
     1081    vbcl_wayland_hlp_dcp_data_source_cancelled,
     1082};
     1083
     1084
     1085/**********************************************************************************************************************************
     1086 * Helper specific code and session callbacks.
     1087 *********************************************************************************************************************************/
     1088
     1089
     1090/**
     1091 * Setup or reset helper context.
     1092 *
     1093 * This function is used on helper init and termination. In case of
     1094 * init, memory is not initialized yet, so it needs to be zeroed.
     1095 * In case of shutdown, memory is already initialized and previously
     1096 * allocated resources must be freed.
     1097 *
     1098 * @param pCtx              Context data.
     1099 * @param fShutdown         A flag to indicate if session resources
     1100 *                          need to be deallocated.
     1101 */
     1102static void vbcl_wayland_hlp_dcp_reset_ctx(vbox_wl_dcp_ctx_t *pCtx, bool fShutdown)
     1103{
     1104    pCtx->Thread = NIL_RTTHREAD;
     1105    pCtx->fShutdown = false;
     1106    pCtx->fIngnoreWlClipIn = false;
     1107    pCtx->fSendToGuest.init(false, VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS);
     1108    pCtx->pClipboardCtx = NULL;
     1109    pCtx->pDisplay = NULL;
     1110    pCtx->pRegistry = NULL;
     1111    pCtx->pSeat = NULL;
     1112    pCtx->pDataDevice = NULL;
     1113    pCtx->pDataControlManager = NULL;
     1114
     1115    if (fShutdown)
     1116        vbcl_wayland_hlp_dcp_session_release(&pCtx->Session);
     1117
     1118    vbcl_wayland_hlp_dcp_session_init(&pCtx->Session);
     1119}
     1120
     1121/**
     1122 * Disconnect from Wayland compositor.
     1123 *
     1124 * Close connection, release resources and reset context data.
     1125 *
     1126 * @param pCtx              Context data.
     1127 */
     1128static void vbcl_wayland_hlp_dcp_disconnect(vbox_wl_dcp_ctx_t *pCtx)
     1129{
     1130    if (RT_VALID_PTR(pCtx->pDataControlManager))
     1131        zwlr_data_control_manager_v1_destroy(pCtx->pDataControlManager);
     1132
     1133    if (RT_VALID_PTR(pCtx->pDataDevice))
     1134        zwlr_data_control_device_v1_destroy(pCtx->pDataDevice);
     1135
     1136    if (RT_VALID_PTR(pCtx->pSeat))
     1137        wl_seat_destroy(pCtx->pSeat);
     1138
     1139    if (RT_VALID_PTR(pCtx->pRegistry))
     1140        wl_registry_destroy(pCtx->pRegistry);
     1141
     1142    if (RT_VALID_PTR(pCtx->pDisplay))
     1143        wl_display_disconnect(pCtx->pDisplay);
     1144
     1145    vbcl_wayland_hlp_dcp_reset_ctx(pCtx, true);
     1146}
     1147
     1148/**
     1149 * Connect to Wayland compositor.
     1150 *
     1151 * Establish connection, bind to all required interfaces.
     1152 *
     1153 * @returns TRUE on success, FALSE otherwise.
     1154 * @param   pCtx                Context data.
     1155 */
     1156static bool vbcl_wayland_hlp_dcp_connect(vbox_wl_dcp_ctx_t *pCtx)
     1157{
     1158    const char *csWaylandDisplay = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY);
     1159    bool fConnected = false;
     1160
     1161    if (RT_VALID_PTR(csWaylandDisplay))
     1162        pCtx->pDisplay = wl_display_connect(csWaylandDisplay);
     1163    else
     1164        VBClLogError("cannot connect to Wayland compositor "
     1165                     VBCL_ENV_WAYLAND_DISPLAY " environment variable not set\n");
     1166
     1167    if (RT_VALID_PTR(pCtx->pDisplay))
     1168    {
     1169        pCtx->pRegistry = wl_display_get_registry(pCtx->pDisplay);
     1170        if (RT_VALID_PTR(pCtx->pRegistry))
     1171        {
     1172            wl_registry_add_listener(pCtx->pRegistry, &g_vbcl_wayland_hlp_registry_cb, (void *)pCtx);
     1173            wl_display_roundtrip(pCtx->pDisplay);
     1174
     1175            if (RT_VALID_PTR(pCtx->pDataControlManager))
     1176            {
     1177                if (RT_VALID_PTR(pCtx->pSeat))
     1178                {
     1179                    pCtx->pDataDevice = zwlr_data_control_manager_v1_get_data_device(pCtx->pDataControlManager, pCtx->pSeat);
     1180                    if (RT_VALID_PTR(pCtx->pDataDevice))
     1181                    {
     1182                        if (RT_VALID_PTR(pCtx->pDataControlManager))
     1183                            fConnected = true;
     1184                        else
     1185                            VBClLogError("cannot get Wayland data control manager interface\n");
     1186                    }
     1187                    else
     1188                        VBClLogError("cannot get Wayland data device interface\n");
     1189                }
     1190                else
     1191                    VBClLogError("cannot get Wayland seat interface\n");
     1192            }
     1193            else
     1194                VBClLogError("cannot get Wayland device manager interface\n");
     1195        }
     1196        else
     1197            VBClLogError("cannot connect to Wayland registry\n");
     1198    }
     1199    else
     1200        VBClLogError("cannot connect to Wayland compositor\n");
     1201
     1202    if (!fConnected)
     1203        vbcl_wayland_hlp_dcp_disconnect(pCtx);
     1204
     1205    return fConnected;
     1206}
     1207
     1208
     1209/**
     1210 * Main loop for Wayland compositor events.
     1211 *
     1212 * All requests to Wayland compositor must be performed in context
     1213 * of this thread.
     1214 *
     1215 * @returns IPRT status code.
     1216 * @param   hThreadSelf         IPRT thread object.
     1217 * @param   pvUser              Context data.
     1218 */
     1219static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_event_loop(RTTHREAD hThreadSelf, void *pvUser)
     1220{
     1221    vbox_wl_dcp_ctx_t *pCtx = (vbox_wl_dcp_ctx_t *)pvUser;
     1222    int rc = VERR_TRY_AGAIN;
     1223
     1224    if (vbcl_wayland_hlp_dcp_connect(pCtx))
     1225    {
     1226        /* Start listening Data Control Device interface. */
     1227        if (zwlr_data_control_device_v1_add_listener(pCtx->pDataDevice, &g_data_device_listener, (void *)pCtx) == 0)
     1228        {
     1229            /* Tell parent thread we are ready. */
     1230            RTThreadUserSignal(hThreadSelf);
     1231
     1232            while (1)
     1233            {
     1234                rc = vbcl_wayland_hlp_dcp_next_event(pCtx);
     1235                if (   rc != VERR_TIMEOUT
     1236                    && RT_FAILURE(rc))
     1237                {
     1238                    VBClLogError("cannot read event from Wayland, rc=%Rrc\n", rc);
     1239                }
     1240
     1241                if (pCtx->fSendToGuest.reset())
     1242                {
     1243                    rc = vbcl_wayland_session_join(&pCtx->Session.Base,
     1244                                                   &vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb,
     1245                                                   NULL);
     1246                }
     1247
     1248                /* Handle graceful thread termination. */
     1249                if (pCtx->fShutdown)
     1250                {
     1251                    rc = VINF_SUCCESS;
     1252                    break;
     1253                }
     1254            }
     1255        }
     1256        else
     1257        {
     1258            rc = VERR_NOT_SUPPORTED;
     1259            VBClLogError("cannot subscribe to Data Control Device events\n");
     1260        }
     1261
     1262        vbcl_wayland_hlp_dcp_disconnect(pCtx);
     1263    }
     1264
     1265    /* Notify parent thread if we failed to start, so it won't be
     1266     * waiting 30 sec to figure this out. */
     1267    if (RT_FAILURE(rc))
     1268        RTThreadUserSignal(hThreadSelf);
     1269
     1270    return rc;
     1271}
    291272
    301273/**
     
    331276static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_probe(void)
    341277{
    35     return VBOX_WAYLAND_HELPER_CAP_NONE;
     1278    vbox_wl_dcp_ctx_t probeCtx;
     1279    int fCaps = VBOX_WAYLAND_HELPER_CAP_NONE;
     1280    VBGHDISPLAYSERVERTYPE enmDisplayServerType = VBGHDisplayServerTypeDetect();
     1281
     1282    vbcl_wayland_hlp_dcp_reset_ctx(&probeCtx, false /* fShutdown */);
     1283    vbcl_wayland_session_init(&probeCtx.Session.Base);
     1284
     1285    if (VBGHDisplayServerTypeIsWaylandAvailable(enmDisplayServerType))
     1286    {
     1287        if (vbcl_wayland_hlp_dcp_connect(&probeCtx))
     1288        {
     1289            fCaps |= VBOX_WAYLAND_HELPER_CAP_CLIPBOARD;
     1290            vbcl_wayland_hlp_dcp_disconnect(&probeCtx);
     1291        }
     1292    }
     1293
     1294    return fCaps;
    361295}
    371296
     
    411300RTDECL(int) vbcl_wayland_hlp_dcp_init(void)
    421301{
    43     return VERR_NOT_SUPPORTED;
     1302    vbcl_wayland_hlp_dcp_reset_ctx(&g_DcpCtx, false /* fShutdown */);
     1303    vbcl_wayland_session_init(&g_DcpCtx.Session.Base);
     1304
     1305    return VBClClipboardThreadStart(&g_DcpCtx.Thread, vbcl_wayland_hlp_dcp_event_loop, "wl-dcp", &g_DcpCtx);
    441306}
    451307
     
    491311RTDECL(int) vbcl_wayland_hlp_dcp_term(void)
    501312{
    51     return VERR_NOT_SUPPORTED;
     1313    int rc;
     1314    int rcThread = 0;
     1315
     1316    /* Set termination flag. Wayland event loop should pick it up
     1317     * on the next iteration. */
     1318    g_DcpCtx.fShutdown = true;
     1319
     1320    /* Wait for Wayland event loop thread to shutdown. */
     1321    rc = RTThreadWait(g_DcpCtx.Thread, RT_MS_30SEC, &rcThread);
     1322    if (RT_SUCCESS(rc))
     1323        VBClLogInfo("Wayland event thread exited with status, rc=%Rrc\n", rcThread);
     1324    else
     1325        VBClLogError("unable to stop Wayland event thread, rc=%Rrc\n", rc);
     1326
     1327    return rc;
     1328}
     1329
     1330/**
     1331 * @interface_method_impl{VBCLWAYLANDHELPER,pfnSetClipboardCtx}
     1332 */
     1333static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_set_clipboard_ctx(PVBGLR3SHCLCMDCTX pCtx)
     1334{
     1335    g_DcpCtx.pClipboardCtx = pCtx;
     1336}
     1337
     1338/**
     1339 * @interface_method_impl{VBCLWAYLANDHELPER,pfnPopup}
     1340 */
     1341static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_popup(void)
     1342{
     1343    return VINF_SUCCESS;
     1344}
     1345
     1346/**
     1347 * Session callback: Copy clipboard to the guest.
     1348 *
     1349 * This callback must be executed in context of Wayland event thread
     1350 * in order to be able to inject clipboard content into Wayland. It is
     1351 * triggered when Wayland client already decided data in which format
     1352 * it wants to request.
     1353 *
     1354 * This callback (1) sets requested clipboard format to the session,
     1355 * (2) waits for clipboard data to be copied from the host, (3) converts
     1356 * host clipboard data into guest representation, and (4) sends clipboard
     1357 * to the guest by writing given file descriptor.
     1358 *
     1359 * @returns IPRT status code.
     1360 * @param   enmSessionType      Session type, must be verified as
     1361 *                              a consistency check.
     1362 * @param   pvUser              User data (Wayland I/O context).
     1363 */
     1364static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join3_cb(
     1365    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     1366{
     1367    struct vbcl_wl_dcp_write_ctx *pPriv = (struct vbcl_wl_dcp_write_ctx *)pvUser;
     1368    AssertPtrReturn(pPriv, VERR_INVALID_PARAMETER);
     1369
     1370    void *pvBuf;
     1371    uint32_t cbBuf;
     1372
     1373    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
     1374             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     1375
     1376    VBCL_LOG_CALLBACK;
     1377
     1378    if (RT_SUCCESS(rc))
     1379    {
     1380        if (RT_VALID_PTR(g_DcpCtx.pClipboardCtx))
     1381        {
     1382            /* Set requested format to the session. */
     1383            g_DcpCtx.Session.clip.uFmt.set(VBoxMimeConvGetIdByMime(pPriv->sMimeType));
     1384
     1385            /* Wait for data in requested format. */
     1386            pvBuf = (void *)g_DcpCtx.Session.clip.pvClipboardBuf.wait();
     1387            cbBuf = g_DcpCtx.Session.clip.cbClipboardBuf.wait();
     1388            if (   cbBuf != g_DcpCtx.Session.clip.cbClipboardBuf.defaults()
     1389                && pvBuf != (void *)g_DcpCtx.Session.clip.pvClipboardBuf.defaults())
     1390            {
     1391                void *pvBufOut;
     1392                size_t cbOut;
     1393
     1394                /* Convert clipboard data from VBox representation into guest format. */
     1395                rc = VBoxMimeConvVBoxToNative(pPriv->sMimeType, pvBuf, cbBuf, &pvBufOut, &cbOut);
     1396                if (RT_SUCCESS(rc))
     1397                {
     1398                    rc = vbcl_wayland_hlp_dcp_write_wl_fd(pPriv->fd, pvBufOut, cbOut);
     1399                    RTMemFree(pvBufOut);
     1400                }
     1401                else
     1402                    VBClLogError("cannot convert '%s' to native format, rc=%Rrc\n", rc);
     1403            }
     1404            else
     1405                rc = VERR_TIMEOUT;
     1406        }
     1407        else
     1408        {
     1409            VBClLogVerbose(2, "cannot send to guest, no host service connection yet\n");
     1410            rc = VERR_TRY_AGAIN;
     1411        }
     1412
     1413        g_DcpCtx.fIngnoreWlClipIn = false;
     1414    }
     1415
     1416    return rc;
     1417}
     1418
     1419/**
     1420 * Enumeration callback used for sending clipboard offers to Wayland client.
     1421 *
     1422 * When host announces its clipboard content, this call back is used in order
     1423 * to send corresponding offers to other Wayland clients.
     1424 *
     1425 * Callback must be executed in context of Wayland event thread.
     1426 *
     1427 * @param   pcszMimeType    Mime-type to advertise.
     1428 * @param   pvUser          User data (DCP data source object).
     1429 */
     1430static DECLCALLBACK(void) vbcl_wayland_hlp_dcp_send_offers(const char *pcszMimeType, void *pvUser)
     1431{
     1432    zwlr_data_control_source_v1 *pDataSource = (zwlr_data_control_source_v1 *)pvUser;
     1433    zwlr_data_control_source_v1_offer(pDataSource, pcszMimeType);
     1434}
     1435
     1436/**
     1437 * Session callback: Advertise clipboard to the guest.
     1438 *
     1439 * This callback must be executed in context of Wayland event thread
     1440 * in order to be able to inject clipboard content into Wayland.
     1441 *
     1442 * This callback (1) prevents Wayland event loop from processing
     1443 * incoming clipboard advertisements before sending any data to
     1444 * other Wayland clients (this is needed in order to avoid feedback
     1445 * loop from our own advertisements), (2) waits for the list of clipboard
     1446 * formats available on the host side (set by vbcl_wayland_hlp_dcp_hg_clip_report_join_cb),
     1447 * and (3) sends data offers for available host clipboard to other clients.
     1448 *
     1449 * @returns IPRT status code.
     1450 * @param   enmSessionType      Session type, must be verified as
     1451 *                              a consistency check.
     1452 * @param   pvUser              User data (unused).
     1453 */
     1454static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join2_cb(
     1455    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     1456{
     1457    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
     1458             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     1459
     1460    RT_NOREF(pvUser);
     1461
     1462    VBCL_LOG_CALLBACK;
     1463
     1464    if (RT_SUCCESS(rc))
     1465    {
     1466        g_DcpCtx.fIngnoreWlClipIn = true;
     1467
     1468        SHCLFORMATS fFmts = g_DcpCtx.Session.clip.fFmts.wait();
     1469        if (fFmts != g_DcpCtx.Session.clip.fFmts.defaults())
     1470        {
     1471            zwlr_data_control_source_v1 *pDataSource =
     1472            zwlr_data_control_manager_v1_create_data_source(g_DcpCtx.pDataControlManager);
     1473
     1474            if (RT_VALID_PTR(pDataSource))
     1475            {
     1476                zwlr_data_control_source_v1_add_listener(
     1477                    (struct zwlr_data_control_source_v1 *)pDataSource, &g_data_source_listener, &g_DcpCtx);
     1478
     1479                VBoxMimeConvEnumerateMimeById(fFmts,
     1480                                              vbcl_wayland_hlp_dcp_send_offers,
     1481                                              pDataSource);
     1482
     1483                zwlr_data_control_device_v1_set_selection(g_DcpCtx.pDataDevice, pDataSource);
     1484            }
     1485            else
     1486                rc = VERR_NO_MEMORY;
     1487        }
     1488        else
     1489            rc = VERR_NO_DATA;
     1490    }
     1491
     1492    return rc;
     1493}
     1494
     1495/**
     1496 * Session callback: Copy clipboard from the host.
     1497 *
     1498 * This callback (1) sets host clipboard formats list to the session,
     1499 * (2) asks Wayland event thread to advertise these formats to the guest,
     1500 * (3) waits for guest to request clipboard in specific format, (4) read
     1501 * host clipboard in this format, and (5) sets clipboard data to the session,
     1502 * so Wayland events thread can inject it into the guest.
     1503 *
     1504 * This callback should not return until clipboard data is read from
     1505 * the host or error occurred. It must block host events loop until
     1506 * current host event is fully processed.
     1507 *
     1508 * @returns IPRT status code.
     1509 * @param   enmSessionType      Session type, must be verified as
     1510 *                              a consistency check.
     1511 * @param   pvUser              User data (host clipboard formats).
     1512 */
     1513static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_hg_clip_report_join_cb(
     1514    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     1515{
     1516    SHCLFORMATS *pfFmts = (SHCLFORMATS *)pvUser;
     1517    AssertPtrReturn(pfFmts, VERR_INVALID_PARAMETER);
     1518
     1519    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
     1520             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     1521
     1522    VBCL_LOG_CALLBACK;
     1523
     1524    if (RT_SUCCESS(rc))
     1525    {
     1526        SHCLFORMAT uFmt;
     1527        void *pvData;
     1528        uint32_t cbData;
     1529
     1530        /* Set list of host clipboard formats to the session. */
     1531        g_DcpCtx.Session.clip.fFmts.set(*pfFmts);
     1532
     1533        /* Ask Wayland event thread to advertise formats to the guest. */
     1534        g_DcpCtx.fSendToGuest.set(true);
     1535        RTThreadPoke(g_DcpCtx.Thread);
     1536
     1537        /* Wait for the guest to request certain clipboard format. */
     1538        uFmt = g_DcpCtx.Session.clip.uFmt.wait();
     1539        if (uFmt != g_DcpCtx.Session.clip.uFmt.defaults())
     1540        {
     1541            /* Read host clipboard in specified format. */
     1542            rc = VBClClipboardReadHostClipboard(g_DcpCtx.pClipboardCtx, uFmt, &pvData, &cbData);
     1543            if (RT_SUCCESS(rc))
     1544            {
     1545                /* Set clipboard data to the session. */
     1546                g_DcpCtx.Session.clip.pvClipboardBuf.set((uint64_t)pvData);
     1547                g_DcpCtx.Session.clip.cbClipboardBuf.set((uint64_t)cbData);
     1548            }
     1549        }
     1550        else
     1551            rc = VERR_TIMEOUT;
     1552
     1553    }
     1554
     1555    return rc;
     1556}
     1557
     1558/**
     1559 * @interface_method_impl{VBCLWAYLANDHELPER,pfnHGClipReport}
     1560 */
     1561static int vbcl_wayland_hlp_dcp_hg_clip_report(SHCLFORMATS fFormats)
     1562{
     1563    int rc = VERR_NO_DATA;
     1564
     1565    VBCL_LOG_CALLBACK;
     1566
     1567    if (fFormats != VBOX_SHCL_FMT_NONE)
     1568    {
     1569        rc = vbcl_wayland_session_end(&g_DcpCtx.Session.Base, NULL, NULL);
     1570        if (RT_SUCCESS(rc))
     1571        {
     1572            rc = vbcl_wayland_session_start(&g_DcpCtx.Session.Base,
     1573                                            VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST,
     1574                                            &vbcl_wayland_hlp_dcp_session_start_generic_cb,
     1575                                            NULL);
     1576
     1577            if (RT_SUCCESS(rc))
     1578                rc = vbcl_wayland_session_join(&g_DcpCtx.Session.Base,
     1579                                               vbcl_wayland_hlp_dcp_hg_clip_report_join_cb,
     1580                                               &fFormats);
     1581        }
     1582        else
     1583            VBClLogError("unable to start session, previous session is still running rc=%Rrc\n", rc);
     1584    }
     1585
     1586    return rc;
     1587}
     1588
     1589/**
     1590 * Session callback: Copy clipboard to the host.
     1591 *
     1592 * This callback sets clipboard format to the session as requested
     1593 * by host, waits for guest clipboard data in requested format and
     1594 * sends data to the host.
     1595 *
     1596 * This callback should not return until clipboard data is sent to
     1597 * the host or error occurred. It must block host events loop until
     1598 * current host event is fully processed.
     1599 *
     1600 * @returns IPRT status code.
     1601 * @param   enmSessionType      Session type, must be verified as
     1602 *                              a consistency check.
     1603 * @param   pvUser              User data (requested format).
     1604 */
     1605static DECLCALLBACK(int) vbcl_wayland_hlp_dcp_gh_clip_read_join_cb(
     1606    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     1607{
     1608    SHCLFORMAT *puFmt = (SHCLFORMAT *)pvUser;
     1609    AssertPtrReturn(puFmt, VERR_INVALID_PARAMETER);
     1610
     1611    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
     1612             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     1613
     1614    VBCL_LOG_CALLBACK;
     1615
     1616    if (RT_SUCCESS(rc))
     1617    {
     1618        void *pvData;
     1619        size_t cbData;
     1620
     1621        /* Store requested clipboard format to the session. */
     1622        g_DcpCtx.Session.clip.uFmt.set(*puFmt);
     1623
     1624        /* Wait for data in requested format. */
     1625        pvData = (void *)g_DcpCtx.Session.clip.pvClipboardBuf.wait();
     1626        cbData = g_DcpCtx.Session.clip.cbClipboardBuf.wait();
     1627        if (   cbData != g_DcpCtx.Session.clip.cbClipboardBuf.defaults()
     1628            && pvData != (void *)g_DcpCtx.Session.clip.pvClipboardBuf.defaults())
     1629        {
     1630            /* Send clipboard data to the host. */
     1631            rc = VbglR3ClipboardWriteDataEx(g_DcpCtx.pClipboardCtx, *puFmt, pvData, cbData);
     1632        }
     1633        else
     1634            rc = VERR_TIMEOUT;
     1635    }
     1636
     1637    return rc;
     1638}
     1639
     1640/**
     1641 * @interface_method_impl{VBCLWAYLANDHELPER,pfnGHClipRead}
     1642 */
     1643static int vbcl_wayland_hlp_dcp_gh_clip_read(SHCLFORMAT uFmt)
     1644{
     1645    int rc;
     1646
     1647    VBCL_LOG_CALLBACK;
     1648
     1649    rc = vbcl_wayland_session_join(&g_DcpCtx.Session.Base,
     1650                                   &vbcl_wayland_hlp_dcp_gh_clip_read_join_cb,
     1651                                   &uFmt);
     1652    return rc;
    521653}
    531654
     
    551656const VBCLWAYLANDHELPER g_WaylandHelperDcp =
    561657{
    57     "wayland-dcp",                  /* .pszName */
    58     vbcl_wayland_hlp_dcp_probe,     /* .pfnProbe */
    59     vbcl_wayland_hlp_dcp_init,      /* .pfnInit */
    60     vbcl_wayland_hlp_dcp_term,      /* .pfnTerm */
     1658    "wayland-dcp",                              /* .pszName */
     1659    vbcl_wayland_hlp_dcp_probe,                 /* .pfnProbe */
     1660    vbcl_wayland_hlp_dcp_init,                  /* .pfnInit */
     1661    vbcl_wayland_hlp_dcp_term,                  /* .pfnTerm */
     1662    vbcl_wayland_hlp_dcp_set_clipboard_ctx,     /* .pfnSetClipboardCtx */
     1663    vbcl_wayland_hlp_dcp_popup,                 /* .pfnPopup */
     1664    vbcl_wayland_hlp_dcp_hg_clip_report,        /* .pfnHGClipReport */
     1665    vbcl_wayland_hlp_dcp_gh_clip_read,          /* .pfnGHClipRead */
    611666};
  • trunk/src/VBox/Additions/x11/VBoxClient/wayland-helper-gtk.cpp

    r100246 r101878  
    11/* $Id$ */
    22/** @file
    3  * Guest Additions - Wayland Desktop Environment helper which uses GTK library.
     3 * Guest Additions - Gtk helper for Wayland.
     4 *
     5 * This module implements Shared Clipboard and Drag-n-Drop
     6 * support for Wayland guests using Gtk library.
    47 */
    58
     
    2629 */
    2730
     31#include <iprt/localipc.h>
     32#include <iprt/rand.h>
     33#include <iprt/semaphore.h>
     34
     35#include <VBox/GuestHost/DisplayServerType.h>
     36#include <VBox/GuestHost/clipboard-helper.h>
     37#include <VBox/GuestHost/mime-type-converter.h>
     38
     39#include "VBoxClient.h"
     40#include "clipboard.h"
    2841#include "wayland-helper.h"
     42#include "wayland-helper-ipc.h"
     43
     44#include "vbox-gtk.h"
     45
     46/** Gtk session data.
     47 *
     48 * A structure which accumulates all the necessary data required to
     49 * maintain session between host and Wayland for clipboard sharing
     50 * and drag-n-drop.*/
     51typedef struct
     52{
     53    /* Generic VBoxClient Wayland session data (synchronization point). */
     54    vbcl_wl_session_t                       Base;
     55    /** Randomly generated session ID, should be used by
     56     *  both VBoxClient and vboxwl tool. */
     57    uint32_t                                uSessionId;
     58    /** IPC connection flow control between VBoxClient and vboxwl tool. */
     59    vbcl::ipc::clipboard::ClipboardIpc      *oClipboardIpc;
     60    /** IPC connection handle. */
     61    RTLOCALIPCSESSION                       hIpcSession;
     62    /** Popup window process handle. */
     63    RTPROCESS                               popupProc;
     64} vbox_wl_gtk_ipc_session_t;
     65
     66/**
     67 * A set of objects required to handle clipboard sharing over
     68 * and drag-n-drop using Gtk library.. */
     69typedef struct
     70{
     71    /** Wayland event loop thread. */
     72    RTTHREAD                                Thread;
     73
     74    /** A flag which indicates that Wayland event loop should terminate. */
     75    volatile bool                           fShutdown;
     76
     77    /** Communication session between host event loop and Wayland. */
     78    vbox_wl_gtk_ipc_session_t               Session;
     79
     80    /** Connection to the host clipboard service. */
     81    PVBGLR3SHCLCMDCTX                       pClipboardCtx;
     82
     83    /** Local IPC server object. */
     84    RTLOCALIPCSERVER                        hIpcServer;
     85} vbox_wl_gtk_ctx_t;
     86
     87/** Helper context. */
     88static vbox_wl_gtk_ctx_t g_GtkCtx;
     89
     90/**
     91 * Start popup process.
     92 *
     93 * @returns IPRT status code.
     94 * @param   pSession    Session data.
     95 */
     96static int vbcl_wayland_hlp_gtk_session_popup(vbox_wl_gtk_ipc_session_t *pSession)
     97{
     98    int rc = VINF_SUCCESS;
     99
     100    /* Make sure valid session is in progress. */
     101    AssertReturn(pSession->uSessionId > 0, VERR_INVALID_PARAMETER);
     102
     103    char *pszSessionId = RTStrAPrintf2("%u", pSession->uSessionId);
     104    if (RT_VALID_PTR(pszSessionId))
     105    {
     106        /* List of vboxwl command line arguments.*/
     107        const char *apszArgs[] =
     108        {
     109            VBOXWL_PATH,
     110            NULL,
     111            VBOXWL_ARG_SESSION_ID,
     112            pszSessionId,
     113            NULL,
     114            NULL
     115        };
     116
     117        /* Log verbosity level to be passed to vboxwl. */
     118        char pszVerobsity[  VBOXWL_VERBOSITY_MAX
     119                          + 2 /* add space for '-' and '\0' */];
     120        RT_ZERO(pszVerobsity);
     121
     122        /* Select vboxwl action depending on session type. */
     123        if      (pSession->Base.enmType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
     124            apszArgs[1] = VBOXWL_ARG_CLIP_HG_COPY;
     125        else if (pSession->Base.enmType == VBCL_WL_CLIPBOARD_SESSION_TYPE_ANNOUNCE_TO_HOST)
     126            apszArgs[1] = VBOXWL_ARG_CLIP_GH_ANNOUNCE;
     127        else if (pSession->Base.enmType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
     128            apszArgs[1] = VBOXWL_ARG_CLIP_GH_COPY;
     129        else
     130            rc = VERR_INVALID_PARAMETER;
     131
     132        /* Once VBoxClient was started with log verbosity level, pass the
     133         * same verbosity level to vboxwl as well. */
     134        if (   RT_SUCCESS(rc)
     135            && g_cVerbosity > 0)
     136        {
     137            pszVerobsity[0] = '-';
     138
     139            memset(&pszVerobsity[1], 'v',
     140                   RT_MIN(g_cVerbosity, VBOXWL_VERBOSITY_MAX));
     141
     142            /* Insert verbosity level into the rest of vboxwl
     143             * command line arguments. */
     144            apszArgs[4] = pszVerobsity;
     145        }
     146
     147        /* Run vboxwl in background. */
     148        if (RT_SUCCESS(rc))
     149            rc = RTProcCreate(VBOXWL_PATH,
     150                              apszArgs, RTENV_DEFAULT,
     151                              RTPROC_FLAGS_SEARCH_PATH, &pSession->popupProc);
     152
     153        VBClLogVerbose(2, "start '%s' command [sid=%u]: rc=%Rrc\n",
     154                       VBOXWL_PATH, pSession->uSessionId, rc);
     155
     156        RTStrFree(pszSessionId);
     157    }
     158    else
     159        rc = VERR_NO_MEMORY;
     160
     161    return rc;
     162}
     163
     164/**
     165 * Prepare new session and start popup process.
     166 *
     167 * @returns IPRT status code.
     168 * @param   pSession        Session data.
     169 */
     170static int vbcl_wayland_hlp_gtk_session_prepare(vbox_wl_gtk_ipc_session_t *pSession)
     171{
     172    int rc = VINF_SUCCESS;
     173
     174    /* Make sure there is no leftovers from previous session. */
     175    Assert(pSession->uSessionId == 0);
     176
     177    /* Initialize session. */
     178    pSession->uSessionId = RTRandU32Ex(1, 0xFFFFFFFF);
     179
     180    pSession->oClipboardIpc = new vbcl::ipc::clipboard::ClipboardIpc();
     181    if (RT_VALID_PTR(pSession->oClipboardIpc))
     182    {
     183        pSession->oClipboardIpc->init(vbcl::ipc::FLOW_DIRECTION_SERVER,
     184                                      pSession->uSessionId);
     185    }
     186    else
     187        rc = VERR_NO_MEMORY;
     188
     189    /* Start helper tool. */
     190    if (RT_SUCCESS(rc))
     191    {
     192        rc = vbcl_wayland_hlp_gtk_session_popup(pSession);
     193        VBClLogVerbose(1, "session id=%u: started: rc=%Rrc\n",
     194                       pSession->uSessionId, rc);
     195    }
     196
     197    return rc;
     198}
     199
     200/**
     201 * Session callback: Generic session initializer.
     202 *
     203 * This callback starts new session.
     204 *
     205 * @returns IPRT status code.
     206 * @param   enmSessionType      Session type (unused).
     207 * @param   pvUser              User data (unused).
     208 */
     209static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_session_start_generic_cb(
     210    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     211{
     212    RT_NOREF(enmSessionType, pvUser);
     213
     214    VBCL_LOG_CALLBACK;
     215
     216    return vbcl_wayland_hlp_gtk_session_prepare(&g_GtkCtx.Session);
     217}
     218
     219/**
     220 * Reset session, terminate popup process and free allocated resources.
     221 *
     222 * @returns IPRT status code.
     223 * @param   enmSessionType      Session type (unused).
     224 * @param   pvUser              User data (session to reset).
     225 */
     226static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_session_end_cb(
     227    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     228{
     229    vbox_wl_gtk_ipc_session_t *pSession = (vbox_wl_gtk_ipc_session_t *)pvUser;
     230    AssertPtrReturn(pSession, VERR_INVALID_PARAMETER);
     231
     232    int rc;
     233
     234    RT_NOREF(enmSessionType);
     235
     236    /* Make sure valid session is in progress. */
     237    AssertReturn(pSession->uSessionId > 0, VERR_INVALID_PARAMETER);
     238
     239    rc = RTProcWait(pSession->popupProc, RTPROCWAIT_FLAGS_BLOCK, NULL);
     240    if (RT_FAILURE(rc))
     241        rc = RTProcTerminate(pSession->popupProc);
     242    if (RT_FAILURE(rc))
     243    {
     244        VBClLogError("session %u: unable to stop popup window process: rc=%Rrc\n",
     245                     pSession->uSessionId, rc);
     246    }
     247
     248    if (RT_SUCCESS(rc))
     249    {
     250        pSession->uSessionId = 0;
     251
     252        pSession->oClipboardIpc->reset();
     253        delete pSession->oClipboardIpc;
     254    }
     255
     256    return rc;
     257}
     258
     259/**
     260 * Session callback: Handle sessions started by host events.
     261 *
     262 * @returns IPRT status code.
     263 * @param   enmSessionType      Session type, must be verified as
     264 *                              a consistency check.
     265 * @param   pvUser              User data (IPC connection handle
     266 *                              to vboxwl tool).
     267 */
     268static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_worker_join_cb(
     269    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     270{
     271    PRTLOCALIPCSESSION phIpcSession = (RTLOCALIPCSESSION *)pvUser;
     272    AssertPtrReturn(phIpcSession, VERR_INVALID_PARAMETER);
     273
     274    const vbcl::ipc::flow_t     *pFlow;
     275
     276    int rc = VINF_SUCCESS;
     277
     278    VBCL_LOG_CALLBACK;
     279
     280    /* Make sure valid session is in progress. */
     281    AssertReturn(g_GtkCtx.Session.uSessionId > 0, VERR_INVALID_PARAMETER);
     282
     283    /* Select corresponding IPC flow depending on session type. */
     284    if      (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
     285        pFlow = vbcl::ipc::clipboard::HGCopyFlow;
     286    else if (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_ANNOUNCE_TO_HOST)
     287        pFlow = vbcl::ipc::clipboard::GHAnnounceAndCopyFlow;
     288    else if (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST)
     289        pFlow = vbcl::ipc::clipboard::GHCopyFlow;
     290    else
     291        rc = VERR_INVALID_PARAMETER;
     292
     293    /* Proceed with selected flow. */
     294    if (RT_SUCCESS(rc))
     295        rc = g_GtkCtx.Session.oClipboardIpc->flow(pFlow, *phIpcSession);
     296
     297    return rc;
     298}
     299
     300/**
     301 * IPC server thread worker.
     302 *
     303 * @returns IPRT status code.
     304 * @param   hThreadSelf     IPRT thread handle.
     305 * @param   pvUser          Helper context data.
     306 */
     307static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_worker(RTTHREAD hThreadSelf, void *pvUser)
     308{
     309    int rc;
     310
     311    vbox_wl_gtk_ctx_t *pCtx = (vbox_wl_gtk_ctx_t *)pvUser;
     312    char szIpcServerName[128];
     313
     314    RTThreadUserSignal(hThreadSelf);
     315
     316    VBClLogVerbose(1, "starting IPC\n");
     317
     318    rc = vbcl_wayland_hlp_gtk_ipc_srv_name(szIpcServerName, sizeof(szIpcServerName));
     319
     320    if (RT_SUCCESS(rc))
     321        rc = RTLocalIpcServerCreate(&pCtx->hIpcServer, szIpcServerName, 0);
     322
     323    if (RT_SUCCESS(rc))
     324        rc = RTLocalIpcServerSetAccessMode(pCtx->hIpcServer, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR);
     325
     326    if (RT_SUCCESS(rc))
     327    {
     328        VBClLogVerbose(1, "started IPC server '%s'\n", szIpcServerName);
     329
     330        vbcl_wayland_session_init(&pCtx->Session.Base);
     331
     332        while (!ASMAtomicReadBool(&pCtx->fShutdown))
     333        {
     334            RTLOCALIPCSESSION hClientSession;
     335
     336            rc = RTLocalIpcServerListen(pCtx->hIpcServer, &hClientSession);
     337            if (RT_SUCCESS(rc))
     338            {
     339                RTUID uUid;
     340
     341                /* Authenticate remote user. Only allow connection from
     342                 * process who belongs to the same UID. */
     343                rc = RTLocalIpcSessionQueryUserId(hClientSession, &uUid);
     344                if (RT_SUCCESS(rc))
     345                {
     346                    RTUID uLocalUID = geteuid();
     347                    if (   uLocalUID != 0
     348                        && uLocalUID == uUid)
     349                    {
     350                        VBClLogVerbose(1, "new IPC connection\n");
     351
     352                        rc = vbcl_wayland_session_join(&pCtx->Session.Base,
     353                                                       &vbcl_wayland_hlp_gtk_worker_join_cb,
     354                                                       &hClientSession);
     355
     356                        VBClLogVerbose(1, "IPC flow completed, rc=%Rrc\n", rc);
     357
     358                        rc = vbcl_wayland_session_end(&pCtx->Session.Base,
     359                                                      &vbcl_wayland_hlp_gtk_session_end_cb,
     360                                                      &pCtx->Session);
     361                        VBClLogVerbose(1, "IPC session ended, rc=%Rrc\n", rc);
     362
     363                    }
     364                    else
     365                        VBClLogError("incoming IPC connection rejected: UID mismatch: %d/%d\n",
     366                                     uLocalUID, uUid);
     367                }
     368                else
     369                    VBClLogError("failed to get remote IPC UID, rc=%Rrc\n", rc);
     370
     371                RTLocalIpcSessionClose(hClientSession);
     372            }
     373            else if (rc != VERR_CANCELLED)
     374                VBClLogVerbose(1, "IPC connection has failed, rc=%Rrc\n", rc);
     375        }
     376
     377        rc = RTLocalIpcServerDestroy(pCtx->hIpcServer);
     378    }
     379    else
     380        VBClLogError("failed to start IPC, rc=%Rrc\n", rc);
     381
     382    VBClLogVerbose(1, "IPC stopped\n");
     383
     384    return rc;
     385}
    29386
    30387/**
     
    33390static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_probe(void)
    34391{
    35     return VBOX_WAYLAND_HELPER_CAP_NONE;
     392    int fCaps = VBOX_WAYLAND_HELPER_CAP_NONE;
     393
     394    if (VBGHDisplayServerTypeIsGtkAvailable())
     395        fCaps |= VBOX_WAYLAND_HELPER_CAP_CLIPBOARD;
     396
     397    return fCaps;
    36398}
    37399
     
    41403RTDECL(int) vbcl_wayland_hlp_gtk_init(void)
    42404{
    43     return VERR_NOT_SUPPORTED;
     405    VBCL_LOG_CALLBACK;
     406
     407    RT_ZERO(g_GtkCtx);
     408
     409    return VBClClipboardThreadStart(&g_GtkCtx.Thread, vbcl_wayland_hlp_gtk_worker, "wl-gtk-ipc", &g_GtkCtx);
    44410}
    45411
     
    49415RTDECL(int) vbcl_wayland_hlp_gtk_term(void)
    50416{
    51     return VERR_NOT_SUPPORTED;
     417    int rc;
     418    int rcThread = 0;
     419
     420    /* Set termination flag. */
     421    g_GtkCtx.fShutdown = true;
     422
     423    /* Cancel IPC loop. */
     424    rc = RTLocalIpcServerCancel(g_GtkCtx.hIpcServer);
     425    if (RT_FAILURE(rc))
     426        VBClLogError("unable to notify IPC server about shutdown, rc=%Rrc\n", rc);
     427
     428    if (RT_SUCCESS(rc))
     429    {
     430        /* Wait for Gtk event loop thread to shutdown. */
     431        rc = RTThreadWait(g_GtkCtx.Thread, RT_MS_30SEC, &rcThread);
     432        VBClLogInfo("gtk event thread exited with status, rc=%Rrc\n", rcThread);
     433    }
     434    else
     435        VBClLogError("unable to stop gtk thread, rc=%Rrc\n", rc);
     436
     437    return rc;
     438}
     439
     440/**
     441 * @interface_method_impl{VBCLWAYLANDHELPER,pfnSetClipboardCtx}
     442 */
     443static DECLCALLBACK(void) vbcl_wayland_hlp_gtk_set_clipboard_ctx(PVBGLR3SHCLCMDCTX pCtx)
     444{
     445    g_GtkCtx.pClipboardCtx = pCtx;
     446}
     447
     448/**
     449 * Session callback: Announce clipboard to the host.
     450 *
     451 * This callback (1) waits for the guest to report its clipboard content
     452 * via IPC connection from vboxwl tool, and (2) reports these formats
     453 * to the host.
     454 *
     455 * @returns IPRT status code.
     456 * @param   enmSessionType      Session type, must be verified as
     457 *                              a consistency check.
     458 * @param   pvUser              User data (unused).
     459 */
     460static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_popup_join_cb(
     461    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     462{
     463    int rc = (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_ANNOUNCE_TO_HOST)
     464             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     465
     466    RT_NOREF(pvUser);
     467
     468    VBCL_LOG_CALLBACK;
     469
     470    if (RT_SUCCESS(rc))
     471    {
     472        SHCLFORMATS fFmts = g_GtkCtx.Session.oClipboardIpc->m_fFmts.wait();
     473        if (fFmts !=  g_GtkCtx.Session.oClipboardIpc->m_fFmts.defaults())
     474            rc = VbglR3ClipboardReportFormats(g_GtkCtx.pClipboardCtx->idClient, fFmts);
     475        else
     476            rc = VERR_TIMEOUT;
     477    }
     478
     479    return rc;
     480}
     481
     482/**
     483 * @interface_method_impl{VBCLWAYLANDHELPER,pfnPopup}
     484 */
     485static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_popup(void)
     486{
     487    int rc;
     488
     489    VBCL_LOG_CALLBACK;
     490
     491    rc = vbcl_wayland_session_start(&g_GtkCtx.Session.Base,
     492                                    VBCL_WL_CLIPBOARD_SESSION_TYPE_ANNOUNCE_TO_HOST,
     493                                    &vbcl_wayland_hlp_gtk_session_start_generic_cb,
     494                                    &g_GtkCtx.Session);
     495    if (RT_SUCCESS(rc))
     496    {
     497        rc = vbcl_wayland_session_join(&g_GtkCtx.Session.Base,
     498                                       &vbcl_wayland_hlp_gtk_popup_join_cb,
     499                                       NULL);
     500    }
     501
     502    return rc;
     503}
     504
     505/**
     506 * Session callback: Copy clipboard from the host.
     507 *
     508 * This callback (1) sets host clipboard formats list to the session,
     509 * (2) waits for guest to request clipboard in specific format, (3) read
     510 * host clipboard in this format, and (4) sets clipboard data to the session,
     511 * so Gtk event thread can inject it into the guest.
     512 *
     513 * This callback should not return until clipboard data is read from
     514 * the host or error occurred. It must block host events loop until
     515 * current host event is fully processed.
     516 *
     517 * @returns IPRT status code.
     518 * @param   enmSessionType      Session type, must be verified as
     519 *                              a consistency check.
     520 * @param   pvUser              User data (host clipboard formats).
     521 */
     522static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_hg_clip_report_join_cb(
     523    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     524{
     525    SHCLFORMATS *pfFmts = (SHCLFORMATS *)pvUser;
     526    AssertPtrReturn(pfFmts, VERR_INVALID_PARAMETER);
     527
     528    SHCLFORMAT uFmt;
     529
     530    int rc =   (enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST)
     531             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     532
     533    VBCL_LOG_CALLBACK;
     534
     535    if (RT_SUCCESS(rc))
     536    {
     537        g_GtkCtx.Session.oClipboardIpc->m_fFmts.set(*pfFmts);
     538
     539        uFmt = g_GtkCtx.Session.oClipboardIpc->m_uFmt.wait();
     540        if (uFmt != g_GtkCtx.Session.oClipboardIpc->m_uFmt.defaults())
     541        {
     542            void *pvData;
     543            uint32_t cbData;
     544
     545            rc = VBClClipboardReadHostClipboard(g_GtkCtx.pClipboardCtx, uFmt, &pvData, &cbData);
     546            if (RT_SUCCESS(rc))
     547            {
     548                g_GtkCtx.Session.oClipboardIpc->m_pvClipboardBuf.set((uint64_t)pvData);
     549                g_GtkCtx.Session.oClipboardIpc->m_cbClipboardBuf.set((uint64_t)cbData);
     550            }
     551        }
     552        else
     553            rc = VERR_TIMEOUT;
     554    }
     555
     556    return rc;
     557}
     558
     559/**
     560 * @interface_method_impl{VBCLWAYLANDHELPER,pfnHGClipReport}
     561 */
     562static int vbcl_wayland_hlp_gtk_hg_clip_report(SHCLFORMATS fFormats)
     563{
     564    int rc = VERR_NO_DATA;
     565
     566    VBCL_LOG_CALLBACK;
     567
     568    if (fFormats != VBOX_SHCL_FMT_NONE)
     569    {
     570        rc = vbcl_wayland_session_start(&g_GtkCtx.Session.Base,
     571                                        VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST,
     572                                        &vbcl_wayland_hlp_gtk_session_start_generic_cb,
     573                                        &g_GtkCtx.Session);
     574        if (RT_SUCCESS(rc))
     575        {
     576            rc = vbcl_wayland_session_join(&g_GtkCtx.Session.Base,
     577                                           &vbcl_wayland_hlp_gtk_hg_clip_report_join_cb,
     578                                           &fFormats);
     579        }
     580    }
     581
     582    return rc;
     583}
     584
     585/**
     586 * Session callback: Copy clipboard to the host.
     587 *
     588 * This callback sets clipboard format to the session as requested
     589 * by host, waits for guest clipboard data in requested format and
     590 * sends data to the host.
     591 *
     592 * This callback should not return until clipboard data is sent to
     593 * the host or error occurred. It must block host events loop until
     594 * current host event is fully processed.
     595 *
     596 * @returns IPRT status code.
     597 * @param   enmSessionType      Session type, must be verified as
     598 *                              a consistency check.
     599 * @param   pvUser              User data (requested format).
     600 */
     601static DECLCALLBACK(int) vbcl_wayland_hlp_gtk_gh_clip_read_join_cb(
     602    vbcl_wl_session_type_t enmSessionType, void *pvUser)
     603{
     604    SHCLFORMAT *puFmt = (SHCLFORMAT *)pvUser;
     605    AssertPtrReturn(puFmt, VERR_INVALID_PARAMETER);
     606
     607    int rc = (   enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST
     608              || enmSessionType == VBCL_WL_CLIPBOARD_SESSION_TYPE_ANNOUNCE_TO_HOST)
     609             ? VINF_SUCCESS : VERR_WRONG_ORDER;
     610
     611    VBCL_LOG_CALLBACK;
     612
     613    if (RT_SUCCESS(rc))
     614    {
     615        void *pvData;
     616        uint32_t cbData;
     617
     618        /* Store requested clipboard format to the session. */
     619        g_GtkCtx.Session.oClipboardIpc->m_uFmt.set(*puFmt);
     620
     621        /* Wait for data in requested format. */
     622        pvData = (void *)g_GtkCtx.Session.oClipboardIpc->m_pvClipboardBuf.wait();
     623        cbData = g_GtkCtx.Session.oClipboardIpc->m_cbClipboardBuf.wait();
     624        if (   cbData != g_GtkCtx.Session.oClipboardIpc->m_cbClipboardBuf.defaults()
     625            && pvData != (void *)g_GtkCtx.Session.oClipboardIpc->m_pvClipboardBuf.defaults())
     626        {
     627            /* Send clipboard data to the host. */
     628            rc = VbglR3ClipboardWriteDataEx(g_GtkCtx.pClipboardCtx, *puFmt, pvData, cbData);
     629        }
     630        else
     631            rc = VERR_TIMEOUT;
     632    }
     633
     634    return rc;
     635}
     636
     637/**
     638 * @interface_method_impl{VBCLWAYLANDHELPER,pfnGHClipRead}
     639 */
     640static int vbcl_wayland_hlp_gtk_gh_clip_read(SHCLFORMAT uFmt)
     641{
     642    int rc = VINF_SUCCESS;
     643
     644    VBCL_LOG_CALLBACK;
     645
     646    if (uFmt != VBOX_SHCL_FMT_NONE)
     647    {
     648        VBClLogVerbose(2, "host wants fmt 0x%x\n", uFmt);
     649
     650        /* This callback can be called in two cases:
     651         *
     652         * 1. Guest has just announced a list of its clipboard
     653         *    formats to the host, and vboxwl tool is still running,
     654         *    IPC session is still active as well. In this case the
     655         *    host can immediately ask for content in specified format.
     656         *
     657         * 2. Guest has already announced list of its clipboard
     658         *    formats to the host some time ago, vboxwl tool is no
     659         *    longer running and IPC session is not active. In this
     660         *    case some app on the host side might want to read
     661         *    clipboard in specific format.
     662         *
     663         * In case (2), we need to start new IPC session and restart
     664         * vboxwl tool again
     665         */
     666        if (!vbcl_wayland_session_is_started(&g_GtkCtx.Session.Base))
     667        {
     668            rc = vbcl_wayland_session_start(&g_GtkCtx.Session.Base,
     669                                            VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST,
     670                                            &vbcl_wayland_hlp_gtk_session_start_generic_cb,
     671                                            NULL);
     672        }
     673
     674        if (RT_SUCCESS(rc))
     675        {
     676            rc = vbcl_wayland_session_join(&g_GtkCtx.Session.Base,
     677                                           &vbcl_wayland_hlp_gtk_gh_clip_read_join_cb,
     678                                           &uFmt);
     679        }
     680    }
     681
     682    VBClLogVerbose(2, "vbcl_wayland_hlp_gtk_gh_clip_read ended rc=%Rrc\n", rc);
     683
     684    return rc;
    52685}
    53686
     
    55688const VBCLWAYLANDHELPER g_WaylandHelperGtk =
    56689{
    57     "wayland-gtk",                  /* .pszName */
    58     vbcl_wayland_hlp_gtk_probe,     /* .pfnProbe */
    59     vbcl_wayland_hlp_gtk_init,      /* .pfnInit */
    60     vbcl_wayland_hlp_gtk_term,      /* .pfnTerm */
     690    "wayland-gtk",                              /* .pszName */
     691    vbcl_wayland_hlp_gtk_probe,                 /* .pfnProbe */
     692    vbcl_wayland_hlp_gtk_init,                  /* .pfnInit */
     693    vbcl_wayland_hlp_gtk_term,                  /* .pfnTerm */
     694    vbcl_wayland_hlp_gtk_set_clipboard_ctx,     /* .pfnSetClipboardCtx */
     695    vbcl_wayland_hlp_gtk_popup,                 /* .pfnPopup */
     696    vbcl_wayland_hlp_gtk_hg_clip_report,        /* .pfnHGClipReport */
     697    vbcl_wayland_hlp_gtk_gh_clip_read,          /* .pfnGHClipRead */
    61698};
  • trunk/src/VBox/Additions/x11/VBoxClient/wayland-helper.h

    r100246 r101878  
    11/* $Id$ */
    22/** @file
    3  * Guest Additions - Definitions for Wayland Desktop Environments helpers.
     3 * Guest Additions - Definitions for Wayland helpers.
    44 */
    55
     
    3232#endif
    3333
    34 #include <iprt/cdefs.h>
    35 #include <iprt/err.h>
    36 
     34#include <iprt/asm.h>
     35#include <iprt/time.h>
     36
     37#include <VBox/VBoxGuestLib.h>
     38#include <VBox/GuestHost/clipboard-helper.h>
     39
     40#include "clipboard.h"
    3741
    3842/** Helper capabilities list. */
     43
     44/** Indicates that helper does not support any functionality (initializer). */
     45#define VBOX_WAYLAND_HELPER_CAP_NONE            (0)
     46/** Indicates that helper supported shared clipboard functionality. */
     47#define VBOX_WAYLAND_HELPER_CAP_CLIPBOARD       RT_BIT(1)
     48/** Indicates that helper supported drag-and-drop functionality. */
     49#define VBOX_WAYLAND_HELPER_CAP_DND             RT_BIT(2)
     50
     51/** Default time interval to wait for value to arrive over IPC. */
     52#define VBCL_WAYLAND_VALUE_WAIT_TIMEOUT_MS      (1000)
     53/** Default time interval to wait for clipboard content to arrive over IPC. */
     54#define VBCL_WAYLAND_DATA_WAIT_TIMEOUT_MS       (2000)
     55/** Generic relax interval while polling value changes. */
     56#define VBCL_WAYLAND_RELAX_INTERVAL_MS          (50)
     57/** Maximum number of participants who can join session. */
     58#define VBCL_WAYLAND_SESSION_USERS_MAX          (10)
     59/** Value which determines if session structure was initialized. */
     60#define VBCL_WAYLAND_SESSION_MAGIC              (0xDEADBEEF)
     61
     62/** Session states. */
    3963typedef enum
    4064{
    41     /** Indicates that helper does not support any functionality (initializer). */
    42     VBOX_WAYLAND_HELPER_CAP_NONE = 0,
    43     /** Indicates that helper supported shared clipboard functionality. */
    44     VBOX_WAYLAND_HELPER_CAP_CLIPBOARD,
    45     /** Indicates that helper supported drag-and-drop functionality. */
    46     VBOX_WAYLAND_HELPER_CAP_DND
    47 } vbox_wayland_helper_cap_t;
     65    /** Session is not active. */
     66    VBCL_WL_SESSION_STATE_IDLE,
     67    /** Session is being initialized. */
     68    VBCL_WL_SESSION_STATE_STARTING,
     69    /** Session has started and now can be joined.  */
     70    VBCL_WL_SESSION_STATE_STARTED,
     71    /** Session is terminating. */
     72    VBCL_WL_SESSION_STATE_TERMINATING
     73} vbcl_wl_session_state_t;
     74
     75/** Session type.
     76 *
     77 * Type determines the purpose of session. It is
     78 * also serves a sanity check purpose when different
     79 * participants join session with certain intention.
     80 * Session type can only be set when session is
     81 * in STARTING state and reset when session is TERMINATING.
     82 */
     83typedef enum
     84{
     85    /** Initializer. */
     86    VBCL_WL_SESSION_TYPE_INVALID,
     87    /** Copy clipboard data to the guest. */
     88    VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_GUEST,
     89    /** Announce clipboard formats to the host. */
     90    VBCL_WL_CLIPBOARD_SESSION_TYPE_ANNOUNCE_TO_HOST,
     91    /** Copy clipboard data to the host. */
     92    VBCL_WL_CLIPBOARD_SESSION_TYPE_COPY_TO_HOST,
     93} vbcl_wl_session_type_t;
     94
     95/** Session private data. */
     96typedef struct
     97{
     98    /** Magic number which indicates if session
     99     *  was previously initialized. */
     100    volatile uint32_t u32Magic;
     101
     102    /** Session state, synchronization element. */
     103    volatile vbcl_wl_session_state_t enmState;
     104
     105    /** Session type. */
     106    vbcl_wl_session_type_t enmType;
     107
     108    /** Session description, used for logging purpose
     109     *  to distinguish between operations flow. */
     110    const char *pcszDesc;
     111
     112    /** Current number of session users. When session
     113     *  switches into TERMINATING state, it will wait
     114     *  number of participants to drop to 1 before releasing
     115     * session resources and resetting internal data to
     116     * default values. */
     117    volatile uint32_t cUsers;
     118} vbcl_wl_session_t;
     119
     120/** Session state change callback.
     121 *
     122 * Data which belongs to a session must be accessed from
     123 * session callback only. It ensures session data integrity
     124 * and prevents access to data when session is not yet
     125 * initialized or already terminated.
     126 *
     127 * @returns IPRT status code.
     128 * @param   enmSessionType      Session type, provided for consistency
     129 *                              check to make sure given callback is
     130 *                              intended to be triggered in context of
     131 *                              given session type.
     132 * @param   pvUser              Optional user data.
     133 */
     134typedef DECLCALLBACKTYPE(int, FNVBCLWLSESSIONCB, (vbcl_wl_session_type_t enmSessionType, void *pvUser));
     135/** Pointer to FNVBCLWLSESSIONCB. */
     136typedef FNVBCLWLSESSIONCB *PFNVBCLWLSESSIONCB;
    48137
    49138/**
     
    61150     * which is compatible with the helper.
    62151     *
    63      * @returns Helpercapabilities bitmask as described by vbox_wayland_helper_cap_t.
     152     * @returns Helpercapabilities bitmask as described by VBOX_WAYLAND_HELPER_CAP_XXX.
    64153     */
    65154    DECLCALLBACKMEMBER(int, pfnProbe, (void));
     
    79168    DECLCALLBACKMEMBER(int, pfnTerm, (void));
    80169
     170    /**
     171     * Callback to set host clipboard connection handle.
     172     *
     173     * @param   pCtx    Host service connection context.
     174     */
     175    DECLCALLBACKMEMBER(void, pfnSetClipboardCtx, (PVBGLR3SHCLCMDCTX pCtx));
     176
     177    /**
     178     * Callback to force guest to announce its clipboard content.
     179     *
     180     * @returns IPRT status code.
     181     */
     182    DECLCALLBACKMEMBER(int, pfnPopup, (void));
     183
     184    PFNHOSTCLIPREPORTFMTS pfnHGClipReport;
     185    PFNHOSTCLIPREAD pfnGHClipRead;
     186
    81187} VBCLWAYLANDHELPER;
     188
     189namespace vbcl
     190{
     191    /**
     192     * This is abstract one-shot data type which can be set by writer and waited
     193     * by reader in a thread-safe way.
     194     *
     195     * Method wait() will wait within predefined interval of time until
     196     * value of this type will be changed to anything different from default
     197     * value which is defined during initialization. Reader must compare
     198     * returned value with what defaults() method returns in order to make
     199     * sure that value was actually set by writer.
     200     *
     201     * Method reset() will atomically reset current value to defaults and
     202     * return previous value. This is useful when writer has previously
     203     * dynamically allocated chunk of memory and reader needs to deallocete
     204     * it in the end.
     205     */
     206    template <class T> class Waitable
     207    {
     208        public:
     209
     210            Waitable()
     211            {};
     212
     213            /**
     214             * Initialize data type.
     215             *
     216             * @param   default_value   Default value, used while
     217             *                          waiting for value change.
     218             * @param   timeoutMs       Time interval to wait for
     219             *                          value change before returning.
     220             */
     221            void init(T default_value, uint64_t timeoutMs)
     222            {
     223                m_Value = default_value;
     224                m_Default = default_value;
     225                m_TimeoutMs = timeoutMs;
     226            }
     227
     228            /**
     229             * Atomically set value.
     230             *
     231             * @param   value   Value to set.
     232             */
     233            void set(T value)
     234            {
     235                ASMAtomicWriteU64(&m_Value, value);
     236            }
     237
     238            /**
     239             * Atomically reset value to defaults and return previous value.
     240             *
     241             * @returns Value which was assigned before reset.
     242             */
     243            uint64_t reset()
     244            {
     245                return ASMAtomicXchgU64(&m_Value, m_Default);
     246            }
     247
     248            /**
     249             * Wait until value will be changed from defaults and return it.
     250             *
     251             * @returns Current value.
     252             */
     253            T wait()
     254            {
     255                uint64_t tsStart = RTTimeMilliTS();
     256
     257                while(   (RTTimeMilliTS() - tsStart) < m_TimeoutMs
     258                      && (ASMAtomicReadU64(&m_Value)) == m_Default)
     259                {
     260                    RTThreadSleep(VBCL_WAYLAND_RELAX_INTERVAL_MS);
     261                }
     262
     263                return m_Value;
     264            }
     265
     266            /**
     267             * Get default value which was set during initialization.
     268             *
     269             * @returns Default value.
     270             */
     271            T defaults()
     272            {
     273                return m_Default;
     274            }
     275
     276        protected:
     277
     278            /** Value itself. */
     279            uint64_t m_Value;
     280            /** Default value. */
     281            uint64_t m_Default;
     282            /** Value change waiting timeout. */
     283            uint64_t m_TimeoutMs;
     284    };
     285}
     286
     287/**
     288 * Initialize session.
     289 *
     290 * This function should be called only once, during initialization step.
     291 *
     292 * @param   pSession    A pointer to session data.
     293 */
     294RTDECL(void) vbcl_wayland_session_init(vbcl_wl_session_t *pSession);
     295
     296/**
     297 * Start new session.
     298 *
     299 * Attempt to change session state from IDLE to STARTED and
     300 * execute initialization callback in between.  If current
     301 * session state is different from IDLE, state transition will
     302 * not be possible and error will be returned.
     303 *
     304 * @returns IPRT status code.
     305 * @param   pSession    Session object.
     306 * @param   enmType     Session type.
     307 * @param   pfnStart    Initialization callback.
     308 * @param   pvUser      User data to pass to initialization callback.
     309 */
     310RTDECL(int) vbcl_wayland_session_start(vbcl_wl_session_t *pSession,
     311                                       vbcl_wl_session_type_t enmType,
     312                                       PFNVBCLWLSESSIONCB pfnStart,
     313                                       void *pvUser);
     314
     315/**
     316 * Join session.
     317 *
     318 * Attempt to grab a reference to a session, execute provided
     319 * callback while holding a reference and release reference.
     320 * This function will fail if current session state is different
     321 * from STARTED.
     322 *
     323 * @returns IPRT status code.
     324 * @param   pSession    Session object.
     325 * @param   pfnJoin     A callback to run while holding session reference.
     326 * @param   pvUser      User data to pass to callback.
     327 * @param   pcszCallee  Text tag which corresponds to calling function (only
     328 *                      for logging)
     329 */
     330RTDECL(int) vbcl_wayland_session_join_ex(vbcl_wl_session_t *pSession,
     331                                         PFNVBCLWLSESSIONCB pfnJoin, void *pvUser,
     332                                         const char *pcszCallee);
     333
     334/**
     335 * Join session (wrapper for vbcl_wayland_session_join_ex).
     336 */
     337#define vbcl_wayland_session_join(pSession, pfnJoin, pvUser) \
     338    vbcl_wayland_session_join_ex(pSession, pfnJoin, pvUser, __func__)
     339
     340/**
     341 * End session.
     342 *
     343 * Attempt to wait until session is no longer in use, execute
     344 * terminating callback and reset session to IDLE state.
     345 *
     346 * @returns IPRT status code.
     347 * @param   pSession    Session object.
     348 * @param   pfnEnd      Termination callback.
     349 * @param   pvUser      User data to pass to termination callback.
     350 */
     351RTDECL(int) vbcl_wayland_session_end(vbcl_wl_session_t *pSession,
     352                                     PFNVBCLWLSESSIONCB pfnEnd, void *pvUser);
     353
     354/**
     355 * Check if session was started.
     356 *
     357 * @returns True if session is started, False otherwise.
     358 * @param   pSession    Session object.
     359 */
     360RTDECL(bool) vbcl_wayland_session_is_started(vbcl_wl_session_t *pSession);
    82361
    83362/** Wayland helper which uses GTK library. */
  • trunk/src/VBox/Additions/x11/VBoxClient/wayland.cpp

    r100248 r101878  
    2626 */
    2727
     28#include <iprt/asm.h>
     29#include <iprt/thread.h>
     30
     31#include <VBox/HostServices/GuestPropertySvc.h>
     32
    2833#include "VBoxClient.h"
     34#include "clipboard.h"
    2935#include "wayland-helper.h"
     36
     37/** Polling interval for input focus monitoring task. */
     38#define VBCL_WAYLAND_WAIT_HOST_FOCUS_TIMEOUT_MS     (250)
     39/** Relax interval for input focus monitoring task. */
     40#define VBCL_WAYLAND_WAIT_HOST_FOCUS_RELAX_MS       (100)
    3041
    3142/** List of available Wayland Desktop Environment helpers. Sorted in order of preference. */
    3243static const VBCLWAYLANDHELPER *g_apWaylandHelpers[] =
    3344{
     45    &g_WaylandHelperDcp,    /* Device Control Protocol helper. */
    3446    &g_WaylandHelperGtk,    /* GTK helper. */
    35     &g_WaylandHelperDcp,    /* Device Control Protocol helper. */
    3647    NULL,                   /* Terminate list. */
    3748};
    3849
     50/** Global flag to tell service to go shutdown when needed. */
     51static bool volatile g_fShutdown = false;
     52
    3953/** Selected helpers for Clipboard and Drag-and-Drop. */
    40 static const VBCLWAYLANDHELPER *g_pWaylandHelperHelperClipboard = NULL;
    41 static const VBCLWAYLANDHELPER *g_pWaylandHelperHelperDnd       = NULL;
     54static const VBCLWAYLANDHELPER *g_pWaylandHelperClipboard = NULL;
     55static const VBCLWAYLANDHELPER *g_pWaylandHelperDnd       = NULL;
     56
     57/** Corresponding threads for host events handling. */
     58static RTTHREAD g_ClipboardThread;
     59static RTTHREAD g_DndThread;
     60static RTTHREAD g_HostInputFocusThread;
     61
     62/**
     63 * Worker for Shared Clipboard events from host.
     64 *
     65 * @returns IPRT status code.
     66 * @param   hThreadSelf     IPRT thread handle.
     67 * @param   pvUser          User data (unused).
     68 */
     69static DECLCALLBACK(int) vbclWaylandClipboardWorker(RTTHREAD hThreadSelf, void *pvUser)
     70{
     71    SHCLCONTEXT ctx;
     72    int rc;
     73
     74    RT_NOREF(pvUser);
     75
     76    RT_ZERO(ctx);
     77
     78    /* Connect to the host service. */
     79    rc = VbglR3ClipboardConnectEx(&ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
     80    /* Notify parent thread. */
     81    RTThreadUserSignal(hThreadSelf);
     82
     83    if (RT_SUCCESS(rc))
     84    {
     85        /* Provide helper with host clipboard service connection handle. */
     86        g_pWaylandHelperClipboard->pfnSetClipboardCtx(&ctx.CmdCtx);
     87
     88        /* Process host events. */
     89        while (!ASMAtomicReadBool(&g_fShutdown))
     90        {
     91            rc = VBClClipboardReadHostEvent(&ctx, g_pWaylandHelperClipboard->pfnHGClipReport,
     92                                            g_pWaylandHelperClipboard->pfnGHClipRead);
     93            if (RT_FAILURE(rc))
     94            {
     95                VBClLogInfo("cannot process host clipboard event, rc=%Rrc\n", rc);
     96                RTThreadSleep(RT_MS_1SEC / 2);
     97            }
     98        }
     99
     100        VbglR3ClipboardDisconnectEx(&ctx.CmdCtx);
     101    }
     102
     103    VBClLogVerbose(2, "clipboard thread, rc=%Rrc\n", rc);
     104
     105    return rc;
     106}
     107
     108/**
     109 * Worker for Drag-and-Drop events from host.
     110 *
     111 * @returns IPRT status code.
     112 * @param   hThreadSelf     IPRT thread handle.
     113 * @param   pvUser          User data (unused).
     114 */
     115static DECLCALLBACK(int) vbclWaylandDndWorker(RTTHREAD hThreadSelf, void *pvUser)
     116{
     117    RT_NOREF(pvUser);
     118
     119    RTThreadUserSignal(hThreadSelf);
     120    return VINF_SUCCESS;
     121}
     122
     123/**
     124 * Worker for VM window focus change polling thread.
     125 *
     126 * Some Wayland helpers need to be notified about VM
     127 * window focus change events. This is needed in order to
     128 * ask about, for example, if guest clipboard content was
     129 * changed since last user interaction. Such guest are not
     130 * able to notify host about clipboard content change and
     131 * needed to be asked implicitly.
     132 *
     133 * @returns IPRT status code.
     134 * @param   hThreadSelf     IPRT thread handle.
     135 * @param   pvUser          User data (unused).
     136 */
     137static DECLCALLBACK(int) vbclWaylandHostInputFocusWorker(RTTHREAD hThreadSelf, void *pvUser)
     138{
     139    int rc;
     140
     141    RT_NOREF(pvUser);
     142
     143    HGCMCLIENTID idClient;
     144
     145    rc = VbglR3GuestPropConnect(&idClient);
     146
     147    RTThreadUserSignal(hThreadSelf);
     148
     149    if (RT_SUCCESS(rc))
     150    {
     151        while (!ASMAtomicReadBool(&g_fShutdown))
     152        {
     153            static char achBuf[GUEST_PROP_MAX_NAME_LEN];
     154            char *pszName = NULL;
     155            char *pszValue = NULL;
     156            char *pszFlags = NULL;
     157            bool fWasDeleted = false;
     158            uint64_t u64Timestamp = 0;
     159
     160            rc = VbglR3GuestPropWait(idClient, VBOX_GUI_FOCUS_CHANGE_GUEST_PROP_NAME, achBuf, sizeof(achBuf), u64Timestamp,
     161                                     VBCL_WAYLAND_WAIT_HOST_FOCUS_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp,
     162                                     &pszFlags, NULL, &fWasDeleted);
     163            if (RT_SUCCESS(rc))
     164            {
     165                uint32_t fFlags = 0;
     166
     167                VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n",
     168                               pszName, pszValue, pszFlags, fWasDeleted);
     169
     170                if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags)))
     171                {
     172                    if (RTStrNCmp(pszName, VBOX_GUI_FOCUS_CHANGE_GUEST_PROP_NAME, GUEST_PROP_MAX_NAME_LEN) == 0)
     173                    {
     174                        if (fFlags & GUEST_PROP_F_RDONLYGUEST)
     175                        {
     176                            if (RT_VALID_PTR(g_pWaylandHelperClipboard))
     177                            {
     178                                if (RTStrNCmp(pszValue, "0", GUEST_PROP_MAX_NAME_LEN) == 0)
     179                                {
     180                                    rc = g_pWaylandHelperClipboard->pfnPopup();
     181                                    VBClLogVerbose(1, "trigger popup, rc=%Rrc\n", rc);
     182                                }
     183                            }
     184                            else
     185                                VBClLogVerbose(1, "will not trigger popup\n");
     186                        }
     187                        else
     188                            VBClLogError("property has invalid attributes\n");
     189                    }
     190                    else
     191                        VBClLogVerbose(1, "unknown property name '%s'\n", pszName);
     192
     193                } else
     194                    VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n",
     195                                 pszName, pszValue, pszFlags, fWasDeleted);
     196
     197            } else if (   rc != VERR_TIMEOUT
     198                     && rc != VERR_INTERRUPTED)
     199            {
     200                VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc);
     201                RTThreadSleep(VBCL_WAYLAND_WAIT_HOST_FOCUS_RELAX_MS);
     202            }
     203        }
     204    }
     205
     206    return rc;
     207}
     208
    42209
    43210/**
     
    68235            /* Try Clipboard helper. */
    69236            if (   fCaps & VBOX_WAYLAND_HELPER_CAP_CLIPBOARD
    70                 && !RT_VALID_PTR(g_pWaylandHelperHelperClipboard))
     237                && !RT_VALID_PTR(g_pWaylandHelperClipboard))
    71238            {
    72239                if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->pfnInit))
     
    74241                    rc = g_apWaylandHelpers[idxHelper]->pfnInit();
    75242                    if (RT_SUCCESS(rc))
    76                         g_pWaylandHelperHelperClipboard = g_apWaylandHelpers[idxHelper];
     243                        g_pWaylandHelperClipboard = g_apWaylandHelpers[idxHelper];
    77244                    else
    78                         VBClLogError("Wayland helper '%s' cannot be initialized, skipping");
     245                        VBClLogError("Wayland helper '%s' cannot be initialized, skipping\n",
     246                                     g_apWaylandHelpers[idxHelper]->pszName);
    79247                }
     248                else
     249                    VBClLogVerbose(1, "Wayland helper '%s' has no initializer, skipping\n",
     250                                   g_apWaylandHelpers[idxHelper]->pszName);
    80251            }
    81252
    82253            /* Try DnD helper. */
    83254            if (   fCaps & VBOX_WAYLAND_HELPER_CAP_DND
    84                 && !RT_VALID_PTR(g_pWaylandHelperHelperDnd))
     255                && !RT_VALID_PTR(g_pWaylandHelperDnd))
    85256            {
    86257                if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->pfnInit))
     
    88259                    rc = g_apWaylandHelpers[idxHelper]->pfnInit();
    89260                    if (RT_SUCCESS(rc))
    90                         g_pWaylandHelperHelperDnd = g_apWaylandHelpers[idxHelper];
     261                        g_pWaylandHelperDnd = g_apWaylandHelpers[idxHelper];
    91262                    else
    92                         VBClLogError("Wayland helper '%s' cannot be initialized, skipping");
     263                        VBClLogError("Wayland helper '%s' cannot be initialized, skipping\n",
     264                                     g_apWaylandHelpers[idxHelper]->pszName);
    93265                }
     266                else
     267                    VBClLogVerbose(1, "Wayland helper '%s' has no initializer, skipping\n",
     268                                   g_apWaylandHelpers[idxHelper]->pszName);
    94269            }
    95270        }
    96271
    97272        /* See if we found all the needed helpers. */
    98         if (   RT_VALID_PTR(g_pWaylandHelperHelperClipboard)
    99             && RT_VALID_PTR(g_pWaylandHelperHelperDnd))
     273        if (   RT_VALID_PTR(g_pWaylandHelperClipboard)
     274            && RT_VALID_PTR(g_pWaylandHelperDnd))
    100275            break;
    101276
     
    104279
    105280    /* Check result. */
    106     if (RT_VALID_PTR(g_pWaylandHelperHelperClipboard))
    107         VBClLogInfo("found Wayland Shared Clipboard helper '%s'\n", g_pWaylandHelperHelperClipboard->pszName);
     281    if (RT_VALID_PTR(g_pWaylandHelperClipboard))
     282        VBClLogInfo("found Wayland Shared Clipboard helper '%s'\n", g_pWaylandHelperClipboard->pszName);
    108283    else
    109284        VBClLogError("Wayland Shared Clipboard helper not found, clipboard sharing not possible\n");
    110285
    111286    /* Check result. */
    112     if (RT_VALID_PTR(g_pWaylandHelperHelperDnd))
    113         VBClLogInfo("found Wayland Drag-and-Drop helper '%s'\n", g_pWaylandHelperHelperDnd->pszName);
     287    if (RT_VALID_PTR(g_pWaylandHelperDnd))
     288        VBClLogInfo("found Wayland Drag-and-Drop helper '%s'\n", g_pWaylandHelperDnd->pszName);
    114289    else
    115290        VBClLogError("Wayland Drag-and-Drop helper not found, drag-and-drop not possible\n");
     
    123298static DECLCALLBACK(int) vbclWaylandWorker(bool volatile *pfShutdown)
    124299{
     300    int rc = VINF_SUCCESS;
     301
    125302    RT_NOREF(pfShutdown);
    126     return VERR_NOT_SUPPORTED;
     303
     304    VBClLogVerbose(1, "starting wayland worker thread\n");
     305
     306    /* Start event loop for clipboard events processing from host. */
     307    if (RT_VALID_PTR(g_pWaylandHelperClipboard))
     308    {
     309        rc = VBClClipboardThreadStart(&g_ClipboardThread, vbclWaylandClipboardWorker, "wl-clip", NULL);
     310        VBClLogVerbose(1, "clipboard thread started, rc=%Rrc\n", rc);
     311    }
     312
     313    /* Start event loop for DnD events processing from host. */
     314    if (   RT_SUCCESS(rc)
     315        && RT_VALID_PTR(g_pWaylandHelperDnd))
     316    {
     317        rc = VBClClipboardThreadStart(&g_DndThread, vbclWaylandDndWorker, "wl-dnd", NULL);
     318        VBClLogVerbose(1, "DnD thread started, rc=%Rrc\n", rc);
     319    }
     320
     321    /* Start polling host input focus events. */
     322    if (RT_SUCCESS(rc))
     323    {
     324        rc = VBClClipboardThreadStart(&g_HostInputFocusThread, vbclWaylandHostInputFocusWorker, "wl-focus", NULL);
     325        VBClLogVerbose(1, "host input focus polling thread started, rc=%Rrc\n", rc);
     326    }
     327
     328    /* Notify parent thread that we are successfully started. */
     329    RTThreadUserSignal(RTThreadSelf());
     330
     331    if (RT_SUCCESS(rc))
     332    {
     333        int rcThread = VINF_SUCCESS;
     334
     335        if (RT_VALID_PTR(g_pWaylandHelperClipboard))
     336        {
     337            rc = RTThreadWait(g_ClipboardThread, RT_INDEFINITE_WAIT, &rcThread);
     338            VBClLogVerbose(1, "clipboard thread finished, rc=%Rrc, rcThread=%Rrc\n", rc, rcThread);
     339        }
     340
     341        if (   RT_SUCCESS(rc)
     342            && RT_VALID_PTR(g_pWaylandHelperDnd))
     343        {
     344            rc = RTThreadWait(g_DndThread, RT_INDEFINITE_WAIT, &rcThread);
     345            VBClLogVerbose(1, "DnD thread finished, rc=%Rrc, rcThread=%Rrc\n", rc, rcThread);
     346        }
     347
     348        if (RT_SUCCESS(rc))
     349        {
     350            rc = RTThreadWait(g_HostInputFocusThread, RT_INDEFINITE_WAIT, &rcThread);
     351            VBClLogVerbose(1, "host input focus polling thread finished, rc=%Rrc, rcThread=%Rrc\n", rc, rcThread);
     352        }
     353    }
     354
     355    VBClLogVerbose(1, "wayland worker thread finished, rc=%Rrc\n", rc);
     356
     357    return rc;
    127358}
    128359
     
    132363static DECLCALLBACK(void) vbclWaylandStop(void)
    133364{
     365    VBClLogVerbose(1, "terminating wayland service: clipboard & DnD host event loops\n");
     366
     367    /* This callback can be called twice (not good, needs to be fixed). Already was shut down? */
     368    if (ASMAtomicReadBool(&g_fShutdown))
     369        return;
     370
     371    ASMAtomicWriteBool(&g_fShutdown, true);
     372
     373    if (RT_VALID_PTR(g_pWaylandHelperClipboard))
     374        RTThreadPoke(g_ClipboardThread);
     375
     376    if (RT_VALID_PTR(g_pWaylandHelperDnd))
     377        RTThreadPoke(g_DndThread);
    134378}
    135379
     
    139383static DECLCALLBACK(int) vbclWaylandTerm(void)
    140384{
    141     return VERR_NOT_SUPPORTED;
     385    int rc = VINF_SUCCESS;
     386
     387    VBClLogVerbose(1, "shutting down wayland service: clipboard & DnD helpers\n");
     388
     389    if (   RT_VALID_PTR(g_pWaylandHelperClipboard)
     390        && RT_VALID_PTR(g_pWaylandHelperClipboard->pfnTerm))
     391        rc = g_pWaylandHelperClipboard->pfnTerm();
     392
     393    if (   RT_SUCCESS(rc)
     394        && RT_VALID_PTR(g_pWaylandHelperDnd)
     395        && RT_VALID_PTR(g_pWaylandHelperDnd->pfnTerm))
     396        rc = g_pWaylandHelperDnd->pfnTerm();
     397
     398    return rc;
    142399}
    143400
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette