VirtualBox

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

Last change on this file since 80074 was 79028, checked in by vboxsync, 6 years ago

Additions/VBoxClient/VMSVGA: use VbglR3GetDisplayChangeRequestMulti.
Change log: Additions/linux: do not forget the last size hint on guest reboot.
For VBoxClient with guests using VMSVGA, switch to using
VbglR3GetDisplayChangeRequestMulti to retrieve guest hints and query hints
before waiting for change notifications. This makes sure that hints send
before a guest reboot will keep their effect over the reboot.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 11.2 KB
Line 
1/* $Id: display-svga-x11.cpp 79028 2019-06-06 15:05:36Z vboxsync $ */
2/** @file
3 * X11 guest client - VMSVGA emulation resize event pass-through to X.Org
4 * guest driver.
5 */
6
7/*
8 * Copyright (C) 2017-2019 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19/*
20 * Known things to test when changing this code. All assume a guest with VMSVGA
21 * active and controlled by X11 or Wayland, and Guest Additions installed and
22 * running, unless otherwise stated.
23 * - On Linux 4.6 and later guests, VBoxClient --vmsvga should be running as
24 * root and not as the logged-in user. Dynamic resizing should work for all
25 * screens in any environment which handles kernel resize notifications,
26 * including at log-in screens. Test GNOME Shell Wayland and GNOME Shell
27 * under X.Org or Unity or KDE at the log-in screen and after log-in.
28 * - Linux 4.10 changed the user-kernel-ABI introduced in 4.6: test both.
29 * - On other guests (than Linux 4.6 or later) running X.Org Server 1.3 or
30 * later, VBoxClient --vmsvga should never be running as root, and should run
31 * (and dynamic resizing and screen enable/disable should work for all
32 * screens) whenever a user is logged in to a supported desktop environment.
33 * - On guests running X.Org Server 1.2 or older, VBoxClient --vmsvga should
34 * never run as root and should run whenever a user is logged in to a
35 * supported desktop environment. Dynamic resizing should work for the first
36 * screen, and enabling others should not be possible.
37 * - When VMSVGA is not enabled, VBoxClient --vmsvga should never stay running.
38 */
39
40#include "VBoxClient.h"
41
42#include <VBox/VBoxGuestLib.h>
43
44#include <iprt/assert.h>
45#include <iprt/err.h>
46#include <iprt/file.h>
47#include <iprt/string.h>
48
49#include <sys/utsname.h>
50
51/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
52/** @todo if this ever changes, dynamically allocate resizeable arrays in the
53 * context structure. */
54#define VMW_MAX_HEADS 32
55
56/* VMWare X.Org driver control parts definitions. */
57
58#include <X11/Xlibint.h>
59
60static bool checkRecentLinuxKernel(void)
61{
62 struct utsname name;
63
64 if (uname(&name) == -1)
65 VBClFatalError(("Failed to get kernel name.\n"));
66 if (strcmp(name.sysname, "Linux"))
67 return false;
68 return (RTStrVersionCompare(name.release, "4.6") >= 0);
69}
70
71struct X11CONTEXT
72{
73 Display *pDisplay;
74 int hRandRMajor;
75 int hVMWMajor;
76};
77
78static void x11Connect(struct X11CONTEXT *pContext)
79{
80 int dummy;
81
82 if (pContext->pDisplay != NULL)
83 VBClFatalError(("%s called with bad argument\n", __func__));
84 pContext->pDisplay = XOpenDisplay(NULL);
85 if (pContext->pDisplay == NULL)
86 return;
87 if ( !XQueryExtension(pContext->pDisplay, "RANDR",
88 &pContext->hRandRMajor, &dummy, &dummy)
89 || !XQueryExtension(pContext->pDisplay, "VMWARE_CTRL",
90 &pContext->hVMWMajor, &dummy, &dummy))
91 {
92 XCloseDisplay(pContext->pDisplay);
93 pContext->pDisplay = NULL;
94 }
95}
96
97#define X11_VMW_TOPOLOGY_REQ 2
98struct X11VMWRECT /* xXineramaScreenInfo in Xlib headers. */
99{
100 int16_t x;
101 int16_t y;
102 uint16_t w;
103 uint16_t h;
104};
105AssertCompileSize(struct X11VMWRECT, 8);
106
107struct X11REQHEADER
108{
109 uint8_t hMajor;
110 uint8_t idType;
111 uint16_t cd;
112};
113
114struct X11VMWTOPOLOGYREQ
115{
116 struct X11REQHEADER header;
117 uint32_t idX11Screen;
118 uint32_t cScreens;
119 uint32_t u32Pad;
120 struct X11VMWRECT aRects[1];
121};
122AssertCompileSize(struct X11VMWTOPOLOGYREQ, 24);
123
124#define X11_VMW_TOPOLOGY_REPLY_SIZE 32
125
126#define X11_VMW_RESOLUTION_REQUEST 1
127struct X11VMWRESOLUTIONREQ
128{
129 struct X11REQHEADER header;
130 uint32_t idX11Screen;
131 uint32_t w;
132 uint32_t h;
133};
134AssertCompileSize(struct X11VMWRESOLUTIONREQ, 16);
135
136#define X11_VMW_RESOLUTION_REPLY_SIZE 32
137
138#define X11_RANDR_GET_SCREEN_REQUEST 5
139struct X11RANDRGETSCREENREQ
140{
141 struct X11REQHEADER header;
142 uint32_t hWindow;
143};
144AssertCompileSize(struct X11RANDRGETSCREENREQ, 8);
145
146#define X11_RANDR_GET_SCREEN_REPLY_SIZE 32
147
148/* This was a macro in old Xlib versions and a function in newer ones; the
149 * display members touched by the macro were declared as ABI for compatibility
150 * reasons. To simplify building with different generations, we duplicate the
151 * code. */
152static void x11GetRequest(struct X11CONTEXT *pContext, uint8_t hMajor,
153 uint8_t idType, size_t cb, struct X11REQHEADER **ppReq)
154{
155 if (pContext->pDisplay->bufptr + cb > pContext->pDisplay->bufmax)
156 _XFlush(pContext->pDisplay);
157 if (pContext->pDisplay->bufptr + cb > pContext->pDisplay->bufmax)
158 VBClFatalError(("%s display buffer overflow.\n", __func__));
159 if (cb % 4 != 0)
160 VBClFatalError(("%s bad parameter.\n", __func__));
161 pContext->pDisplay->last_req = pContext->pDisplay->bufptr;
162 *ppReq = (struct X11REQHEADER *)pContext->pDisplay->bufptr;
163 (*ppReq)->hMajor = hMajor;
164 (*ppReq)->idType = idType;
165 (*ppReq)->cd = cb / 4;
166 pContext->pDisplay->bufptr += cb;
167 pContext->pDisplay->request++;
168}
169
170static void x11SendHints(struct X11CONTEXT *pContext, struct X11VMWRECT *pRects,
171 unsigned cRects)
172{
173 unsigned i;
174 struct X11VMWTOPOLOGYREQ *pReqTopology;
175 uint8_t repTopology[X11_VMW_TOPOLOGY_REPLY_SIZE];
176 struct X11VMWRESOLUTIONREQ *pReqResolution;
177 uint8_t repResolution[X11_VMW_RESOLUTION_REPLY_SIZE];
178
179 if (!VALID_PTR(pContext->pDisplay))
180 VBClFatalError(("%s bad display argument.\n", __func__));
181 if (cRects == 0)
182 return;
183 /* Try a topology (multiple screen) request. */
184 x11GetRequest(pContext, pContext->hVMWMajor, X11_VMW_TOPOLOGY_REQ,
185 sizeof(struct X11VMWTOPOLOGYREQ)
186 + sizeof(struct X11VMWRECT) * (cRects - 1),
187 (struct X11REQHEADER **)&pReqTopology);
188 pReqTopology->idX11Screen = DefaultScreen(pContext->pDisplay);
189 pReqTopology->cScreens = cRects;
190 for (i = 0; i < cRects; ++i)
191 pReqTopology->aRects[i] = pRects[i];
192 _XSend(pContext->pDisplay, NULL, 0);
193 if (_XReply(pContext->pDisplay, (xReply *)&repTopology, 0, xTrue))
194 return;
195 /* That failed, so try the old single screen set resolution. We prefer
196 * simpler code to negligeably improved efficiency, so we just always try
197 * both requests instead of doing version checks or caching. */
198 x11GetRequest(pContext, pContext->hVMWMajor, X11_VMW_RESOLUTION_REQUEST,
199 sizeof(struct X11VMWRESOLUTIONREQ),
200 (struct X11REQHEADER **)&pReqResolution);
201 pReqResolution->idX11Screen = DefaultScreen(pContext->pDisplay);
202 pReqResolution->w = pRects[0].w;
203 pReqResolution->h = pRects[0].h;
204 if (_XReply(pContext->pDisplay, (xReply *)&repResolution, 0, xTrue))
205 return;
206 /* What now? */
207 VBClFatalError(("%s failed to set resolution\n", __func__));
208}
209
210/** Call RRGetScreenInfo to wake up the server to the new modes. */
211static void x11GetScreenInfo(struct X11CONTEXT *pContext)
212{
213 struct X11RANDRGETSCREENREQ *pReqGetScreen;
214 uint8_t repGetScreen[X11_RANDR_GET_SCREEN_REPLY_SIZE];
215
216 if (!VALID_PTR(pContext->pDisplay))
217 VBClFatalError(("%s bad display argument.\n", __func__));
218 x11GetRequest(pContext, pContext->hRandRMajor, X11_RANDR_GET_SCREEN_REQUEST,
219 sizeof(struct X11RANDRGETSCREENREQ),
220 (struct X11REQHEADER **)&pReqGetScreen);
221 pReqGetScreen->hWindow = DefaultRootWindow(pContext->pDisplay);
222 _XSend(pContext->pDisplay, NULL, 0);
223 if (!_XReply(pContext->pDisplay, (xReply *)&repGetScreen, 0, xTrue))
224 VBClFatalError(("%s failed to set resolution\n", __func__));
225}
226
227static const char *getPidFilePath()
228{
229 return ".vboxclient-display-svga-x11.pid";
230}
231
232static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
233{
234 (void)ppInterface;
235 (void)fDaemonised;
236 struct X11CONTEXT x11Context = { NULL };
237 unsigned i;
238 int rc;
239 struct X11VMWRECT aRects[VMW_MAX_HEADS];
240 unsigned cHeads;
241
242 if (checkRecentLinuxKernel())
243 return VINF_SUCCESS;
244 x11Connect(&x11Context);
245 if (x11Context.pDisplay == NULL)
246 return VINF_SUCCESS;
247 /* Initialise the guest library. */
248 rc = VbglR3InitUser();
249 if (RT_FAILURE(rc))
250 VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc));
251 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
252 if (RT_FAILURE(rc))
253 VBClFatalError(("Failed to request display change events, rc=%Rrc\n", rc));
254 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
255 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
256 return VINF_SUCCESS;
257 if (RT_FAILURE(rc))
258 VBClFatalError(("Failed to register resizing support, rc=%Rrc\n", rc));
259 for (;;)
260 {
261 uint32_t events;
262 struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
263 uint32_t cDisplaysOut;
264
265 /* Query the first size without waiting. This lets us e.g. pick up
266 * the last event before a guest reboot when we start again after. */
267 rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, true);
268 if (RT_FAILURE(rc))
269 VBClFatalError(("Failed to get display change request, rc=%Rrc\n", rc));
270 if (cDisplaysOut > VMW_MAX_HEADS)
271 VBClFatalError(("Display change request contained, rc=%Rrc\n", rc));
272 for (i = 0, cHeads = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
273 {
274 if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
275 {
276 if ((i == 0) || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
277 {
278 aRects[cHeads].x = aDisplays[i].xOrigin < INT16_MAX
279 ? (int16_t)aDisplays[i].xOrigin : 0;
280 aRects[cHeads].y = aDisplays[i].yOrigin < INT16_MAX
281 ? (int16_t)aDisplays[i].yOrigin : 0;
282 } else {
283 aRects[cHeads].x = aRects[cHeads - 1].x + aRects[cHeads - 1].w;
284 aRects[cHeads].y = aRects[cHeads - 1].y;
285 }
286 aRects[cHeads].w = (int16_t)RT_MIN(aDisplays[i].cx, INT16_MAX);
287 aRects[cHeads].h = (int16_t)RT_MIN(aDisplays[i].cy, INT16_MAX);
288 ++cHeads;
289 }
290 }
291 x11SendHints(&x11Context, aRects, cHeads);
292 x11GetScreenInfo(&x11Context);
293 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events);
294 if (RT_FAILURE(rc))
295 VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc));
296 }
297}
298
299static struct VBCLSERVICE interface =
300{
301 getPidFilePath,
302 VBClServiceDefaultHandler, /* Init */
303 run,
304 VBClServiceDefaultCleanup
305}, *pInterface = &interface;
306
307struct VBCLSERVICE **VBClDisplaySVGAX11Service()
308{
309 return &pInterface;
310}
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