VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp@ 106061

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

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.1 KB
Line 
1/* $Id: display-svga-session.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * Guest Additions - VMSVGA Desktop Environment user session assistant.
4 *
5 * This service connects to VBoxDRMClient IPC server, listens for
6 * its commands and reports current display offsets to it. If IPC
7 * server is not available, it forks legacy 'VBoxClient --vmsvga
8 * service and terminates.
9 */
10
11/*
12 * Copyright (C) 2017-2024 Oracle and/or its affiliates.
13 *
14 * This file is part of VirtualBox base platform packages, as
15 * available from https://www.virtualbox.org.
16 *
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU General Public License
19 * as published by the Free Software Foundation, in version 3 of the
20 * License.
21 *
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, see <https://www.gnu.org/licenses>.
29 *
30 * SPDX-License-Identifier: GPL-3.0-only
31 */
32
33/*
34 * This service is an IPC client for VBoxDRMClient daemon. It is also
35 * a proxy bridge to a Desktop Environment specific code (so called
36 * Desktop Environment helpers).
37 *
38 * Once started, it will try to enumerate and probe all the registered
39 * helpers and if appropriate helper found, it will forward incoming IPC
40 * commands to it as well as send helper's commands back to VBoxDRMClient.
41 * Generic helper is a special one. It will be used by default if all the
42 * other helpers are failed on probe. Moreover, generic helper provides
43 * helper functions that can be used by other helpers as well. For example,
44 * once Gnome3 Desktop Environment is running on X11, it will be also use
45 * display offsets change notification monitor of a generic helper.
46 *
47 * Multiple instances of this daemon are allowed to run in parallel
48 * with the following limitations.
49 * A single user cannot run multiple daemon instances per single TTY device,
50 * however, multiple instances are allowed for the user on different
51 * TTY devices (i.e. in case if user runs multiple X servers on different
52 * terminals). On multiple TTY devices multiple users can run multiple
53 * daemon instances (i.e. in case of "switch user" DE configuration when
54 * multiple X/Wayland servers are running on separate TTY devices).
55 */
56
57#include "VBoxClient.h"
58#include "display-ipc.h"
59#include "display-helper.h"
60
61#include <VBox/VBoxGuestLib.h>
62
63#include <iprt/localipc.h>
64#include <iprt/asm.h>
65#include <iprt/errcore.h>
66#include <iprt/path.h>
67#include <iprt/linux/sysfs.h>
68
69
70/** Handle to IPC client connection. */
71VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
72
73/** IPC client handle critical section. */
74static RTCRITSECT g_hClientCritSect;
75
76/** List of available Desktop Environment specific display helpers. */
77static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] =
78{
79 &g_DisplayHelperGnome3, /* GNOME3 helper. */
80 &g_DisplayHelperGeneric, /* Generic helper. */
81 NULL, /* Terminate list. */
82};
83
84/** Selected Desktop Environment specific display helper. */
85static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL;
86
87/** IPC connection session handle. */
88static RTLOCALIPCSESSION g_hSession = 0;
89
90/**
91 * Callback for display offsets change events provided by Desktop Environment specific display helper.
92 *
93 * @returns IPRT status code.
94 * @param cDisplays Number of displays which have changed offset.
95 * @param aDisplays Display data.
96 */
97static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
98{
99 int rc = RTCritSectEnter(&g_hClientCritSect);
100
101 if (RT_SUCCESS(rc))
102 {
103 rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays);
104 int rc2 = RTCritSectLeave(&g_hClientCritSect);
105 if (RT_FAILURE(rc2))
106 VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2);
107 }
108 else
109 VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc);
110
111 return rc;
112}
113
114/**
115 * @interface_method_impl{VBCLSERVICE,pfnInit}
116 */
117static DECLCALLBACK(int) vbclSVGASessionInit(void)
118{
119 int rc;
120 RTLOCALIPCSESSION hSession;
121 int idxDisplayHelper = 0;
122
123 /** Custom log prefix to be used for logger instance of this process. */
124 static const char *pszLogPrefix = "VBoxClient VMSVGA:";
125
126 VBClLogSetLogPrefix(pszLogPrefix);
127
128 rc = RTCritSectInit(&g_hClientCritSect);
129 if (RT_FAILURE(rc))
130 {
131 VBClLogError("unable to init locking, rc=%Rrc\n", rc);
132 return rc;
133 }
134
135 /* Go through list of available Desktop Environment specific helpers and try to pick up one. */
136 while (g_apDisplayHelpers[idxDisplayHelper])
137 {
138 if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe)
139 {
140 VBClLogInfo("probing Desktop Environment helper '%s'\n",
141 g_apDisplayHelpers[idxDisplayHelper]->pszName);
142
143 rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe();
144
145 /* Found compatible helper. */
146 if (RT_SUCCESS(rc))
147 {
148 /* Initialize it. */
149 if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit)
150 {
151 rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit();
152 }
153
154 /* Some helpers might have no .pfnInit(), that's ok. */
155 if (RT_SUCCESS(rc))
156 {
157 /* Subscribe to display offsets change event. */
158 if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification)
159 {
160 g_apDisplayHelpers[idxDisplayHelper]->
161 pfnSubscribeDisplayOffsetChangeNotification(
162 vbclSVGASessionDisplayOffsetChanged);
163 }
164
165 g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper];
166 break;
167 }
168 else
169 VBClLogError("compatible Desktop Environment "
170 "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc);
171 }
172 }
173
174 idxDisplayHelper++;
175 }
176
177 /* Make sure we found compatible Desktop Environment specific helper. */
178 if (g_pDisplayHelper)
179 {
180 VBClLogInfo("using Desktop Environment specific display helper '%s'\n",
181 g_pDisplayHelper->pszName);
182 }
183 else
184 {
185 VBClLogError("unable to find Desktop Environment specific display helper\n");
186 return VERR_NOT_IMPLEMENTED;
187 }
188
189 /* Attempt to connect to VBoxDRMClient IPC server. */
190 rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0);
191 if (RT_SUCCESS(rc))
192 {
193 g_hSession = hSession;
194 }
195 else
196 VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc);
197
198 /* We cannot initialize ourselves, start legacy service and terminate. */
199 if (RT_FAILURE(rc))
200 {
201 /* Free helper resources. */
202 if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
203 g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
204
205 if (g_pDisplayHelper->pfnTerm)
206 {
207 rc = g_pDisplayHelper->pfnTerm();
208 VBClLogInfo("helper service terminated, rc=%Rrc\n", rc);
209 }
210
211 rc = VbglR3DrmLegacyClientStart();
212 VBClLogInfo("starting legacy service, rc=%Rrc\n", rc);
213 /* Force return status, so parent thread wont be trying to start worker thread. */
214 rc = VERR_NOT_AVAILABLE;
215 }
216
217 return rc;
218}
219
220/**
221 * A callback function which is triggered on IPC data receive.
222 *
223 * @returns IPRT status code.
224 * @param idCmd DRM IPC command ID.
225 * @param pvData DRM IPC command payload.
226 * @param cbData Size of DRM IPC command payload.
227 */
228static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
229{
230 VBOXDRMIPCCLTCMD enmCmd =
231 (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ?
232 (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID;
233
234 int rc = VERR_INVALID_PARAMETER;
235
236 AssertReturn(pvData, VERR_INVALID_PARAMETER);
237 AssertReturn(cbData, VERR_INVALID_PARAMETER);
238 AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER);
239
240 switch (enmCmd)
241 {
242 case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY:
243 {
244 if (g_pDisplayHelper->pfnSetPrimaryDisplay)
245 {
246 PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData;
247 static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX;
248
249 if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX
250 && idPrimaryDisplayCached != pCmd->idDisplay)
251 {
252 rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay);
253 /* Update cache. */
254 idPrimaryDisplayCached = pCmd->idDisplay;
255 }
256 else
257 VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay);
258 }
259
260 break;
261 }
262 default:
263 {
264 VBClLogError("received unknown IPC command 0x%x\n", idCmd);
265 break;
266 }
267 }
268
269 return rc;
270}
271
272/**
273 * Reconnect to DRM IPC server.
274 */
275static int vbclSVGASessionReconnect(void)
276{
277 int rc = VERR_GENERAL_FAILURE;
278
279 rc = RTCritSectEnter(&g_hClientCritSect);
280 if (RT_FAILURE(rc))
281 {
282 VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc);
283 return rc;
284 }
285
286 /* Check if session was not closed before. */
287 if (RT_VALID_PTR(g_hSession))
288 {
289 rc = RTLocalIpcSessionClose(g_hSession);
290 if (RT_FAILURE(rc))
291 VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc);
292
293 rc = vbDrmIpcClientReleaseResources(&g_hClient);
294 if (RT_FAILURE(rc))
295 VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc);
296 }
297
298 rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0);
299 if (RT_SUCCESS(rc))
300 {
301 rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
302 if (RT_FAILURE(rc))
303 VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc);
304 }
305 else
306 VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc);
307
308 int rc2 = RTCritSectLeave(&g_hClientCritSect);
309 if (RT_FAILURE(rc2))
310 VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc);
311
312 return rc;
313}
314
315/**
316 * @interface_method_impl{VBCLSERVICE,pfnWorker}
317 */
318static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown)
319{
320 int rc = VINF_SUCCESS;
321
322 /* Notify parent thread that we started successfully. */
323 rc = RTThreadUserSignal(RTThreadSelf());
324 if (RT_FAILURE(rc))
325 VBClLogError("unable to notify parent thread about successful start\n");
326
327 rc = RTCritSectEnter(&g_hClientCritSect);
328
329 if (RT_FAILURE(rc))
330 {
331 VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc);
332 return rc;
333 }
334
335 rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
336 int rc2 = RTCritSectLeave(&g_hClientCritSect);
337 if (RT_FAILURE(rc2))
338 VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc);
339
340 if (RT_FAILURE(rc))
341 {
342 VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc);
343 return rc;
344 }
345
346 for (;;)
347 {
348 rc = vbDrmIpcConnectionHandler(&g_hClient);
349
350 /* Try to shutdown thread as soon as possible. */
351 if (ASMAtomicReadBool(pfShutdown))
352 {
353 /* Shutdown requested. */
354 break;
355 }
356
357 /* Normal case, there was no incoming messages for a while. */
358 if (rc == VERR_TIMEOUT)
359 {
360 continue;
361 }
362 else if (RT_FAILURE(rc))
363 {
364 VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc);
365
366 /* Relax a bit before spinning the loop. */
367 RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
368 /* Try to reconnect to server. */
369 rc = vbclSVGASessionReconnect();
370 }
371 }
372
373 /* Check if session was not closed before. */
374 if (RT_VALID_PTR(g_hSession))
375 {
376 rc2 = RTCritSectEnter(&g_hClientCritSect);
377 if (RT_SUCCESS(rc2))
378 {
379 rc2 = vbDrmIpcClientReleaseResources(&g_hClient);
380 if (RT_FAILURE(rc2))
381 VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2);
382
383 rc2 = RTCritSectLeave(&g_hClientCritSect);
384 if (RT_FAILURE(rc2))
385 VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc);
386 }
387 else
388 VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc);
389 }
390
391 return rc;
392}
393
394/**
395 * @interface_method_impl{VBCLSERVICE,pfnStop}
396 */
397static DECLCALLBACK(void) vbclSVGASessionStop(void)
398{
399 int rc;
400
401 /* Check if session was not closed before. */
402 if (!RT_VALID_PTR(g_hSession))
403 return;
404
405 /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */
406 rc = RTLocalIpcSessionFlush(g_hSession);
407 if (RT_FAILURE(rc))
408 VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc);
409
410 rc = RTLocalIpcSessionCancel(g_hSession);
411 if (RT_FAILURE(rc))
412 VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc);
413}
414
415/**
416 * @interface_method_impl{VBCLSERVICE,pfnTerm}
417 */
418static DECLCALLBACK(int) vbclSVGASessionTerm(void)
419{
420 int rc = VINF_SUCCESS;
421
422 if (g_hSession)
423 {
424 rc = RTLocalIpcSessionClose(g_hSession);
425 g_hSession = 0;
426
427 if (RT_FAILURE(rc))
428 VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc);
429 }
430
431 if (g_pDisplayHelper)
432 {
433 if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
434 g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
435
436 if (g_pDisplayHelper->pfnTerm)
437 {
438 rc = g_pDisplayHelper->pfnTerm();
439 if (RT_FAILURE(rc))
440 VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n",
441 rc, g_pDisplayHelper->pszName);
442 }
443 }
444
445 return VINF_SUCCESS;
446}
447
448VBCLSERVICE g_SvcDisplaySVGASession =
449{
450 "vmsvga-session", /* szName */
451 "VMSVGA display assistant", /* pszDescription */
452 ".vboxclient-vmsvga-session", /* pszPidFilePathTemplate */
453 NULL, /* pszUsage */
454 NULL, /* pszOptions */
455 NULL, /* pfnOption */
456 vbclSVGASessionInit, /* pfnInit */
457 vbclSVGASessionWorker, /* pfnWorker */
458 vbclSVGASessionStop, /* pfnStop */
459 vbclSVGASessionTerm, /* pfnTerm */
460};
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