VirtualBox

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

Last change on this file was 106786, checked in by vboxsync, 3 months ago

Additions: Linux/Wayland: Rename VBClClipboardThreadStart to vbcl_wayland_thread_start and put it into common place, bugref:10796.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 14.0 KB
Line 
1/* $Id: wayland.cpp 106786 2024-10-30 11:33:02Z vboxsync $ */
2/** @file
3 * Guest Additions - Wayland Desktop Environment assistant.
4 */
5
6/*
7 * Copyright (C) 2017-2024 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#include <VBox/HostServices/VBoxClipboardSvc.h>
33
34#include "VBoxClient.h"
35#include "clipboard.h"
36#include "wayland-helper.h"
37
38/** Polling interval for input focus monitoring task. */
39#define VBCL_WAYLAND_WAIT_HOST_FOCUS_TIMEOUT_MS (250)
40/** Relax interval for input focus monitoring task. */
41#define VBCL_WAYLAND_WAIT_HOST_FOCUS_RELAX_MS (100)
42
43/** List of available Wayland Desktop Environment helpers. Sorted in order of preference. */
44static const VBCLWAYLANDHELPER *g_apWaylandHelpers[] =
45{
46 &g_WaylandHelperDcp, /* Device Control Protocol helper. */
47 &g_WaylandHelperGtk, /* GTK helper. */
48 NULL, /* Terminate list. */
49};
50
51/** Global flag to tell service to go shutdown when needed. */
52static bool volatile g_fShutdown = false;
53
54/** Selected helpers for Clipboard and Drag-and-Drop. */
55static const VBCLWAYLANDHELPER *g_pWaylandHelperClipboard = NULL;
56static const VBCLWAYLANDHELPER *g_pWaylandHelperDnd = NULL;
57
58/** Corresponding threads for host events handling. */
59static RTTHREAD g_ClipboardThread;
60static RTTHREAD g_DndThread;
61static RTTHREAD g_HostInputFocusThread;
62
63/**
64 * Worker for Shared Clipboard events from host.
65 *
66 * @returns IPRT status code.
67 * @param hThreadSelf IPRT thread handle.
68 * @param pvUser User data (unused).
69 */
70static DECLCALLBACK(int) vbclWaylandClipboardWorker(RTTHREAD hThreadSelf, void *pvUser)
71{
72 SHCLCONTEXT ctx;
73 int rc;
74
75 RT_NOREF(pvUser);
76
77 RT_ZERO(ctx);
78
79 /* Connect to the host service. */
80 rc = VbglR3ClipboardConnectEx(&ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
81 /* Notify parent thread. */
82 RTThreadUserSignal(hThreadSelf);
83
84 if (RT_SUCCESS(rc))
85 {
86 /* Provide helper with host clipboard service connection handle. */
87 g_pWaylandHelperClipboard->clip.pfnSetClipboardCtx(&ctx.CmdCtx);
88
89 /* Process host events. */
90 while (!ASMAtomicReadBool(&g_fShutdown))
91 {
92 rc = VBClClipboardReadHostEvent(&ctx,
93 g_pWaylandHelperClipboard->clip.pfnHGClipReport,
94 g_pWaylandHelperClipboard->clip.pfnGHClipRead);
95 if (RT_FAILURE(rc))
96 {
97 VBClLogInfo("cannot process host clipboard event, rc=%Rrc\n", rc);
98 RTThreadSleep(RT_MS_1SEC / 2);
99 }
100 }
101
102 VbglR3ClipboardDisconnectEx(&ctx.CmdCtx);
103 }
104
105 VBClLogVerbose(2, "clipboard thread, rc=%Rrc\n", rc);
106
107 return rc;
108}
109
110/**
111 * Worker for Drag-and-Drop events from host.
112 *
113 * @returns IPRT status code.
114 * @param hThreadSelf IPRT thread handle.
115 * @param pvUser User data (unused).
116 */
117static DECLCALLBACK(int) vbclWaylandDndWorker(RTTHREAD hThreadSelf, void *pvUser)
118{
119 RT_NOREF(pvUser);
120
121 RTThreadUserSignal(hThreadSelf);
122 return VINF_SUCCESS;
123}
124
125/**
126 * Worker for VM window focus change polling thread.
127 *
128 * Some Wayland helpers need to be notified about VM
129 * window focus change events. This is needed in order to
130 * ask about, for example, if guest clipboard content was
131 * changed since last user interaction. Such guest are not
132 * able to notify host about clipboard content change and
133 * needed to be asked implicitly.
134 *
135 * @returns IPRT status code.
136 * @param hThreadSelf IPRT thread handle.
137 * @param pvUser User data (unused).
138 */
139static DECLCALLBACK(int) vbclWaylandHostInputFocusWorker(RTTHREAD hThreadSelf, void *pvUser)
140{
141 int rc;
142
143 RT_NOREF(pvUser);
144
145 HGCMCLIENTID idClient;
146
147 rc = VbglR3GuestPropConnect(&idClient);
148
149 RTThreadUserSignal(hThreadSelf);
150
151 if (RT_SUCCESS(rc))
152 {
153 while (!ASMAtomicReadBool(&g_fShutdown))
154 {
155 static char achBuf[GUEST_PROP_MAX_NAME_LEN];
156 char *pszName = NULL;
157 char *pszValue = NULL;
158 char *pszFlags = NULL;
159 bool fWasDeleted = false;
160 uint64_t u64Timestamp = 0;
161
162 rc = VbglR3GuestPropWait(idClient, VBOX_GUI_FOCUS_CHANGE_GUEST_PROP_NAME, achBuf, sizeof(achBuf), u64Timestamp,
163 VBCL_WAYLAND_WAIT_HOST_FOCUS_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp,
164 &pszFlags, NULL, &fWasDeleted);
165 if (RT_SUCCESS(rc))
166 {
167 uint32_t fFlags = 0;
168
169 VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n",
170 pszName, pszValue, pszFlags, fWasDeleted);
171
172 if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags)))
173 {
174 if (RTStrNCmp(pszName, VBOX_GUI_FOCUS_CHANGE_GUEST_PROP_NAME, GUEST_PROP_MAX_NAME_LEN) == 0)
175 {
176 if (fFlags & GUEST_PROP_F_RDONLYGUEST)
177 {
178 if (RT_VALID_PTR(g_pWaylandHelperClipboard))
179 {
180 if (RTStrNCmp(pszValue, "0", GUEST_PROP_MAX_NAME_LEN) == 0)
181 {
182 rc = g_pWaylandHelperClipboard->clip.pfnPopup();
183 VBClLogVerbose(1, "trigger popup, rc=%Rrc\n", rc);
184 }
185 }
186 else
187 VBClLogVerbose(1, "will not trigger popup\n");
188 }
189 else
190 VBClLogError("property has invalid attributes\n");
191 }
192 else
193 VBClLogVerbose(1, "unknown property name '%s'\n", pszName);
194
195 } else
196 VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n",
197 pszName, pszValue, pszFlags, fWasDeleted);
198
199 } else if ( rc != VERR_TIMEOUT
200 && rc != VERR_INTERRUPTED)
201 {
202 VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc);
203 RTThreadSleep(VBCL_WAYLAND_WAIT_HOST_FOCUS_RELAX_MS);
204 }
205 }
206 }
207
208 return rc;
209}
210
211
212/**
213 * @interface_method_impl{VBCLSERVICE,pfnInit}
214 */
215static DECLCALLBACK(int) vbclWaylandInit(void)
216{
217 int rc = VERR_NOT_SUPPORTED;
218 int idxHelper = 0;
219
220 /** Custom log prefix to be used for logger instance of this process. */
221 static const char *pszLogPrefix = "VBoxClient Wayland:";
222
223 VBClLogSetLogPrefix(pszLogPrefix);
224
225 /* Go through list of available helpers and try to pick up one. */
226 while (g_apWaylandHelpers[idxHelper])
227 {
228 if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->pfnProbe))
229 {
230 int fCaps = VBOX_WAYLAND_HELPER_CAP_NONE;
231
232 VBClLogInfo("probing Wayland helper '%s'\n",
233 g_apWaylandHelpers[idxHelper]->pszName);
234
235 fCaps = g_apWaylandHelpers[idxHelper]->pfnProbe();
236
237 /* Try Clipboard helper. */
238 if ( fCaps & VBOX_WAYLAND_HELPER_CAP_CLIPBOARD
239 && !RT_VALID_PTR(g_pWaylandHelperClipboard))
240 {
241 if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->clip.pfnInit))
242 {
243 rc = g_apWaylandHelpers[idxHelper]->clip.pfnInit();
244 if (RT_SUCCESS(rc))
245 g_pWaylandHelperClipboard = g_apWaylandHelpers[idxHelper];
246 else
247 VBClLogError("Wayland helper '%s' cannot be initialized, skipping\n",
248 g_apWaylandHelpers[idxHelper]->pszName);
249 }
250 else
251 VBClLogVerbose(1, "Wayland helper '%s' has no initializer, skipping\n",
252 g_apWaylandHelpers[idxHelper]->pszName);
253 }
254
255 /* Try DnD helper. */
256 if ( fCaps & VBOX_WAYLAND_HELPER_CAP_DND
257 && !RT_VALID_PTR(g_pWaylandHelperDnd))
258 {
259 if (RT_VALID_PTR(g_apWaylandHelpers[idxHelper]->dnd.pfnInit))
260 {
261 rc = g_apWaylandHelpers[idxHelper]->dnd.pfnInit();
262 if (RT_SUCCESS(rc))
263 g_pWaylandHelperDnd = g_apWaylandHelpers[idxHelper];
264 else
265 VBClLogError("Wayland helper '%s' cannot be initialized, skipping\n",
266 g_apWaylandHelpers[idxHelper]->pszName);
267 }
268 else
269 VBClLogVerbose(1, "Wayland helper '%s' has no initializer, skipping\n",
270 g_apWaylandHelpers[idxHelper]->pszName);
271 }
272 }
273
274 /* See if we found all the needed helpers. */
275 if ( RT_VALID_PTR(g_pWaylandHelperClipboard)
276 && RT_VALID_PTR(g_pWaylandHelperDnd))
277 break;
278
279 idxHelper++;
280 }
281
282 /* Check result. */
283 if (RT_VALID_PTR(g_pWaylandHelperClipboard))
284 VBClLogInfo("found Wayland Shared Clipboard helper '%s'\n", g_pWaylandHelperClipboard->pszName);
285 else
286 VBClLogError("Wayland Shared Clipboard helper not found, clipboard sharing not possible\n");
287
288 /* Check result. */
289 if (RT_VALID_PTR(g_pWaylandHelperDnd))
290 VBClLogInfo("found Wayland Drag-and-Drop helper '%s'\n", g_pWaylandHelperDnd->pszName);
291 else
292 VBClLogError("Wayland Drag-and-Drop helper not found, drag-and-drop not possible\n");
293
294 return rc;
295}
296
297/**
298 * @interface_method_impl{VBCLSERVICE,pfnWorker}
299 */
300static DECLCALLBACK(int) vbclWaylandWorker(bool volatile *pfShutdown)
301{
302 int rc = VINF_SUCCESS;
303
304 RT_NOREF(pfShutdown);
305
306 VBClLogVerbose(1, "starting wayland worker thread\n");
307
308 /* Start event loop for clipboard events processing from host. */
309 if (RT_VALID_PTR(g_pWaylandHelperClipboard))
310 {
311 rc = vbcl_wayland_thread_start(&g_ClipboardThread, vbclWaylandClipboardWorker, "wl-clip", NULL);
312 VBClLogVerbose(1, "clipboard thread started, rc=%Rrc\n", rc);
313 }
314
315 /* Start event loop for DnD events processing from host. */
316 if ( RT_SUCCESS(rc)
317 && RT_VALID_PTR(g_pWaylandHelperDnd))
318 {
319 rc = vbcl_wayland_thread_start(&g_DndThread, vbclWaylandDndWorker, "wl-dnd", NULL);
320 VBClLogVerbose(1, "DnD thread started, rc=%Rrc\n", rc);
321 }
322
323 /* Start polling host input focus events. */
324 if (RT_SUCCESS(rc))
325 {
326 rc = vbcl_wayland_thread_start(&g_HostInputFocusThread, vbclWaylandHostInputFocusWorker, "wl-focus", NULL);
327 VBClLogVerbose(1, "host input focus polling thread started, rc=%Rrc\n", rc);
328 }
329
330 /* Notify parent thread that we are successfully started. */
331 RTThreadUserSignal(RTThreadSelf());
332
333 if (RT_SUCCESS(rc))
334 {
335 int rcThread = VINF_SUCCESS;
336
337 if (RT_VALID_PTR(g_pWaylandHelperClipboard))
338 {
339 rc = RTThreadWait(g_ClipboardThread, RT_INDEFINITE_WAIT, &rcThread);
340 VBClLogVerbose(1, "clipboard thread finished, rc=%Rrc, rcThread=%Rrc\n", rc, rcThread);
341 }
342
343 if ( RT_SUCCESS(rc)
344 && RT_VALID_PTR(g_pWaylandHelperDnd))
345 {
346 rc = RTThreadWait(g_DndThread, RT_INDEFINITE_WAIT, &rcThread);
347 VBClLogVerbose(1, "DnD thread finished, rc=%Rrc, rcThread=%Rrc\n", rc, rcThread);
348 }
349
350 if (RT_SUCCESS(rc))
351 {
352 rc = RTThreadWait(g_HostInputFocusThread, RT_INDEFINITE_WAIT, &rcThread);
353 VBClLogVerbose(1, "host input focus polling thread finished, rc=%Rrc, rcThread=%Rrc\n", rc, rcThread);
354 }
355 }
356
357 VBClLogVerbose(1, "wayland worker thread finished, rc=%Rrc\n", rc);
358
359 return rc;
360}
361
362/**
363 * @interface_method_impl{VBCLSERVICE,pfnStop}
364 */
365static DECLCALLBACK(void) vbclWaylandStop(void)
366{
367 VBClLogVerbose(1, "terminating wayland service: clipboard & DnD host event loops\n");
368
369 /* This callback can be called twice (not good, needs to be fixed). Already was shut down? */
370 if (ASMAtomicReadBool(&g_fShutdown))
371 return;
372
373 ASMAtomicWriteBool(&g_fShutdown, true);
374
375 if (RT_VALID_PTR(g_pWaylandHelperClipboard))
376 RTThreadPoke(g_ClipboardThread);
377
378 if (RT_VALID_PTR(g_pWaylandHelperDnd))
379 RTThreadPoke(g_DndThread);
380}
381
382/**
383 * @interface_method_impl{VBCLSERVICE,pfnTerm}
384 */
385static DECLCALLBACK(int) vbclWaylandTerm(void)
386{
387 int rc = VINF_SUCCESS;
388
389 VBClLogVerbose(1, "shutting down wayland service: clipboard & DnD helpers\n");
390
391 if ( RT_VALID_PTR(g_pWaylandHelperClipboard)
392 && RT_VALID_PTR(g_pWaylandHelperClipboard->clip.pfnTerm))
393 rc = g_pWaylandHelperClipboard->clip.pfnTerm();
394
395 if ( RT_SUCCESS(rc)
396 && RT_VALID_PTR(g_pWaylandHelperDnd)
397 && RT_VALID_PTR(g_pWaylandHelperDnd->dnd.pfnTerm))
398 rc = g_pWaylandHelperDnd->dnd.pfnTerm();
399
400 return rc;
401}
402
403VBCLSERVICE g_SvcWayland =
404{
405 "wayland", /* szName */
406 "Wayland assistant", /* pszDescription */
407 ".vboxclient-wayland", /* pszPidFilePathTemplate */
408 NULL, /* pszUsage */
409 NULL, /* pszOptions */
410 NULL, /* pfnOption */
411 vbclWaylandInit, /* pfnInit */
412 vbclWaylandWorker, /* pfnWorker */
413 vbclWaylandStop, /* pfnStop */
414 vbclWaylandTerm, /* pfnTerm */
415};
Note: See TracBrowser for help on using the repository browser.

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