Merge branch 'ebassi/lazier-a11y' into 'main'

a11y: Watch EventListenerRegistered/Deregistered signals

See merge request GNOME/gtk!7298
This commit is contained in:
Matthias Clasen
2024-08-30 20:17:17 +00:00
3 changed files with 286 additions and 17 deletions

View File

@@ -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);

View File

@@ -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"
@@ -45,6 +46,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 +73,10 @@ struct _GtkAtSpiRoot
GtkAtSpiCache *cache;
GListModel *toplevels;
/* HashTable<str, uint> */
GHashTable *event_listeners;
bool can_use_event_listeners;
};
enum
@@ -90,6 +96,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);
@@ -481,6 +488,198 @@ gtk_at_spi_root_child_changed (GtkAtSpiRoot *self,
window_ref);
}
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;
unsigned int *count;
if (self->event_listeners == NULL)
self->event_listeners = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
g_free);
g_variant_get (parameters, "(ssas)", &sender, &event_name, &event_types);
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);
}
}
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;
unsigned int *count;
g_variant_get (parameters, "(&s&s)", &sender, &event);
if (G_UNLIKELY (self->event_listeners == 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;
}
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);
}
}
}
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,
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))
{
unsigned int *count;
GTK_DEBUG (A11Y, "Registering event listener (%s, %s) on the a11y bus",
sender,
event_name[0] != 0 ? event_name : "(none)");
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);
g_variant_unref (listeners);
g_variant_unref (reply);
}
typedef struct {
GtkAtSpiRoot *root;
GtkAtSpiRootRegisterFunc register_func;
@@ -539,6 +738,61 @@ on_registration_reply (GObject *gobject,
self->toplevels = gtk_window_get_toplevels ();
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",
"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);
self->can_use_event_listeners = true;
}
static gboolean
@@ -818,3 +1072,16 @@ 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);
/* 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;
}

View File

@@ -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