1 | /* $Id: display.cpp 57357 2015-08-14 15:04:46Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * X11 guest client - display management.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2006-2012 Oracle Corporation
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
10 | * available from http://www.virtualbox.org. This file is free software;
|
---|
11 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
12 | * General Public License (GPL) as published by the Free Software
|
---|
13 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
14 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
15 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
16 | */
|
---|
17 |
|
---|
18 | /** @todo this should probably be replaced by something IPRT */
|
---|
19 | /* For system() and WEXITSTATUS() */
|
---|
20 | #include <stdlib.h>
|
---|
21 | #include <sys/types.h>
|
---|
22 | #include <sys/wait.h>
|
---|
23 | #include <errno.h>
|
---|
24 |
|
---|
25 | #include <X11/Xlib.h>
|
---|
26 | #include <X11/Xatom.h>
|
---|
27 |
|
---|
28 | #include <iprt/asm.h>
|
---|
29 | #include <iprt/assert.h>
|
---|
30 | #include <iprt/err.h>
|
---|
31 | #include <iprt/file.h>
|
---|
32 | #include <iprt/mem.h>
|
---|
33 | #include <iprt/string.h>
|
---|
34 | #include <iprt/thread.h>
|
---|
35 | #include <VBox/log.h>
|
---|
36 | #include <VBox/VMMDev.h>
|
---|
37 | #include <VBox/VBoxGuestLib.h>
|
---|
38 |
|
---|
39 | #include "VBoxClient.h"
|
---|
40 |
|
---|
41 | /* TESTING: Dynamic resizing and mouse integration toggling should work
|
---|
42 | * correctly with a range of X servers (pre-1.3, 1.3 and later under Linux, 1.3
|
---|
43 | * and later under Solaris) with Guest Additions installed. Switching to a
|
---|
44 | * virtual terminal while a user session is in place should disable dynamic
|
---|
45 | * resizing and cursor integration, switching back should re-enable them. */
|
---|
46 |
|
---|
47 | /** Most recent information received for a particular screen. */
|
---|
48 | struct screenInformation
|
---|
49 | {
|
---|
50 | unsigned cx;
|
---|
51 | unsigned cy;
|
---|
52 | unsigned cBPP;
|
---|
53 | unsigned x;
|
---|
54 | unsigned y;
|
---|
55 | bool fEnabled;
|
---|
56 | bool fUpdateSize;
|
---|
57 | volatile bool fUpdatePosition;
|
---|
58 | };
|
---|
59 |
|
---|
60 | /** Display magic number, start of a UUID. */
|
---|
61 | #define DISPLAYSTATE_MAGIC UINT32_C(0xf0029993)
|
---|
62 |
|
---|
63 | /** State information needed for the service. The main VBoxClient code provides
|
---|
64 | * the daemon logic needed by all services. */
|
---|
65 | struct DISPLAYSTATE
|
---|
66 | {
|
---|
67 | /** The service interface. */
|
---|
68 | struct VBCLSERVICE *pInterface;
|
---|
69 | /** Magic number for sanity checks. */
|
---|
70 | uint32_t magic;
|
---|
71 | /** Are we initialised yet? */
|
---|
72 | bool mfInit;
|
---|
73 | /** The connection to the server. */
|
---|
74 | Display *pDisplay;
|
---|
75 | /** Can we use version 1.2 or later of the RandR protocol here? */
|
---|
76 | bool fHaveRandR12;
|
---|
77 | /** The command argument to use for the xrandr binary. Currently only
|
---|
78 | * used to support the non-standard location on some Solaris systems -
|
---|
79 | * would it make sense to use absolute paths on all systems? */
|
---|
80 | const char *pcszXrandr;
|
---|
81 | /** The number of screens we are currently aware of. */
|
---|
82 | unsigned cScreensTracked;
|
---|
83 | /** Array of information about different screens. */
|
---|
84 | struct screenInformation *paScreenInformation;
|
---|
85 | };
|
---|
86 |
|
---|
87 | /** Thread to monitor and react to X server VT switches and exits. */
|
---|
88 | static int pfnMonitorThread(RTTHREAD self, void *pvUser)
|
---|
89 | {
|
---|
90 | struct DISPLAYSTATE *pState = (struct DISPLAYSTATE *)pvUser;
|
---|
91 | Display *pDisplay;
|
---|
92 | bool fHasVT = true;
|
---|
93 |
|
---|
94 | pDisplay = XOpenDisplay(NULL);
|
---|
95 | if (!pDisplay)
|
---|
96 | VBClFatalError(("Failed to open the X11 display\n"));
|
---|
97 | XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask);
|
---|
98 | while (true)
|
---|
99 | {
|
---|
100 | XEvent event;
|
---|
101 |
|
---|
102 | XNextEvent(pDisplay, &event);
|
---|
103 | /* This property is deleted when the server regains the virtual
|
---|
104 | * terminal. Force the main thread to call xrandr again, as old X
|
---|
105 | * servers could not handle it while switched out. */
|
---|
106 | if (pState->fHaveRandR12)
|
---|
107 | continue;
|
---|
108 | if ( event.type != PropertyNotify
|
---|
109 | || event.xproperty.state != PropertyDelete
|
---|
110 | || event.xproperty.window != DefaultRootWindow(pDisplay)
|
---|
111 | || event.xproperty.atom != XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False))
|
---|
112 | continue;
|
---|
113 | LogRel(("VBoxClient/Display: entered virtual terminal.\n"));
|
---|
114 | ASMAtomicWriteBool(&pState->paScreenInformation[0].fUpdateSize, true);
|
---|
115 | VbglR3InterruptEventWaits();
|
---|
116 | }
|
---|
117 | return VINF_SUCCESS; /* Should never be reached. */
|
---|
118 | }
|
---|
119 |
|
---|
120 | static int startMonitorThread(struct DISPLAYSTATE *pState)
|
---|
121 | {
|
---|
122 | int rc;
|
---|
123 |
|
---|
124 | rc = RTThreadCreate(NULL, pfnMonitorThread, (void *)pState, 0, RTTHREADTYPE_INFREQUENT_POLLER, 0, "VT_MONITOR");
|
---|
125 | if (rc != VINF_SUCCESS)
|
---|
126 | VBClFatalError(("Failed to start the VT monitor thread, rc=%Rrc\n", rc));
|
---|
127 | return VINF_SUCCESS;
|
---|
128 | }
|
---|
129 |
|
---|
130 | static int initDisplay(struct DISPLAYSTATE *pState)
|
---|
131 | {
|
---|
132 | char szCommand[256];
|
---|
133 | int status;
|
---|
134 |
|
---|
135 | /* Initialise the guest library. */
|
---|
136 | int rc = VbglR3InitUser();
|
---|
137 | if (RT_FAILURE(rc))
|
---|
138 | VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc));
|
---|
139 | pState->pDisplay = XOpenDisplay(NULL);
|
---|
140 | if (!pState->pDisplay)
|
---|
141 | return VERR_NOT_FOUND;
|
---|
142 | pState->fHaveRandR12 = false;
|
---|
143 | pState->pcszXrandr = "xrandr";
|
---|
144 | if (RTFileExists("/usr/X11/bin/xrandr"))
|
---|
145 | pState->pcszXrandr = "/usr/X11/bin/xrandr";
|
---|
146 | status = system(pState->pcszXrandr);
|
---|
147 | if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
|
---|
148 | VBClFatalError(("Failed to execute the xrandr utility.\n"));
|
---|
149 | RTStrPrintf(szCommand, sizeof(szCommand), "%s --q12", pState->pcszXrandr);
|
---|
150 | status = system(szCommand);
|
---|
151 | if (WEXITSTATUS(status) == 0)
|
---|
152 | pState->fHaveRandR12 = true;
|
---|
153 | pState->cScreensTracked = 0;
|
---|
154 | pState->paScreenInformation = NULL;
|
---|
155 | return VINF_SUCCESS;
|
---|
156 | }
|
---|
157 |
|
---|
158 | static void updateScreenInformation(struct DISPLAYSTATE *pState, unsigned cx, unsigned cy, unsigned cBPP, unsigned iDisplay,
|
---|
159 | unsigned x, unsigned y, bool fEnabled, bool fUpdatePosition)
|
---|
160 | {
|
---|
161 | uint32_t i;
|
---|
162 |
|
---|
163 | if (iDisplay >= pState->cScreensTracked)
|
---|
164 | {
|
---|
165 | pState->paScreenInformation =
|
---|
166 | (struct screenInformation *)RTMemRealloc(pState->paScreenInformation,
|
---|
167 | (iDisplay + 1) * sizeof(*pState->paScreenInformation));
|
---|
168 | if (!pState->paScreenInformation)
|
---|
169 | VBClFatalError(("Failed to re-allocate screen information.\n"));
|
---|
170 | for (i = pState->cScreensTracked; i < iDisplay + 1; ++i)
|
---|
171 | RT_ZERO(pState->paScreenInformation[i]);
|
---|
172 | pState->cScreensTracked = iDisplay + 1;
|
---|
173 | }
|
---|
174 | pState->paScreenInformation[iDisplay].cx = cx;
|
---|
175 | pState->paScreenInformation[iDisplay].cy = cy;
|
---|
176 | pState->paScreenInformation[iDisplay].cBPP = cBPP;
|
---|
177 | pState->paScreenInformation[iDisplay].x = x;
|
---|
178 | pState->paScreenInformation[iDisplay].y = y;
|
---|
179 | pState->paScreenInformation[iDisplay].fEnabled = fEnabled;
|
---|
180 | pState->paScreenInformation[iDisplay].fUpdateSize = true;
|
---|
181 | pState->paScreenInformation[iDisplay].fUpdatePosition = fUpdatePosition;
|
---|
182 | }
|
---|
183 |
|
---|
184 | static void updateSizeHintsProperty(struct DISPLAYSTATE *pState)
|
---|
185 | {
|
---|
186 | long *paSizeHints = (long *)RTMemTmpAllocZ(pState->cScreensTracked * sizeof(long) * 2);
|
---|
187 | unsigned i;
|
---|
188 |
|
---|
189 | if (paSizeHints == NULL)
|
---|
190 | VBClFatalError(("Failed to allocate size hint property memory.\n"));
|
---|
191 | for (i = 0; i < pState->cScreensTracked; ++i)
|
---|
192 | {
|
---|
193 | if (pState->paScreenInformation[i].fEnabled)
|
---|
194 | paSizeHints[2 * i] = (pState->paScreenInformation[i].cx & 0x8fff) << 16
|
---|
195 | | (pState->paScreenInformation[i].cy & 0x8fff);
|
---|
196 | else if (pState->paScreenInformation[i].cx != 0 && pState->paScreenInformation[i].cy != 0)
|
---|
197 | paSizeHints[2 * i] = -1;
|
---|
198 | if ( pState->paScreenInformation[i].fEnabled
|
---|
199 | && pState->paScreenInformation[i].fUpdatePosition)
|
---|
200 | paSizeHints[2 * i + 1] = (pState->paScreenInformation[i].x & 0x8fff) << 16
|
---|
201 | | (pState->paScreenInformation[i].y & 0x8fff);
|
---|
202 | else
|
---|
203 | paSizeHints[2 * i + 1] = -1;
|
---|
204 | }
|
---|
205 | XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOX_SIZE_HINTS", 0),
|
---|
206 | XA_INTEGER, 32, PropModeReplace, (unsigned char *)paSizeHints, pState->cScreensTracked * 2);
|
---|
207 | XFlush(pState->pDisplay);
|
---|
208 | RTMemTmpFree(paSizeHints);
|
---|
209 | }
|
---|
210 |
|
---|
211 | static void notifyXServerRandR11(struct DISPLAYSTATE *pState)
|
---|
212 | {
|
---|
213 | char szCommand[256];
|
---|
214 |
|
---|
215 | /** @note The xrandr command can fail if something else accesses RandR at
|
---|
216 | * the same time. We just ignore failure for now and let the user try
|
---|
217 | * again as we do not know what someone else is doing. */
|
---|
218 | if ( pState->paScreenInformation[0].fUpdateSize
|
---|
219 | && pState->paScreenInformation[0].cx > 0 && pState->paScreenInformation[0].cy > 0)
|
---|
220 | {
|
---|
221 | int ret;
|
---|
222 |
|
---|
223 | RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u",
|
---|
224 | pState->pcszXrandr, pState->paScreenInformation[0].cx, pState->paScreenInformation[0].cy);
|
---|
225 | ret = system(szCommand);
|
---|
226 | LogRel(("VBoxClient/Display: executed \"%s\", returned %d.\n", szCommand, ret));
|
---|
227 | pState->paScreenInformation[0].fUpdateSize = false;
|
---|
228 | }
|
---|
229 | }
|
---|
230 |
|
---|
231 | static void updateMouseCapabilities(struct DISPLAYSTATE *pState)
|
---|
232 | {
|
---|
233 | uint32_t fFeatures = 0;
|
---|
234 | int rc;
|
---|
235 |
|
---|
236 | rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
|
---|
237 |
|
---|
238 | if (rc != VINF_SUCCESS)
|
---|
239 | VBClFatalError(("Failed to get mouse status, rc=%Rrc\n", rc));
|
---|
240 | XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay),
|
---|
241 | XInternAtom(pState->pDisplay, "VBOX_MOUSE_CAPABILITIES", 0), XA_INTEGER, 32, PropModeReplace,
|
---|
242 | (unsigned char *)&fFeatures, 1);
|
---|
243 | XFlush(pState->pDisplay);
|
---|
244 | }
|
---|
245 |
|
---|
246 | /**
|
---|
247 | * Display change request monitor thread function.
|
---|
248 | */
|
---|
249 | static void runDisplay(struct DISPLAYSTATE *pState)
|
---|
250 | {
|
---|
251 | int rc;
|
---|
252 | unsigned i, cScreensTracked;
|
---|
253 | uint32_t fModeSet = false;
|
---|
254 |
|
---|
255 | LogRelFlowFunc(("\n"));
|
---|
256 | rc = VbglR3VideoModeGetHighestSavedScreen(&cScreensTracked);
|
---|
257 | if (rc != VINF_SUCCESS && rc != VERR_NOT_SUPPORTED)
|
---|
258 | VBClFatalError(("Failed to get the number of saved screen modes, rc=%Rrc\n", rc));
|
---|
259 | /* Make sure that we have an entry for screen 1 at least. */
|
---|
260 | updateScreenInformation(pState, 1024, 768, 0, 1, 0, 0, true, false);
|
---|
261 | if (rc == VINF_SUCCESS)
|
---|
262 | {
|
---|
263 | /* The "8" is for the sanity test below. */
|
---|
264 | for (i = 0; i < RT_MAX(cScreensTracked + 1, 8); ++i)
|
---|
265 | {
|
---|
266 | unsigned cx = 0, cy = 0, cBPP = 0, x = 0, y = 0;
|
---|
267 | bool fEnabled = true;
|
---|
268 |
|
---|
269 | rc = VbglR3RetrieveVideoMode(i, &cx, &cy, &cBPP, &x, &y,
|
---|
270 | &fEnabled);
|
---|
271 | /* Sanity test for VbglR3VideoModeGetHighestSavedScreen(). */
|
---|
272 | if (i > cScreensTracked && rc != VERR_NOT_FOUND)
|
---|
273 | VBClFatalError(("Internal error retrieving the number of saved screen modes.\n"));
|
---|
274 | if (rc == VINF_SUCCESS)
|
---|
275 | updateScreenInformation(pState, cx, cy, cBPP, i, x, y, fEnabled, true);
|
---|
276 | }
|
---|
277 | }
|
---|
278 | /* Semantics: when VBOX_HAS_GRAPHICS is set, the X server driver assumes
|
---|
279 | * that a client capable of forwarding mode hints will be present for the
|
---|
280 | * rest of the X session. If we crash things will not work as they should.
|
---|
281 | * I thought that preferable to implementing complex crash-handling logic.
|
---|
282 | */
|
---|
283 | XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOX_HAS_GRAPHICS", 0),
|
---|
284 | XA_INTEGER, 32, PropModeReplace, (unsigned char *)&fModeSet, 1);
|
---|
285 | while (true)
|
---|
286 | {
|
---|
287 | uint32_t fEvents;
|
---|
288 | updateMouseCapabilities(pState);
|
---|
289 | updateSizeHintsProperty(pState);
|
---|
290 | if (!pState->fHaveRandR12)
|
---|
291 | notifyXServerRandR11(pState);
|
---|
292 | rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST | VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED, RT_INDEFINITE_WAIT,
|
---|
293 | &fEvents);
|
---|
294 | /* Interrupted is used to re-set the mode without changing it. */
|
---|
295 | if (rc == VERR_INTERRUPTED)
|
---|
296 | rc = VINF_SUCCESS;
|
---|
297 | if (RT_FAILURE(rc)) /* VERR_NO_MEMORY? */
|
---|
298 | VBClFatalError(("event wait failed, rc=%Rrc\n", rc));
|
---|
299 | /* If it is a size hint, set the new size. */
|
---|
300 | if (fEvents & VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST)
|
---|
301 | {
|
---|
302 | uint32_t cx = 0, cy = 0, cBPP = 0, iDisplay = 0, x = 0, y = 0;
|
---|
303 | bool fEnabled = true, fUpdatePosition = true;
|
---|
304 | VMMDevSeamlessMode Mode;
|
---|
305 |
|
---|
306 | rc = VbglR3GetDisplayChangeRequest(&cx, &cy, &cBPP, &iDisplay,
|
---|
307 | &x, &y, &fEnabled,
|
---|
308 | &fUpdatePosition, true);
|
---|
309 | if (rc != VINF_SUCCESS)
|
---|
310 | VBClFatalError(("Failed to get display change request, rc=%Rrc\n",
|
---|
311 | rc));
|
---|
312 | else
|
---|
313 | LogRelFlowFunc(("Got size hint from host cx=%d, cy=%d, bpp=%d, iDisplay=%d, x=%d, y=%d fEnabled=%d\n",
|
---|
314 | cx, cy, cBPP, iDisplay, x, y, fEnabled));
|
---|
315 | if (iDisplay > INT32_MAX)
|
---|
316 | VBClFatalError(("Received a size hint for too high display number %u\n",
|
---|
317 | (unsigned) iDisplay));
|
---|
318 | updateScreenInformation(pState, cx, cy, cBPP, iDisplay, x, y, fEnabled, fUpdatePosition);
|
---|
319 | rc = VbglR3SeamlessGetLastEvent(&Mode);
|
---|
320 | if (RT_FAILURE(rc))
|
---|
321 | VBClFatalError(("Failed to check seamless mode, rc=%Rrc\n", rc));
|
---|
322 | if (Mode == VMMDev_Seamless_Disabled)
|
---|
323 | {
|
---|
324 | rc = VbglR3SaveVideoMode(iDisplay, cx, cy, cBPP, x, y,
|
---|
325 | fEnabled);
|
---|
326 | if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED)
|
---|
327 | VBClFatalError(("Failed to save size hint, rc=%Rrc\n", rc));
|
---|
328 | }
|
---|
329 | }
|
---|
330 | }
|
---|
331 | }
|
---|
332 |
|
---|
333 | static const char *getPidFilePath()
|
---|
334 | {
|
---|
335 | return ".vboxclient-display.pid";
|
---|
336 | }
|
---|
337 |
|
---|
338 | static struct DISPLAYSTATE *getStateFromInterface(struct VBCLSERVICE **ppInterface)
|
---|
339 | {
|
---|
340 | struct DISPLAYSTATE *pSelf = (struct DISPLAYSTATE *)ppInterface;
|
---|
341 | if (pSelf->magic != DISPLAYSTATE_MAGIC)
|
---|
342 | VBClFatalError(("Bad display service object!\n"));
|
---|
343 | return pSelf;
|
---|
344 | }
|
---|
345 |
|
---|
346 | static int init(struct VBCLSERVICE **ppInterface)
|
---|
347 | {
|
---|
348 | struct DISPLAYSTATE *pSelf = getStateFromInterface(ppInterface);
|
---|
349 | int rc;
|
---|
350 |
|
---|
351 | if (pSelf->mfInit)
|
---|
352 | return VERR_WRONG_ORDER;
|
---|
353 | rc = initDisplay(pSelf);
|
---|
354 | if (RT_FAILURE(rc))
|
---|
355 | return rc;
|
---|
356 | rc = VbglR3CtlFilterMask(VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED | VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
|
---|
357 | if (RT_FAILURE(rc))
|
---|
358 | VBClFatalError(("Failed to set filter mask, rc=%Rrc.\n", rc));
|
---|
359 | if (RT_SUCCESS(rc))
|
---|
360 | pSelf->mfInit = true;
|
---|
361 | return rc;
|
---|
362 | }
|
---|
363 |
|
---|
364 | static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
|
---|
365 | {
|
---|
366 | struct DISPLAYSTATE *pSelf = getStateFromInterface(ppInterface);
|
---|
367 | int rc;
|
---|
368 |
|
---|
369 | if (!pSelf->mfInit)
|
---|
370 | return VERR_WRONG_ORDER;
|
---|
371 | rc = startMonitorThread(pSelf);
|
---|
372 | if (RT_FAILURE(rc))
|
---|
373 | VBClFatalError(("Failed to start the VT monitor thread: %Rrc\n", rc));
|
---|
374 | runDisplay(pSelf);
|
---|
375 | return VERR_INTERNAL_ERROR; /* "Should never reach here." */
|
---|
376 | }
|
---|
377 |
|
---|
378 | static void cleanup(struct VBCLSERVICE **ppInterface)
|
---|
379 | {
|
---|
380 | NOREF(ppInterface);
|
---|
381 | VbglR3CtlFilterMask(0, VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED | VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST);
|
---|
382 | VbglR3Term();
|
---|
383 | }
|
---|
384 |
|
---|
385 | struct VBCLSERVICE vbclDisplayInterface =
|
---|
386 | {
|
---|
387 | getPidFilePath,
|
---|
388 | init,
|
---|
389 | run,
|
---|
390 | cleanup
|
---|
391 | };
|
---|
392 |
|
---|
393 | struct VBCLSERVICE **VBClGetDisplayService()
|
---|
394 | {
|
---|
395 | struct DISPLAYSTATE *pService = (struct DISPLAYSTATE *)RTMemAlloc(sizeof(*pService));
|
---|
396 |
|
---|
397 | if (!pService)
|
---|
398 | VBClFatalError(("Out of memory\n"));
|
---|
399 | pService->pInterface = &vbclDisplayInterface;
|
---|
400 | pService->magic = DISPLAYSTATE_MAGIC;
|
---|
401 | pService->mfInit = false;
|
---|
402 | return &pService->pInterface;
|
---|
403 | }
|
---|