VirtualBox

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

Last change on this file since 94076 was 94076, checked in by vboxsync, 3 years ago

Additions: Linux: VBoxDRMClient: sync screen layout with DE representation, ​bugref:10134.

In some cases, screen layout which is reported by DE might differ from what was reported
to VBoxDRMClient by host. In this commit, when receiving DE notification, VBoxClient will
report (to VBoxDRMClient) not just display offsets, but entire layout data. VBoxDRMClient
will then validate this data and apply it to DRM stack if needed.

In particular, sometimes DE might report screen layout which can have overlapping displays.
Such layout will be fixed by VBoxDRMClient (display offsets will be realigned) and re-injected
into DRM stack.

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