VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use