1 /* mutter.c - implements the DBUS interface to mutter
3 Copyright 2020 Red Hat, Inc.
6 Julien Ropé <jrope@redhat.com>
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 #include "vdagentd-proto.h"
32 // MUTTER DBUS FORMAT STRINGS
33 #define MODE_BASE_FORMAT "siiddad"
34 #define MODE_FORMAT "(" MODE_BASE_FORMAT "a{sv})"
35 #define MODES_FORMAT "a" MODE_FORMAT
36 #define MONITOR_SPEC_FORMAT "(ssss)"
37 #define MONITOR_FORMAT "(" MONITOR_SPEC_FORMAT MODES_FORMAT "a{sv})"
38 #define MONITORS_FORMAT "a" MONITOR_FORMAT
40 #define LOGICAL_MONITOR_MONITORS_FORMAT "a" MONITOR_SPEC_FORMAT
41 #define LOGICAL_MONITOR_FORMAT "(iidub" LOGICAL_MONITOR_MONITORS_FORMAT "a{sv})"
42 #define LOGICAL_MONITORS_FORMAT "a" LOGICAL_MONITOR_FORMAT
44 #define CURRENT_STATE_FORMAT "(u" MONITORS_FORMAT LOGICAL_MONITORS_FORMAT "a{sv})"
47 struct VDAgentMutterDBus
{
48 GDBusProxy
*dbus_proxy
;
49 GHashTable
*connector_mapping
;
53 * Initialise a communication to Mutter through its DBUS interface.
55 * Errors can indicate that another compositor is used. This is not a blocker, and we should default
56 * to use a different API then.
59 * An initialise VDAgentMutterDBus structure if successful.
60 * NULL if an error occurred.
62 VDAgentMutterDBus
*vdagent_mutter_create(GHashTable
*connector_mapping
)
65 VDAgentMutterDBus
*mutter
= g_new0(VDAgentMutterDBus
, 1);
67 mutter
->connector_mapping
= g_hash_table_ref(connector_mapping
);
69 GDBusProxyFlags flags
= (G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
70 | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
71 | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
);
73 mutter
->dbus_proxy
= g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION
,
76 "org.gnome.Mutter.DisplayConfig",
77 "/org/gnome/Mutter/DisplayConfig",
78 "org.gnome.Mutter.DisplayConfig",
81 if (!mutter
->dbus_proxy
) {
82 syslog(LOG_WARNING
, "display: failed to create dbus proxy: %s", error
->message
);
83 g_clear_error(&error
);
84 vdagent_mutter_destroy(mutter
);
92 void vdagent_mutter_destroy(VDAgentMutterDBus
*mutter
)
94 g_clear_object(&mutter
->dbus_proxy
);
95 g_hash_table_unref(mutter
->connector_mapping
);
99 /** Look through a list of logical monitor to find the one provided.
100 * Returns the corresponding x and y position of the monitor on the desktop.
101 * This function is a helper to vdagent_mutter_get_resolution().
104 * logical_monitor: initialized GVariant iterator. It will be copied to look through the items
105 * so that its original position is not modified.
106 * connector: name of the connector that must be found
107 * x and y: will received the found position
110 static void vdagent_mutter_get_monitor_position(GVariantIter
*logical_monitors
,
111 const gchar
*connector
, int *x
, int *y
)
113 GVariant
*logical_monitor
= NULL
;
114 GVariantIter
*logical_monitor_iterator
= g_variant_iter_copy(logical_monitors
);
115 while (g_variant_iter_next(logical_monitor_iterator
, "@"LOGICAL_MONITOR_FORMAT
,
117 GVariantIter
*tmp_monitors
= NULL
;
119 g_variant_get_child(logical_monitor
, 0, "i", x
);
120 g_variant_get_child(logical_monitor
, 1, "i", y
);
121 g_variant_get_child(logical_monitor
, 5, LOGICAL_MONITOR_MONITORS_FORMAT
, &tmp_monitors
);
123 g_variant_unref(logical_monitor
);
125 GVariant
*tmp_monitor
= NULL
;
126 gboolean found
= FALSE
;
127 while (!found
&& g_variant_iter_next(tmp_monitors
, "@"MONITOR_SPEC_FORMAT
, &tmp_monitor
)) {
128 const gchar
*tmp_connector
;
130 g_variant_get_child(tmp_monitor
, 0, "&s", &tmp_connector
);
132 if (g_strcmp0(connector
, tmp_connector
) == 0) {
135 g_variant_unref(tmp_monitor
);
138 g_variant_iter_free(tmp_monitors
);
145 g_variant_iter_free(logical_monitor_iterator
);
148 GArray
*vdagent_mutter_get_resolutions(VDAgentMutterDBus
*mutter
,
149 int *desktop_width
, int *desktop_height
, int *screen_count
)
151 GError
*error
= NULL
;
152 GArray
*res_array
= NULL
;
154 // keep track of monitors we find and are not mapped to SPICE displays
155 // we will map them back later (assuming display ID == monitor index)
156 // this prevents the need from looping twice on all DBUS items
157 GArray
*not_found_array
= NULL
;
163 GVariant
*values
= g_dbus_proxy_call_sync(mutter
->dbus_proxy
,
166 G_DBUS_CALL_FLAGS_NONE
,
167 -1, // use proxy default timeout
171 syslog(LOG_WARNING
, "display: failed to call GetCurrentState from mutter over DBUS");
173 syslog(LOG_WARNING
, " error message: %s", error
->message
);
174 g_clear_error(&error
);
179 res_array
= g_array_new(FALSE
, FALSE
, sizeof(struct vdagentd_guest_xorg_resolution
));
180 not_found_array
= g_array_new(FALSE
, FALSE
, sizeof(struct vdagentd_guest_xorg_resolution
));
182 GVariantIter
*monitors
= NULL
;
183 GVariantIter
*logical_monitors
= NULL
;
185 g_variant_get_child(values
, 1, MONITORS_FORMAT
, &monitors
);
186 g_variant_get_child(values
, 2, LOGICAL_MONITORS_FORMAT
, &logical_monitors
);
189 GVariant
*monitor
= NULL
;
190 *screen_count
= g_variant_iter_n_children(monitors
);
192 while (g_variant_iter_next(monitors
, "@"MONITOR_FORMAT
, &monitor
)) {
194 const gchar
*connector
= NULL
;
195 GVariantIter
*modes
= NULL
;
196 GVariant
*monitor_specs
= NULL
;
198 g_variant_get_child(monitor
, 0, "@"MONITOR_SPEC_FORMAT
, &monitor_specs
);
199 g_variant_get_child(monitor_specs
, 0, "&s", &connector
);
200 g_variant_get_child(monitor
, 1, MODES_FORMAT
, &modes
);
202 g_variant_unref(monitor_specs
);
203 g_variant_unref(monitor
);
206 GVariant
*mode
= NULL
;
207 while (g_variant_iter_next(modes
, "@"MODE_FORMAT
, &mode
)) {
208 GVariant
*properties
= NULL
;
211 g_variant_get_child(mode
, 6, "@a{sv}", &properties
);
212 if (!g_variant_lookup(properties
, "is-current", "b", &is_current
)) {
215 g_variant_unref(properties
);
218 g_variant_unref(mode
);
222 struct vdagentd_guest_xorg_resolution curr
;
223 vdagent_mutter_get_monitor_position(logical_monitors
, connector
, &curr
.x
, &curr
.y
);
224 g_variant_get_child(mode
, 1, "i", &curr
.width
);
225 g_variant_get_child(mode
, 2, "i", &curr
.height
);
226 g_variant_unref(mode
);
228 // compute the size of the desktop based on the dimension of the monitors
229 if (curr
.x
+ curr
.width
> *desktop_width
) {
230 *desktop_width
= curr
.x
+ curr
.width
;
232 if (curr
.y
+ curr
.height
> *desktop_height
) {
233 *desktop_height
= curr
.y
+ curr
.height
;
237 if (g_hash_table_lookup_extended(mutter
->connector_mapping
, connector
, NULL
, &value
)) {
238 curr
.display_id
= GPOINTER_TO_UINT(value
);
240 "Found monitor %s with geometry %dx%d+%d-%d - associating it to SPICE display #%d",
241 connector
, curr
.width
, curr
.height
, curr
.x
, curr
.y
, curr
.display_id
);
242 g_array_append_val(res_array
, curr
);
244 syslog(LOG_DEBUG
, "No SPICE display found for connector %s", connector
);
245 g_array_append_val(not_found_array
, curr
);
250 g_variant_iter_free(modes
);
253 g_variant_iter_free(logical_monitors
);
254 g_variant_iter_free(monitors
);
258 if (res_array
->len
== 0) {
259 syslog(LOG_DEBUG
, "%s: No Spice display ID matching - assuming display ID == Monitor index",
261 g_array_free(res_array
, TRUE
);
262 res_array
= not_found_array
;
264 struct vdagentd_guest_xorg_resolution
*res
;
265 res
= (struct vdagentd_guest_xorg_resolution
*)res_array
->data
;
266 for (i
= 0; i
< res_array
->len
; i
++) {
267 res
[i
].display_id
= i
;
271 g_array_free(not_found_array
, TRUE
);
274 g_variant_unref(values
);