VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp@ 94330

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

Additions: Linux: VBoxDRMClient: sync screen layout with DE representation, ​bugref:10134.

In some cases, screen layout which is reported by DE might differ from what was reported
to VBoxDRMClient by host. In this commit, when receiving DE notification, VBoxClient will
report (to VBoxDRMClient) not just display offsets, but entire layout data. VBoxDRMClient
will then validate this data and apply it to DRM stack if needed.

In particular, sometimes DE might report screen layout which can have overlapping displays.
Such layout will be fixed by VBoxDRMClient (display offsets will be realigned) and re-injected
into DRM stack.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.2 KB
Line 
1/* $Id: display-helper-generic.cpp 94076 2022-03-03 15:46:36Z vboxsync $ */
2/** @file
3 * Guest Additions - Generic Desktop Environment helper.
4 *
5 * A generic helper for X11 Client which performs Desktop Environment
6 * specific actions utilizing libXrandr.
7 */
8
9/*
10 * Copyright (C) 2006-2022 Oracle Corporation
11 *
12 * This file is part of VirtualBox Open Source Edition (OSE), as
13 * available from http://www.virtualbox.org. This file is free software;
14 * you can redistribute it and/or modify it under the terms of the GNU
15 * General Public License (GPL) as published by the Free Software
16 * Foundation, in version 2 as it comes in the "COPYING" file of the
17 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19 */
20
21#include "VBoxClient.h"
22#include "display-helper.h"
23
24#include <stdio.h>
25#include <stdlib.h>
26
27#include <VBox/log.h>
28#include <VBox/xrandr.h>
29
30#include <iprt/errcore.h>
31#include <iprt/asm.h>
32#include <iprt/thread.h>
33#include <iprt/mem.h>
34#include <iprt/list.h>
35
36/** Load libxrandr symbols needed for us. */
37#include <VBox/xrandr.h>
38/* Declarations of the functions that we need from libXrandr. */
39#define VBOX_XRANDR_GENERATE_BODY
40#include <VBox/xrandr-calls.h>
41
42#include <X11/Xlibint.h>
43
44/** Name of Display Change Monitor thread. */
45#define VBCL_HLP_DCM_THREAD_NAME "dcm-task"
46
47/** Display Change Monitor thread. */
48static RTTHREAD g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
49
50/** Global flag which is triggered when service requested to shutdown. */
51static bool volatile g_fShutdown;
52
53/** Node of monitors info list. */
54typedef struct vbcl_hlp_generic_monitor_list_t
55{
56 /** List node. */
57 RTLISTNODE Node;
58 /** Pointer to xRandr monitor info. */
59 XRRMonitorInfo *pMonitorInfo;
60} vbcl_hlp_generic_monitor_list_t;
61
62/** Pointer to display change event notification callback (set by external function call). */
63static FNDISPLAYOFFSETCHANGE *g_pfnDisplayOffsetChangeCb;
64
65/**
66 * Determine monitor name strings order in a list of monitors which is sorted in ascending way.
67 *
68 * @return TRUE if first name should go first in a list, FALSE otherwise.
69 * @param pszName1 First monitor name.
70 * @param pszName2 Second monitor name.
71 */
72static bool vbcl_hlp_generic_order_names(char *pszName1, char *pszName2)
73{
74 AssertReturn(pszName1, false);
75 AssertReturn(pszName2, false);
76
77 char *pszFirst = pszName1;
78 char *pszSecond = pszName2;
79
80 while (*pszFirst && *pszSecond)
81 {
82 if (*pszFirst < *pszSecond)
83 return true;
84
85 pszFirst++;
86 pszSecond++;
87 }
88
89 return false;
90}
91
92/**
93 * Insert monitor info into the list sorted ascending.
94 *
95 * @return IPRT status code.
96 * @param pDisplay X11 display handle to fetch monitor name string from.
97 * @param pListHead Head of monitors info list.
98 * @param pMonitorInfo Monitor info ti be inserted into the list.
99 */
100static int vbcl_hlp_generic_monitor_list_insert_sorted(
101 Display *pDisplay, vbcl_hlp_generic_monitor_list_t *pListHead, XRRMonitorInfo *pMonitorInfo)
102{
103 vbcl_hlp_generic_monitor_list_t *pNode = (vbcl_hlp_generic_monitor_list_t *)RTMemAllocZ(sizeof(vbcl_hlp_generic_monitor_list_t));
104 vbcl_hlp_generic_monitor_list_t *pNodeIter;
105 char *pszMonitorName;
106
107 AssertReturn(pNode, VERR_NO_MEMORY);
108
109 pNode->pMonitorInfo = pMonitorInfo;
110
111 if (RTListIsEmpty(&pListHead->Node))
112 {
113 RTListNodeInsertAfter(&pListHead->Node, &pNode->Node);
114 return VINF_SUCCESS;
115 }
116
117 pszMonitorName = XGetAtomName(pDisplay, pMonitorInfo->name);
118 AssertReturn(pszMonitorName, VERR_NO_MEMORY);
119
120 RTListForEach(&pListHead->Node, pNodeIter, vbcl_hlp_generic_monitor_list_t, Node)
121 {
122 char *pszIterMonitorName = XGetAtomName(pDisplay, pNodeIter->pMonitorInfo->name);
123
124 if (vbcl_hlp_generic_order_names(pszMonitorName, pszIterMonitorName))
125 {
126 RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
127 XFree((void *)pszIterMonitorName);
128 XFree((void *)pszMonitorName);
129 return VINF_SUCCESS;
130 }
131
132 XFree((void *)pszIterMonitorName);
133 }
134
135 XFree((void *)pszMonitorName);
136
137 /* If we reached the end of the list, it means that monitor
138 * should be placed in the end (according to alphabetical sorting). */
139 RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
140
141 return VINF_SUCCESS;
142}
143
144/**
145 * Release monitors info list resources.
146 *
147 * @param pListHead List head.
148 */
149static void vbcl_hlp_generic_free_monitor_list(vbcl_hlp_generic_monitor_list_t *pListHead)
150{
151 vbcl_hlp_generic_monitor_list_t *pEntry, *pNextEntry;
152
153 RTListForEachSafe(&pListHead->Node, pEntry, pNextEntry, vbcl_hlp_generic_monitor_list_t, Node)
154 {
155 RTListNodeRemove(&pEntry->Node);
156 RTMemFree(pEntry);
157 }
158}
159
160/**
161 * Handle received RRScreenChangeNotify event.
162 *
163 * @param pDisplay X11 display handle.
164 */
165static void vbcl_hlp_generic_process_display_change_event(Display *pDisplay)
166{
167 int iCount;
168 uint32_t idxDisplay = 0;
169 XRRMonitorInfo *pMonitorsInfo = XRRGetMonitors(pDisplay, DefaultRootWindow(pDisplay), true, &iCount);
170 if (pMonitorsInfo && iCount > 0 && iCount < VBOX_DRMIPC_MONITORS_MAX)
171 {
172 int rc;
173 vbcl_hlp_generic_monitor_list_t pMonitorsInfoList, *pIter;
174 struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX];
175
176 RTListInit(&pMonitorsInfoList.Node);
177
178 /* Put monitors info into sorted (by monitor name) list. */
179 for (int i = 0; i < iCount; i++)
180 {
181 rc = vbcl_hlp_generic_monitor_list_insert_sorted(pDisplay, &pMonitorsInfoList, &pMonitorsInfo[i]);
182 if (RT_FAILURE(rc))
183 {
184 VBClLogError("unable to fill monitors info list, rc=%Rrc\n", rc);
185 break;
186 }
187 }
188
189 /* Now iterate over sorted list of monitor configurations. */
190 RTListForEach(&pMonitorsInfoList.Node, pIter, vbcl_hlp_generic_monitor_list_t, Node)
191 {
192 char *pszMonitorName = XGetAtomName(pDisplay, pIter->pMonitorInfo->name);
193
194 VBClLogVerbose(1, "reporting monitor %s offset: (%d, %d)\n",
195 pszMonitorName, pIter->pMonitorInfo->x, pIter->pMonitorInfo->y);
196
197 XFree((void *)pszMonitorName);
198
199 aDisplays[idxDisplay].x = pIter->pMonitorInfo->x;
200 aDisplays[idxDisplay].y = pIter->pMonitorInfo->y;
201 aDisplays[idxDisplay].w = pIter->pMonitorInfo->width;
202 aDisplays[idxDisplay].h = pIter->pMonitorInfo->height;
203
204 idxDisplay++;
205 }
206
207 vbcl_hlp_generic_free_monitor_list(&pMonitorsInfoList);
208
209 XRRFreeMonitors(pMonitorsInfo);
210
211 if (g_pfnDisplayOffsetChangeCb)
212 {
213 rc = g_pfnDisplayOffsetChangeCb(idxDisplay, aDisplays);
214 if (RT_FAILURE(rc))
215 VBClLogError("unable to notify subscriber about monitors info change, rc=%Rrc\n", rc);
216 }
217 }
218 else
219 VBClLogError("cannot get monitors info\n");
220}
221
222/** Worker thread for display change events monitoring. */
223static DECLCALLBACK(int) vbcl_hlp_generic_display_change_event_monitor_worker(RTTHREAD ThreadSelf, void *pvUser)
224{
225 int rc = VERR_GENERAL_FAILURE;
226
227 RT_NOREF(pvUser);
228
229 VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker started\n");
230
231 Display *pDisplay = XOpenDisplay(NULL);
232 if (pDisplay)
233 {
234 bool fSuccess;
235 int iEventBase, iErrorBase /* unused */, iMajor, iMinor;
236
237 fSuccess = XRRQueryExtension(pDisplay, &iEventBase, &iErrorBase);
238 fSuccess &= XRRQueryVersion(pDisplay, &iMajor, &iMinor);
239
240 if (fSuccess && iMajor >= 1 && iMinor > 3)
241 {
242 /* All required checks are now passed. Notify parent thread that we started. */
243 RTThreadUserSignal(ThreadSelf);
244
245 /* Only receive events we need. */
246 XRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask);
247
248 /* Monitor main loop. */
249 while (!ASMAtomicReadBool(&g_fShutdown))
250 {
251 XEvent Event;
252
253 if (XPending(pDisplay) > 0)
254 {
255 XNextEvent(pDisplay, &Event);
256 switch (Event.type - iEventBase)
257 {
258 case RRScreenChangeNotify:
259 {
260 vbcl_hlp_generic_process_display_change_event(pDisplay);
261 break;
262 }
263
264 default:
265 break;
266 }
267 }
268 else
269 RTThreadSleep(RT_MS_1SEC / 2);
270 }
271 }
272 else
273 {
274 VBClLogError("dcm monitor cannot find XRandr 1.3+ extension\n");
275 rc = VERR_NOT_AVAILABLE;
276 }
277
278 XCloseDisplay(pDisplay);
279 }
280 else
281 {
282 VBClLogError("dcm monitor cannot open X Display\n");
283 rc = VERR_NOT_AVAILABLE;
284 }
285
286 VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker ended\n");
287
288 return rc;
289}
290
291static void vbcl_hlp_generic_start_display_change_monitor()
292{
293 int rc;
294
295 rc = RTXrandrLoadLib();
296 if (RT_SUCCESS(rc))
297 {
298 /* Start thread which will monitor display change events. */
299 rc = RTThreadCreate(&g_vbclHlpGenericDcmThread, vbcl_hlp_generic_display_change_event_monitor_worker, (void *)NULL, 0,
300 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, VBCL_HLP_DCM_THREAD_NAME);
301 if (RT_SUCCESS(rc))
302 {
303 rc = RTThreadUserWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC);
304 }
305 else
306 g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
307
308 VBClLogInfo("attempt to start display change monitor thread, rc=%Rrc\n", rc);
309
310 }
311 else
312 VBClLogInfo("libXrandr not available, will not monitor display change events, rc=%Rrc\n", rc);
313}
314
315/**
316 * @interface_method_impl{VBCLDISPLAYHELPER,pfnSetPrimaryDisplay}
317 */
318static DECLCALLBACK(int) vbcl_hlp_generic_set_primary_display(uint32_t idDisplay)
319{
320 XRRScreenResources *pScreenResources;
321 Display *pDisplay;
322
323 int rc = VERR_INVALID_PARAMETER;
324
325 pDisplay = XOpenDisplay(NULL);
326 if (pDisplay)
327 {
328 pScreenResources = XRRGetScreenResources(pDisplay, DefaultRootWindow(pDisplay));
329 if (pScreenResources)
330 {
331 if ((int)idDisplay < pScreenResources->noutput)
332 {
333 XRRSetOutputPrimary(pDisplay, DefaultRootWindow(pDisplay), pScreenResources->outputs[idDisplay]);
334 VBClLogInfo("display %u has been set as primary\n", idDisplay);
335 rc = VINF_SUCCESS;
336 }
337 else
338 VBClLogError("cannot set display %u as primary: index out of range\n", idDisplay);
339
340 XRRFreeScreenResources(pScreenResources);
341 }
342 else
343 VBClLogError("cannot set display %u as primary: libXrandr can not get screen resources\n", idDisplay);
344
345 XCloseDisplay(pDisplay);
346 }
347 else
348 VBClLogError("cannot set display %u as primary: cannot connect to X11\n", idDisplay);
349
350 return rc;
351}
352
353/**
354 * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
355 */
356static DECLCALLBACK(int) vbcl_hlp_generic_probe(void)
357{
358 /* Generic helper always supposed to return positive status on probe(). This
359 * helper is a fallback one in case all the other helpers were failed to detect
360 * their environments. */
361 return VINF_SUCCESS;
362}
363
364/**
365 * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit}
366 */
367RTDECL(int) vbcl_hlp_generic_init(void)
368{
369 ASMAtomicWriteBool(&g_fShutdown, false);
370
371 /* Attempt to start display change events monitor. */
372 vbcl_hlp_generic_start_display_change_monitor();
373
374 /* Always return positive status for generic (fallback, last resort) helper. */
375 return VINF_SUCCESS;
376}
377
378/**
379 * @interface_method_impl{VBCLDISPLAYHELPER,pfnTerm}
380 */
381RTDECL(int) vbcl_hlp_generic_term(void)
382{
383 int rc = VINF_SUCCESS;
384
385 if (g_vbclHlpGenericDcmThread != NIL_RTTHREAD)
386 {
387 /* Signal thread we are going to shutdown. */
388 ASMAtomicWriteBool(&g_fShutdown, true);
389
390 /* Wait for thread to terminate gracefully. */
391 rc = RTThreadWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC, NULL);
392 }
393
394 return rc;
395}
396
397/**
398 * @interface_method_impl{VBCLDISPLAYHELPER,pfnSubscribeDisplayOffsetChangeNotification}
399 */
400RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb)
401{
402 g_pfnDisplayOffsetChangeCb = pfnCb;
403}
404
405/**
406 * @interface_method_impl{VBCLDISPLAYHELPER,pfnUnsubscribeDisplayOffsetChangeNotification}
407 */
408RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void)
409{
410 g_pfnDisplayOffsetChangeCb = NULL;
411}
412
413/* Helper callbacks. */
414const VBCLDISPLAYHELPER g_DisplayHelperGeneric =
415{
416 "GENERIC", /* .pszName */
417 vbcl_hlp_generic_probe, /* .pfnProbe */
418 vbcl_hlp_generic_init, /* .pfnInit */
419 vbcl_hlp_generic_term, /* .pfnTerm */
420 vbcl_hlp_generic_set_primary_display, /* .pfnSetPrimaryDisplay */
421 vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
422 vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
423};
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