VirtualBox

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

Last change on this file was 99620, checked in by vboxsync, 12 months ago

Guest / Host: Renamed SessionType -> DisplayServerType (better naming to reflect its purpose). bugref:10427

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.8 KB
Line 
1/* $Id: display-helper-gnome3.cpp 99620 2023-05-05 09:08:00Z vboxsync $ */
2/** @file
3 * Guest Additions - Gnome3 Desktop Environment helper.
4 *
5 * A helper for X11/Wayland Client which performs Gnome Desktop
6 * Environment specific actions.
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/**
32 * This helper implements communication protocol between gnome-settings-daemon
33 * and itself using interface defined in (revision e88467f9):
34 *
35 * https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/org.gnome.Mutter.DisplayConfig.xml
36 */
37
38#include "VBoxClient.h"
39#include "display-helper.h"
40
41#include <stdio.h>
42#include <stdlib.h>
43
44#include <VBox/log.h>
45#include <VBox/VBoxGuestLib.h>
46
47#include <iprt/env.h>
48#include <iprt/initterm.h>
49#include <iprt/message.h>
50#include <iprt/dir.h>
51#include <iprt/err.h>
52#include <iprt/mem.h>
53#include <iprt/string.h>
54
55/** Load libDbus symbols needed for us. */
56#include <VBox/dbus.h>
57/* Declarations of the functions that we need from libXrandr. */
58#define VBOX_DBUS_GENERATE_BODY
59#include <VBox/dbus-calls.h>
60
61/** D-bus parameters for connecting to Gnome display service. */
62#define VBOXCLIENT_HELPER_DBUS_DESTINATION "org.gnome.Mutter.DisplayConfig"
63#define VBOXCLIENT_HELPER_DBUS_PATH "/org/gnome/Mutter/DisplayConfig"
64#define VBOXCLIENT_HELPER_DBUS_IFACE "org.gnome.Mutter.DisplayConfig"
65#define VBOXCLIENT_HELPER_DBUS_GET_METHOD "GetCurrentState"
66#define VBOXCLIENT_HELPER_DBUS_APPLY_METHOD "ApplyMonitorsConfig"
67
68/** D-bus communication timeout value, milliseconds.*/
69#define VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS (1 * 1000)
70
71/** gnome-settings-daemon ApplyMonitorsConfig method:
72 * 0: verify - test if configuration can be applied and do not change anything,
73 * 1: temporary - apply configuration temporary, all will be reverted after re-login,
74 * 2: persistent - apply configuration permanently (asks for user confirmation).
75 */
76#define VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD (1)
77
78/**
79 * Helper macro which is used in order to simplify code when batch of
80 * values needed to be parsed out of D-bus. Macro prevents execution
81 * of the 'next' command if 'previous' one was failed (tracked via
82 * local variable _ret). It is required that '_ret' should be initialized
83 * to TRUE before batch started.
84 *
85 * @param _ret Local variable which is used in order to track execution flow.
86 * @param _call A function (with full arguments) which returns 'dbus_bool_t'.
87 */
88#define VBCL_HLP_GNOME3_NEXT(_ret, _call) \
89 { _ret &= _ret ? _call : _ret; if (!ret) VBClLogError(__FILE__ ":%d: check fail here!\n", __LINE__); }
90
91/**
92 * This structure describes sub-part of physical monitor state
93 * required to compose a payload for calling ApplyMonitorsConfig method. */
94struct vbcl_hlp_gnome3_physical_display_state
95{
96 /** Physical display connector name string. */
97 char *connector;
98 /** Current mode name string for physical display. */
99 char *mode;
100};
101
102/**
103 * Verify if data represented by D-bus message iteration corresponds to given data type.
104 *
105 * @return True if D-bus message iteration corresponds to given data type, False otherwise.
106 * @param iter D-bus message iteration.
107 * @param type D-bus data type.
108 */
109static dbus_bool_t vbcl_hlp_gnome3_verify_data_type(DBusMessageIter *iter, int type)
110{
111 if (!iter)
112 return false;
113
114 if (dbus_message_iter_get_arg_type(iter) != type)
115 return false;
116
117 return true;
118}
119
120/**
121 * Verifies D-bus iterator signature.
122 *
123 * @return True if iterator signature matches to given one.
124 * @param iter D-bus iterator to check.
125 * @param signature Expected iterator signature.
126 */
127static dbus_bool_t vbcl_hlp_gnome3_check_iter_signature(DBusMessageIter *iter, const char *signature)
128{
129 char *iter_signature;
130 dbus_bool_t match;
131
132 if ( !iter
133 || !signature)
134 {
135 return false;
136 }
137
138 /* In case of dbus_message_iter_get_signature() returned memory should be freed by us. */
139 iter_signature = dbus_message_iter_get_signature(iter);
140 match = (strcmp(iter_signature, signature) == 0);
141
142 if (!match)
143 VBClLogError("iter signature mismatch: '%s' vs. '%s'\n", signature, iter_signature);
144
145 if (iter_signature)
146 dbus_free(iter_signature);
147
148 return match;
149}
150
151/**
152 * Verifies D-bus message signature.
153 *
154 * @return True if message signature matches to given one.
155 * @param message D-bus message to check.
156 * @param signature Expected message signature.
157 */
158static dbus_bool_t vbcl_hlp_gnome3_check_message_signature(DBusMessage *message, const char *signature)
159{
160 char *message_signature;
161 dbus_bool_t match;
162
163 if ( !message
164 || !signature)
165 {
166 return false;
167 }
168
169 /* In case of dbus_message_get_signature() returned memory need NOT be freed by us. */
170 message_signature = dbus_message_get_signature(message);
171 match = (strcmp(message_signature, signature) == 0);
172
173 if (!match)
174 VBClLogError("message signature mismatch: '%s' vs. '%s'\n", signature, message_signature);
175
176 return match;
177}
178
179/**
180 * Jump into DBUS_TYPE_ARRAY iter container and initialize sub-iterator
181 * aimed to traverse container child nodes.
182 *
183 * @return True if operation was successful, False otherwise.
184 * @param iter D-bus iter of type DBUS_TYPE_ARRAY.
185 * @param array Returned sub-iterator.
186 */
187static dbus_bool_t vbcl_hlp_gnome3_iter_get_array(DBusMessageIter *iter, DBusMessageIter *array)
188{
189 if (!iter || !array)
190 return false;
191
192 if (vbcl_hlp_gnome3_verify_data_type(iter, DBUS_TYPE_ARRAY))
193 {
194 dbus_message_iter_recurse(iter, array);
195 /* Move to the next iter, returned value not important. */
196 dbus_message_iter_next(iter);
197 return true;
198 }
199 else
200 {
201 VBClLogError(
202 "cannot get array: argument signature '%s' does not match to type of array\n",
203 dbus_message_iter_get_signature(iter));
204 }
205
206 return false;
207}
208
209/**
210 * Get value of D-bus iter of specified simple type (numerals, strings).
211 *
212 * @return True if operation was successful, False otherwise.
213 * @param iter D-bus iter of type simple type.
214 * @param type D-bus data type.
215 * @param value Returned value.
216 */
217static dbus_bool_t vbcl_hlp_gnome3_iter_get_basic(DBusMessageIter *iter, int type, void *value)
218{
219 if (!iter || !value)
220 return false;
221
222 if (vbcl_hlp_gnome3_verify_data_type(iter, type))
223 {
224 dbus_message_iter_get_basic(iter, value);
225 /* Move to the next iter, returned value not important. */
226 dbus_message_iter_next(iter);
227 return true;
228 }
229 else
230 {
231 VBClLogError(
232 "cannot get value: argument signature '%s' does not match to specified type\n",
233 dbus_message_iter_get_signature(iter));
234 }
235
236 return false;
237}
238
239/**
240 * Lookup simple value (numeral, string, bool etc) in D-bus dictionary
241 * by given key and type.
242 *
243 * @return True value is found, False otherwise.
244 * @param dict D-bus iterator which represents dictionary.
245 * @param key_match Dictionary key.
246 * @param type Type of value.
247 * @param value Returning value.
248 */
249static dbus_bool_t vbcl_hlp_gnome3_lookup_dict(DBusMessageIter *dict, const char *key_match, int type, void *value)
250{
251 dbus_bool_t found = false;
252
253 if (!dict || !key_match)
254 return false;
255
256 if (!vbcl_hlp_gnome3_check_iter_signature(dict, "{sv}"))
257 return false;
258
259 do
260 {
261 dbus_bool_t ret = true;
262 DBusMessageIter iter;
263 char *key = NULL;
264
265 /* Proceed to part a{ > sv < } of a{sv}. */
266 dbus_message_iter_recurse(dict, &iter);
267
268 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
269 AssertReturn(ret, false);
270
271 /* Proceed to part a{ > s < v} of a{sv}. */
272 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_STRING, &key));
273
274 /* Check if key matches. */
275 if (strcmp(key_match, key) == 0)
276 {
277 DBusMessageIter value_iter;
278
279 /* Proceed to part a{s > v < } of a{sv}. */
280 dbus_message_iter_recurse(&iter, &value_iter);
281 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&value_iter, type, value));
282
283 /* Make sure there are no more arguments. */
284 VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&value_iter));
285
286 if (ret)
287 {
288 found = true;
289 break;
290 }
291 }
292 }
293 while (dbus_message_iter_next(dict));
294
295 return found;
296}
297
298/**
299 * Go through available modes and pick up the one which has property 'is-current' set.
300 * See GetCurrentState interface documentation for more details. Returned string memory
301 * must be freed by calling function.
302 *
303 * @return Mode name as a string if found, NULL otherwise.
304 * @param modes List of monitor modes.
305 */
306static char *vbcl_hlp_gnome3_lookup_monitor_current_mode(DBusMessageIter *modes)
307{
308 char *szCurrentMode = NULL;
309 DBusMessageIter modes_iter;
310
311 /* De-serialization parameters for 'modes': (siiddada{sv}). */
312 char *id = NULL;
313 int32_t width = 0;
314 int32_t height = 0;
315 double refresh_rate = 0;
316 double preferred_scale = 0;
317 DBusMessageIter supported_scales;
318 DBusMessageIter properties;
319
320 if (!modes)
321 return NULL;
322
323 if(!vbcl_hlp_gnome3_check_iter_signature(modes, "(siiddada{sv})"))
324 return NULL;
325
326 do
327 {
328 static const char *key_match = "is-current";
329 dbus_bool_t default_mode_found = false;
330 dbus_bool_t ret = true;
331
332 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
333 AssertReturn(ret, NULL);
334
335 /* Proceed to part a( > siiddada{sv} < ) of a(siiddada{sv}). */
336 dbus_message_iter_recurse(modes, &modes_iter);
337 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_STRING, &id));
338 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &width));
339 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &height));
340 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &refresh_rate));
341 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &preferred_scale));
342 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &supported_scales));
343 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &properties));
344
345 ret = vbcl_hlp_gnome3_lookup_dict(&properties, key_match, DBUS_TYPE_BOOLEAN, &default_mode_found);
346 if (ret && default_mode_found)
347 {
348 szCurrentMode = strdup(id);
349 break;
350 }
351 }
352 while (dbus_message_iter_next(modes));
353
354 return szCurrentMode;
355}
356
357/**
358 * Parse physical monitors list entry. See GetCurrentState interface documentation for more details.
359 *
360 * @return True if monitors list entry has been successfully parsed, False otherwise.
361 * @param physical_monitors_in D-bus iterator representing list of physical monitors.
362 * @param connector Connector name (out).
363 * @param vendor Vendor name (out).
364 * @param product Product name (out).
365 * @param physical_monitor_serial Serial number (out).
366 * @param modes List of monitor modes (out).
367 * @param physical_monitor_properties A D-bus dictionary containing monitor properties (out).
368 */
369static dbus_bool_t vbcl_hlp_gnome3_parse_physical_monitor_record(
370 DBusMessageIter *physical_monitors_in,
371 char **connector,
372 char **vendor,
373 char **product,
374 char **physical_monitor_serial,
375 DBusMessageIter *modes,
376 DBusMessageIter *physical_monitor_properties)
377{
378 dbus_bool_t ret = true;
379
380 DBusMessageIter physical_monitors_in_iter;
381 DBusMessageIter physical_monitors_in_description_iter;
382
383 if ( !physical_monitors_in
384 || !connector
385 || !vendor
386 || !product
387 || !physical_monitor_serial
388 || !modes
389 || !physical_monitor_properties)
390 {
391 return false;
392 }
393
394 /* Validate signature. */
395 if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})"))
396 return false;
397
398 /* Proceed to part ( > (ssss)a(siiddada{sv})a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */
399 dbus_message_iter_recurse(physical_monitors_in, &physical_monitors_in_iter);
400
401 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
402 AssertReturn(ret, false);
403
404 /* Proceed to part ( > (ssss) < a(siiddada{sv})a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */
405 dbus_message_iter_recurse(&physical_monitors_in_iter, &physical_monitors_in_description_iter);
406 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, connector));
407 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, vendor));
408 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, product));
409 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, physical_monitor_serial));
410
411 /* Proceed to part ((ssss) > a(siiddada{sv}) < a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */
412 if (ret)
413 dbus_message_iter_next(&physical_monitors_in_iter);
414
415 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, modes));
416
417 /* Proceed to part ((ssss)a(siiddada{sv}) > a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */
418 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, physical_monitor_properties));
419
420 /* Make sure there are no more arguments. */
421 VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&physical_monitors_in_iter));
422
423 return ret;
424}
425
426/**
427 * Parse logical monitors list entry. See GetCurrentState interface documentation for more details.
428 *
429 * @return True if monitors list entry has been successfully parsed, False otherwise.
430 * @param logical_monitors_in D-bus iterator representing list of logical monitors.
431 * @param x Monitor X position (out).
432 * @param y Monitor Y position (out).
433 * @param scale Monitor scale factor (out).
434 * @param transform Current monitor transform (rotation) (out).
435 * @param primary A flag which indicates if monitor is set as primary (out).
436 * @param monitors List of physical monitors which are displaying this logical monitor (out).
437 * @param properties List of monitor properties (out).
438 */
439static dbus_bool_t vbcl_hlp_gnome3_parse_logical_monitor_record(
440 DBusMessageIter *logical_monitors_in,
441 int32_t *x,
442 int32_t *y,
443 double *scale,
444 uint32_t *transform,
445 dbus_bool_t *primary,
446 DBusMessageIter *monitors,
447 DBusMessageIter *properties)
448{
449 dbus_bool_t ret = true;
450
451 /* Iter used to traverse logical monitor parameters: @a(iiduba(ssss)a{sv}). */
452 DBusMessageIter logical_monitors_in_iter;
453
454 if ( !logical_monitors_in
455 || !x
456 || !y
457 || !scale
458 || !transform
459 || !primary
460 || !monitors
461 || !properties)
462
463 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
464 AssertReturn(ret, false);
465
466 /* Proceed to part @a( > iiduba(ssss)a{sv} < ) of @a(iiduba(ssss)a{sv}). */
467 dbus_message_iter_recurse(logical_monitors_in, &logical_monitors_in_iter);
468 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, x));
469 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, y));
470 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_DOUBLE, scale));
471 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_UINT32, transform));
472 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_BOOLEAN, primary));
473 /* Proceed to part @a(iidub > a(ssss) < a{sv}) of @a(iiduba(ssss)a{sv}). */
474 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, monitors));
475 /* Proceed to part @a(iiduba(ssss) > a{sv} < ) of @a(iiduba(ssss)a{sv}). */
476 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, properties));
477
478 /* Make sure there are no more arguments. */
479 VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&logical_monitors_in_iter));
480
481 return ret;
482}
483
484/**
485 * Get list of physical monitors parameters from D-bus iterator.
486 *
487 * Once this function was traversed 'physical_monitors_in' iterator, we are in the
488 * end of the list of physical monitors parameters. So, it is important to do it once.
489 *
490 * @return True if monitors parameters were successfully discovered, False otherwise.
491 * @param physical_monitors_in D-bus iterator representing list of physical monitors.
492 * @param state Storage to put monitors state to.
493 * @param state_records_max Size of state storage.
494 * @param cPhysicalMonitors Actual number of physical displays parsed.
495 */
496static dbus_bool_t vbcl_hlp_gnome3_get_physical_monitors_state(
497 DBusMessageIter *physical_monitors_in,
498 vbcl_hlp_gnome3_physical_display_state *state,
499 uint32_t state_records_max,
500 uint32_t *cPhysicalMonitors)
501{
502 dbus_bool_t ret = true;
503 uint32_t iMonitor = 0;
504
505 if ( !physical_monitors_in
506 || !state
507 || !cPhysicalMonitors)
508 {
509 return false;
510 }
511
512 /* Validate signature. */
513 if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})"))
514 return false;
515
516 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
517 AssertReturn(ret, false);
518
519 do
520 {
521 char *connector = NULL;
522 char *vendor = NULL;
523 char *product = NULL;
524 char *physical_monitor_serial = NULL;
525 DBusMessageIter modes;
526 DBusMessageIter physical_monitor_properties;
527
528 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_physical_monitor_record(
529 physical_monitors_in, &connector, &vendor, &product, &physical_monitor_serial,
530 &modes, &physical_monitor_properties));
531
532 if (iMonitor < state_records_max)
533 {
534 state[iMonitor].connector = connector;
535 state[iMonitor].mode = vbcl_hlp_gnome3_lookup_monitor_current_mode(&modes);
536
537 /* Check if both parameters were discovered successfully. */
538 VBCL_HLP_GNOME3_NEXT(ret, state[iMonitor].connector && state[iMonitor].mode);
539 }
540
541 iMonitor++;
542
543 }
544 while (ret && dbus_message_iter_next(physical_monitors_in));
545
546 if (iMonitor >= state_records_max)
547 {
548 VBClLogError("physical monitors list is too big (%u)\n", iMonitor);
549 ret = false;
550 }
551
552 *cPhysicalMonitors = iMonitor;
553
554 return ret;
555}
556
557/**
558 * Release monitors state resources.
559 *
560 * @param state Array of monitor states.
561 * @param cPhysicalMonitors Number of elements in array.
562 */
563static void vbcl_hlp_gnome3_free_physical_monitors_state(
564 vbcl_hlp_gnome3_physical_display_state *state,
565 uint32_t cPhysicalMonitors)
566{
567 if (!state || !cPhysicalMonitors)
568 return;
569
570 for (uint32_t i = 0; i < cPhysicalMonitors; i++)
571 {
572 /* Only free() what we allocated ourselves. */
573 if (state[i].mode)
574 free(state[i].mode);
575 }
576}
577
578/**
579 * Add dictionary element with boolean value into an array.
580 *
581 * @return True on success, False otherwise.
582 * @param parent_iter Array to add dictionary element into.
583 * @param key Dictionary key.
584 * @param value Boolean value for given key.
585 */
586static dbus_bool_t vbcl_hlp_gnome3_add_dict_bool_entry(
587 DBusMessageIter *parent_iter, const char *key, const dbus_bool_t value)
588{
589 dbus_bool_t ret = true;
590
591 DBusMessageIter sub_iter_key;
592 DBusMessageIter sub_iter_value;
593
594 RT_ZERO(sub_iter_key);
595 RT_ZERO(sub_iter_value);
596
597 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
598 AssertReturn(ret, false);
599
600 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(parent_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_iter_key));
601 {
602 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_key, DBUS_TYPE_STRING, &key));
603
604 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter_key, ((int) 'v'), "b", &sub_iter_value));
605 {
606 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_value, DBUS_TYPE_BOOLEAN, &value));
607 }
608
609 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter_key, &sub_iter_value));
610 }
611 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(parent_iter, &sub_iter_key));
612
613 return ret;
614}
615
616/**
617 * This function is responsible for gathering current display
618 * information (via its helper functions), compose a payload
619 * for ApplyMonitorsConfig method and finally send configuration
620 * change to gnome-settings-daemon over D-bus.
621 *
622 * @return IPRT status code.
623 * @param connection Handle to D-bus connection.
624 * @param serial Serial number obtained from GetCurrentState interface,
625 * needs to be passed to ApplyMonitorsConfig.
626 * @param physical_monitors_in List of physical monitors (see GetCurrentState).
627 * @param logical_monitors_in List of logical monitors (see GetCurrentState).
628 * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary.
629 */
630static int vbcl_hlp_gnome3_convert_and_apply_display_settings(
631 DBusConnection *connection,
632 uint32_t serial,
633 DBusMessageIter *physical_monitors_in,
634 DBusMessageIter *logical_monitors_in,
635 uint32_t idPrimaryDisplay)
636{
637 int rc = VERR_INVALID_PARAMETER;
638 uint32_t iLogicalMonitor = 0;
639 uint32_t cPhysicalMonitors = 0;
640 int32_t method = VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD;
641
642 dbus_bool_t ret = true;
643 DBusError error;
644 DBusMessage *reply = NULL;;
645 DBusMessage *message = NULL;
646 DBusMessageIter message_iter;
647 DBusMessageIter logical_monitors_out_iter;
648 DBusMessageIter properties_out_iter;
649
650 struct vbcl_hlp_gnome3_physical_display_state
651 physical_monitors_state[VBOX_DRMIPC_MONITORS_MAX];
652
653 if ( !connection
654 || !physical_monitors_in
655 || !logical_monitors_in)
656 {
657 return VERR_INVALID_PARAMETER;
658 }
659
660 /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */
661 RT_ZERO(message_iter);
662 RT_ZERO(logical_monitors_out_iter);
663 RT_ZERO(properties_out_iter);
664
665 message = dbus_message_new_method_call(
666 VBOXCLIENT_HELPER_DBUS_DESTINATION,
667 VBOXCLIENT_HELPER_DBUS_PATH,
668 VBOXCLIENT_HELPER_DBUS_IFACE,
669 VBOXCLIENT_HELPER_DBUS_APPLY_METHOD);
670 if (!message)
671 {
672 VBClLogError("unable to apply monitors config: no memory\n");
673 return VERR_NO_MEMORY;
674 }
675
676 /* Start composing payload for ApplyMonitorsConfig method: (uu@a(iiduba(ssa{sv}))@a{sv}). */
677 dbus_message_iter_init_append(message, &message_iter);
678
679 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
680 AssertReturn(ret, VERR_INVALID_PARAMETER);
681
682 /* Get list of physical monitors parameters. */
683 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_get_physical_monitors_state(
684 physical_monitors_in, physical_monitors_state, VBOX_DRMIPC_MONITORS_MAX, &cPhysicalMonitors));
685
686 /* ( >u< u@a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
687 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &serial));
688 /* (u >u< @a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
689 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &method));
690
691 /* Parameter "monitors" of method ApplyMonitorsConfig.
692 * Part (uu >@a(iiduba(ssa{sv}))< @a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
693 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "(iiduba(ssa{sv}))", &logical_monitors_out_iter));
694
695 /* Iterate over current configuration monitors (@logical_monitors
696 * parameter of GetCurrentState interface) and compose the rest part of message. */
697 do
698 {
699 /* De-serialization parameters for @logical_monitors data (see GetCurrentState interface documentation). */
700 int32_t x = 0;
701 int32_t y = 0;
702 double scale = 0;
703 uint32_t transform = 0;
704 dbus_bool_t primary = false;
705 dbus_bool_t isPrimary = false;
706 DBusMessageIter monitors;
707 DBusMessageIter properties;
708
709 /* These iterators are used in order to compose sub-containers of the message. */
710 DBusMessageIter sub_iter0;
711 DBusMessageIter sub_iter1;
712 DBusMessageIter sub_iter2;
713 DBusMessageIter sub_iter3;
714 /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */
715 RT_ZERO(sub_iter0);
716 RT_ZERO(sub_iter1);
717 RT_ZERO(sub_iter2);
718 RT_ZERO(sub_iter3);
719
720 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_logical_monitor_record(
721 logical_monitors_in, &x, &y, &scale, &transform, &primary, &monitors, &properties));
722
723 if (ret)
724 {
725 /* Whether current display supposed to be set as primary. */
726 isPrimary = (iLogicalMonitor == idPrimaryDisplay);
727
728 /* Compose part (uu@a( > iiduba(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
729 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&logical_monitors_out_iter, DBUS_TYPE_STRUCT, NULL, &sub_iter0));
730 {
731 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &x));
732 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &y));
733 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_DOUBLE, &scale));
734 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_UINT32, &transform));
735 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_BOOLEAN, &isPrimary));
736
737 /* Compose part (uu@a(iidub > a(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
738 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter0, DBUS_TYPE_ARRAY, "(ssa{sv})", &sub_iter1));
739 {
740 /* Compose part (uu@a(iiduba > (ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
741 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter1, DBUS_TYPE_STRUCT, NULL, &sub_iter2));
742 {
743 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].connector));
744 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].mode));
745
746 /* Compose part (uu@a(iiduba(ss > a{sv} < ))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
747 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter2, DBUS_TYPE_ARRAY, "{sv}", &sub_iter3));
748 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-current", true));
749 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-preferred", true));
750 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter2, &sub_iter3));
751 }
752 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter1, &sub_iter2));
753 }
754 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter0, &sub_iter1));
755 }
756 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&logical_monitors_out_iter, &sub_iter0));
757
758 iLogicalMonitor++;
759
760 if (!ret)
761 {
762 dbus_message_iter_abandon_container_if_open(&sub_iter2, &sub_iter3);
763 dbus_message_iter_abandon_container_if_open(&sub_iter1, &sub_iter2);
764 dbus_message_iter_abandon_container_if_open(&sub_iter0, &sub_iter1);
765 dbus_message_iter_abandon_container_if_open(&logical_monitors_out_iter, &sub_iter0);
766 }
767 }
768 else
769 {
770 break;
771 }
772
773 }
774 while (ret && dbus_message_iter_next(logical_monitors_in));
775
776 /* Finish with parameter "monitors" of method ApplyMonitorsConfig. */
777 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &logical_monitors_out_iter));
778
779 /* Parameter "properties" of method ApplyMonitorsConfig (empty dict).
780 * Part (uu@a(iiduba(ssa{sv})) >@a{sv}< ) of (uu@a(iiduba(ssa{sv}))@a{sv}).*/
781 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "{sv}", &properties_out_iter));
782 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &properties_out_iter));
783
784 if (ret)
785 {
786 dbus_error_init(&error);
787
788 reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error);
789 if (reply)
790 {
791 VBClLogInfo("display %d has been set as primary\n", idPrimaryDisplay);
792 dbus_message_unref(reply);
793 rc = VINF_SUCCESS;
794 }
795 else
796 {
797 VBClLogError("unable to apply monitors config: %s\n",
798 dbus_error_is_set(&error) ? error.message : "unknown error");
799 dbus_error_free(&error);
800 rc = VERR_INVALID_PARAMETER;
801 }
802 }
803 else
804 {
805 VBClLogError("unable to apply monitors config: cannot compose monitors config\n");
806
807 dbus_message_iter_abandon_container_if_open(&message_iter, &logical_monitors_out_iter);
808 dbus_message_iter_abandon_container_if_open(&message_iter, &properties_out_iter);
809
810 rc = VERR_INVALID_PARAMETER;
811 }
812
813 /* Clean physical monitors state. */
814 vbcl_hlp_gnome3_free_physical_monitors_state(physical_monitors_state, cPhysicalMonitors);
815
816 dbus_message_unref(message);
817
818 return rc;
819}
820
821/**
822 * This function parses GetCurrentState interface call reply and passes it for further processing.
823 *
824 * @return IPRT status code.
825 * @param connection Handle to D-bus connection.
826 * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary.
827 * @param reply Reply message of GetCurrentState call.
828 */
829static int vbcl_hlp_gnome3_process_current_display_layout(
830 DBusConnection *connection, uint32_t idPrimaryDisplay, DBusMessage *reply)
831{
832 static const char *expected_signature = "ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv}";
833
834 dbus_bool_t ret = true;
835 DBusMessageIter iter;
836 int rc = VERR_GENERAL_FAILURE;
837
838 uint32_t serial = 0;
839 DBusMessageIter monitors;
840 DBusMessageIter logical_monitors_in;
841 DBusMessageIter properties;
842
843 if (!reply)
844 {
845 return VERR_INVALID_PARAMETER;
846 }
847
848 /* Parse VBOXCLIENT_HELPER_DBUS_GET_METHOD reply payload:
849 *
850 * (u@a((ssss)a(siiddada{sv})a{sv})@a(iiduba(ssss)a{sv})@a{sv}).
851 *
852 * Method return the following arguments: monitors, logical_monitors, properties.
853 */
854
855 /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
856 AssertReturn(ret, VERR_INVALID_PARAMETER);
857
858 /* Important: in order to avoid libdbus asserts during parsing, its signature should be verified at first. */
859 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_check_message_signature(reply, expected_signature));
860
861 VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_init(reply, &iter));
862 if (ret)
863 {
864 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_UINT32, &serial));
865 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &monitors));
866 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &logical_monitors_in));
867 VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &properties));
868
869 /* Make sure there are no more arguments. */
870 if (ret && !dbus_message_iter_has_next(&iter))
871 {
872 rc = vbcl_hlp_gnome3_convert_and_apply_display_settings(
873 connection, serial, &monitors, &logical_monitors_in, idPrimaryDisplay);
874 }
875 else
876 {
877 VBClLogError("cannot fetch current displays configuration: incorrect number of arguments\n");
878 rc = VERR_INVALID_PARAMETER;
879 }
880 }
881 else
882 {
883 VBClLogError("cannot fetch current displays configuration: no data\n");
884 rc = VERR_INVALID_PARAMETER;
885 }
886
887 return rc;
888}
889
890/**
891 * This function establishes D-bus connection, requests gnome-settings-daemon
892 * to provide current display configuration via GetCurrentState interface call
893 * and passes this information further to helper functions in order to set
894 * requested display as primary.
895 *
896 * @return IPRT status code.
897 * @param idPrimaryDisplay A display ID which is requested to be set as primary.
898 */
899static DECLCALLBACK(int) vbcl_hlp_gnome3_set_primary_display(uint32_t idPrimaryDisplay)
900{
901 int rc = VERR_GENERAL_FAILURE;
902
903 DBusConnection *connection = NULL;
904 DBusMessage *message = NULL;
905 DBusError error;
906
907 rc = RTDBusLoadLib();
908 if (RT_FAILURE(rc))
909 {
910 VBClLogError("unable to load D-bus library\n");
911 return VERR_SYMBOL_NOT_FOUND;
912 }
913
914 dbus_error_init(&error);
915 connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
916 if (!dbus_error_is_set(&error))
917 {
918 message = dbus_message_new_method_call(
919 VBOXCLIENT_HELPER_DBUS_DESTINATION,
920 VBOXCLIENT_HELPER_DBUS_PATH,
921 VBOXCLIENT_HELPER_DBUS_IFACE,
922 VBOXCLIENT_HELPER_DBUS_GET_METHOD);
923
924 if (message)
925 {
926 DBusMessage *reply;
927
928 reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error);
929 if (!dbus_error_is_set(&error))
930 {
931 rc = vbcl_hlp_gnome3_process_current_display_layout(connection, idPrimaryDisplay, reply);
932 dbus_message_unref(reply);
933 }
934 else
935 {
936 VBClLogError("unable to get current display configuration: %s\n", error.message);
937 dbus_error_free(&error);
938 rc = VERR_INVALID_PARAMETER;
939 }
940
941 dbus_message_unref(message);
942 }
943 else
944 {
945 VBClLogError("unable to get current display configuration: no memory\n");
946 rc = VERR_NO_MEMORY;
947 }
948
949 dbus_connection_flush(connection);
950 }
951 else
952 {
953 VBClLogError("unable to establish dbus connection: %s\n", error.message);
954 dbus_error_free(&error);
955 rc = VERR_INVALID_HANDLE;
956 }
957
958 return rc;
959}
960
961/**
962 * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
963 */
964static DECLCALLBACK(int) vbcl_hlp_gnome3_probe(void)
965{
966 const char *pszCurrentDesktop = RTEnvGet(VBGH_ENV_XDG_CURRENT_DESKTOP);
967
968 /* GNOME3 identifies itself by XDG_CURRENT_DESKTOP environment variable.
969 * It can slightly vary for different distributions, but we assume that this
970 * variable should at least contain sub-string 'GNOME' in its value. */
971 if (pszCurrentDesktop && RTStrStr(pszCurrentDesktop, "GNOME"))
972 return VINF_SUCCESS;
973
974 return VERR_NOT_FOUND;
975}
976
977/**
978 * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit}
979 */
980static DECLCALLBACK(int) vbcl_hlp_gnome3_init(void)
981{
982 int rc;
983
984 if (VBClGetDisplayServerType() == VBGHDISPLAYSERVERTYPE_X11)
985 {
986 rc = vbcl_hlp_generic_init();
987 VBClLogInfo("attempt to start generic helper routines, rc=%Rrc\n", rc);
988 }
989
990 return VINF_SUCCESS;
991}
992
993/**
994 * @interface_method_impl{VBCLDISPLAYHELPER,pfnTerm}
995 */
996static DECLCALLBACK(int) vbcl_hlp_gnome3_term(void)
997{
998 int rc;
999
1000 if (VBClGetDisplayServerType() == VBGHDISPLAYSERVERTYPE_X11)
1001 {
1002 rc = vbcl_hlp_generic_term();
1003 VBClLogInfo("attempt to stop generic helper routines, rc=%Rrc\n", rc);
1004 }
1005
1006 return VINF_SUCCESS;
1007}
1008
1009/* Helper callbacks. */
1010const VBCLDISPLAYHELPER g_DisplayHelperGnome3 =
1011{
1012 "GNOME3", /* .pszName */
1013 vbcl_hlp_gnome3_probe, /* .pfnProbe */
1014 vbcl_hlp_gnome3_init, /* .pfnInit */
1015 vbcl_hlp_gnome3_term, /* .pfnTerm */
1016 vbcl_hlp_gnome3_set_primary_display, /* .pfnSetPrimaryDisplay */
1017 vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
1018 vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
1019};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use