1 | /* gtkatspiroot.c: AT-SPI root object |
2 | * |
3 | * Copyright 2020 GNOME Foundation |
4 | * |
5 | * SPDX-License-Identifier: LGPL-2.1-or-later |
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, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include "gtkatspirootprivate.h" |
24 | |
25 | #include "gtkatspicacheprivate.h" |
26 | #include "gtkatspicontextprivate.h" |
27 | #include "gtkaccessibleprivate.h" |
28 | #include "gtkatspiprivate.h" |
29 | #include "gtkatspiutilsprivate.h" |
30 | |
31 | #include "gtkdebug.h" |
32 | #include "gtkwindow.h" |
33 | #include "gtkprivate.h" |
34 | |
35 | #include "a11y/atspi/atspi-accessible.h" |
36 | #include "a11y/atspi/atspi-application.h" |
37 | |
38 | #include <locale.h> |
39 | |
40 | #include <glib/gi18n-lib.h> |
41 | #include <gio/gio.h> |
42 | |
43 | #define ATSPI_VERSION "2.1" |
44 | |
45 | #define ATSPI_PATH_PREFIX "/org/a11y/atspi" |
46 | #define ATSPI_ROOT_PATH ATSPI_PATH_PREFIX "/accessible/root" |
47 | #define ATSPI_CACHE_PATH ATSPI_PATH_PREFIX "/cache" |
48 | |
49 | struct _GtkAtSpiRoot |
50 | { |
51 | GObject parent_instance; |
52 | |
53 | char *bus_address; |
54 | GDBusConnection *connection; |
55 | |
56 | char *base_path; |
57 | |
58 | const char *root_path; |
59 | |
60 | const char *toolkit_name; |
61 | const char *version; |
62 | const char *atspi_version; |
63 | |
64 | char *desktop_name; |
65 | char *desktop_path; |
66 | |
67 | gint32 application_id; |
68 | guint register_id; |
69 | |
70 | GList *queued_contexts; |
71 | GtkAtSpiCache *cache; |
72 | |
73 | GListModel *toplevels; |
74 | }; |
75 | |
76 | enum |
77 | { |
78 | PROP_BUS_ADDRESS = 1, |
79 | |
80 | N_PROPS |
81 | }; |
82 | |
83 | static GParamSpec *obj_props[N_PROPS]; |
84 | |
85 | G_DEFINE_TYPE (GtkAtSpiRoot, gtk_at_spi_root, G_TYPE_OBJECT) |
86 | |
87 | static void |
88 | gtk_at_spi_root_finalize (GObject *gobject) |
89 | { |
90 | GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (ptr: gobject); |
91 | |
92 | g_clear_handle_id (&self->register_id, g_source_remove); |
93 | |
94 | g_free (mem: self->bus_address); |
95 | g_free (mem: self->base_path); |
96 | g_free (mem: self->desktop_name); |
97 | g_free (mem: self->desktop_path); |
98 | |
99 | G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject); |
100 | } |
101 | |
102 | static void |
103 | gtk_at_spi_root_dispose (GObject *gobject) |
104 | { |
105 | GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (ptr: gobject); |
106 | |
107 | g_clear_object (&self->cache); |
108 | g_clear_object (&self->connection); |
109 | g_clear_pointer (&self->queued_contexts, g_list_free); |
110 | |
111 | G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject); |
112 | } |
113 | |
114 | static void |
115 | gtk_at_spi_root_set_property (GObject *gobject, |
116 | guint prop_id, |
117 | const GValue *value, |
118 | GParamSpec *pspec) |
119 | { |
120 | GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (ptr: gobject); |
121 | |
122 | switch (prop_id) |
123 | { |
124 | case PROP_BUS_ADDRESS: |
125 | self->bus_address = g_value_dup_string (value); |
126 | break; |
127 | |
128 | default: |
129 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
130 | } |
131 | } |
132 | |
133 | static void |
134 | gtk_at_spi_root_get_property (GObject *gobject, |
135 | guint prop_id, |
136 | GValue *value, |
137 | GParamSpec *pspec) |
138 | { |
139 | GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (ptr: gobject); |
140 | |
141 | switch (prop_id) |
142 | { |
143 | case PROP_BUS_ADDRESS: |
144 | g_value_set_string (value, v_string: self->bus_address); |
145 | break; |
146 | |
147 | default: |
148 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
149 | } |
150 | } |
151 | |
152 | static void |
153 | handle_application_method (GDBusConnection *connection, |
154 | const gchar *sender, |
155 | const gchar *object_path, |
156 | const gchar *interface_name, |
157 | const gchar *method_name, |
158 | GVariant *parameters, |
159 | GDBusMethodInvocation *invocation, |
160 | gpointer user_data) |
161 | { |
162 | if (g_strcmp0 (str1: method_name, str2: "GetLocale" ) == 0) |
163 | { |
164 | guint lctype; |
165 | const char *locale; |
166 | |
167 | int types[] = { |
168 | LC_MESSAGES, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME |
169 | }; |
170 | |
171 | g_variant_get (value: parameters, format_string: "(u)" , &lctype); |
172 | if (lctype >= G_N_ELEMENTS (types)) |
173 | { |
174 | g_dbus_method_invocation_return_error (invocation, |
175 | G_IO_ERROR, |
176 | code: G_IO_ERROR_INVALID_ARGUMENT, |
177 | format: "Not a known locale facet: %u" , lctype); |
178 | return; |
179 | } |
180 | |
181 | locale = setlocale (category: types[lctype], NULL); |
182 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)" , locale)); |
183 | } |
184 | } |
185 | |
186 | static GVariant * |
187 | handle_application_get_property (GDBusConnection *connection, |
188 | const gchar *sender, |
189 | const gchar *object_path, |
190 | const gchar *interface_name, |
191 | const gchar *property_name, |
192 | GError **error, |
193 | gpointer user_data) |
194 | { |
195 | GtkAtSpiRoot *self = user_data; |
196 | GVariant *res = NULL; |
197 | |
198 | if (g_strcmp0 (str1: property_name, str2: "Id" ) == 0) |
199 | res = g_variant_new_int32 (value: self->application_id); |
200 | else if (g_strcmp0 (str1: property_name, str2: "ToolkitName" ) == 0) |
201 | res = g_variant_new_string (string: self->toolkit_name); |
202 | else if (g_strcmp0 (str1: property_name, str2: "Version" ) == 0) |
203 | res = g_variant_new_string (string: self->version); |
204 | else if (g_strcmp0 (str1: property_name, str2: "AtspiVersion" ) == 0) |
205 | res = g_variant_new_string (string: self->atspi_version); |
206 | else |
207 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
208 | format: "Unknown property '%s'" , property_name); |
209 | |
210 | return res; |
211 | } |
212 | |
213 | static gboolean |
214 | handle_application_set_property (GDBusConnection *connection, |
215 | const gchar *sender, |
216 | const gchar *object_path, |
217 | const gchar *interface_name, |
218 | const gchar *property_name, |
219 | GVariant *value, |
220 | GError **error, |
221 | gpointer user_data) |
222 | { |
223 | GtkAtSpiRoot *self = user_data; |
224 | |
225 | if (g_strcmp0 (str1: property_name, str2: "Id" ) == 0) |
226 | { |
227 | g_variant_get (value, format_string: "i" , &(self->application_id)); |
228 | } |
229 | else |
230 | { |
231 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
232 | format: "Invalid property '%s'" , property_name); |
233 | return FALSE; |
234 | } |
235 | |
236 | return TRUE; |
237 | } |
238 | |
239 | static void |
240 | handle_accessible_method (GDBusConnection *connection, |
241 | const gchar *sender, |
242 | const gchar *object_path, |
243 | const gchar *interface_name, |
244 | const gchar *method_name, |
245 | GVariant *parameters, |
246 | GDBusMethodInvocation *invocation, |
247 | gpointer user_data) |
248 | { |
249 | GtkAtSpiRoot *self = user_data; |
250 | |
251 | if (g_strcmp0 (str1: method_name, str2: "GetRole" ) == 0) |
252 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(u)" , ATSPI_ROLE_APPLICATION)); |
253 | else if (g_strcmp0 (str1: method_name, str2: "GetRoleName" ) == 0) |
254 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)" , "application" )); |
255 | else if (g_strcmp0 (str1: method_name, str2: "GetLocalizedRoleName" ) == 0) |
256 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(s)" , C_("accessibility" , "application" ))); |
257 | else if (g_strcmp0 (str1: method_name, str2: "GetState" ) == 0) |
258 | { |
259 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)" )); |
260 | |
261 | g_variant_builder_open (builder: &builder, G_VARIANT_TYPE ("au" )); |
262 | g_variant_builder_add (builder: &builder, format_string: "u" , 0); |
263 | g_variant_builder_add (builder: &builder, format_string: "u" , 0); |
264 | g_variant_builder_close (builder: &builder); |
265 | |
266 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_builder_end (builder: &builder)); |
267 | } |
268 | else if (g_strcmp0 (str1: method_name, str2: "GetAttributes" ) == 0) |
269 | { |
270 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})" )); |
271 | |
272 | g_variant_builder_open (builder: &builder, G_VARIANT_TYPE ("a{ss}" )); |
273 | g_variant_builder_add (builder: &builder, format_string: "{ss}" , "toolkit" , "GTK" ); |
274 | g_variant_builder_close (builder: &builder); |
275 | |
276 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_builder_end (builder: &builder)); |
277 | } |
278 | else if (g_strcmp0 (str1: method_name, str2: "GetApplication" ) == 0) |
279 | { |
280 | g_dbus_method_invocation_return_value (invocation, |
281 | parameters: g_variant_new (format_string: "((so))" , |
282 | self->desktop_name, |
283 | self->desktop_path)); |
284 | } |
285 | else if (g_strcmp0 (str1: method_name, str2: "GetChildAtIndex" ) == 0) |
286 | { |
287 | int idx, real_idx = 0; |
288 | |
289 | g_variant_get (value: parameters, format_string: "(i)" , &idx); |
290 | |
291 | GtkWidget *window = NULL; |
292 | guint n_toplevels = g_list_model_get_n_items (list: self->toplevels); |
293 | for (guint i = 0; i < n_toplevels; i++) |
294 | { |
295 | window = g_list_model_get_item (list: self->toplevels, position: i); |
296 | |
297 | g_object_unref (object: window); |
298 | |
299 | if (!gtk_widget_get_visible (widget: window)) |
300 | continue; |
301 | |
302 | if (real_idx == idx) |
303 | break; |
304 | |
305 | real_idx += 1; |
306 | } |
307 | |
308 | if (window == NULL) |
309 | return; |
310 | |
311 | GtkATContext *context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: window)); |
312 | |
313 | const char *name = g_dbus_connection_get_unique_name (connection: self->connection); |
314 | const char *path = gtk_at_spi_context_get_context_path (self: GTK_AT_SPI_CONTEXT (ptr: context)); |
315 | |
316 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "((so))" , name, path)); |
317 | } |
318 | else if (g_strcmp0 (str1: method_name, str2: "GetChildren" ) == 0) |
319 | { |
320 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)" )); |
321 | |
322 | guint n_toplevels = g_list_model_get_n_items (list: self->toplevels); |
323 | for (guint i = 0; i < n_toplevels; i++) |
324 | { |
325 | GtkWidget *window = g_list_model_get_item (list: self->toplevels, position: i); |
326 | |
327 | g_object_unref (object: window); |
328 | |
329 | if (!gtk_widget_get_visible (widget: window)) |
330 | continue; |
331 | |
332 | GtkATContext *context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: window)); |
333 | const char *name = g_dbus_connection_get_unique_name (connection: self->connection); |
334 | const char *path = gtk_at_spi_context_get_context_path (self: GTK_AT_SPI_CONTEXT (ptr: context)); |
335 | |
336 | g_variant_builder_add (builder: &builder, format_string: "(so)" , name, path); |
337 | } |
338 | |
339 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(a(so))" , &builder)); |
340 | } |
341 | else if (g_strcmp0 (str1: method_name, str2: "GetIndexInParent" ) == 0) |
342 | { |
343 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(i)" , -1)); |
344 | } |
345 | else if (g_strcmp0 (str1: method_name, str2: "GetRelationSet" ) == 0) |
346 | { |
347 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))" )); |
348 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(a(ua(so)))" , &builder)); |
349 | } |
350 | else if (g_strcmp0 (str1: method_name, str2: "GetInterfaces" ) == 0) |
351 | { |
352 | GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("as" )); |
353 | |
354 | g_variant_builder_add (builder: &builder, format_string: "s" , atspi_accessible_interface.name); |
355 | g_variant_builder_add (builder: &builder, format_string: "s" , atspi_application_interface.name); |
356 | |
357 | g_dbus_method_invocation_return_value (invocation, parameters: g_variant_new (format_string: "(as)" , &builder)); |
358 | } |
359 | } |
360 | |
361 | static GVariant * |
362 | handle_accessible_get_property (GDBusConnection *connection, |
363 | const gchar *sender, |
364 | const gchar *object_path, |
365 | const gchar *interface_name, |
366 | const gchar *property_name, |
367 | GError **error, |
368 | gpointer user_data) |
369 | { |
370 | GtkAtSpiRoot *self = user_data; |
371 | GVariant *res = NULL; |
372 | |
373 | if (g_strcmp0 (str1: property_name, str2: "Name" ) == 0) |
374 | res = g_variant_new_string (string: g_get_prgname () ? g_get_prgname () : "Unnamed" ); |
375 | else if (g_strcmp0 (str1: property_name, str2: "Description" ) == 0) |
376 | res = g_variant_new_string (string: g_get_application_name () ? g_get_application_name () : "No description" ); |
377 | else if (g_strcmp0 (str1: property_name, str2: "Locale" ) == 0) |
378 | res = g_variant_new_string (string: setlocale (LC_MESSAGES, NULL)); |
379 | else if (g_strcmp0 (str1: property_name, str2: "AccessibleId" ) == 0) |
380 | res = g_variant_new_string (string: "" ); |
381 | else if (g_strcmp0 (str1: property_name, str2: "Parent" ) == 0) |
382 | res = g_variant_new (format_string: "(so)" , self->desktop_name, self->desktop_path); |
383 | else if (g_strcmp0 (str1: property_name, str2: "ChildCount" ) == 0) |
384 | { |
385 | guint n_toplevels = g_list_model_get_n_items (list: self->toplevels); |
386 | int n_children = 0; |
387 | |
388 | for (guint i = 0; i < n_toplevels; i++) |
389 | { |
390 | GtkWidget *window = g_list_model_get_item (list: self->toplevels, position: i); |
391 | |
392 | if (gtk_widget_get_visible (widget: window)) |
393 | n_children += 1; |
394 | |
395 | g_object_unref (object: window); |
396 | } |
397 | |
398 | res = g_variant_new_int32 (value: n_children); |
399 | } |
400 | else |
401 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
402 | format: "Unknown property '%s'" , property_name); |
403 | |
404 | return res; |
405 | } |
406 | |
407 | static const GDBusInterfaceVTable root_application_vtable = { |
408 | handle_application_method, |
409 | handle_application_get_property, |
410 | handle_application_set_property, |
411 | }; |
412 | |
413 | static const GDBusInterfaceVTable root_accessible_vtable = { |
414 | handle_accessible_method, |
415 | handle_accessible_get_property, |
416 | NULL, |
417 | }; |
418 | |
419 | void |
420 | gtk_at_spi_root_child_changed (GtkAtSpiRoot *self, |
421 | GtkAccessibleChildChange change, |
422 | GtkAccessible *child) |
423 | { |
424 | guint n, i; |
425 | int idx = 0; |
426 | GVariant *window_ref; |
427 | GtkAccessibleChildState state; |
428 | |
429 | if (!self->toplevels) |
430 | return; |
431 | |
432 | for (i = 0, n = g_list_model_get_n_items (list: self->toplevels); i < n; i++) |
433 | { |
434 | GtkAccessible *item = g_list_model_get_item (list: self->toplevels, position: i); |
435 | |
436 | g_object_unref (object: item); |
437 | |
438 | if (item == child) |
439 | break; |
440 | |
441 | if (!gtk_accessible_should_present (self: item)) |
442 | continue; |
443 | |
444 | idx++; |
445 | } |
446 | |
447 | if (child == NULL) |
448 | { |
449 | window_ref = gtk_at_spi_null_ref (); |
450 | } |
451 | else |
452 | { |
453 | GtkATContext *context = gtk_accessible_get_at_context (self: child); |
454 | |
455 | window_ref = gtk_at_spi_context_to_ref (self: GTK_AT_SPI_CONTEXT (ptr: context)); |
456 | } |
457 | |
458 | switch (change) |
459 | { |
460 | case GTK_ACCESSIBLE_CHILD_CHANGE_ADDED: |
461 | state = GTK_ACCESSIBLE_CHILD_STATE_ADDED; |
462 | break; |
463 | case GTK_ACCESSIBLE_CHILD_CHANGE_REMOVED: |
464 | state = GTK_ACCESSIBLE_CHILD_STATE_REMOVED; |
465 | break; |
466 | default: |
467 | g_assert_not_reached (); |
468 | } |
469 | |
470 | gtk_at_spi_emit_children_changed (connection: self->connection, |
471 | path: self->root_path, |
472 | state, |
473 | idx, |
474 | child_ref: gtk_at_spi_root_to_ref (self), |
475 | sender_ref: window_ref); |
476 | } |
477 | |
478 | typedef struct { |
479 | GtkAtSpiRoot *root; |
480 | GtkAtSpiRootRegisterFunc register_func; |
481 | } RegistrationData; |
482 | |
483 | static void |
484 | on_registration_reply (GObject *gobject, |
485 | GAsyncResult *result, |
486 | gpointer user_data) |
487 | { |
488 | RegistrationData *data = user_data; |
489 | GtkAtSpiRoot *self = data->root; |
490 | |
491 | GError *error = NULL; |
492 | GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), res: result, error: &error); |
493 | |
494 | self->register_id = 0; |
495 | |
496 | if (error != NULL) |
497 | { |
498 | g_critical ("Unable to register the application: %s" , error->message); |
499 | g_error_free (error); |
500 | return; |
501 | } |
502 | |
503 | if (reply != NULL) |
504 | { |
505 | g_variant_get (value: reply, format_string: "((so))" , |
506 | &self->desktop_name, |
507 | &self->desktop_path); |
508 | g_variant_unref (value: reply); |
509 | |
510 | GTK_NOTE (A11Y, g_message ("Connected to the a11y registry at (%s, %s)" , |
511 | self->desktop_name, |
512 | self->desktop_path)); |
513 | } |
514 | |
515 | /* Register the cache object */ |
516 | self->cache = gtk_at_spi_cache_new (connection: self->connection, ATSPI_CACHE_PATH, root: self); |
517 | |
518 | /* Drain the list of queued GtkAtSpiContexts, and add them to the cache */ |
519 | if (self->queued_contexts != NULL) |
520 | { |
521 | self->queued_contexts = g_list_reverse (list: self->queued_contexts); |
522 | for (GList *l = self->queued_contexts; l != NULL; l = l->next) |
523 | { |
524 | if (data->register_func != NULL) |
525 | data->register_func (self, l->data); |
526 | |
527 | gtk_at_spi_cache_add_context (self: self->cache, context: l->data); |
528 | } |
529 | |
530 | g_clear_pointer (&self->queued_contexts, g_list_free); |
531 | } |
532 | |
533 | self->toplevels = gtk_window_get_toplevels (); |
534 | |
535 | g_free (mem: data); |
536 | } |
537 | |
538 | static gboolean |
539 | root_register (gpointer user_data) |
540 | { |
541 | RegistrationData *data = user_data; |
542 | GtkAtSpiRoot *self = data->root; |
543 | const char *unique_name; |
544 | |
545 | /* Register the root element; every application has a single root, so we only |
546 | * need to do this once. |
547 | * |
548 | * The root element is used to advertise our existence on the accessibility |
549 | * bus, and it's the entry point to the accessible objects tree. |
550 | * |
551 | * The announcement is split into two phases: |
552 | * |
553 | * 1. we register the org.a11y.atspi.Application and org.a11y.atspi.Accessible |
554 | * interfaces at the well-known object path |
555 | * 2. we invoke the org.a11y.atspi.Socket.Embed method with the connection's |
556 | * unique name and the object path |
557 | * 3. the ATSPI registry daemon will set the org.a11y.atspi.Application.Id |
558 | * property on the given object path |
559 | * 4. the registration concludes when the Embed method returns us the desktop |
560 | * name and object path |
561 | */ |
562 | self->toolkit_name = "GTK" ; |
563 | self->version = PACKAGE_VERSION; |
564 | self->atspi_version = ATSPI_VERSION; |
565 | self->root_path = ATSPI_ROOT_PATH; |
566 | |
567 | unique_name = g_dbus_connection_get_unique_name (connection: self->connection); |
568 | |
569 | g_dbus_connection_register_object (self->connection, |
570 | self->root_path, |
571 | (GDBusInterfaceInfo *) &atspi_application_interface, |
572 | &root_application_vtable, |
573 | self, |
574 | NULL, |
575 | NULL); |
576 | g_dbus_connection_register_object (self->connection, |
577 | self->root_path, |
578 | (GDBusInterfaceInfo *) &atspi_accessible_interface, |
579 | &root_accessible_vtable, |
580 | self, |
581 | NULL, |
582 | NULL); |
583 | |
584 | GTK_NOTE (A11Y, g_message ("Registering (%s, %s) on the a11y bus" , |
585 | unique_name, |
586 | self->root_path)); |
587 | |
588 | g_dbus_connection_call (connection: self->connection, |
589 | bus_name: "org.a11y.atspi.Registry" , |
590 | ATSPI_ROOT_PATH, |
591 | interface_name: "org.a11y.atspi.Socket" , |
592 | method_name: "Embed" , |
593 | parameters: g_variant_new (format_string: "((so))" , unique_name, self->root_path), |
594 | G_VARIANT_TYPE ("((so))" ), |
595 | flags: G_DBUS_CALL_FLAGS_NONE, timeout_msec: -1, |
596 | NULL, |
597 | callback: on_registration_reply, |
598 | user_data: data); |
599 | |
600 | return G_SOURCE_REMOVE; |
601 | } |
602 | |
603 | /*< private > |
604 | * gtk_at_spi_root_queue_register: |
605 | * @self: a `GtkAtSpiRoot` |
606 | * @context: the AtSpi context to register |
607 | * @func: the function to call when the root has been registered |
608 | * |
609 | * Queues the registration of the root object on the AT-SPI bus. |
610 | */ |
611 | void |
612 | gtk_at_spi_root_queue_register (GtkAtSpiRoot *self, |
613 | GtkAtSpiContext *context, |
614 | GtkAtSpiRootRegisterFunc func) |
615 | { |
616 | /* The cache is available if the root has finished registering itself; if we |
617 | * are still waiting for the registration to finish, add the context to a queue |
618 | */ |
619 | if (self->cache != NULL) |
620 | { |
621 | if (func != NULL) |
622 | func (self, context); |
623 | |
624 | gtk_at_spi_cache_add_context (self: self->cache, context); |
625 | return; |
626 | } |
627 | else |
628 | { |
629 | if (g_list_find (list: self->queued_contexts, data: context) == NULL) |
630 | self->queued_contexts = g_list_prepend (list: self->queued_contexts, data: context); |
631 | } |
632 | |
633 | /* Ignore multiple registration requests while one is already in flight */ |
634 | if (self->register_id != 0) |
635 | return; |
636 | |
637 | RegistrationData *data = g_new (RegistrationData, 1); |
638 | data->root = self; |
639 | data->register_func = func; |
640 | |
641 | self->register_id = g_idle_add (function: root_register, data); |
642 | gdk_source_set_static_name_by_id (tag: self->register_id, name: "[gtk] ATSPI root registration" ); |
643 | } |
644 | |
645 | void |
646 | gtk_at_spi_root_unregister (GtkAtSpiRoot *self, |
647 | GtkAtSpiContext *context) |
648 | { |
649 | if (self->queued_contexts != NULL) |
650 | self->queued_contexts = g_list_remove (list: self->queued_contexts, data: context); |
651 | |
652 | if (self->cache != NULL) |
653 | gtk_at_spi_cache_remove_context (self: self->cache, context); |
654 | } |
655 | |
656 | static void |
657 | gtk_at_spi_root_constructed (GObject *gobject) |
658 | { |
659 | GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (ptr: gobject); |
660 | |
661 | GError *error = NULL; |
662 | |
663 | /* The accessibility bus is a fully managed bus */ |
664 | self->connection = |
665 | g_dbus_connection_new_for_address_sync (address: self->bus_address, |
666 | flags: G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | |
667 | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, |
668 | NULL, NULL, |
669 | error: &error); |
670 | |
671 | if (error != NULL) |
672 | { |
673 | g_critical ("Unable to connect to the accessibility bus at '%s': %s" , |
674 | self->bus_address, |
675 | error->message); |
676 | g_error_free (error); |
677 | goto out; |
678 | } |
679 | |
680 | /* We use the application's object path to build the path of each |
681 | * accessible object exposed on the accessibility bus; the path is |
682 | * also used to access the object cache |
683 | */ |
684 | GApplication *application = g_application_get_default (); |
685 | |
686 | if (application != NULL && g_application_get_is_registered (application)) |
687 | { |
688 | const char *app_path = g_application_get_dbus_object_path (application); |
689 | |
690 | /* No need to validate the path */ |
691 | self->base_path = g_strconcat (string1: app_path, "/a11y" , NULL); |
692 | } |
693 | else |
694 | { |
695 | const char *program_name = g_get_prgname (); |
696 | |
697 | char *base_name = NULL; |
698 | if (program_name == NULL || *program_name == 0) |
699 | base_name = g_strdup (str: "unknown" ); |
700 | else if (*program_name == '/') |
701 | base_name = g_path_get_basename (file_name: program_name); |
702 | else |
703 | base_name = g_strdup (str: program_name); |
704 | |
705 | self->base_path = g_strconcat (string1: "/org/gtk/application/" , |
706 | base_name, |
707 | "/a11y" , |
708 | NULL); |
709 | |
710 | g_free (mem: base_name); |
711 | |
712 | /* Turn potentially invalid program names into something that can be |
713 | * used as a DBus path |
714 | */ |
715 | size_t len = strlen (s: self->base_path); |
716 | for (size_t i = 0; i < len; i++) |
717 | { |
718 | char c = self->base_path[i]; |
719 | |
720 | if (c == '/') |
721 | continue; |
722 | |
723 | if ((c >= '0' && c <= '9') || |
724 | (c >= 'A' && c <= 'Z') || |
725 | (c >= 'a' && c <= 'z') || |
726 | (c == '_')) |
727 | continue; |
728 | |
729 | self->base_path[i] = '_'; |
730 | } |
731 | } |
732 | |
733 | out: |
734 | G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->constructed (gobject); |
735 | } |
736 | |
737 | static void |
738 | gtk_at_spi_root_class_init (GtkAtSpiRootClass *klass) |
739 | { |
740 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
741 | |
742 | gobject_class->constructed = gtk_at_spi_root_constructed; |
743 | gobject_class->set_property = gtk_at_spi_root_set_property; |
744 | gobject_class->get_property = gtk_at_spi_root_get_property; |
745 | gobject_class->dispose = gtk_at_spi_root_dispose; |
746 | gobject_class->finalize = gtk_at_spi_root_finalize; |
747 | |
748 | obj_props[PROP_BUS_ADDRESS] = |
749 | g_param_spec_string (name: "bus-address" , NULL, NULL, |
750 | NULL, |
751 | flags: G_PARAM_CONSTRUCT_ONLY | |
752 | G_PARAM_READWRITE | |
753 | G_PARAM_STATIC_STRINGS); |
754 | |
755 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: obj_props); |
756 | } |
757 | |
758 | static void |
759 | gtk_at_spi_root_init (GtkAtSpiRoot *self) |
760 | { |
761 | } |
762 | |
763 | GtkAtSpiRoot * |
764 | gtk_at_spi_root_new (const char *bus_address) |
765 | { |
766 | g_return_val_if_fail (bus_address != NULL, NULL); |
767 | |
768 | return g_object_new (GTK_TYPE_AT_SPI_ROOT, |
769 | first_property_name: "bus-address" , bus_address, |
770 | NULL); |
771 | } |
772 | |
773 | GDBusConnection * |
774 | gtk_at_spi_root_get_connection (GtkAtSpiRoot *self) |
775 | { |
776 | g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); |
777 | |
778 | return self->connection; |
779 | } |
780 | |
781 | GtkAtSpiCache * |
782 | gtk_at_spi_root_get_cache (GtkAtSpiRoot *self) |
783 | { |
784 | g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); |
785 | |
786 | return self->cache; |
787 | } |
788 | |
789 | /*< private > |
790 | * gtk_at_spi_root_to_ref: |
791 | * @self: a `GtkAtSpiRoot` |
792 | * |
793 | * Returns an ATSPI object reference for the `GtkAtSpiRoot` node. |
794 | * |
795 | * Returns: (transfer floating): a `GVariant` with the root reference |
796 | */ |
797 | GVariant * |
798 | gtk_at_spi_root_to_ref (GtkAtSpiRoot *self) |
799 | { |
800 | g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); |
801 | |
802 | return g_variant_new (format_string: "(so)" , |
803 | g_dbus_connection_get_unique_name (connection: self->connection), |
804 | self->root_path); |
805 | } |
806 | |
807 | const char * |
808 | gtk_at_spi_root_get_base_path (GtkAtSpiRoot *self) |
809 | { |
810 | g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); |
811 | |
812 | return self->base_path; |
813 | } |
814 | |