VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/wayland.cpp@ 101878

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

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

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 13.9 KB
Line 
1/* $Id: wayland.cpp 101878 2023-11-06 15:36:24Z vboxsync $ */
2/** @file
3 * Guest Additions - Wayland Desktop Environment assistant.
4 */
5
6/*
7 * Copyright (C) 2017-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#include <iprt/asm.h>
29#include <iprt/thread.h>
30
31#include <VBox/HostServices/GuestPropertySvc.h>
32
33#include "VBoxClient.h"
34#include "clipboard.h"
35#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)
41
42/** List of available Wayland Desktop Environment helpers. Sorted in order of preference. */
43static const VBCLWAYLANDHELPER *g_apWaylandHelpers[] =
44{
45 &g_WaylandHelperDcp, /* Device Control Protocol helper. */
46 &g_WaylandHelperGtk, /* GTK helper. */
47 NULL, /* Terminate list. */
48};
49
50/** Global flag to tell service to go shutdown when needed. */
51static bool volatile g_fShutdown = false;
52
53/** Selected helpers for Clipboard and Drag-and-Drop. */
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
209
210/**
211 * @interface_method_impl{VBCLSERVICE,pfnInit}
212 */
213static DECLCALLBACK(int) vbclWaylandInit(void)
214{
215 int rc = VERR_NOT_SUPPORTED;
216 int idxHelper = 0;
217
218 /** Custom log prefix to be used for logger instance of this process. */
219 static const char *pszLogPrefix = "VBoxClient Wayland:";
220
221 VBClLogSetLogPrefix(pszLogPrefix);
222
223 /* Go through list of available helpers and try to pick up one. */
224 while (g_apWaylandHelpers[idxHelper])
225 {
226 if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->pfnProbe))
227 {
228 int fCaps = VBOX_WAYLAND_HELPER_CAP_NONE;
229
230 VBClLogInfo("probing Wayland helper '%s'\n",
231 g_apWaylandHelpers[idxHelper]->pszName);
232
233 fCaps = g_apWaylandHelpers[idxHelper]->pfnProbe();
234
235 /* Try Clipboard helper. */
236 if ( fCaps & VBOX_WAYLAND_HELPER_CAP_CLIPBOARD
237 && !RT_VALID_PTR(g_pWaylandHelperClipboard))
238 {
239 if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->pfnInit))
240 {
241 rc = g_apWaylandHelpers[idxHelper]->pfnInit();
242 if (RT_SUCCESS(rc))
243 g_pWaylandHelperClipboard = g_apWaylandHelpers[idxHelper];
244 else
245 VBClLogError("Wayland helper '%s' cannot be initialized, skipping\n",
246 g_apWaylandHelpers[idxHelper]->pszName);
247 }
248 else
249 VBClLogVerbose(1, "Wayland helper '%s' has no initializer, skipping\n",
250 g_apWaylandHelpers[idxHelper]->pszName);
251 }
252
253 /* Try DnD helper. */
254 if ( fCaps & VBOX_WAYLAND_HELPER_CAP_DND
255 && !RT_VALID_PTR(g_pWaylandHelperDnd))
256 {
257 if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->pfnInit))
258 {
259 rc = g_apWaylandHelpers[idxHelper]->pfnInit();
260 if (RT_SUCCESS(rc))
261 g_pWaylandHelperDnd = g_apWaylandHelpers[idxHelper];
262 else
263 VBClLogError("Wayland helper '%s' cannot be initialized, skipping\n",
264 g_apWaylandHelpers[idxHelper]->pszName);
265 }
266 else
267 VBClLogVerbose(1, "Wayland helper '%s' has no initializer, skipping\n",
268 g_apWaylandHelpers[idxHelper]->pszName);
269 }
270 }
271
272 /* See if we found all the needed helpers. */
273 if ( RT_VALID_PTR(g_pWaylandHelperClipboard)
274 && RT_VALID_PTR(g_pWaylandHelperDnd))
275 break;
276
277 idxHelper++;
278 }
279
280 /* Check result. */
281 if (RT_VALID_PTR(g_pWaylandHelperClipboard))
282 VBClLogInfo("found Wayland Shared Clipboard helper '%s'\n", g_pWaylandHelperClipboard->pszName);
283 else
284 VBClLogError("Wayland Shared Clipboard helper not found, clipboard sharing not possible\n");
285
286 /* Check result. */
287 if (RT_VALID_PTR(g_pWaylandHelperDnd))
288 VBClLogInfo("found Wayland Drag-and-Drop helper '%s'\n", g_pWaylandHelperDnd->pszName);
289 else
290 VBClLogError("Wayland Drag-and-Drop helper not found, drag-and-drop not possible\n");
291
292 return rc;
293}
294
295/**
296 * @interface_method_impl{VBCLSERVICE,pfnWorker}
297 */
298static DECLCALLBACK(int) vbclWaylandWorker(bool volatile *pfShutdown)
299{
300 int rc = VINF_SUCCESS;
301
302 RT_NOREF(pfShutdown);
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;
358}
359
360/**
361 * @interface_method_impl{VBCLSERVICE,pfnStop}
362 */
363static DECLCALLBACK(void) vbclWaylandStop(void)
364{
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);
378}
379
380/**
381 * @interface_method_impl{VBCLSERVICE,pfnTerm}
382 */
383static DECLCALLBACK(int) vbclWaylandTerm(void)
384{
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;
399}
400
401VBCLSERVICE g_SvcWayland =
402{
403 "wayland", /* szName */
404 "Wayland assistant", /* pszDescription */
405 ".vboxclient-wayland", /* pszPidFilePathTemplate */
406 NULL, /* pszUsage */
407 NULL, /* pszOptions */
408 NULL, /* pfnOption */
409 vbclWaylandInit, /* pfnInit */
410 vbclWaylandWorker, /* pfnWorker */
411 vbclWaylandStop, /* pfnStop */
412 vbclWaylandTerm, /* pfnTerm */
413};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use