VirtualBox

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

Last change on this file since 83123 was 83123, checked in by vboxsync, 5 years ago

bugref:9637. Some cleanup.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.3 KB
Line 
1/* $Id: display-svga-x11.cpp 83123 2020-02-20 11:38:39Z 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-2020 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#include "stdio.h"
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#include "seamless-x11.h"
57
58#ifdef RT_OS_LINUX
59# include <sys/ioctl.h>
60#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */
61# include <sys/ioccom.h>
62#endif
63
64#define USE_XRANDR_BIN
65
66
67struct X11VMWRECT /* xXineramaScreenInfo in Xlib headers. */
68{
69 int16_t x;
70 int16_t y;
71 uint16_t w;
72 uint16_t h;
73};
74AssertCompileSize(struct X11VMWRECT, 8);
75
76struct X11CONTEXT
77{
78 Display *pDisplay;
79 int hRandRMajor;
80 int hVMWMajor;
81 int hRandRMinor;
82 int hRandREventBase;
83 int hRandRErrorBase;
84 int hEventMask;
85 Window rootWindow;
86};
87
88#define MAX_MODE_NAME_LEN 64
89#define MAX_COMMAND_LINE_LEN 512
90#define MAX_MODE_LINE_LEN 512
91
92static const char *szDefaultOutputNamePrefix = "Virtual";
93static const char *pcszXrandr = "xrandr";
94static const char *pcszCvt = "cvt";
95/** The number of outputs (monitors, including disconnect ones) xrandr reports. */
96static int iOutputCount = 0;
97
98struct DRMCONTEXT
99{
100 RTFILE hDevice;
101};
102
103struct RANDROUTPUT
104{
105 int32_t x;
106 int32_t y;
107 uint32_t width;
108 uint32_t height;
109 bool fEnabled;
110};
111
112/** Forward declarations. */
113static void x11Connect(struct X11CONTEXT *pContext);
114
115static bool init(struct X11CONTEXT *pContext)
116{
117 if (!pContext)
118 return false;
119 x11Connect(pContext);
120 if (pContext->pDisplay == NULL)
121 return false;
122 return true;
123}
124
125static void x11Connect(struct X11CONTEXT *pContext)
126{
127 int dummy;
128 if (pContext->pDisplay != NULL)
129 VBClLogFatalError("%s called with bad argument\n", __func__);
130 pContext->pDisplay = XOpenDisplay(NULL);
131 if (pContext->pDisplay == NULL)
132 return;
133 if(!XQueryExtension(pContext->pDisplay, "VMWARE_CTRL",
134 &pContext->hVMWMajor, &dummy, &dummy))
135 {
136 XCloseDisplay(pContext->pDisplay);
137 pContext->pDisplay = NULL;
138 }
139 if (!XRRQueryExtension(pContext->pDisplay, &pContext->hRandREventBase, &pContext->hRandRErrorBase))
140 {
141 XCloseDisplay(pContext->pDisplay);
142 pContext->pDisplay = NULL;
143 }
144 if (!XRRQueryVersion(pContext->pDisplay, &pContext->hRandRMajor, &pContext->hRandRMinor))
145 {
146 XCloseDisplay(pContext->pDisplay);
147 pContext->pDisplay = NULL;
148 }
149 pContext->hEventMask = RRScreenChangeNotifyMask;
150 if (pContext->hRandRMinor >= 2)
151 pContext->hEventMask |= RRCrtcChangeNotifyMask |
152 RROutputChangeNotifyMask |
153 RROutputPropertyNotifyMask;
154 pContext->rootWindow = DefaultRootWindow(pContext->pDisplay);
155}
156
157/** run the xrandr command without options to get the total # of outputs (monitors) including
158 * disbaled ones. */
159static int determineOutputCount()
160{
161 int iCount = 0;
162 char szCommand[MAX_COMMAND_LINE_LEN];
163 RTStrPrintf(szCommand, sizeof(szCommand), "%s ", pcszXrandr);
164
165 FILE *pFile;
166 pFile = popen(szCommand, "r");
167 if (pFile == NULL)
168 {
169 VBClLogError("Failed to run %s\n", szCommand);
170 return VMW_MAX_HEADS;
171 }
172 char szModeLine[MAX_COMMAND_LINE_LEN];
173 while (fgets(szModeLine, sizeof(szModeLine), pFile) != NULL)
174 {
175 if (RTStrIStr(szModeLine, szDefaultOutputNamePrefix))
176 ++iCount;
177 }
178 if (iCount == 0)
179 iCount = VMW_MAX_HEADS;
180 return iCount;
181}
182
183/** Parse a single line of the output of xrandr command to extract Mode name.
184 * Assumed to be null terminated and in following format:
185 * e.g. 1016x559_vbox 59.70. where 1016x559_vbox is the mode name and at least one space in front
186 * Set mode name @p outPszModeName and return true if the name can be found, false otherwise.
187 * outPszModeNameis assumed to be of length MAX_MODE_NAME_LEN. */
188static bool parseModeLine(char *pszLine, char *outPszModeName)
189{
190 char *p = pszLine;
191 (void*)outPszModeName;
192 /* Copy chars to outPszModeName starting from the first non-space until outPszModeName ends with 'vbox'*/
193 size_t iNameIndex = 0;
194 bool fInitialSpace = true;
195
196 while(*p)
197 {
198 if (*p != ' ')
199 fInitialSpace = false;
200 if (!fInitialSpace && iNameIndex < MAX_MODE_NAME_LEN)
201 {
202 outPszModeName[iNameIndex] = *p;
203 ++iNameIndex;
204 if (iNameIndex >= 4)
205 {
206 if (outPszModeName[iNameIndex-1] == 'x' &&
207 outPszModeName[iNameIndex-2] == 'o' &&
208 outPszModeName[iNameIndex-3] == 'b')
209 break;
210 }
211 }
212 ++p;
213 }
214 outPszModeName[iNameIndex] = '\0';
215 return true;
216}
217
218/** Parse the output of the xrandr command to try to remove custom modes.
219 * This function assumes all the outputs are named as VirtualX. */
220static void removeCustomModesFromOutputs()
221{
222 char szCommand[MAX_COMMAND_LINE_LEN];
223 RTStrPrintf(szCommand, sizeof(szCommand), "%s ", pcszXrandr);
224
225 FILE *pFile;
226 pFile = popen(szCommand, "r");
227 if (pFile == NULL)
228 {
229 VBClLogError("Failed to run %s\n", szCommand);
230 return;
231 }
232 char szModeLine[MAX_COMMAND_LINE_LEN];
233 char szModeName[MAX_MODE_NAME_LEN];
234 int iCount = 0;
235 char szRmModeCommand[MAX_COMMAND_LINE_LEN];
236 char szDelModeCommand[MAX_COMMAND_LINE_LEN];
237
238 while (fgets(szModeLine, sizeof(szModeLine), pFile) != NULL)
239 {
240 if (RTStrIStr(szModeLine, szDefaultOutputNamePrefix))
241 {
242 ++iCount;
243 continue;
244 }
245 if (iCount > 0 && RTStrIStr(szModeLine, "_vbox"))
246 {
247 parseModeLine(szModeLine, szModeName);
248 if (strlen(szModeName) >= 4)
249 {
250 /* Delete the mode from the outout. this fails if the mode is currently in use. */
251 RTStrPrintf(szDelModeCommand, sizeof(szDelModeCommand), "%s --delmode Virtual%d %s", pcszXrandr, iCount, szModeName);
252 system(szDelModeCommand);
253 /* Delete the mode from the xserver. note that this will fail if some output has still has this mode (even if unused).
254 * thus this will fail most of the time. */
255 RTStrPrintf(szRmModeCommand, sizeof(szRmModeCommand), "%s --rmmode %s", pcszXrandr, szModeName);
256 system(szRmModeCommand);
257 }
258 }
259 }
260}
261
262static void getModeNameAndLineFromCVT(int iWidth, int iHeight, char *pszOutModeName, char *pszOutModeLine)
263{
264 char szCvtCommand[MAX_COMMAND_LINE_LEN];
265 const int iFreq = 60;
266 const int iMinNameLen = 4;
267 /* Make release builds happy. */
268 (void)iMinNameLen;
269 RTStrPrintf(szCvtCommand, sizeof(szCvtCommand), "%s %d %d %d", pcszCvt, iWidth, iHeight, iFreq);
270 FILE *pFile;
271 pFile = popen(szCvtCommand, "r");
272 if (pFile == NULL)
273 {
274 VBClLogError("Failed to run %s\n", szCvtCommand);
275 return;
276 }
277
278 char szModeLine[MAX_COMMAND_LINE_LEN];
279 while (fgets(szModeLine, sizeof(szModeLine), pFile) != NULL)
280 {
281 if (RTStrStr(szModeLine, "Modeline"))
282 {
283 if(szModeLine[strlen(szModeLine) - 1] == '\n')
284 szModeLine[strlen(szModeLine) - 1] = '\0';
285 size_t iFirstQu = RTStrOffCharOrTerm(szModeLine, '\"');
286 size_t iModeLineLen = strlen(szModeLine);
287 /* Make release builds happy. */
288 (void)iModeLineLen;
289 Assert(iFirstQu < iModeLineLen - iMinNameLen);
290
291 char *p = &(szModeLine[iFirstQu + 1]);
292 size_t iSecondQu = RTStrOffCharOrTerm(p, '_');
293 Assert(iSecondQu > iMinNameLen);
294 Assert(iSecondQu < MAX_MODE_NAME_LEN);
295 Assert(iSecondQu < iModeLineLen);
296
297 RTStrCopy(pszOutModeName, iSecondQu + 2, p);
298 RTStrCat(pszOutModeName, MAX_MODE_NAME_LEN, "vbox");
299 iSecondQu = RTStrOffCharOrTerm(p, '\"');
300 RTStrCopy(pszOutModeLine, MAX_MODE_LINE_LEN, &(szModeLine[iFirstQu + iSecondQu + 2]));
301 break;
302 }
303 }
304}
305
306/** Add a new mode to xserver and to the output */
307static void addMode(const char *pszModeName, const char *pszModeLine)
308{
309 char szNewModeCommand[MAX_COMMAND_LINE_LEN];
310 RTStrPrintf(szNewModeCommand, sizeof(szNewModeCommand), "%s --newmode \"%s\" %s", pcszXrandr, pszModeName, pszModeLine);
311 system(szNewModeCommand);
312
313 char szAddModeCommand[1024];
314 /* try to add the new mode to all possible outputs. we currently dont care if most the are disabled. */
315 for(int i = 0; i < iOutputCount; ++i)
316 {
317 RTStrPrintf(szAddModeCommand, sizeof(szAddModeCommand), "%s --addmode Virtual%d \"%s\"", pcszXrandr, i + 1, pszModeName);
318 system(szAddModeCommand);
319 }
320}
321
322static bool checkDefaultModes(struct RANDROUTPUT *pOutput, int iOutputIndex, char *pszModeName)
323{
324 const char szError[] = "cannot find mode";
325 char szXranrCommand[MAX_COMMAND_LINE_LEN];
326 RTStrPrintf(szXranrCommand, sizeof(szXranrCommand),
327 "%s --dryrun --output Virtual%u --mode %dx%d --pos %dx%d 2>/dev/stdout", pcszXrandr, iOutputIndex,
328 pOutput->width, pOutput->height, pOutput->x, pOutput->y);
329 RTStrPrintf(pszModeName, MAX_MODE_NAME_LEN, "%dx%d", pOutput->width, pOutput->height);
330 FILE *pFile;
331 pFile = popen(szXranrCommand, "r");
332 if (pFile == NULL)
333 {
334 VBClLogError("Failed to run %s\n", szXranrCommand);
335 return false;
336 }
337 char szResult[64];
338 if (fgets(szResult, sizeof(szResult), pFile) != NULL)
339 {
340 if (RTStrIStr(szResult, szError))
341 return false;
342 }
343 return true;
344}
345
346/** Construct the xrandr command which sets the whole monitor topology each time. */
347static void setXrandrModes(struct RANDROUTPUT *paOutputs)
348{
349 char szCommand[MAX_COMMAND_LINE_LEN];
350 RTStrPrintf(szCommand, sizeof(szCommand), "%s ", pcszXrandr);
351
352 for (int i = 0; i < iOutputCount; ++i)
353 {
354 char line[64];
355 if (!paOutputs[i].fEnabled)
356 RTStrPrintf(line, sizeof(line), "--output Virtual%u --off ", i + 1);
357 else
358 {
359 char szModeName[MAX_MODE_NAME_LEN];
360 char szModeLine[MAX_MODE_LINE_LEN];
361 /* Check if there is a default mode for the widthxheight and if not create and add a custom mode. */
362 if (!checkDefaultModes(&(paOutputs[i]), i + 1, szModeName))
363 {
364 getModeNameAndLineFromCVT(paOutputs[i].width, paOutputs[i].height, szModeName, szModeLine);
365 addMode(szModeName, szModeLine);
366 }
367 else
368 RTStrPrintf(szModeName, sizeof(szModeName), "%dx%d ", paOutputs[i].width, paOutputs[i].height);
369
370 RTStrPrintf(line, sizeof(line), "--output Virtual%u --mode %s --pos %dx%d ", i + 1,
371 szModeName, paOutputs[i].x, paOutputs[i].y);
372 }
373 RTStrCat(szCommand, sizeof(szCommand), line);
374 }
375 system(szCommand);
376 VBClLogInfo("=======xrandr topology command:=====\n%s\n", szCommand);
377 removeCustomModesFromOutputs();
378}
379
380static const char *getName()
381{
382 return "Display SVGA X11";
383}
384
385static const char *getPidFilePath()
386{
387 return ".vboxclient-display-svga-x11.pid";
388}
389
390static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
391{
392 iOutputCount = determineOutputCount();
393 (void)ppInterface;
394 (void)fDaemonised;
395 int rc;
396 uint32_t events;
397 /* Do not acknowledge the first event we query for to pick up old events,
398 * e.g. from before a guest reboot. */
399 bool fAck = false;
400 struct X11CONTEXT x11Context = { NULL };
401 if (!init(&x11Context))
402 return VINF_SUCCESS;
403 static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS];
404
405 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
406 if (RT_FAILURE(rc))
407 VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
408 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
409 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
410 return VINF_SUCCESS;
411 if (RT_FAILURE(rc))
412 VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
413
414 int eventMask = RRScreenChangeNotifyMask;
415 if (x11Context.hRandRMinor >= 2)
416 eventMask |= RRCrtcChangeNotifyMask |
417 RROutputChangeNotifyMask |
418 RROutputPropertyNotifyMask;
419 if (x11Context.hRandRMinor >= 4)
420 eventMask |= RRProviderChangeNotifyMask |
421 RRProviderPropertyNotifyMask |
422 RRResourceChangeNotifyMask;
423 for (;;)
424 {
425 struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
426 uint32_t cDisplaysOut;
427 /* Query the first size without waiting. This lets us e.g. pick up
428 * the last event before a guest reboot when we start again after. */
429 rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck);
430 fAck = true;
431 if (RT_FAILURE(rc))
432 VBClLogFatalError("Failed to get display change request, rc=%Rrc\n", rc);
433 if (cDisplaysOut > VMW_MAX_HEADS)
434 VBClLogFatalError("Display change request contained, rc=%Rrc\n", rc);
435 if (cDisplaysOut > 0)
436 {
437 for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
438 {
439 uint32_t idDisplay = aDisplays[i].idDisplay;
440 if (idDisplay >= VMW_MAX_HEADS)
441 continue;
442 aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags;
443 if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
444 {
445 if ((idDisplay == 0) || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
446 {
447 aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin;
448 aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin;
449 } else {
450 aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx;
451 aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin;
452 }
453 aMonitors[idDisplay].cx = aDisplays[i].cx;
454 aMonitors[idDisplay].cy = aDisplays[i].cy;
455 }
456 }
457 /* Create a whole topology and send it to xrandr. */
458 struct RANDROUTPUT aOutputs[VMW_MAX_HEADS];
459 int iRunningX = 0;
460 for (int j = 0; j < iOutputCount; ++j)
461 {
462 aOutputs[j].x = iRunningX;
463 aOutputs[j].y = aMonitors[j].yOrigin;
464 aOutputs[j].width = aMonitors[j].cx;
465 aOutputs[j].height = aMonitors[j].cy;
466 aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED);
467 if (aOutputs[j].fEnabled)
468 iRunningX += aOutputs[j].width;
469 }
470 setXrandrModes(aOutputs);
471 }
472 do
473 {
474 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events);
475 } while (rc == VERR_INTERRUPTED);
476 if (RT_FAILURE(rc))
477 VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc);
478 }
479}
480
481static struct VBCLSERVICE interface =
482{
483 getName,
484 getPidFilePath,
485 VBClServiceDefaultHandler, /* Init */
486 run,
487 VBClServiceDefaultCleanup
488}, *pInterface = &interface;
489
490struct VBCLSERVICE **VBClDisplaySVGAX11Service()
491{
492 return &pInterface;
493}
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