VirtualBox

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

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

scm --update-copyright-year

  • 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 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * X11 Seamless mode.
4 */
5
6/*
7 * Copyright (C) 2008-2022 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