VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp@ 90057

Last change on this file since 90057 was 90057, checked in by vboxsync, 3 years ago

Additions: X11: seamless: prevent hang on shutdown (2), bugref:10032.

When X11 is no longer running, XCloseDisplay() call will indefinitely
hang on internal X11 mutex preventing service from shutdown. In this
commit, on shutdown code path, we examine if XOpenDisplay() returns non-NULL,
meaning that X server is still running, and only after this XCloseDisplay()
is called. In theory, this check still might not work (in case of bad timing),
but in general it improves sutuation and service is able to terminate smoothly.

In addition, before calling XNextEvent(), X11 monitor thread attempts to examine
with XPending() if there are some events actually pending. This is needed because
XNextEvent() might wait forever for the next event to arrive and block service
termination.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 16.3 KB
Line 
1/* $Id: seamless-x11.cpp 90057 2021-07-06 11:19:50Z vboxsync $ */
2/** @file
3 * X11 Seamless mode.
4 */
5
6/*
7 * Copyright (C) 2008-2020 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
19/*********************************************************************************************************************************
20* Header files *
21*********************************************************************************************************************************/
22
23#include <iprt/errcore.h>
24#include <iprt/assert.h>
25#include <iprt/vector.h>
26#include <iprt/thread.h>
27#include <VBox/log.h>
28
29#include "seamless-x11.h"
30#include "VBoxClient.h"
31
32#include <X11/Xatom.h>
33#include <X11/Xmu/WinUtil.h>
34
35#include <limits.h>
36
37#ifdef TESTCASE
38#undef DefaultRootWindow
39#define DefaultRootWindow XDefaultRootWindow
40#endif
41
42/*****************************************************************************
43* Static functions *
44*****************************************************************************/
45
46static unsigned char *XXGetProperty (Display *aDpy, Window aWnd, Atom aPropType,
47 const char *aPropName, unsigned long *nItems)
48{
49 LogRelFlowFuncEnter();
50 Atom propNameAtom = XInternAtom (aDpy, aPropName,
51 True /* only_if_exists */);
52 if (propNameAtom == None)
53 {
54 return NULL;
55 }
56
57 Atom actTypeAtom = None;
58 int actFmt = 0;
59 unsigned long nBytesAfter = 0;
60 unsigned char *propVal = 0;
61 int rc = XGetWindowProperty (aDpy, aWnd, propNameAtom,
62 0, LONG_MAX, False /* delete */,
63 aPropType, &actTypeAtom, &actFmt,
64 nItems, &nBytesAfter, &propVal);
65 if (rc != Success)
66 return NULL;
67
68 LogRelFlowFuncLeave();
69 return propVal;
70}
71
72/**
73 * Initialise the guest and ensure that it is capable of handling seamless mode
74 *
75 * @param pHostCallback host callback.
76 * @returns true if it can handle seamless, false otherwise
77 */
78int SeamlessX11::init(PFNSENDREGIONUPDATE pHostCallback)
79{
80 int rc = VINF_SUCCESS;
81
82 LogRelFlowFuncEnter();
83 if (mHostCallback != NULL) /* Assertion */
84 {
85 VBClLogError("Attempting to initialise seamless guest object twice!\n");
86 return VERR_INTERNAL_ERROR;
87 }
88 if (!(mDisplay = XOpenDisplay(NULL)))
89 {
90 VBClLogError("Seamless guest object failed to acquire a connection to the display\n");
91 return VERR_ACCESS_DENIED;
92 }
93 mHostCallback = pHostCallback;
94 mEnabled = false;
95 unmonitorClientList();
96 LogRelFlowFuncLeaveRC(rc);
97 return rc;
98}
99
100/**
101 * Shutdown seamless event monitoring.
102 */
103void SeamlessX11::uninit(void)
104{
105 if (mHostCallback)
106 stop();
107 mHostCallback = NULL;
108
109 /* Before closing a Display, make sure X11 is still running. The indicator
110 * that is when XOpenDisplay() returns non NULL. If it is not a
111 * case, XCloseDisplay() will hang on internal X11 mutex forever. */
112 Display *pDisplay = XOpenDisplay(NULL);
113 if (pDisplay)
114 {
115 XCloseDisplay(pDisplay);
116 if (mDisplay)
117 {
118 XCloseDisplay(mDisplay);
119 mDisplay = NULL;
120 }
121 }
122
123 if (mpRects)
124 {
125 RTMemFree(mpRects);
126 mpRects = NULL;
127 }
128}
129
130/**
131 * Read information about currently visible windows in the guest and subscribe to X11
132 * events about changes to this information.
133 *
134 * @note This class does not contain its own event thread, so an external thread must
135 * call nextConfigurationEvent() for as long as events are wished.
136 * @todo This function should switch the guest to fullscreen mode.
137 */
138int SeamlessX11::start(void)
139{
140 int rc = VINF_SUCCESS;
141 /** Dummy values for XShapeQueryExtension */
142 int error, event;
143
144 LogRelFlowFuncEnter();
145 if (mEnabled)
146 return VINF_SUCCESS;
147 mSupportsShape = XShapeQueryExtension(mDisplay, &event, &error);
148 mEnabled = true;
149 monitorClientList();
150 rebuildWindowTree();
151 LogRelFlowFuncLeaveRC(rc);
152 return rc;
153}
154
155/** Stop reporting seamless events to the host. Free information about guest windows
156 and stop requesting updates. */
157void SeamlessX11::stop(void)
158{
159 LogRelFlowFuncEnter();
160 if (!mEnabled)
161 return;
162 mEnabled = false;
163 unmonitorClientList();
164 freeWindowTree();
165 LogRelFlowFuncLeave();
166}
167
168void SeamlessX11::monitorClientList(void)
169{
170 LogRelFlowFuncEnter();
171 XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask | SubstructureNotifyMask);
172}
173
174void SeamlessX11::unmonitorClientList(void)
175{
176 LogRelFlowFuncEnter();
177 XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask);
178}
179
180/**
181 * Recreate the table of toplevel windows of clients on the default root window of the
182 * X server.
183 */
184void SeamlessX11::rebuildWindowTree(void)
185{
186 LogRelFlowFuncEnter();
187 freeWindowTree();
188 addClients(DefaultRootWindow(mDisplay));
189 mChanged = true;
190}
191
192
193/**
194 * Look at the list of children of a virtual root window and add them to the list of clients
195 * if they belong to a client which is not a virtual root.
196 *
197 * @param hRoot the virtual root window to be examined
198 */
199void SeamlessX11::addClients(const Window hRoot)
200{
201 /** Unused out parameters of XQueryTree */
202 Window hRealRoot, hParent;
203 /** The list of children of the root supplied, raw pointer */
204 Window *phChildrenRaw = NULL;
205 /** The list of children of the root supplied, auto-pointer */
206 Window *phChildren;
207 /** The number of children of the root supplied */
208 unsigned cChildren;
209
210 LogRelFlowFuncEnter();
211 if (!XQueryTree(mDisplay, hRoot, &hRealRoot, &hParent, &phChildrenRaw, &cChildren))
212 return;
213 phChildren = phChildrenRaw;
214 for (unsigned i = 0; i < cChildren; ++i)
215 addClientWindow(phChildren[i]);
216 XFree(phChildrenRaw);
217 LogRelFlowFuncLeave();
218}
219
220
221void SeamlessX11::addClientWindow(const Window hWin)
222{
223 LogRelFlowFuncEnter();
224 XWindowAttributes winAttrib;
225 bool fAddWin = true;
226 Window hClient = XmuClientWindow(mDisplay, hWin);
227
228 if (isVirtualRoot(hClient))
229 fAddWin = false;
230 if (fAddWin && !XGetWindowAttributes(mDisplay, hWin, &winAttrib))
231 {
232 VBClLogError("Failed to get the window attributes for window %d\n", hWin);
233 fAddWin = false;
234 }
235 if (fAddWin && (winAttrib.map_state == IsUnmapped))
236 fAddWin = false;
237 XSizeHints dummyHints;
238 long dummyLong;
239 /* Apparently (?) some old kwin versions had unwanted client windows
240 * without normal hints. */
241 if (fAddWin && (!XGetWMNormalHints(mDisplay, hClient, &dummyHints,
242 &dummyLong)))
243 {
244 LogRelFlowFunc(("window %lu, client window %lu has no size hints\n", hWin, hClient));
245 fAddWin = false;
246 }
247 if (fAddWin)
248 {
249 XRectangle *pRects = NULL;
250 int cRects = 0, iOrdering;
251 bool hasShape = false;
252
253 LogRelFlowFunc(("adding window %lu, client window %lu\n", hWin,
254 hClient));
255 if (mSupportsShape)
256 {
257 XShapeSelectInput(mDisplay, hWin, ShapeNotifyMask);
258 pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects, &iOrdering);
259 if (!pRects)
260 cRects = 0;
261 else
262 {
263 if ( (cRects > 1)
264 || (pRects[0].x != 0)
265 || (pRects[0].y != 0)
266 || (pRects[0].width != winAttrib.width)
267 || (pRects[0].height != winAttrib.height)
268 )
269 hasShape = true;
270 }
271 }
272 mGuestWindows.addWindow(hWin, hasShape, winAttrib.x, winAttrib.y,
273 winAttrib.width, winAttrib.height, cRects,
274 pRects);
275 }
276 LogRelFlowFuncLeave();
277}
278
279
280/**
281 * Checks whether a window is a virtual root.
282 * @returns true if it is, false otherwise
283 * @param hWin the window to be examined
284 */
285bool SeamlessX11::isVirtualRoot(Window hWin)
286{
287 unsigned char *windowTypeRaw = NULL;
288 Atom *windowType;
289 unsigned long ulCount;
290 bool rc = false;
291
292 LogRelFlowFuncEnter();
293 windowTypeRaw = XXGetProperty(mDisplay, hWin, XA_ATOM, WM_TYPE_PROP, &ulCount);
294 if (windowTypeRaw != NULL)
295 {
296 windowType = (Atom *)(windowTypeRaw);
297 if ( (ulCount != 0)
298 && (*windowType == XInternAtom(mDisplay, WM_TYPE_DESKTOP_PROP, True)))
299 rc = true;
300 }
301 if (windowTypeRaw)
302 XFree(windowTypeRaw);
303 LogRelFlowFunc(("returning %RTbool\n", rc));
304 return rc;
305}
306
307DECLCALLBACK(int) VBoxGuestWinFree(VBoxGuestWinInfo *pInfo, void *pvParam)
308{
309 Display *pDisplay = (Display *)pvParam;
310
311 XShapeSelectInput(pDisplay, pInfo->Core.Key, 0);
312 delete pInfo;
313 return VINF_SUCCESS;
314}
315
316/**
317 * Free all information in the tree of visible windows
318 */
319void SeamlessX11::freeWindowTree(void)
320{
321 /* We use post-increment in the operation to prevent the iterator from being invalidated. */
322 LogRelFlowFuncEnter();
323 mGuestWindows.detachAll(VBoxGuestWinFree, mDisplay);
324 LogRelFlowFuncLeave();
325}
326
327
328/**
329 * Waits for a position or shape-related event from guest windows
330 *
331 * @note Called from the guest event thread.
332 */
333void SeamlessX11::nextConfigurationEvent(void)
334{
335 XEvent event;
336
337 LogRelFlowFuncEnter();
338 /* Start by sending information about the current window setup to the host. We do this
339 here because we want to send all such information from a single thread. */
340 if (mChanged && mEnabled)
341 {
342 updateRects();
343 mHostCallback(mpRects, mcRects);
344 }
345 mChanged = false;
346
347 if (XPending(mDisplay) > 0)
348 {
349 /* We execute this even when seamless is disabled, as it also waits for
350 * enable and disable notification. */
351 XNextEvent(mDisplay, &event);
352 } else
353 {
354 /* This function is called in a loop by upper layer. In order to
355 * prevent CPU spinning, sleep a bit before returning. */
356 RTThreadSleep(300 /* ms */);
357 return;
358 }
359
360 if (!mEnabled)
361 return;
362 switch (event.type)
363 {
364 case ConfigureNotify:
365 {
366 XConfigureEvent *pConf = &event.xconfigure;
367 LogRelFlowFunc(("configure event, window=%lu, x=%i, y=%i, w=%i, h=%i, send_event=%RTbool\n",
368 (unsigned long) pConf->window, (int) pConf->x,
369 (int) pConf->y, (int) pConf->width,
370 (int) pConf->height, pConf->send_event));
371 }
372 doConfigureEvent(event.xconfigure.window);
373 break;
374 case MapNotify:
375 LogRelFlowFunc(("map event, window=%lu, send_event=%RTbool\n",
376 (unsigned long) event.xmap.window,
377 event.xmap.send_event));
378 rebuildWindowTree();
379 break;
380 case PropertyNotify:
381 if ( event.xproperty.atom != XInternAtom(mDisplay, "_NET_CLIENT_LIST", True /* only_if_exists */)
382 || event.xproperty.window != DefaultRootWindow(mDisplay))
383 break;
384 LogRelFlowFunc(("_NET_CLIENT_LIST property event on root window\n"));
385 rebuildWindowTree();
386 break;
387 case VBoxShapeNotify: /* This is defined wrong in my X11 header files! */
388 LogRelFlowFunc(("shape event, window=%lu, send_event=%RTbool\n",
389 (unsigned long) event.xany.window,
390 event.xany.send_event));
391 /* the window member in xany is in the same place as in the shape event */
392 doShapeEvent(event.xany.window);
393 break;
394 case UnmapNotify:
395 LogRelFlowFunc(("unmap event, window=%lu, send_event=%RTbool\n",
396 (unsigned long) event.xunmap.window,
397 event.xunmap.send_event));
398 rebuildWindowTree();
399 break;
400 default:
401 break;
402 }
403 LogRelFlowFunc(("processed event\n"));
404}
405
406/**
407 * Handle a configuration event in the seamless event thread by setting the new position.
408 *
409 * @param hWin the window to be examined
410 */
411void SeamlessX11::doConfigureEvent(Window hWin)
412{
413 VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin);
414 if (pInfo)
415 {
416 XWindowAttributes winAttrib;
417
418 if (!XGetWindowAttributes(mDisplay, hWin, &winAttrib))
419 return;
420 pInfo->mX = winAttrib.x;
421 pInfo->mY = winAttrib.y;
422 pInfo->mWidth = winAttrib.width;
423 pInfo->mHeight = winAttrib.height;
424 mChanged = true;
425 }
426}
427
428/**
429 * Handle a window shape change event in the seamless event thread.
430 *
431 * @param hWin the window to be examined
432 */
433void SeamlessX11::doShapeEvent(Window hWin)
434{
435 LogRelFlowFuncEnter();
436 VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin);
437 if (pInfo)
438 {
439 XRectangle *pRects;
440 int cRects = 0, iOrdering;
441
442 pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects,
443 &iOrdering);
444 if (!pRects)
445 cRects = 0;
446 pInfo->mhasShape = true;
447 if (pInfo->mpRects)
448 XFree(pInfo->mpRects);
449 pInfo->mcRects = cRects;
450 pInfo->mpRects = pRects;
451 mChanged = true;
452 }
453 LogRelFlowFuncLeave();
454}
455
456/**
457 * Gets the list of visible rectangles
458 */
459RTRECT *SeamlessX11::getRects(void)
460{
461 return mpRects;
462}
463
464/**
465 * Gets the number of rectangles in the visible rectangle list
466 */
467size_t SeamlessX11::getRectCount(void)
468{
469 return mcRects;
470}
471
472RTVEC_DECL(RectList, RTRECT)
473
474static DECLCALLBACK(int) getRectsCallback(VBoxGuestWinInfo *pInfo, struct RectList *pRects)
475{
476 if (pInfo->mhasShape)
477 {
478 for (int i = 0; i < pInfo->mcRects; ++i)
479 {
480 RTRECT *pRect;
481
482 pRect = RectListPushBack(pRects);
483 if (!pRect)
484 return VERR_NO_MEMORY;
485 pRect->xLeft = pInfo->mX
486 + pInfo->mpRects[i].x;
487 pRect->yBottom = pInfo->mY
488 + pInfo->mpRects[i].y
489 + pInfo->mpRects[i].height;
490 pRect->xRight = pInfo->mX
491 + pInfo->mpRects[i].x
492 + pInfo->mpRects[i].width;
493 pRect->yTop = pInfo->mY
494 + pInfo->mpRects[i].y;
495 }
496 }
497 else
498 {
499 RTRECT *pRect;
500
501 pRect = RectListPushBack(pRects);
502 if (!pRect)
503 return VERR_NO_MEMORY;
504 pRect->xLeft = pInfo->mX;
505 pRect->yBottom = pInfo->mY
506 + pInfo->mHeight;
507 pRect->xRight = pInfo->mX
508 + pInfo->mWidth;
509 pRect->yTop = pInfo->mY;
510 }
511 return VINF_SUCCESS;
512}
513
514/**
515 * Updates the list of seamless rectangles
516 */
517int SeamlessX11::updateRects(void)
518{
519 LogRelFlowFuncEnter();
520 struct RectList rects = RTVEC_INITIALIZER;
521
522 if (mcRects != 0)
523 {
524 int rc = RectListReserve(&rects, mcRects * 2);
525 if (RT_FAILURE(rc))
526 return rc;
527 }
528 mGuestWindows.doWithAll((PFNVBOXGUESTWINCALLBACK)getRectsCallback, &rects);
529 if (mpRects)
530 RTMemFree(mpRects);
531 mcRects = RectListSize(&rects);
532 mpRects = RectListDetach(&rects);
533 LogRelFlowFuncLeave();
534 return VINF_SUCCESS;
535}
536
537/**
538 * Send a client event to wake up the X11 seamless event loop prior to stopping it.
539 *
540 * @note This function should only be called from the host event thread.
541 */
542bool SeamlessX11::interruptEventWait(void)
543{
544 bool rc = false;
545 Display *pDisplay = XOpenDisplay(NULL);
546
547 LogRelFlowFuncEnter();
548 if (pDisplay == NULL)
549 {
550 VBClLogError("Failed to open X11 display\n");
551 return false;
552 }
553
554 /* Message contents set to zero. */
555 XClientMessageEvent clientMessage = { ClientMessage, 0, 0, 0, 0, 0, 8 };
556
557 if (XSendEvent(pDisplay, DefaultRootWindow(mDisplay), false,
558 PropertyChangeMask, (XEvent *)&clientMessage))
559 rc = true;
560 XCloseDisplay(pDisplay);
561 LogRelFlowFunc(("returning %RTbool\n", rc));
562 return rc;
563}
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