VirtualBox

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

Last change on this file since 93560 was 93423, checked in by vboxsync, 3 years ago

Additions: Linux: update description for guest screen resizing code, bugref:10134.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.1 KB
Line 
1/* $Id: display-helper-generic.cpp 93423 2022-01-24 20:53:37Z 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 static RTPOINT aDisplayOffsets[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 aDisplayOffsets[idxDisplay].x = pIter->pMonitorInfo->x;
200 aDisplayOffsets[idxDisplay].y = pIter->pMonitorInfo->y;
201 idxDisplay++;
202 }
203
204 vbcl_hlp_generic_free_monitor_list(&pMonitorsInfoList);
205
206 XRRFreeMonitors(pMonitorsInfo);
207
208 if (g_pfnDisplayOffsetChangeCb)
209 {
210 rc = g_pfnDisplayOffsetChangeCb(idxDisplay, aDisplayOffsets);
211 if (RT_FAILURE(rc))
212 VBClLogError("unable to notify subscriber about monitors info change, rc=%Rrc\n", rc);
213 }
214 }
215 else
216 VBClLogError("cannot get monitors info\n");
217}
218
219/** Worker thread for display change events monitoring. */
220static DECLCALLBACK(int) vbcl_hlp_generic_display_change_event_monitor_worker(RTTHREAD ThreadSelf, void *pvUser)
221{
222 int rc = VERR_GENERAL_FAILURE;
223
224 RT_NOREF(pvUser);
225
226 VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker started\n");
227
228 Display *pDisplay = XOpenDisplay(NULL);
229 if (pDisplay)
230 {
231 bool fSuccess;
232 int iEventBase, iErrorBase /* unused */, iMajor, iMinor;
233
234 fSuccess = XRRQueryExtension(pDisplay, &iEventBase, &iErrorBase);
235 fSuccess &= XRRQueryVersion(pDisplay, &iMajor, &iMinor);
236
237 if (fSuccess && iMajor >= 1 && iMinor > 3)
238 {
239 /* All required checks are now passed. Notify parent thread that we started. */
240 RTThreadUserSignal(ThreadSelf);
241
242 /* Only receive events we need. */
243 XRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask);
244
245 /* Monitor main loop. */
246 while (!ASMAtomicReadBool(&g_fShutdown))
247 {
248 XEvent Event;
249
250 if (XPending(pDisplay) > 0)
251 {
252 XNextEvent(pDisplay, &Event);
253 switch (Event.type - iEventBase)
254 {
255 case RRScreenChangeNotify:
256 {
257 vbcl_hlp_generic_process_display_change_event(pDisplay);
258 break;
259 }
260
261 default:
262 break;
263 }
264 }
265 else
266 RTThreadSleep(RT_MS_1SEC / 2);
267 }
268 }
269 else
270 {
271 VBClLogError("dcm monitor cannot find XRandr 1.3+ extension\n");
272 rc = VERR_NOT_AVAILABLE;
273 }
274
275 XCloseDisplay(pDisplay);
276 }
277 else
278 {
279 VBClLogError("dcm monitor cannot open X Display\n");
280 rc = VERR_NOT_AVAILABLE;
281 }
282
283 VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker ended\n");
284
285 return rc;
286}
287
288static void vbcl_hlp_generic_start_display_change_monitor()
289{
290 int rc;
291
292 rc = RTXrandrLoadLib();
293 if (RT_SUCCESS(rc))
294 {
295 /* Start thread which will monitor display change events. */
296 rc = RTThreadCreate(&g_vbclHlpGenericDcmThread, vbcl_hlp_generic_display_change_event_monitor_worker, (void *)NULL, 0,
297 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, VBCL_HLP_DCM_THREAD_NAME);
298 if (RT_SUCCESS(rc))
299 {
300 rc = RTThreadUserWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC);
301 }
302 else
303 g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
304
305 VBClLogInfo("attempt to start display change monitor thread, rc=%Rrc\n", rc);
306
307 }
308 else
309 VBClLogInfo("libXrandr not available, will not monitor display change events, rc=%Rrc\n", rc);
310}
311
312/**
313 * @interface_method_impl{VBCLDISPLAYHELPER,pfnSetPrimaryDisplay}
314 */
315static DECLCALLBACK(int) vbcl_hlp_generic_set_primary_display(uint32_t idDisplay)
316{
317 XRRScreenResources *pScreenResources;
318 Display *pDisplay;
319
320 int rc = VERR_INVALID_PARAMETER;
321
322 pDisplay = XOpenDisplay(NULL);
323 if (pDisplay)
324 {
325 pScreenResources = XRRGetScreenResources(pDisplay, DefaultRootWindow(pDisplay));
326 if (pScreenResources)
327 {
328 if ((int)idDisplay < pScreenResources->noutput)
329 {
330 XRRSetOutputPrimary(pDisplay, DefaultRootWindow(pDisplay), pScreenResources->outputs[idDisplay]);
331 VBClLogInfo("display %u has been set as primary\n", idDisplay);
332 rc = VINF_SUCCESS;
333 }
334 else
335 VBClLogError("cannot set display %u as primary: index out of range\n", idDisplay);
336
337 XRRFreeScreenResources(pScreenResources);
338 }
339 else
340 VBClLogError("cannot set display %u as primary: libXrandr can not get screen resources\n", idDisplay);
341
342 XCloseDisplay(pDisplay);
343 }
344 else
345 VBClLogError("cannot set display %u as primary: cannot connect to X11\n", idDisplay);
346
347 return rc;
348}
349
350/**
351 * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
352 */
353static DECLCALLBACK(int) vbcl_hlp_generic_probe(void)
354{
355 /* Generic helper always supposed to return positive status on probe(). This
356 * helper is a fallback one in case all the other helpers were failed to detect
357 * their environments. */
358 return VINF_SUCCESS;
359}
360
361/**
362 * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit}
363 */
364RTDECL(int) vbcl_hlp_generic_init(void)
365{
366 ASMAtomicWriteBool(&g_fShutdown, false);
367
368 /* Attempt to start display change events monitor. */
369 vbcl_hlp_generic_start_display_change_monitor();
370
371 /* Always return positive status for generic (fallback, last resort) helper. */
372 return VINF_SUCCESS;
373}
374
375/**
376 * @interface_method_impl{VBCLDISPLAYHELPER,pfnTerm}
377 */
378RTDECL(int) vbcl_hlp_generic_term(void)
379{
380 int rc = VINF_SUCCESS;
381
382 if (g_vbclHlpGenericDcmThread != NIL_RTTHREAD)
383 {
384 /* Signal thread we are going to shutdown. */
385 ASMAtomicWriteBool(&g_fShutdown, true);
386
387 /* Wait for thread to terminate gracefully. */
388 rc = RTThreadWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC, NULL);
389 }
390
391 return rc;
392}
393
394/**
395 * @interface_method_impl{VBCLDISPLAYHELPER,pfnSubscribeDisplayOffsetChangeNotification}
396 */
397RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb)
398{
399 g_pfnDisplayOffsetChangeCb = pfnCb;
400}
401
402/**
403 * @interface_method_impl{VBCLDISPLAYHELPER,pfnUnsubscribeDisplayOffsetChangeNotification}
404 */
405RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void)
406{
407 g_pfnDisplayOffsetChangeCb = NULL;
408}
409
410/* Helper callbacks. */
411const VBCLDISPLAYHELPER g_DisplayHelperGeneric =
412{
413 "GENERIC", /* .pszName */
414 vbcl_hlp_generic_probe, /* .pfnProbe */
415 vbcl_hlp_generic_init, /* .pfnInit */
416 vbcl_hlp_generic_term, /* .pfnTerm */
417 vbcl_hlp_generic_set_primary_display, /* .pfnSetPrimaryDisplay */
418 vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
419 vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
420};
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