VirtualBox

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

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

Additions: Linux: introduce helpers for Desktop Environment specific actions to be triggered from VBoxClient, bugref:10134.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.4 KB
Line 
1/* $Id: display-helper-generic.cpp 93370 2022-01-20 17:44:36Z vboxsync $ */
2/** @file
3 * A generic helper for X11 Client which performs Desktop Environment
4 * specific actions.
5 */
6
7/*
8 * Copyright (C) 2006-2022 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#include "VBoxClient.h"
20#include "display-helper.h"
21
22#include <stdio.h>
23#include <stdlib.h>
24
25#include <VBox/log.h>
26#include <VBox/xrandr.h>
27
28#include <iprt/err.h>
29#include <iprt/asm.h>
30#include <iprt/thread.h>
31#include <iprt/mem.h>
32#include <iprt/list.h>
33
34/** Load */
35#include <VBox/xrandr.h>
36/* Declarations of the functions that we need from libXrandr. */
37#define VBOX_XRANDR_GENERATE_BODY
38#include <VBox/xrandr-calls.h>
39
40#include <X11/Xlibint.h>
41
42/* A log prefix which is specific for this source file. */
43#define VBCL_HLP_LOG_PREFIX "display-helper-generic "
44
45/** Name of Display Change Monitor thread. */
46#define VBCL_HLP_DCM_THREAD_NAME "dcm-task"
47
48/** Display Change Monitor thread. */
49static RTTHREAD vbclHlpGenericDcmThread = NIL_RTTHREAD;
50
51/** Global flag which is triggered when service requested to shutdown. */
52static bool volatile g_fShutdown;
53
54/** Node of monitors info list. */
55typedef struct vbcl_hlp_generic_monitor_list_t
56{
57 /** List node. */
58 RTLISTNODE Node;
59 /** Pointer to xRandr monitor info. */
60 XRRMonitorInfo *pMonitorInfo;
61} vbcl_hlp_generic_monitor_list_t;
62
63/** Pointer to display change event notification callback (set by external function call). */
64static FNDISPLAYOFFSETCHANGE *g_pfnDisplayOffsetChangeCb;
65
66/**
67 * Determine monitor name strings order in a list of monitors which is sorted in ascending way.
68 *
69 * @return TRUE if first name should go first in a list, FALSE otherwise.
70 * @param pszName1 First monitor name.
71 * @param pszName1 Second monitor name.
72 */
73static bool vbcl_hlp_generic_order_names(char *pszName1, char *pszName2)
74{
75 AssertReturn(pszName1, false);
76 AssertReturn(pszName2, false);
77
78 char *pszFirst = pszName1;
79 char *pszSecond = pszName2;
80
81 while (*pszFirst && *pszSecond)
82 {
83 if (*pszFirst < *pszSecond)
84 return true;
85
86 pszFirst++;
87 pszSecond++;
88 }
89
90 return false;
91}
92
93/**
94 * Insert monitor info into the list sorted ascending.
95 *
96 * @return IPRT status code.
97 * @param pDisplay X11 display handle to fetch monitor name string from.
98 * @param pListHead Head of monitors info list.
99 * @param pMonitorInfo Monitor info ti be inserted into the list.
100 */
101static int vbcl_hlp_generic_monitor_list_insert_sorted(
102 Display *pDisplay, vbcl_hlp_generic_monitor_list_t *pListHead, XRRMonitorInfo *pMonitorInfo)
103{
104 vbcl_hlp_generic_monitor_list_t *pNode = (vbcl_hlp_generic_monitor_list_t *)RTMemAllocZ(sizeof(vbcl_hlp_generic_monitor_list_t));
105 vbcl_hlp_generic_monitor_list_t *pNodeIter;
106 char *pszMonitorName;
107
108 AssertReturn(pNode, VERR_NO_MEMORY);
109
110 pNode->pMonitorInfo = pMonitorInfo;
111
112 if (RTListIsEmpty(&pListHead->Node))
113 {
114 RTListNodeInsertAfter(&pListHead->Node, &pNode->Node);
115 return VINF_SUCCESS;
116 }
117
118 pszMonitorName = XGetAtomName(pDisplay, pMonitorInfo->name);
119 AssertReturn(pszMonitorName, VERR_NO_MEMORY);
120
121 RTListForEach(&pListHead->Node, pNodeIter, vbcl_hlp_generic_monitor_list_t, Node)
122 {
123 char *pszIterMonitorName = XGetAtomName(pDisplay, pNodeIter->pMonitorInfo->name);
124
125 if (vbcl_hlp_generic_order_names(pszMonitorName, pszIterMonitorName))
126 {
127 RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
128 XFree((void *)pszIterMonitorName);
129 XFree((void *)pszMonitorName);
130 return VINF_SUCCESS;
131 }
132
133 XFree((void *)pszIterMonitorName);
134 }
135
136 XFree((void *)pszMonitorName);
137
138 /* If we reached the end of the list, it means that monitor
139 * should be placed in the end (according to alphabetical sorting). */
140 RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
141
142 return VINF_SUCCESS;
143}
144
145/**
146 * Release monitors info list resources.
147 *
148 * @param pListHead List head.
149 */
150static void vbcl_hlp_generic_free_monitor_list(vbcl_hlp_generic_monitor_list_t *pListHead)
151{
152 vbcl_hlp_generic_monitor_list_t *pEntry, *pNextEntry;
153
154 RTListForEachSafe(&pListHead->Node, pEntry, pNextEntry, vbcl_hlp_generic_monitor_list_t, Node)
155 {
156 RTListNodeRemove(&pEntry->Node);
157 RTMemFree(pEntry);
158 }
159}
160
161/**
162 * Handle received RRScreenChangeNotify event.
163 *
164 * @param pDisplay X11 display handle.
165 */
166static void vbcl_hlp_generic_process_display_change_event(Display *pDisplay)
167{
168 int iCount, idxDisplay = 0;
169 XRRMonitorInfo *pMonitorsInfo = XRRGetMonitors(pDisplay, DefaultRootWindow(pDisplay), true, &iCount);
170 if (pMonitorsInfo && iCount && 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(VBCL_HLP_LOG_PREFIX "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, VBCL_HLP_LOG_PREFIX "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(iCount, aDisplayOffsets);
211 if (RT_FAILURE(rc))
212 VBClLogError(VBCL_HLP_LOG_PREFIX "unable to notify subscriber about monitors info change, rc=%Rrc\n", rc);
213 }
214 }
215 else
216 VBClLogError(VBCL_HLP_LOG_PREFIX "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(VBCL_HLP_LOG_PREFIX "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(VBCL_HLP_LOG_PREFIX "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
288RTDECL(int) 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(&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(vbclHlpGenericDcmThread, RT_MS_5SEC);
301 }
302 else
303 vbclHlpGenericDcmThread = NIL_RTTHREAD;
304
305 VBClLogInfo(VBCL_HLP_LOG_PREFIX "attempt to start display change monitor thread, rc=%Rrc\n", rc);
306
307 }
308 else
309 VBClLogInfo(VBCL_HLP_LOG_PREFIX "libXrandr not available, will not monitor display change events, rc=%Rrc\n", rc);
310
311 return rc;
312}
313
314/**
315 * @interface_method_impl{VBCLDISPLAYHELPER,pfnSetPrimaryDisplay}
316 */
317static int vbcl_hlp_generic_set_primary_display(uint32_t idDisplay)
318{
319 XRRScreenResources *pScreenResources;
320 Display *pDisplay;
321
322 int rc = VERR_INVALID_PARAMETER;
323
324 pDisplay = XOpenDisplay(NULL);
325 if (pDisplay)
326 {
327 pScreenResources = XRRGetScreenResources(pDisplay, DefaultRootWindow(pDisplay));
328 if (pScreenResources)
329 {
330 if ((int)idDisplay < pScreenResources->noutput)
331 {
332 XRRSetOutputPrimary(pDisplay, DefaultRootWindow(pDisplay), pScreenResources->outputs[idDisplay]);
333 VBClLogInfo(VBCL_HLP_LOG_PREFIX "display %u has been set as primary\n", idDisplay);
334 rc = VINF_SUCCESS;
335 }
336 else
337 VBClLogError(VBCL_HLP_LOG_PREFIX "cannot set display %u as primary: index out of range\n", idDisplay);
338
339 XRRFreeScreenResources(pScreenResources);
340 }
341 else
342 VBClLogError(VBCL_HLP_LOG_PREFIX "cannot set display %u as primary: libXrandr can not get screen resources\n", idDisplay);
343
344 XCloseDisplay(pDisplay);
345 }
346 else
347 VBClLogError(VBCL_HLP_LOG_PREFIX "cannot set display %u as primary: cannot connect to X11\n", idDisplay);
348
349 return rc;
350}
351
352/**
353 * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
354 */
355static int vbcl_hlp_generic_probe(void)
356{
357 /* Generic helper always supposed to return positive status on probe(). This
358 * helper is a fallback one in case all the other helpers were failed to detect
359 * their environments. */
360 return VINF_SUCCESS;
361}
362
363/**
364 * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit}
365 */
366RTDECL(int) vbcl_hlp_generic_init(void)
367{
368 ASMAtomicWriteBool(&g_fShutdown, false);
369
370 /* Attempt to start display change events monitor. Ignore rc,
371 * error will be printed inside function anyway. */
372 (void)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 (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(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