VirtualBox

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

Last change on this file was 98594, checked in by vboxsync, 15 months ago

Additions: X11: Make legacy display resize service to gracefully shutdown, bugref:10359.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.6 KB
Line 
1/* $Id: display.cpp 98594 2023-02-15 16:48:37Z vboxsync $ */
2/** @file
3 * X11 guest client - display management.
4 */
5
6/*
7 * Copyright (C) 2006-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 "VBoxClient.h"
29
30#include <iprt/errcore.h>
31#include <iprt/file.h>
32#include <iprt/mem.h>
33#include <iprt/string.h>
34#include <iprt/asm.h>
35
36#include <X11/Xlib.h>
37#include <X11/Xatom.h>
38#include <X11/extensions/Xrandr.h>
39
40/** @todo this should probably be replaced by something IPRT */
41/* For system() and WEXITSTATUS() */
42#include <stdlib.h>
43#include <sys/types.h>
44#include <sys/wait.h>
45#include <errno.h>
46#include <limits.h>
47#include <poll.h>
48#include <time.h>
49#include <dlfcn.h>
50
51/* TESTING: Dynamic resizing and mouse integration toggling should work
52 * correctly with a range of X servers (pre-1.3, 1.3 and later under Linux, 1.3
53 * and later under Solaris) with Guest Additions installed. Switching to a
54 * virtual terminal while a user session is in place should disable dynamic
55 * resizing and cursor integration, switching back should re-enable them. */
56
57/** State information needed for the service. The main VBoxClient code provides
58 * the daemon logic needed by all services. */
59struct DISPLAYSTATE
60{
61 /** Are we initialised yet? */
62 bool mfInit;
63 /** The connection to the server. */
64 Display *pDisplay;
65 /** The RandR extension base event number. */
66 int cRREventBase;
67 /** Can we use version 1.2 or later of the RandR protocol here? */
68 bool fHaveRandR12;
69 /** The command argument to use for the xrandr binary. Currently only
70 * used to support the non-standard location on some Solaris systems -
71 * would it make sense to use absolute paths on all systems? */
72 const char *pcszXrandr;
73 /** Was there a recent mode hint with no following root window resize, and
74 * if so, have we waited for a reasonable time? */
75 time_t timeLastModeHint;
76 /** Handle to libXrandr. */
77 void *pRandLibraryHandle;
78 /** Handle to pXRRSelectInput. */
79 void (*pXRRSelectInput) (Display *, Window, int);
80 /** Handle to pXRRQueryExtension. */
81 Bool (*pXRRQueryExtension) (Display *, int *, int *);
82};
83
84static struct DISPLAYSTATE g_DisplayState;
85
86static unsigned char *getRootProperty(struct DISPLAYSTATE *pState, const char *pszName,
87 long cItems, Atom type)
88{
89 Atom actualType = None;
90 int iFormat = 0;
91 unsigned long cReturned = 0;
92 unsigned long cAfter = 0;
93 unsigned char *pData = 0;
94
95 if (XGetWindowProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay),
96 XInternAtom(pState->pDisplay, pszName, 0), 0, cItems,
97 False /* delete */, type, &actualType, &iFormat,
98 &cReturned, &cAfter, &pData))
99 return NULL;
100 return pData;
101}
102
103static void doResize(struct DISPLAYSTATE *pState)
104{
105 /** @note The xrandr command can fail if something else accesses RandR at
106 * the same time. We just ignore failure for now as we do not know what
107 * someone else is doing. */
108 if (!pState->fHaveRandR12)
109 {
110 char szCommand[256];
111 unsigned char *pData;
112
113 pData = getRootProperty(pState, "VBOXVIDEO_PREFERRED_MODE", 1, XA_INTEGER);
114 if (pData != NULL)
115 {
116 RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u",
117 pState->pcszXrandr, ((unsigned long *)pData)[0] >> 16, ((unsigned long *)pData)[0] & 0xFFFF);
118 int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc);
119 XFree(pData);
120 }
121 }
122 else
123 {
124 const char szCommandBase[] =
125 "%s --output VGA-0 --auto --output VGA-1 --auto --right-of VGA-0 "
126 "--output VGA-2 --auto --right-of VGA-1 --output VGA-3 --auto --right-of VGA-2 "
127 "--output VGA-4 --auto --right-of VGA-3 --output VGA-5 --auto --right-of VGA-4 "
128 "--output VGA-6 --auto --right-of VGA-5 --output VGA-7 --auto --right-of VGA-6 "
129 "--output VGA-8 --auto --right-of VGA-7 --output VGA-9 --auto --right-of VGA-8 "
130 "--output VGA-10 --auto --right-of VGA-9 --output VGA-11 --auto --right-of VGA-10 "
131 "--output VGA-12 --auto --right-of VGA-11 --output VGA-13 --auto --right-of VGA-12 "
132 "--output VGA-14 --auto --right-of VGA-13 --output VGA-15 --auto --right-of VGA-14 "
133 "--output VGA-16 --auto --right-of VGA-15 --output VGA-17 --auto --right-of VGA-16 "
134 "--output VGA-18 --auto --right-of VGA-17 --output VGA-19 --auto --right-of VGA-18 "
135 "--output VGA-20 --auto --right-of VGA-19 --output VGA-21 --auto --right-of VGA-20 "
136 "--output VGA-22 --auto --right-of VGA-21 --output VGA-23 --auto --right-of VGA-22 "
137 "--output VGA-24 --auto --right-of VGA-23 --output VGA-25 --auto --right-of VGA-24 "
138 "--output VGA-26 --auto --right-of VGA-25 --output VGA-27 --auto --right-of VGA-26 "
139 "--output VGA-28 --auto --right-of VGA-27 --output VGA-29 --auto --right-of VGA-28 "
140 "--output VGA-30 --auto --right-of VGA-29 --output VGA-31 --auto --right-of VGA-30";
141 char szCommand[sizeof(szCommandBase) + 256];
142 RTStrPrintf(szCommand, sizeof(szCommand), szCommandBase, pState->pcszXrandr);
143 int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc);
144 }
145}
146
147/** Main loop: handle display hot-plug events, property updates (which can
148 * signal VT switches hot-plug in old X servers). */
149static void runDisplay(struct DISPLAYSTATE *pState, bool volatile *pfShutdown)
150{
151 Display *pDisplay = pState->pDisplay;
152 long cValue = 1;
153
154 /* One way or another we want the preferred mode at server start-up. */
155 doResize(pState);
156 XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask | StructureNotifyMask);
157 if (pState->fHaveRandR12)
158 pState->pXRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask);
159 /* Semantics: when VBOXCLIENT_STARTED is set, pre-1.3 X.Org Server driver
160 * assumes that a client capable of handling mode hints will be present for the
161 * rest of the X session. If we crash things will not work as they should.
162 * I thought that preferable to implementing complex crash-handling logic.
163 */
164 XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOXCLIENT_STARTED", 0),
165 XA_INTEGER, 32, PropModeReplace, (unsigned char *)&cValue, 1);
166 /* Interrupting this cleanly will be more work than making it robust
167 * against spontaneous termination, especially as it will never get
168 * properly tested, so I will go for the second. */
169 while (!ASMAtomicReadBool(pfShutdown))
170 {
171 XEvent event;
172 struct pollfd PollFd;
173 int pollTimeOut = -1;
174 int cFds;
175
176 /* Do not handle overflow. */
177 if (pState->timeLastModeHint > 0 && pState->timeLastModeHint < INT_MAX - 2)
178 pollTimeOut = 2 - (time(0) - pState->timeLastModeHint);
179 PollFd.fd = ConnectionNumber(pDisplay);
180 PollFd.events = POLLIN; /* Hang-up is always reported. */
181 XFlush(pDisplay);
182 cFds = poll(&PollFd, 1, pollTimeOut >= 0 ? pollTimeOut * 1000 : -1);
183 while (XPending(pDisplay))
184 {
185 XNextEvent(pDisplay, &event);
186 /* This property is deleted when the server regains the virtual
187 * terminal. Force the main thread to call xrandr again, as old X
188 * servers could not handle it while switched out. */
189 if ( !pState->fHaveRandR12
190 && event.type == PropertyNotify
191 && event.xproperty.state == PropertyDelete
192 && event.xproperty.window == DefaultRootWindow(pDisplay)
193 && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False))
194 doResize(pState);
195 if ( !pState->fHaveRandR12
196 && event.type == PropertyNotify
197 && event.xproperty.state == PropertyNewValue
198 && event.xproperty.window == DefaultRootWindow(pDisplay)
199 && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_PREFERRED_MODE", False))
200 doResize(pState);
201 if ( pState->fHaveRandR12
202 && event.type == pState->cRREventBase + RRScreenChangeNotify)
203 pState->timeLastModeHint = time(0);
204 if ( event.type == ConfigureNotify
205 && event.xproperty.window == DefaultRootWindow(pDisplay))
206 pState->timeLastModeHint = 0;
207 }
208 if (cFds == 0 && pState->timeLastModeHint > 0)
209 doResize(pState);
210 }
211}
212
213static int initDisplay(struct DISPLAYSTATE *pState)
214{
215 char szCommand[256];
216 int status;
217
218 pState->pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */);
219 if (!pState->pRandLibraryHandle)
220 pState->pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */);
221 if (!pState->pRandLibraryHandle)
222 pState->pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */);
223
224 if (!RT_VALID_PTR(pState->pRandLibraryHandle))
225 {
226 VBClLogFatalError("Could not locate libXrandr for dlopen\n");
227 return VERR_NOT_FOUND;
228 }
229
230 *(void **)(&pState->pXRRSelectInput) = dlsym(pState->pRandLibraryHandle, "XRRSelectInput");
231 *(void **)(&pState->pXRRQueryExtension) = dlsym(pState->pRandLibraryHandle, "XRRQueryExtension");
232
233 if ( !RT_VALID_PTR(pState->pXRRSelectInput)
234 || !RT_VALID_PTR(pState->pXRRQueryExtension))
235 {
236 VBClLogFatalError("Could not load required libXrandr symbols\n");
237 dlclose(pState->pRandLibraryHandle);
238 pState->pRandLibraryHandle = NULL;
239 return VERR_NOT_FOUND;
240 }
241
242 pState->pDisplay = XOpenDisplay(NULL);
243 if (!pState->pDisplay)
244 return VERR_NOT_FOUND;
245 if (!pState->pXRRQueryExtension(pState->pDisplay, &pState->cRREventBase, &status))
246 return VERR_NOT_FOUND;
247 pState->fHaveRandR12 = false;
248 pState->pcszXrandr = "xrandr";
249 if (RTFileExists("/usr/X11/bin/xrandr"))
250 pState->pcszXrandr = "/usr/X11/bin/xrandr";
251 status = system(pState->pcszXrandr);
252 if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
253 VBClLogFatalError("Failed to execute the xrandr utility\n");
254 RTStrPrintf(szCommand, sizeof(szCommand), "%s --q12", pState->pcszXrandr);
255 status = system(szCommand);
256 if (WEXITSTATUS(status) == 0)
257 pState->fHaveRandR12 = true;
258 return VINF_SUCCESS;
259}
260
261/**
262 * @interface_method_impl{VBCLSERVICE,pfnInit}
263 */
264static DECLCALLBACK(int) init(void)
265{
266 struct DISPLAYSTATE *pSelf = &g_DisplayState;
267 int rc;
268
269 if (pSelf->mfInit)
270 return VERR_WRONG_ORDER;
271 rc = initDisplay(pSelf);
272 if (RT_FAILURE(rc))
273 return rc;
274 if (RT_SUCCESS(rc))
275 pSelf->mfInit = true;
276 return rc;
277}
278
279/**
280 * @interface_method_impl{VBCLSERVICE,pfnWorker}
281 */
282static DECLCALLBACK(int) run(bool volatile *pfShutdown)
283{
284 struct DISPLAYSTATE *pSelf = &g_DisplayState;
285
286 if (!pSelf->mfInit)
287 return VERR_WRONG_ORDER;
288
289 runDisplay(pSelf, pfShutdown);
290
291 return VINF_SUCCESS;
292}
293
294/**
295 * @interface_method_impl{VBCLSERVICE,pfnStop}
296 */
297static DECLCALLBACK(void) stop(void)
298{
299 /* Nothing to do here. Implement empty callback, so
300 * main thread can set pfShutdown=true on process termination. */
301}
302
303VBCLSERVICE g_SvcDisplayLegacy =
304{
305 "dp-legacy-x11", /* szName */
306 "Legacy display assistant", /* pszDescription */
307 ".vboxclient-display", /* pszPidFilePathTemplate */
308 NULL, /* pszUsage */
309 NULL, /* pszOptions */
310 NULL, /* pfnOption */
311 init, /* pfnInit */
312 run, /* pfnWorker */
313 stop, /* pfnStop */
314 NULL, /* pfnTerm */
315};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use