VirtualBox

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

Last change on this file since 98534 was 98534, checked in by vboxsync, 22 months ago

add/x11/VBoxClient: Style fix. Annotated structure members in the initializer while at it. bugref:10308

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