From 4ea4dc9176a3f33cf2752cc65290d98810e0f2bd Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Fri, 24 May 2024 19:15:09 +0100 Subject: [PATCH 1/5] a11y: Watch EventListenerRegistered/Deregistered signals Assistive technologies using AT-SPI typically register themselves on the accessibility bus through the org.a11y.atspi.Registry.RegisterEvent method, which will emit the EventListenerRegistered signal. We can use that signal (and its corresponding EventListenerDeregistered sibling) to know whether there is at least an AT on the other side of the accessibility bus. --- gtk/a11y/gtkatspiroot.c | 110 +++++++++++++++++++++++++++++++++ gtk/a11y/gtkatspirootprivate.h | 3 + 2 files changed, 113 insertions(+) diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c index 8727851add..de4e875d1d 100644 --- a/gtk/a11y/gtkatspiroot.c +++ b/gtk/a11y/gtkatspiroot.c @@ -45,6 +45,7 @@ #define ATSPI_PATH_PREFIX "/org/a11y/atspi" #define ATSPI_ROOT_PATH ATSPI_PATH_PREFIX "/accessible/root" #define ATSPI_CACHE_PATH ATSPI_PATH_PREFIX "/cache" +#define ATSPI_REGISTRY_PATH ATSPI_PATH_PREFIX "/registry" struct _GtkAtSpiRoot { @@ -71,6 +72,9 @@ struct _GtkAtSpiRoot GtkAtSpiCache *cache; GListModel *toplevels; + + /* HashTable */ + GHashTable *event_listeners; }; enum @@ -90,6 +94,7 @@ gtk_at_spi_root_finalize (GObject *gobject) GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject); g_clear_handle_id (&self->register_id, g_source_remove); + g_clear_pointer (&self->event_listeners, g_hash_table_unref); g_free (self->bus_address); g_free (self->base_path); @@ -541,6 +546,81 @@ on_registration_reply (GObject *gobject, g_free (data); } +static void +on_event_listener_registered (GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + + if (g_strcmp0 (object_path, ATSPI_REGISTRY_PATH) == 0 && + g_strcmp0 (interface_name, "org.a11y.atspi.Registry") == 0 && + g_strcmp0 (signal_name, "EventListenerRegistered") == 0) + { + char *sender = NULL; + char *event_name = NULL; + char **event_types = NULL; + + if (self->event_listeners == NULL) + self->event_listeners = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + NULL); + + g_variant_get (parameters, "(ssas)", &sender, &event_name, &event_types); + + GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus", + sender, + event_name[0] != 0 ? event_name : "(none)"); + + g_hash_table_add (self->event_listeners, sender); + + g_free (event_name); + g_strfreev (event_types); + } +} + +static void +on_event_listener_deregistered (GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + + if (g_strcmp0 (object_path, ATSPI_REGISTRY_PATH) == 0 && + g_strcmp0 (interface_name, "org.a11y.atspi.Registry") == 0 && + g_strcmp0 (signal_name, "EventListenerDeregistered") == 0) + { + const char *sender = NULL; + const char *event = NULL; + + if (self->event_listeners == NULL) + { + g_critical ("Received org.a11y.atspi.Registry::EventListenerDeregistered without " + "a corresponding EventListenerRegistered signal."); + return; + } + + g_variant_get (parameters, "(&s&s)", &sender, &event); + + if (g_hash_table_contains (self->event_listeners, sender)) + { + GTK_DEBUG (A11Y, "Deregistering event listener (%s, %s) on the a11y bus", + sender, + event[0] != 0 ? event : "(none)"); + + g_hash_table_remove (self->event_listeners, sender); + } + } +} + static gboolean root_register (gpointer user_data) { @@ -587,6 +667,27 @@ root_register (gpointer user_data) NULL, NULL); + g_dbus_connection_signal_subscribe (self->connection, + "org.a11y.atspi.Registry", + "org.a11y.atspi.Registry", + "EventListenerRegistered", + ATSPI_REGISTRY_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_event_listener_registered, + self, + NULL); + g_dbus_connection_signal_subscribe (self->connection, + "org.a11y.atspi.Registry", + "org.a11y.atspi.Registry", + "EventListenerDeregistered", + ATSPI_REGISTRY_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_event_listener_deregistered, + self, + NULL); + GTK_DEBUG (A11Y, "Registering (%s, %s) on the a11y bus", unique_name, self->root_path); @@ -818,3 +919,12 @@ gtk_at_spi_root_get_base_path (GtkAtSpiRoot *self) return self->base_path; } + +gboolean +gtk_at_spi_root_has_event_listeners (GtkAtSpiRoot *self) +{ + g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), FALSE); + + return self->event_listeners != NULL && + g_hash_table_size (self->event_listeners) != 0; +} diff --git a/gtk/a11y/gtkatspirootprivate.h b/gtk/a11y/gtkatspirootprivate.h index 7b626fc28e..627cf3f5b1 100644 --- a/gtk/a11y/gtkatspirootprivate.h +++ b/gtk/a11y/gtkatspirootprivate.h @@ -63,4 +63,7 @@ gtk_at_spi_root_child_changed (GtkAtSpiRoot *self, GtkAccessibleChildChange change, GtkAccessible *child); +gboolean +gtk_at_spi_root_has_event_listeners (GtkAtSpiRoot *self); + G_END_DECLS From 307efe2172114bf2cfbcc13e9c98ff8f2f2232d0 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Sat, 25 May 2024 00:19:33 +0100 Subject: [PATCH 2/5] a11y: Do not emit event signals if there are no listeners Now that we track event listeners, we can skip the accessibility event emission altogether if we know nothing is on the other side. --- gtk/a11y/gtkatspicontext.c | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c index 397bb5d551..52da24a9b2 100644 --- a/gtk/a11y/gtkatspicontext.c +++ b/gtk/a11y/gtkatspicontext.c @@ -760,7 +760,7 @@ emit_text_changed (GtkAtSpiContext *self, int end, const char *text) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -780,7 +780,7 @@ emit_text_selection_changed (GtkAtSpiContext *self, const char *kind, int cursor_position) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; if (strcmp (kind, "text-caret-moved") == 0) @@ -807,7 +807,7 @@ static void emit_selection_changed (GtkAtSpiContext *self, const char *kind) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -825,7 +825,7 @@ emit_state_changed (GtkAtSpiContext *self, const char *name, gboolean enabled) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -841,7 +841,7 @@ emit_state_changed (GtkAtSpiContext *self, static void emit_defunct (GtkAtSpiContext *self) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -858,13 +858,10 @@ emit_property_changed (GtkAtSpiContext *self, const char *name, GVariant *value) { - GVariant *value_owned = g_variant_ref_sink (value); + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) + return; - if (self->connection == NULL) - { - g_variant_unref (value_owned); - return; - } + GVariant *value_owned = g_variant_ref_sink (value); g_dbus_connection_emit_signal (self->connection, NULL, @@ -884,7 +881,7 @@ emit_bounds_changed (GtkAtSpiContext *self, int width, int height) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -904,7 +901,9 @@ emit_children_changed (GtkAtSpiContext *self, GtkAccessibleChildState state) { /* If we don't have a connection on either contexts, we cannot emit a signal */ - if (self->connection == NULL || child_context->connection == NULL) + if (self->connection == NULL || + child_context->connection == NULL || + !gtk_at_spi_root_has_event_listeners (self->root)) return; GVariant *context_ref = gtk_at_spi_context_to_ref (self); @@ -922,7 +921,7 @@ static void emit_window_event (GtkAtSpiContext *self, const char *event_type) { - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -1587,7 +1586,7 @@ gtk_at_spi_context_update_caret_position (GtkATContext *context) GtkAccessibleText *accessible_text = GTK_ACCESSIBLE_TEXT (accessible); guint offset; - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; offset = gtk_accessible_text_get_caret_position (accessible_text); @@ -1611,7 +1610,7 @@ gtk_at_spi_context_update_selection_bound (GtkATContext *context) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; g_dbus_connection_emit_signal (self->connection, @@ -1636,7 +1635,7 @@ gtk_at_spi_context_update_text_contents (GtkATContext *context, { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); - if (self->connection == NULL) + if (self->connection == NULL || !gtk_at_spi_root_has_event_listeners (self->root)) return; GtkAccessible *accessible = gtk_at_context_get_accessible (context); From d9f75581be3a522e3fb43e60fef679ba967c284e Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Sat, 25 May 2024 00:53:42 +0100 Subject: [PATCH 3/5] a11y: Get event listeners on root registration We ask the AT-SPI registry for event listeners already registered when connecting the root object, in case an AT is running. --- gtk/a11y/gtkatspiroot.c | 218 +++++++++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 81 deletions(-) diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c index de4e875d1d..2ba47f03aa 100644 --- a/gtk/a11y/gtkatspiroot.c +++ b/gtk/a11y/gtkatspiroot.c @@ -486,66 +486,6 @@ gtk_at_spi_root_child_changed (GtkAtSpiRoot *self, window_ref); } -typedef struct { - GtkAtSpiRoot *root; - GtkAtSpiRootRegisterFunc register_func; -} RegistrationData; - -static void -on_registration_reply (GObject *gobject, - GAsyncResult *result, - gpointer user_data) -{ - RegistrationData *data = user_data; - GtkAtSpiRoot *self = data->root; - - GError *error = NULL; - GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error); - - self->register_id = 0; - - if (error != NULL) - { - g_critical ("Unable to register the application: %s", error->message); - g_error_free (error); - return; - } - - if (reply != NULL) - { - g_variant_get (reply, "((so))", - &self->desktop_name, - &self->desktop_path); - g_variant_unref (reply); - - GTK_DEBUG (A11Y, "Connected to the a11y registry at (%s, %s)", - self->desktop_name, - self->desktop_path); - } - - /* Register the cache object */ - self->cache = gtk_at_spi_cache_new (self->connection, ATSPI_CACHE_PATH, self); - - /* Drain the list of queued GtkAtSpiContexts, and add them to the cache */ - if (self->queued_contexts != NULL) - { - self->queued_contexts = g_list_reverse (self->queued_contexts); - for (GList *l = self->queued_contexts; l != NULL; l = l->next) - { - if (data->register_func != NULL) - data->register_func (self, l->data); - - gtk_at_spi_cache_add_context (self->cache, l->data); - } - - g_clear_pointer (&self->queued_contexts, g_list_free); - } - - self->toplevels = gtk_window_get_toplevels (); - - g_free (data); -} - static void on_event_listener_registered (GDBusConnection *connection, const char *sender_name, @@ -621,6 +561,143 @@ on_event_listener_deregistered (GDBusConnection *connection, } } +static void +on_registered_events_reply (GObject *gobject, + GAsyncResult *result, + gpointer data) +{ + GError *error = NULL; + GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error); + if (error != NULL) + { + g_critical ("Unable to get the list of registered event listeners: %s", error->message); + g_error_free (error); + return; + } + + GtkAtSpiRoot *self = data; + GVariant *listeners = g_variant_get_child_value (reply, 0); + GVariantIter *iter; + const char *sender, *event_name; + + if (self->event_listeners == NULL) + self->event_listeners = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + g_variant_get (listeners, "a(ss)", &iter); + while (g_variant_iter_loop (iter, "(&s&s)", &sender, &event_name)) + { + GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus", + sender, + event_name[0] != 0 ? event_name : "(none)"); + + if (!g_hash_table_contains (self->event_listeners, sender)) + g_hash_table_add (self->event_listeners, g_strdup (sender)); + } + + g_variant_iter_free (iter); + g_variant_unref (listeners); + g_variant_unref (reply); +} + +typedef struct { + GtkAtSpiRoot *root; + GtkAtSpiRootRegisterFunc register_func; +} RegistrationData; + +static void +on_registration_reply (GObject *gobject, + GAsyncResult *result, + gpointer user_data) +{ + RegistrationData *data = user_data; + GtkAtSpiRoot *self = data->root; + + GError *error = NULL; + GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error); + + self->register_id = 0; + + if (error != NULL) + { + g_critical ("Unable to register the application: %s", error->message); + g_error_free (error); + return; + } + + if (reply != NULL) + { + g_variant_get (reply, "((so))", + &self->desktop_name, + &self->desktop_path); + g_variant_unref (reply); + + GTK_DEBUG (A11Y, "Connected to the a11y registry at (%s, %s)", + self->desktop_name, + self->desktop_path); + } + + /* Register the cache object */ + self->cache = gtk_at_spi_cache_new (self->connection, ATSPI_CACHE_PATH, self); + + /* Drain the list of queued GtkAtSpiContexts, and add them to the cache */ + if (self->queued_contexts != NULL) + { + self->queued_contexts = g_list_reverse (self->queued_contexts); + for (GList *l = self->queued_contexts; l != NULL; l = l->next) + { + if (data->register_func != NULL) + data->register_func (self, l->data); + + gtk_at_spi_cache_add_context (self->cache, l->data); + } + + g_clear_pointer (&self->queued_contexts, g_list_free); + } + + self->toplevels = gtk_window_get_toplevels (); + + g_free (data); + + /* Subscribe to notifications on the registered event listeners */ + g_dbus_connection_signal_subscribe (self->connection, + "org.a11y.atspi.Registry", + "org.a11y.atspi.Registry", + "EventListenerRegistered", + ATSPI_REGISTRY_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_event_listener_registered, + self, + NULL); + g_dbus_connection_signal_subscribe (self->connection, + "org.a11y.atspi.Registry", + "org.a11y.atspi.Registry", + "EventListenerDeregistered", + ATSPI_REGISTRY_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + on_event_listener_deregistered, + self, + NULL); + + /* Get the list of ATs listening to events, in case they were started + * before the application; we want to delay the D-Bus traffic as much + * as possible until we know something is listening on the accessibility + * bus + */ + g_dbus_connection_call (self->connection, + "org.a11y.atspi.Registry", + ATSPI_REGISTRY_PATH, + "org.a11y.atspi.Registry", + "GetRegisteredEvents", + g_variant_new ("()"), + G_VARIANT_TYPE ("(a(ss))"), + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, + on_registered_events_reply, + self); +} + static gboolean root_register (gpointer user_data) { @@ -667,27 +744,6 @@ root_register (gpointer user_data) NULL, NULL); - g_dbus_connection_signal_subscribe (self->connection, - "org.a11y.atspi.Registry", - "org.a11y.atspi.Registry", - "EventListenerRegistered", - ATSPI_REGISTRY_PATH, - NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - on_event_listener_registered, - self, - NULL); - g_dbus_connection_signal_subscribe (self->connection, - "org.a11y.atspi.Registry", - "org.a11y.atspi.Registry", - "EventListenerDeregistered", - ATSPI_REGISTRY_PATH, - NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - on_event_listener_deregistered, - self, - NULL); - GTK_DEBUG (A11Y, "Registering (%s, %s) on the a11y bus", unique_name, self->root_path); From 58f2d3fbe74cee306fa666c087a985edecdccaa4 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 27 May 2024 13:09:55 +0100 Subject: [PATCH 4/5] a11y: Add reference counting to event listeners Event listeners can register themselves multiple times, and deregister themselves as well. We need to remove an event listener only if it dropped all its events. --- gtk/a11y/gtkatspiroot.c | 83 +++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c index 2ba47f03aa..d69582d23c 100644 --- a/gtk/a11y/gtkatspiroot.c +++ b/gtk/a11y/gtkatspiroot.c @@ -73,7 +73,7 @@ struct _GtkAtSpiRoot GListModel *toplevels; - /* HashTable */ + /* HashTable */ GHashTable *event_listeners; }; @@ -504,19 +504,34 @@ on_event_listener_registered (GDBusConnection *connection, char *sender = NULL; char *event_name = NULL; char **event_types = NULL; + unsigned int *count; if (self->event_listeners == NULL) self->event_listeners = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - NULL); + g_free); g_variant_get (parameters, "(ssas)", &sender, &event_name, &event_types); - GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus", - sender, - event_name[0] != 0 ? event_name : "(none)"); - - g_hash_table_add (self->event_listeners, sender); + count = g_hash_table_lookup (self->event_listeners, sender); + if (count == NULL) + { + GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus", + sender, + event_name[0] != 0 ? event_name : "(none)"); + count = g_new (unsigned int, 1); + *count = 1; + g_hash_table_insert (self->event_listeners, sender, count); + } + else if (*count == G_MAXUINT) + { + g_critical ("Reference count for event listener %s reached saturation", sender); + } + else + { + GTK_DEBUG (A11Y, "Incrementing refcount for event listener %s", sender); + *count += 1; + } g_free (event_name); g_strfreev (event_types); @@ -540,22 +555,37 @@ on_event_listener_deregistered (GDBusConnection *connection, { const char *sender = NULL; const char *event = NULL; - - if (self->event_listeners == NULL) - { - g_critical ("Received org.a11y.atspi.Registry::EventListenerDeregistered without " - "a corresponding EventListenerRegistered signal."); - return; - } + unsigned int *count; g_variant_get (parameters, "(&s&s)", &sender, &event); - if (g_hash_table_contains (self->event_listeners, sender)) + if (G_UNLIKELY (self->event_listeners == NULL)) { - GTK_DEBUG (A11Y, "Deregistering event listener (%s, %s) on the a11y bus", - sender, - event[0] != 0 ? event : "(none)"); + g_critical ("Received org.a11y.atspi.Registry::EventListenerDeregistered for " + "sender (%s, %s) without a corresponding EventListenerRegistered " + "signal.", + sender, event[0] != '\0' ? event : "(no event)"); + return; + } + count = g_hash_table_lookup (self->event_listeners, sender); + if (G_UNLIKELY (count == NULL)) + { + g_critical ("Received org.a11y.atspi.Registry::EventListenerDeregistered for " + "sender (%s, %s) without a corresponding EventListenerRegistered " + "signal.", + sender, event[0] != '\0' ? event : "(no event)"); + return; + } + + if (*count > 1) + { + GTK_DEBUG (A11Y, "Decreasing refcount for listener %s", sender); + *count -= 1; + } + else + { + GTK_DEBUG (A11Y, "Deregistering event listener %s on the a11y bus", sender); g_hash_table_remove (self->event_listeners, sender); } } @@ -586,12 +616,25 @@ on_registered_events_reply (GObject *gobject, g_variant_get (listeners, "a(ss)", &iter); while (g_variant_iter_loop (iter, "(&s&s)", &sender, &event_name)) { + unsigned int *count; + GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus", sender, event_name[0] != 0 ? event_name : "(none)"); - if (!g_hash_table_contains (self->event_listeners, sender)) - g_hash_table_add (self->event_listeners, g_strdup (sender)); + count = g_hash_table_lookup (self->event_listeners, sender); + if (count == NULL) + { + count = g_new (unsigned int, 1); + *count = 1; + g_hash_table_insert (self->event_listeners, g_strdup (sender), count); + } + else if (*count == G_MAXUINT) + { + g_critical ("Reference count for event listener %s reached saturation", sender); + } + else + *count += 1; } g_variant_iter_free (iter); From b8072d8562493347f7d8be0c23caade063e844ae Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Fri, 30 Aug 2024 14:24:55 +0100 Subject: [PATCH 5/5] a11y: Add a check for sandboxed accessibility bus When sandboxed, the accessibility bus needs a recent version of Flatpak and its D-Bus proxy in order to relay event registrations. --- gtk/a11y/gtkatspiroot.c | 60 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c index d69582d23c..23bf042ff9 100644 --- a/gtk/a11y/gtkatspiroot.c +++ b/gtk/a11y/gtkatspiroot.c @@ -31,6 +31,7 @@ #include "gtkdebug.h" #include "gtkwindow.h" #include "gtkprivate.h" +#include "gdkprivate.h" #include "a11y/atspi/atspi-accessible.h" #include "a11y/atspi/atspi-application.h" @@ -75,6 +76,7 @@ struct _GtkAtSpiRoot /* HashTable */ GHashTable *event_listeners; + bool can_use_event_listeners; }; enum @@ -591,6 +593,42 @@ on_event_listener_deregistered (GDBusConnection *connection, } } +static bool +check_flatpak_portal_version (GDBusConnection *connection, + unsigned int minimum_version) +{ + GError *error = NULL; + + GVariant *res = + g_dbus_connection_call_sync (connection, + "org.freedesktop.portal.Flatpak", + "/org/freedesktop/portal/Flatpak", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", "org.freedesktop.portal.Flatpak", "version"), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (error != NULL) + { + g_warning ("Unable to retrieve the Flatpak portal version: %s", + error->message); + g_clear_error (&error); + return false; + } + + guint32 version = 0; + g_variant_get (res, "(u)", &version); + g_variant_unref (res); + + GTK_DEBUG (A11Y, "Flatpak portal version: %u (required: %u)", version, minimum_version); + + return version >= minimum_version; +} + static void on_registered_events_reply (GObject *gobject, GAsyncResult *result, @@ -701,6 +739,20 @@ on_registration_reply (GObject *gobject, g_free (data); + /* Check if we're running inside a sandbox. + * + * Flatpak applications need to have the D-Bus proxy set up inside the + * sandbox to allow event registration signals to propagate, so we + * check if the version of the Flatpak portal is recent enough. + */ + if (gdk_should_use_portal () && + !check_flatpak_portal_version (self->connection, 7)) + { + GTK_DEBUG (A11Y, "Sandboxed does not allow event listener registration"); + self->can_use_event_listeners = false; + return; + } + /* Subscribe to notifications on the registered event listeners */ g_dbus_connection_signal_subscribe (self->connection, "org.a11y.atspi.Registry", @@ -739,6 +791,8 @@ on_registration_reply (GObject *gobject, NULL, on_registered_events_reply, self); + + self->can_use_event_listeners = true; } static gboolean @@ -1024,6 +1078,10 @@ gtk_at_spi_root_has_event_listeners (GtkAtSpiRoot *self) { g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), FALSE); + /* If we can't rely on event listeners, we default to being chatty */ + if (!self->can_use_event_listeners) + return TRUE; + return self->event_listeners != NULL && - g_hash_table_size (self->event_listeners) != 0; + g_hash_table_size (self->event_listeners) != 0; }