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
49struct _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
76enum
77{
78 PROP_BUS_ADDRESS = 1,
79
80 N_PROPS
81};
82
83static GParamSpec *obj_props[N_PROPS];
84
85G_DEFINE_TYPE (GtkAtSpiRoot, gtk_at_spi_root, G_TYPE_OBJECT)
86
87static void
88gtk_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
102static void
103gtk_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
114static void
115gtk_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
133static void
134gtk_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
152static void
153handle_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
186static GVariant *
187handle_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
213static gboolean
214handle_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
239static void
240handle_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
361static GVariant *
362handle_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
407static const GDBusInterfaceVTable root_application_vtable = {
408 handle_application_method,
409 handle_application_get_property,
410 handle_application_set_property,
411};
412
413static const GDBusInterfaceVTable root_accessible_vtable = {
414 handle_accessible_method,
415 handle_accessible_get_property,
416 NULL,
417};
418
419void
420gtk_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
478typedef struct {
479 GtkAtSpiRoot *root;
480 GtkAtSpiRootRegisterFunc register_func;
481} RegistrationData;
482
483static void
484on_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
538static gboolean
539root_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 */
611void
612gtk_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
645void
646gtk_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
656static void
657gtk_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
733out:
734 G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->constructed (gobject);
735}
736
737static void
738gtk_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
758static void
759gtk_at_spi_root_init (GtkAtSpiRoot *self)
760{
761}
762
763GtkAtSpiRoot *
764gtk_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
773GDBusConnection *
774gtk_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
781GtkAtSpiCache *
782gtk_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 */
797GVariant *
798gtk_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
807const char *
808gtk_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

source code of gtk/gtk/a11y/gtkatspiroot.c