VirtualBox

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

Last change on this file since 99114 was 98535, checked in by vboxsync, 2 years ago

add/x11/VBoxClient: Style fixes. src\VBox\Additions\x11\VBoxClient\seamless-x11.cpp

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