VirtualBox

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

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

bugref:9637. small fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 28.2 KB
Line 
1/* $Id: display-svga-x11.cpp 83234 2020-03-09 08:10:44Z 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 <dlfcn.h>
41#include "VBoxClient.h"
42
43#include <VBox/VBoxGuestLib.h>
44
45#include <iprt/asm.h>
46#include <iprt/assert.h>
47#include <iprt/err.h>
48#include <iprt/file.h>
49#include <iprt/string.h>
50#include <iprt/thread.h>
51
52#include <X11/Xlibint.h>
53#include <X11/extensions/Xrandr.h>
54#include <X11/extensions/panoramiXproto.h>
55
56/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
57/** @todo if this ever changes, dynamically allocate resizeable arrays in the
58 * context structure. */
59#define VMW_MAX_HEADS 32
60/** Monitor positions array. Allocated here and deallocated in the class descructor. */
61RTPOINT *mpMonitorPositions;
62/** Thread to listen to some of the X server events. */
63RTTHREAD mX11MonitorThread = NIL_RTTHREAD;
64/** Shutdown indicator for the monitor thread. */
65static bool g_fMonitorThreadShutdown = false;
66
67
68typedef struct {
69 CARD8 reqType; /* always X_VMwareCtrlReqCode */
70 CARD8 VMwareCtrlReqType; /* always X_VMwareCtrlSetTopology */
71 CARD16 length B16;
72 CARD32 screen B32;
73 CARD32 number B32;
74 CARD32 pad1 B32;
75} xVMwareCtrlSetTopologyReq;
76#define sz_xVMwareCtrlSetTopologyReq 16
77
78#define X_VMwareCtrlSetTopology 2
79
80typedef struct {
81 BYTE type; /* X_Reply */
82 BYTE pad1;
83 CARD16 sequenceNumber B16;
84 CARD32 length B32;
85 CARD32 screen B32;
86 CARD32 pad2 B32;
87 CARD32 pad3 B32;
88 CARD32 pad4 B32;
89 CARD32 pad5 B32;
90 CARD32 pad6 B32;
91} xVMwareCtrlSetTopologyReply;
92#define sz_xVMwareCtrlSetTopologyReply 32
93
94struct X11VMWRECT
95{
96 int16_t x;
97 int16_t y;
98 uint16_t w;
99 uint16_t h;
100};
101AssertCompileSize(struct X11VMWRECT, 8);
102
103struct X11CONTEXT
104{
105 Display *pDisplay;
106 int hRandRMajor;
107 int hVMWCtrlMajorOpCode;
108 int hRandRMinor;
109 int hRandREventBase;
110 int hRandRErrorBase;
111 int hEventMask;
112 /** The number of outputs (monitors, including disconnect ones) xrandr reports. */
113 int hOutputCount;
114 Window rootWindow;
115 void *pRandLibraryHandle;
116 void (*pXRRSelectInput) (Display *, Window, int);
117 Bool (*pXRRQueryExtension) (Display *, int *, int *);
118 Status (*pXRRQueryVersion) (Display *, int *, int*);
119 XRRMonitorInfo* (*pXRRGetMonitors)(Display *, Window, Bool, int *);
120 void (*pXRRFreeMonitors)(XRRMonitorInfo *);
121};
122
123static X11CONTEXT x11Context;
124
125#define MAX_MODE_NAME_LEN 64
126#define MAX_COMMAND_LINE_LEN 512
127#define MAX_MODE_LINE_LEN 512
128
129static const char *szDefaultOutputNamePrefix = "Virtual";
130static const char *pcszXrandr = "xrandr";
131static const char *pcszCvt = "cvt";
132
133struct RANDROUTPUT
134{
135 int32_t x;
136 int32_t y;
137 uint32_t width;
138 uint32_t height;
139 bool fEnabled;
140};
141
142/** Forward declarations. */
143static void x11Connect();
144static int determineOutputCount();
145
146#define checkFunctionPtr(pFunction) \
147 do{ \
148 if (!pFunction) \
149 { \
150 VBClLogFatalError("Could not find symbol address\n"); \
151 dlclose(x11Context.pRandLibraryHandle); \
152 x11Context.pRandLibraryHandle = NULL; \
153 return VERR_NOT_FOUND; \
154 } \
155 }while(0)
156
157bool VMwareCtrlSetTopology(Display *dpy, int hExtensionMajorOpcode,
158 int screen, xXineramaScreenInfo extents[], int number)
159{
160 xVMwareCtrlSetTopologyReply rep;
161 xVMwareCtrlSetTopologyReq *req;
162
163 long len;
164
165 LockDisplay(dpy);
166
167 GetReq(VMwareCtrlSetTopology, req);
168 req->reqType = hExtensionMajorOpcode;
169 req->VMwareCtrlReqType = X_VMwareCtrlSetTopology;
170 req->screen = screen;
171 req->number = number;
172
173 len = ((long) number) << 1;
174 SetReqLen(req, len, len);
175 len <<= 2;
176 _XSend(dpy, (char *)extents, len);
177
178 if (!_XReply(dpy, (xReply *)&rep,
179 (SIZEOF(xVMwareCtrlSetTopologyReply) - SIZEOF(xReply)) >> 2,
180 xFalse))
181 {
182 UnlockDisplay(dpy);
183 SyncHandle();
184 return false;
185 }
186 UnlockDisplay(dpy);
187 SyncHandle();
188 return true;
189}
190
191/** This function assumes monitors are named as from Virtual1 to VirtualX. */
192static int getMonitorIdFromName(const char *sMonitorName)
193{
194 if (!sMonitorName)
195 return -1;
196 int iLen = strlen(sMonitorName);
197 if (iLen <= 0)
198 return -1;
199 int iBase = 10;
200 int iResult = 0;
201 for (int i = iLen - 1; i >= 0; --i)
202 {
203 /* Stop upon seeing the first non-numeric char. */
204 if (sMonitorName[i] < 48 || sMonitorName[i] > 57)
205 break;
206 iResult += (sMonitorName[i] - 48) * iBase / 10;
207 iBase *= 10;
208 }
209 return iResult;
210}
211
212static void sendMonitorPositions(RTPOINT *pPositions, size_t cPositions)
213{
214 if (cPositions && !pPositions)
215 {
216 VBClLogError(("Monitor position update called with NULL pointer!\n"));
217 return;
218 }
219 VbglR3SeamlessSendMonitorPositions(cPositions, pPositions);
220}
221
222static void queryMonitorPositions()
223{
224 static const int iSentinelPosition = -1;
225 if (mpMonitorPositions)
226 {
227 free(mpMonitorPositions);
228 mpMonitorPositions = NULL;
229 }
230
231 // XRRScreenResources *pScreenResources = XRRGetScreenResources(x11Context.pDisplay, DefaultRootWindow(x11Context.pDisplay));
232 // AssertReturnVoid(pScreenResources);
233 // XRRFreeScreenResources(pScreenResources);
234
235 int iMonitorCount = 0;
236 XRRMonitorInfo *pMonitorInfo = NULL;
237#ifdef WITH_DISTRO_XRAND_XINERAMA
238 pMonitorInfo = XRRGetMonitors(x11Context.pDisplay, DefaultRootWindow(x11Context.pDisplay), true, &iMonitorCount);
239#else
240 if (x11Context.pXRRGetMonitors)
241 x11Context.pXRRGetMonitors(x11Context.pDisplay, DefaultRootWindow(x11Context.pDisplay), true, &iMonitorCount);
242#endif
243 if (!pMonitorInfo)
244 return;
245 if (iMonitorCount == -1)
246 VBClLogError("Could not get monitor info\n");
247 else
248 {
249 mpMonitorPositions = (RTPOINT*)malloc(x11Context.hOutputCount * sizeof(RTPOINT));
250 /** @todo memset? */
251 for (int i = 0; i < x11Context.hOutputCount; ++i)
252 {
253 mpMonitorPositions[i].x = iSentinelPosition;
254 mpMonitorPositions[i].y = iSentinelPosition;
255 }
256 for (int i = 0; i < iMonitorCount; ++i)
257 {
258 int iMonitorID = getMonitorIdFromName(XGetAtomName(x11Context.pDisplay, pMonitorInfo[i].name)) - 1;
259 if (iMonitorID >= x11Context.hOutputCount || iMonitorID == -1)
260 continue;
261 VBClLogInfo("Monitor %d (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
262 i,
263 pMonitorInfo[i].width, pMonitorInfo[i].height,
264 pMonitorInfo[i].x, pMonitorInfo[i].y);
265 mpMonitorPositions[iMonitorID].x = pMonitorInfo[i].x;
266 mpMonitorPositions[iMonitorID].y = pMonitorInfo[i].y;
267 }
268 if (iMonitorCount > 0)
269 sendMonitorPositions(mpMonitorPositions, x11Context.hOutputCount);
270 }
271#ifdef WITH_DISTRO_XRAND_XINERAMA
272 XRRFreeMonitors(pMonitorInfo);
273#else
274 if (x11Context.pXRRFreeMonitors)
275 x11Context.pXRRFreeMonitors(pMonitorInfo);
276#endif
277}
278
279static void monitorRandREvents()
280{
281 XEvent event;
282 XNextEvent(x11Context.pDisplay, &event);
283 int eventTypeOffset = event.type - x11Context.hRandREventBase;
284 switch (eventTypeOffset)
285 {
286 case RRScreenChangeNotify:
287 VBClLogInfo("RRScreenChangeNotify\n");
288 queryMonitorPositions();
289 break;
290 case RRNotify:
291 VBClLogInfo("RRNotify\n");
292 break;
293 default:
294 VBClLogInfo("Unknown RR event: %d\n", event.type);
295 break;
296 }
297}
298
299/**
300 * @callback_method_impl{FNRTTHREAD}
301 */
302static DECLCALLBACK(int) x11MonitorThreadFunction(RTTHREAD ThreadSelf, void *pvUser)
303{
304 RT_NOREF(ThreadSelf, pvUser);
305 while (!ASMAtomicReadBool(&g_fMonitorThreadShutdown))
306 {
307 monitorRandREvents();
308 }
309 return 0;
310}
311
312static int startX11MonitorThread()
313{
314 int rc;
315
316 Assert(g_fMonitorThreadShutdown == false);
317 if (mX11MonitorThread == NIL_RTTHREAD)
318 {
319 rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThreadFunction, NULL /*pvUser*/, 0 /*cbStack*/,
320 RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "X11 events");
321 if (RT_FAILURE(rc))
322 VBClLogFatalError("Warning: failed to start X11 monitor thread (VBoxClient) rc=%Rrc!\n", rc);
323 }
324 else
325 rc = VINF_ALREADY_INITIALIZED;
326 return rc;
327}
328
329static int stopX11MonitorThread(void)
330{
331 int rc;
332 if (mX11MonitorThread != NIL_RTTHREAD)
333 {
334 ASMAtomicWriteBool(&g_fMonitorThreadShutdown, true);
335 /** @todo Send event to thread to get it out of XNextEvent. */
336 //????????
337 //mX11Monitor.interruptEventWait();
338 rc = RTThreadWait(mX11MonitorThread, RT_MS_1SEC, NULL /*prc*/);
339 if (RT_SUCCESS(rc))
340 {
341 mX11MonitorThread = NIL_RTTHREAD;
342 g_fMonitorThreadShutdown = false;
343 }
344 else
345 VBClLogError("Failed to stop X11 monitor thread, rc=%Rrc!\n", rc);
346 }
347 return rc;
348}
349
350static bool callVMWCTRL()
351{
352 const int hHeight = 600;
353 const int hWidth = 800;
354
355 xXineramaScreenInfo *extents = (xXineramaScreenInfo *)malloc(x11Context.hOutputCount * sizeof(xXineramaScreenInfo));
356 if (!extents)
357 return false;
358 int hRunningOffset = 0;
359 for (int i = 0; i < x11Context.hOutputCount; ++i)
360 {
361 extents[i].x_org = hRunningOffset;
362 extents[i].y_org = 0;
363 extents[i].width = hWidth;
364 extents[i].height = hHeight;
365 hRunningOffset += hWidth;
366 }
367 return VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
368 DefaultScreen(x11Context.pDisplay),
369 extents, x11Context.hOutputCount);
370}
371
372static bool init()
373{
374 x11Connect();
375 if (x11Context.pDisplay == NULL)
376 return false;
377 callVMWCTRL();
378 if (RT_FAILURE(startX11MonitorThread()))
379 return false;
380#ifdef WITH_DISTRO_XRAND_XINERAMA
381 XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, x11Context.hEventMask);
382#else
383 if (x11Context.pXRRSelectInput)
384 x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, x11Context.hEventMask);
385#endif
386 return true;
387}
388
389static void cleanup()
390{
391 if (mpMonitorPositions)
392 {
393 free(mpMonitorPositions);
394 mpMonitorPositions = NULL;
395 }
396 stopX11MonitorThread();
397 if (x11Context.pRandLibraryHandle)
398 {
399 dlclose(x11Context.pRandLibraryHandle);
400 x11Context.pRandLibraryHandle = NULL;
401 }
402#ifdef WITH_DISTRO_XRAND_XINERAMA
403 XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
404#else
405 if (x11Context.pXRRSelectInput)
406 x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
407#endif
408 XCloseDisplay(x11Context.pDisplay);
409}
410
411#ifndef WITH_DISTRO_XRAND_XINERAMA
412static int openLibRandR()
413{
414 x11Context.pRandLibraryHandle = dlopen("/usr/lib/x86_64-linux-gnu/libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */);
415
416 if (!x11Context.pRandLibraryHandle)
417 {
418 VBClLogFatalError("Could not locate libXranr for dlopen\n");
419 return VERR_NOT_FOUND;
420 }
421
422 *(void **)(&x11Context.pXRRSelectInput) = dlsym(x11Context.pRandLibraryHandle, "XRRSelectInput");
423 checkFunctionPtr(x11Context.pXRRSelectInput);
424
425 *(void **)(&x11Context.pXRRQueryExtension) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryExtension");
426 checkFunctionPtr(x11Context.pXRRQueryExtension);
427
428 *(void **)(&x11Context.pXRRQueryVersion) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryVersion");
429 checkFunctionPtr(x11Context.pXRRQueryVersion);
430
431 *(void **)(&x11Context.pXRRGetMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRGetMonitors");
432 checkFunctionPtr(x11Context.pXRRGetMonitors);
433
434 *(void **)(&x11Context.pXRRFreeMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeMonitors");
435 checkFunctionPtr(x11Context.pXRRFreeMonitors);
436
437 return VINF_SUCCESS;
438}
439#endif
440
441static void x11Connect()
442{
443 int dummy;
444 if (x11Context.pDisplay != NULL)
445 VBClLogFatalError("%s called with bad argument\n", __func__);
446 x11Context.pDisplay = XOpenDisplay(NULL);
447 if (x11Context.pDisplay == NULL)
448 return;
449 if (!XQueryExtension(x11Context.pDisplay, "VMWARE_CTRL",
450 &x11Context.hVMWCtrlMajorOpCode, &dummy, &dummy))
451 {
452 XCloseDisplay(x11Context.pDisplay);
453 x11Context.pDisplay = NULL;
454 return;
455 }
456 bool fSuccess = false;
457#ifdef WITH_DISTRO_XRAND_XINERAMA
458 fSuccess = XRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
459#else
460 if (x11Context.pXRRQueryExtension)
461 fSuccess = x11Context.pXRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
462#endif
463 if (fSuccess)
464 {
465 fSuccess = false;
466#ifdef WITH_DISTRO_XRAND_XINERAMA
467 fSuccess = XRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
468#else
469 if (x11Context.pXRRQueryVersion)
470 fSuccess = x11Context.pXRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
471#endif
472 if (!fSuccess)
473 {
474 XCloseDisplay(x11Context.pDisplay);
475 x11Context.pDisplay = NULL;
476 return;
477 }
478 }
479 x11Context.hEventMask = 0;
480 x11Context.hEventMask = RRScreenChangeNotifyMask;
481 if (x11Context.hRandRMinor >= 2)
482 x11Context.hEventMask |= RRCrtcChangeNotifyMask
483 | RROutputChangeNotifyMask
484 | RROutputPropertyNotifyMask;
485 x11Context.rootWindow = DefaultRootWindow(x11Context.pDisplay);
486 x11Context.hOutputCount = determineOutputCount();
487 x11Context.pXRRSelectInput = NULL;
488 x11Context.pRandLibraryHandle = NULL;
489 x11Context.pXRRQueryExtension = NULL;
490 x11Context.pXRRQueryVersion = NULL;
491 x11Context.pXRRGetMonitors = NULL;
492 x11Context.pXRRFreeMonitors = NULL;
493#ifndef WITH_DISTRO_XRAND_XINERAMA
494 if (openLibRandR() != VINF_SUCCESS)
495 {
496 XCloseDisplay(x11Context.pDisplay);
497 x11Context.pDisplay = NULL;
498 return;
499 }
500#endif
501}
502
503/** run the xrandr command without options to get the total # of outputs (monitors) including
504 * disbaled ones. */
505static int determineOutputCount()
506{
507 int iCount = 0;
508 char szCommand[MAX_COMMAND_LINE_LEN];
509 RTStrPrintf(szCommand, sizeof(szCommand), "%s ", pcszXrandr);
510
511 FILE *pFile;
512 pFile = popen(szCommand, "r");
513 if (pFile == NULL)
514 {
515 VBClLogError("Failed to run %s\n", szCommand);
516 return VMW_MAX_HEADS;
517 }
518 char szModeLine[MAX_COMMAND_LINE_LEN];
519 while (fgets(szModeLine, sizeof(szModeLine), pFile) != NULL)
520 {
521 if (RTStrIStr(szModeLine, szDefaultOutputNamePrefix))
522 ++iCount;
523 }
524 if (iCount == 0)
525 iCount = VMW_MAX_HEADS;
526 return iCount;
527}
528
529/** Parse a single line of the output of xrandr command to extract Mode name.
530 * Assumed to be null terminated and in following format:
531 * e.g. 1016x559_vbox 59.70. where 1016x559_vbox is the mode name and at least one space in front
532 * Set mode name @p outPszModeName and return true if the name can be found, false otherwise.
533 * outPszModeNameis assumed to be of length MAX_MODE_NAME_LEN. */
534static bool parseModeLine(char *pszLine, char *outPszModeName)
535{
536 char *p = pszLine;
537 (void*)outPszModeName;
538 /* Copy chars to outPszModeName starting from the first non-space until outPszModeName ends with 'vbox'*/
539 size_t iNameIndex = 0;
540 bool fInitialSpace = true;
541
542 while (*p)
543 {
544 if (*p != ' ')
545 fInitialSpace = false;
546 if (!fInitialSpace && iNameIndex < MAX_MODE_NAME_LEN)
547 {
548 outPszModeName[iNameIndex] = *p;
549 ++iNameIndex;
550 if (iNameIndex >= 4)
551 {
552 if ( outPszModeName[iNameIndex-1] == 'x'
553 && outPszModeName[iNameIndex-2] == 'o'
554 && outPszModeName[iNameIndex-3] == 'b')
555 break;
556 }
557 }
558 ++p;
559 }
560 outPszModeName[iNameIndex] = '\0';
561 return true;
562}
563
564/** Parse the output of the xrandr command to try to remove custom modes.
565 * This function assumes all the outputs are named as VirtualX. */
566static void removeCustomModesFromOutputs()
567{
568 char szCommand[MAX_COMMAND_LINE_LEN];
569 RTStrPrintf(szCommand, sizeof(szCommand), "%s ", pcszXrandr);
570
571 FILE *pFile;
572 pFile = popen(szCommand, "r");
573 if (pFile == NULL)
574 {
575 VBClLogError("Failed to run %s\n", szCommand);
576 return;
577 }
578 char szModeLine[MAX_COMMAND_LINE_LEN];
579 char szModeName[MAX_MODE_NAME_LEN];
580 int iCount = 0;
581 char szRmModeCommand[MAX_COMMAND_LINE_LEN];
582 char szDelModeCommand[MAX_COMMAND_LINE_LEN];
583
584 while (fgets(szModeLine, sizeof(szModeLine), pFile) != NULL)
585 {
586 if (RTStrIStr(szModeLine, szDefaultOutputNamePrefix))
587 {
588 ++iCount;
589 continue;
590 }
591 if (iCount > 0 && RTStrIStr(szModeLine, "_vbox"))
592 {
593 parseModeLine(szModeLine, szModeName);
594 if (strlen(szModeName) >= 4)
595 {
596 /* Delete the mode from the outout. this fails if the mode is currently in use. */
597 RTStrPrintf(szDelModeCommand, sizeof(szDelModeCommand), "%s --delmode Virtual%d %s", pcszXrandr, iCount, szModeName);
598 system(szDelModeCommand);
599 /* Delete the mode from the xserver. note that this will fail if some output has still has this mode (even if unused).
600 * thus this will fail most of the time. */
601 RTStrPrintf(szRmModeCommand, sizeof(szRmModeCommand), "%s --rmmode %s", pcszXrandr, szModeName);
602 system(szRmModeCommand);
603 }
604 }
605 }
606}
607
608static void getModeNameAndLineFromCVT(int iWidth, int iHeight, char *pszOutModeName, char *pszOutModeLine)
609{
610 char szCvtCommand[MAX_COMMAND_LINE_LEN];
611 const int iFreq = 60;
612 const int iMinNameLen = 4;
613 /* Make release builds happy. */
614 (void)iMinNameLen;
615 RTStrPrintf(szCvtCommand, sizeof(szCvtCommand), "%s %d %d %d", pcszCvt, iWidth, iHeight, iFreq);
616 FILE *pFile;
617 pFile = popen(szCvtCommand, "r");
618 if (pFile == NULL)
619 {
620 VBClLogError("Failed to run %s\n", szCvtCommand);
621 return;
622 }
623
624 char szModeLine[MAX_COMMAND_LINE_LEN];
625 while (fgets(szModeLine, sizeof(szModeLine), pFile) != NULL)
626 {
627 if (RTStrStr(szModeLine, "Modeline"))
628 {
629 if(szModeLine[strlen(szModeLine) - 1] == '\n')
630 szModeLine[strlen(szModeLine) - 1] = '\0';
631 size_t iFirstQu = RTStrOffCharOrTerm(szModeLine, '\"');
632 size_t iModeLineLen = strlen(szModeLine);
633 /* Make release builds happy. */
634 (void)iModeLineLen;
635 Assert(iFirstQu < iModeLineLen - iMinNameLen);
636
637 char *p = &(szModeLine[iFirstQu + 1]);
638 size_t iSecondQu = RTStrOffCharOrTerm(p, '_');
639 Assert(iSecondQu > iMinNameLen);
640 Assert(iSecondQu < MAX_MODE_NAME_LEN);
641 Assert(iSecondQu < iModeLineLen);
642
643 RTStrCopy(pszOutModeName, iSecondQu + 2, p);
644 RTStrCat(pszOutModeName, MAX_MODE_NAME_LEN, "vbox");
645 iSecondQu = RTStrOffCharOrTerm(p, '\"');
646 RTStrCopy(pszOutModeLine, MAX_MODE_LINE_LEN, &(szModeLine[iFirstQu + iSecondQu + 2]));
647 break;
648 }
649 }
650}
651
652/** Add a new mode to xserver and to the output */
653static void addMode(const char *pszModeName, const char *pszModeLine)
654{
655 char szNewModeCommand[MAX_COMMAND_LINE_LEN];
656 RTStrPrintf(szNewModeCommand, sizeof(szNewModeCommand), "%s --newmode \"%s\" %s", pcszXrandr, pszModeName, pszModeLine);
657 system(szNewModeCommand);
658
659 char szAddModeCommand[1024];
660 /* try to add the new mode to all possible outputs. we currently dont care if most the are disabled. */
661 for(int i = 0; i < x11Context.hOutputCount; ++i)
662 {
663 RTStrPrintf(szAddModeCommand, sizeof(szAddModeCommand), "%s --addmode Virtual%d \"%s\"", pcszXrandr, i + 1, pszModeName);
664 system(szAddModeCommand);
665 }
666}
667
668static bool checkDefaultModes(struct RANDROUTPUT *pOutput, int iOutputIndex, char *pszModeName)
669{
670 const char szError[] = "cannot find mode";
671 char szXranrCommand[MAX_COMMAND_LINE_LEN];
672 RTStrPrintf(szXranrCommand, sizeof(szXranrCommand),
673 "%s --dryrun --output Virtual%u --mode %dx%d --pos %dx%d 2>/dev/stdout", pcszXrandr, iOutputIndex,
674 pOutput->width, pOutput->height, pOutput->x, pOutput->y);
675 RTStrPrintf(pszModeName, MAX_MODE_NAME_LEN, "%dx%d", pOutput->width, pOutput->height);
676 FILE *pFile;
677 pFile = popen(szXranrCommand, "r");
678 if (pFile == NULL)
679 {
680 VBClLogError("Failed to run %s\n", szXranrCommand);
681 return false;
682 }
683 char szResult[64];
684 if (fgets(szResult, sizeof(szResult), pFile) != NULL)
685 {
686 if (RTStrIStr(szResult, szError))
687 return false;
688 }
689 return true;
690}
691
692/** Construct the xrandr command which sets the whole monitor topology each time. */
693static void setXrandrModes(struct RANDROUTPUT *paOutputs)
694{
695 char szCommand[MAX_COMMAND_LINE_LEN];
696 RTStrPrintf(szCommand, sizeof(szCommand), "%s ", pcszXrandr);
697
698 for (int i = 0; i < x11Context.hOutputCount; ++i)
699 {
700 char line[64];
701 if (!paOutputs[i].fEnabled)
702 RTStrPrintf(line, sizeof(line), "--output Virtual%u --off ", i + 1);
703 else
704 {
705 char szModeName[MAX_MODE_NAME_LEN];
706 char szModeLine[MAX_MODE_LINE_LEN];
707 /* Check if there is a default mode for the widthxheight and if not create and add a custom mode. */
708 if (!checkDefaultModes(&(paOutputs[i]), i + 1, szModeName))
709 {
710 getModeNameAndLineFromCVT(paOutputs[i].width, paOutputs[i].height, szModeName, szModeLine);
711 addMode(szModeName, szModeLine);
712 }
713 else
714 RTStrPrintf(szModeName, sizeof(szModeName), "%dx%d ", paOutputs[i].width, paOutputs[i].height);
715
716 RTStrPrintf(line, sizeof(line), "--output Virtual%u --mode %s --pos %dx%d ", i + 1,
717 szModeName, paOutputs[i].x, paOutputs[i].y);
718 }
719 RTStrCat(szCommand, sizeof(szCommand), line);
720 }
721 system(szCommand);
722 VBClLogInfo("=======xrandr topology command:=====\n%s\n", szCommand);
723 removeCustomModesFromOutputs();
724}
725
726static const char *getName()
727{
728 return "Display SVGA X11";
729}
730
731static const char *getPidFilePath()
732{
733 return ".vboxclient-display-svga-x11.pid";
734}
735
736static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
737{
738 RT_NOREF(ppInterface, fDaemonised);
739 int rc;
740 uint32_t events;
741 /* Do not acknowledge the first event we query for to pick up old events,
742 * e.g. from before a guest reboot. */
743 bool fAck = false;
744
745 if (!init())
746 return VINF_SUCCESS;
747 static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS];
748
749 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
750 if (RT_FAILURE(rc))
751 VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
752 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
753 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
754 return VINF_SUCCESS;
755 if (RT_FAILURE(rc))
756 VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
757
758 for (;;)
759 {
760 struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
761 uint32_t cDisplaysOut;
762 /* Query the first size without waiting. This lets us e.g. pick up
763 * the last event before a guest reboot when we start again after. */
764 rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck);
765 fAck = true;
766 if (RT_FAILURE(rc))
767 VBClLogFatalError("Failed to get display change request, rc=%Rrc\n", rc);
768 if (cDisplaysOut > VMW_MAX_HEADS)
769 VBClLogFatalError("Display change request contained, rc=%Rrc\n", rc);
770 if (cDisplaysOut > 0)
771 {
772 for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
773 {
774 uint32_t idDisplay = aDisplays[i].idDisplay;
775 if (idDisplay >= VMW_MAX_HEADS)
776 continue;
777 aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags;
778 if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
779 {
780 if (idDisplay == 0 || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
781 {
782 aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin;
783 aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin;
784 } else {
785 aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx;
786 aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin;
787 }
788 aMonitors[idDisplay].cx = aDisplays[i].cx;
789 aMonitors[idDisplay].cy = aDisplays[i].cy;
790 }
791 }
792 /* Create a whole topology and send it to xrandr. */
793 struct RANDROUTPUT aOutputs[VMW_MAX_HEADS];
794 int iRunningX = 0;
795 for (int j = 0; j < x11Context.hOutputCount; ++j)
796 {
797 aOutputs[j].x = iRunningX;
798 aOutputs[j].y = aMonitors[j].yOrigin;
799 aOutputs[j].width = aMonitors[j].cx;
800 aOutputs[j].height = aMonitors[j].cy;
801 aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED);
802 if (aOutputs[j].fEnabled)
803 iRunningX += aOutputs[j].width;
804 }
805 setXrandrModes(aOutputs);
806 }
807 do
808 {
809 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events);
810 } while (rc == VERR_INTERRUPTED);
811 if (RT_FAILURE(rc))
812 VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc);
813 }
814 cleanup();
815}
816
817static struct VBCLSERVICE interface =
818{
819 getName,
820 getPidFilePath,
821 VBClServiceDefaultHandler, /* Init */
822 run,
823 VBClServiceDefaultCleanup
824}, *pInterface = &interface;
825
826struct VBCLSERVICE **VBClDisplaySVGAX11Service()
827{
828 return &pInterface;
829}
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