1 /*
2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4 *
5 * Copyright 2019 SUSE LLC.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 /* atspi-mutter.c: support for keyboard/mouse handling using the
24 * mutter/gnome-shell remote desktop interfaces
25 *
26 * This functionality is analogous to the X11-based code in
27 * device-event-controller-x11.c. Placing the code here, rather than in the
28 * registry daemon, allows the relevant dbus calls to come directly from the
29 * AT-SPI client, rather than at-spi2-registryd being an intermediary,
30 * which may be useful if a distribution wishes to lock down access to the
31 * remote desktop interfaces.
32 */
33
34 #include "atspi-private.h"
35
36 typedef struct
37 {
38 DBusConnection *bus;
39 const char *rd_session_id;
40 const char *rd_session_path;
41 const char *sc_session_id;
42 const char *sc_session_path;
43 const char *sc_stream_path;
44 dbus_uint64_t window_id;
45 gboolean window_id_is_explicit;
46 } ATSPI_MUTTER_DATA;
47
48 static ATSPI_MUTTER_DATA data;
49
50 #define MUTTER_REMOTE_DESKTOP_BUS_NAME "org.gnome.Mutter.RemoteDesktop"
51 #define MUTTER_REMOTE_DESKTOP_OBJECT_PATH "/org/gnome/Mutter/RemoteDesktop"
52 #define MUTTER_REMOTE_DESKTOP_INTERFACE "org.gnome.Mutter.RemoteDesktop"
53 #define MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE "org.gnome.Mutter.RemoteDesktop.Session"
54 #define MUTTER_SCREEN_CAST_BUS_NAME "org.gnome.Mutter.ScreenCast"
55 #define MUTTER_SCREEN_CAST_OBJECT_PATH "/org/gnome/Mutter/ScreenCast"
56 #define MUTTER_SCREEN_CAST_INTERFACE "org.gnome.Mutter.ScreenCast"
57 #define MUTTER_SCREEN_CAST_SESSION_INTERFACE "org.gnome.Mutter.ScreenCast.Session"
58
59 /* TODO: consider porting this to gdbus */
60
61 static void
ensure_bus()62 ensure_bus ()
63 {
64 if (data.bus)
65 return;
66 data.bus = dbus_bus_get (DBUS_BUS_SESSION, NULL);
67 }
68
69 static gboolean
ensure_rd_session_path(GError ** error)70 ensure_rd_session_path (GError **error)
71 {
72 char *session_path;
73 DBusError d_error;
74
75 if (data.rd_session_path)
76 return (data.rd_session_path[0] != '\0');
77 ensure_bus ();
78
79 dbus_error_init (&d_error);
80 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, MUTTER_REMOTE_DESKTOP_OBJECT_PATH, MUTTER_REMOTE_DESKTOP_INTERFACE, "CreateSession", &d_error, "=>o", &session_path);
81
82 data.rd_session_path = g_strdup (session_path);
83 if (!data.rd_session_path[0])
84 return FALSE;
85
86 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "Start", &d_error, "");
87 return TRUE;
88 }
89
90 static dbus_uint64_t
get_window_id(const char * name)91 get_window_id (const char *name)
92 {
93 DBusMessage *message, *reply;
94 DBusError d_error;
95 dbus_uint64_t window_id;
96 DBusMessageIter iter, iter_array, iter_dict, iter_sub_array, iter_sub_dict;
97 const char *prop_name;
98 const char *cur_name;
99 dbus_bool_t cur_focus;
100 gboolean have_focus;
101
102 dbus_error_init (&d_error);
103 message = dbus_message_new_method_call (MUTTER_REMOTE_DESKTOP_BUS_NAME, "/org/gnome/Shell/Introspect", "org.gnome.Shell.Introspect", "GetWindows");
104 reply = dbus_connection_send_with_reply_and_block (data.bus, message, -1, &d_error);
105 dbus_message_unref (message);
106
107 if (!reply)
108 return FALSE;
109 if (strcmp (dbus_message_get_signature (reply), "a{ta{sv}}") != 0)
110 {
111 dbus_message_unref (reply);
112 return FALSE;
113 }
114
115 dbus_message_iter_init (reply, &iter);
116 dbus_message_iter_recurse (&iter, &iter_array);
117 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
118 {
119 dbus_message_iter_recurse (&iter_array, &iter_dict);
120 dbus_message_iter_get_basic (&iter_dict, &window_id);
121 dbus_message_iter_next (&iter_dict);
122 dbus_message_iter_recurse (&iter_dict, &iter_sub_array);
123 cur_name = NULL;
124 have_focus = FALSE;
125 while (dbus_message_iter_get_arg_type (&iter_sub_array) != DBUS_TYPE_INVALID)
126 {
127 dbus_message_iter_recurse (&iter_sub_array, &iter_sub_dict);
128 dbus_message_iter_get_basic (&iter_sub_dict, &prop_name);
129 if (!strcmp (prop_name, "wm-class"))
130 {
131 DBusMessageIter iter_variant;
132 dbus_message_iter_next (&iter_sub_dict);
133 dbus_message_iter_recurse (&iter_sub_dict, &iter_variant);
134 dbus_message_iter_get_basic (&iter_variant, &cur_name);
135 }
136 if (!strcmp (prop_name, "has-focus"))
137 {
138 DBusMessageIter iter_variant;
139 dbus_message_iter_next (&iter_sub_dict);
140 dbus_message_iter_recurse (&iter_sub_dict, &iter_variant);
141 dbus_message_iter_get_basic (&iter_variant, &cur_focus);
142 have_focus = TRUE;
143 }
144 if (cur_name && have_focus)
145 {
146 if ((name && !strcmp (name, cur_name)) || cur_focus)
147 {
148 dbus_message_unref (reply);
149 return window_id;
150 }
151 break;
152 }
153 dbus_message_iter_next (&iter_sub_array);
154 }
155 dbus_message_iter_next (&iter_array);
156 }
157
158 dbus_message_unref (reply);
159 return 0;
160 }
161
162 static gboolean
ensure_rd_session_id(GError ** error)163 ensure_rd_session_id (GError **error)
164 {
165 DBusMessage *message, *reply;
166 DBusError d_error;
167 const char *interface = "org.gnome.Mutter.RemoteDesktop.Session";
168 const char *prop_name = "SessionId";
169 DBusMessageIter iter, iter_variant;
170 const char *session_id;
171
172 if (data.rd_session_id)
173 return (data.rd_session_id[0] != '\0');
174
175 if (!ensure_rd_session_path (error))
176 return FALSE;
177
178 message = dbus_message_new_method_call (MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, "org.freedesktop.DBus.Properties", "Get");
179 dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &prop_name, DBUS_TYPE_INVALID);
180
181 dbus_error_init (&d_error);
182 reply = dbus_connection_send_with_reply_and_block (data.bus, message, -1, &d_error);
183 dbus_message_unref (message);
184 if (!reply)
185 return FALSE;
186 if (strcmp (dbus_message_get_signature (reply), "v") != 0)
187 {
188 dbus_message_unref (reply);
189 return FALSE;
190 }
191 dbus_message_iter_init (reply, &iter);
192 dbus_message_iter_recurse (&iter, &iter_variant);
193 dbus_message_iter_get_basic (&iter_variant, &session_id);
194 data.rd_session_id = g_strdup (session_id);
195 dbus_message_unref (reply);
196 return TRUE;
197 }
198
199 static gboolean
ensure_sc_session(GError ** error)200 ensure_sc_session (GError **error)
201 {
202 DBusMessage *message, *reply;
203 DBusError d_error;
204 DBusMessageIter iter, iter_array, iter_dict_entry, iter_variant;
205 const char *prop_name = "remote-desktop-session-id";
206 const char *sc_session_path;
207
208 if (!ensure_rd_session_id (error))
209 return FALSE;
210
211 if (data.sc_session_path)
212 return (data.sc_session_path[0] != '\0');
213
214 message = dbus_message_new_method_call (MUTTER_SCREEN_CAST_BUS_NAME, MUTTER_SCREEN_CAST_OBJECT_PATH, MUTTER_SCREEN_CAST_INTERFACE, "CreateSession");
215 dbus_message_iter_init_append (message, &iter);
216 dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &iter_array);
217 dbus_message_iter_open_container (&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict_entry);
218 dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &prop_name);
219 dbus_message_iter_open_container (&iter_dict_entry, DBUS_TYPE_VARIANT, "s", &iter_variant);
220 dbus_message_iter_append_basic (&iter_variant, DBUS_TYPE_STRING, &data.rd_session_id);
221 dbus_message_iter_close_container (&iter_dict_entry, &iter_variant);
222 dbus_message_iter_close_container (&iter_array, &iter_dict_entry);
223 dbus_message_iter_close_container (&iter, &iter_array);
224 dbus_error_init (&d_error);
225 reply = dbus_connection_send_with_reply_and_block (data.bus, message, -1, &d_error);
226 dbus_message_unref (message);
227 if (!reply)
228 return FALSE;
229 if (!dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &sc_session_path, DBUS_TYPE_INVALID))
230 {
231 dbus_message_unref (reply);
232 return FALSE;
233 }
234
235 data.sc_session_path = g_strdup (sc_session_path);
236 dbus_message_unref (reply);
237 return TRUE;
238 }
239
240 static gboolean
init_mutter(gboolean need_window,GError ** error)241 init_mutter (gboolean need_window, GError **error)
242 {
243 dbus_uint64_t window_id;
244 const char *prop_name = "window-id";
245 DBusError d_error;
246 DBusMessageIter iter, iter_array, iter_dict_entry, iter_variant;
247 DBusMessage *message, *reply;
248 const char *sc_stream_path;
249
250 if (!ensure_rd_session_path (error))
251 return FALSE;
252
253 if (!need_window)
254 return TRUE;
255
256 window_id = (data.window_id_is_explicit) ? data.window_id
257 : get_window_id (NULL);
258 if (!window_id)
259 return FALSE;
260
261 if (!ensure_sc_session (error))
262 return FALSE;
263
264 if (window_id == data.window_id)
265 return TRUE;
266
267 message = dbus_message_new_method_call (MUTTER_SCREEN_CAST_BUS_NAME, data.sc_session_path, MUTTER_SCREEN_CAST_SESSION_INTERFACE, "RecordWindow");
268 dbus_message_iter_init_append (message, &iter);
269 dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &iter_array);
270 dbus_message_iter_open_container (&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict_entry);
271 dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &prop_name);
272 dbus_message_iter_open_container (&iter_dict_entry, DBUS_TYPE_VARIANT, "t", &iter_variant);
273 dbus_message_iter_append_basic (&iter_variant, DBUS_TYPE_UINT64, &window_id);
274 dbus_message_iter_close_container (&iter_dict_entry, &iter_variant);
275 dbus_message_iter_close_container (&iter_array, &iter_dict_entry);
276 dbus_message_iter_close_container (&iter, &iter_array);
277 dbus_error_init (&d_error);
278 reply = dbus_connection_send_with_reply_and_block (data.bus, message, -1, &d_error);
279 dbus_message_unref (message);
280 if (!reply)
281 return FALSE;
282 if (!dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &sc_stream_path, DBUS_TYPE_INVALID))
283 {
284 dbus_message_unref (reply);
285 return FALSE;
286 }
287
288 data.sc_stream_path = g_strdup (sc_stream_path);
289 dbus_message_unref (reply);
290 data.window_id = window_id;
291 return TRUE;
292 }
293
294 gboolean
_atspi_mutter_generate_keyboard_event(glong keyval,const gchar * keystring,AtspiKeySynthType synth_type,GError ** error)295 _atspi_mutter_generate_keyboard_event (glong keyval,
296 const gchar *keystring,
297 AtspiKeySynthType synth_type, GError **error)
298 {
299 DBusError d_error;
300 dbus_uint32_t d_keyval = keyval;
301
302 if (!init_mutter (FALSE, error))
303 return FALSE;
304
305 dbus_error_init (&d_error);
306 switch (synth_type)
307 {
308 case ATSPI_KEY_PRESS:
309 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyKeyboardKeycode", &d_error, "ub", d_keyval, TRUE);
310 break;
311 case ATSPI_KEY_RELEASE:
312 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyKeyboardKeycode", &d_error, "ub", d_keyval, FALSE);
313 break;
314 case ATSPI_KEY_PRESSRELEASE:
315 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyKeyboardKeycode", &d_error, "ub", d_keyval, TRUE);
316 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyKeyboardKeycode", &d_error, "ub", d_keyval, FALSE);
317 break;
318 case ATSPI_KEY_SYM:
319 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyKeyboardKeysyme", &d_error, "ub", d_keyval, TRUE);
320 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyKeyboardKeysyme", &d_error, "ub", d_keyval, FALSE);
321 break;
322 default:
323 /* TODO: set error */
324 g_warning ("%s: unsupported type", __func__);
325 return FALSE;
326 }
327 if (dbus_error_is_set (&d_error))
328 {
329 g_warning ("GenerateKeyboardEvent failed: %s", d_error.message);
330 dbus_error_free (&d_error);
331 return FALSE;
332 }
333
334 return TRUE;
335 }
336
337 gboolean
_atspi_mutter_generate_mouse_event(glong x,glong y,const gchar * name,GError ** error)338 _atspi_mutter_generate_mouse_event (glong x, glong y, const gchar *name, GError **error)
339 {
340 gint button = 0;
341 double d_x = x, d_y = y;
342 DBusError d_error;
343
344 if (!init_mutter (TRUE, error))
345 return FALSE;
346
347 dbus_error_init (&d_error);
348 switch (name[0])
349 {
350 case 'b':
351 button = name[1] - '1';
352 if (button < 0 || button > 4)
353 return FALSE;
354 if (x != -1 && y != -1)
355 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerMotionAbsolute", &d_error, "sdd", data.sc_stream_path, d_x, d_y);
356 switch (name[2])
357 {
358 case 'p':
359 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, TRUE);
360 break;
361 case 'r':
362 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, FALSE);
363 break;
364 case 'c':
365 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, TRUE);
366 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, FALSE);
367 break;
368 case 'd':
369 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, TRUE);
370 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, FALSE);
371 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, TRUE);
372 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerButton", &d_error, "ib", button, FALSE);
373 break;
374 default:
375 return FALSE;
376 }
377 break;
378 case 'a': /* absolute motion */
379 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerMotionAbsolute", &d_error, "sdd", data.sc_stream_path, d_x, d_y);
380 break;
381 case 'r': /* relative */
382 dbind_method_call_reentrant (data.bus, MUTTER_REMOTE_DESKTOP_BUS_NAME, data.rd_session_path, MUTTER_REMOTE_DESKTOP_SESSION_INTERFACE, "NotifyPointerMotionRelative", &d_error, "dd", d_x, d_y);
383 break;
384 default:
385 return FALSE;
386 }
387 return TRUE;
388 }
389
390 void
_atspi_mutter_set_reference_window(AtspiAccessible * accessible)391 _atspi_mutter_set_reference_window (AtspiAccessible *accessible)
392 {
393 if (accessible)
394 {
395 AtspiRole role = atspi_accessible_get_role (accessible, NULL);
396 gchar *name;
397 g_return_if_fail (role != ATSPI_ROLE_APPLICATION);
398 name = atspi_accessible_get_name (accessible, NULL);
399 data.window_id = get_window_id (name);
400 data.window_id_is_explicit = TRUE;
401 }
402
403 else
404 {
405 data.window_id_is_explicit = FALSE;
406 }
407 }
408