From 0bcda4ecf47f04cb76fb642f05d498d1dd941e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= Date: Wed, 5 Feb 2025 19:15:54 +0100 Subject: [PATCH] backends: Implement A11y monitor interface Implement the org.freedesktop.a11y.KeyboardMonitor interface, allowing screen readers to interact with Mutter and grab shortcuts or full keyboard interaction. Carlos Garnacho: Move setup to ::constructed. Part-of: --- src/backends/meta-a11y-manager.c | 274 +++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) diff --git a/src/backends/meta-a11y-manager.c b/src/backends/meta-a11y-manager.c index 22de01030..5f8d5b1ee 100644 --- a/src/backends/meta-a11y-manager.c +++ b/src/backends/meta-a11y-manager.c @@ -44,14 +44,253 @@ enum static GParamSpec *props[N_PROPS]; +typedef struct _MetaA11yKeystroke +{ + uint32_t keysym; + ClutterModifierType modifiers; +} MetaA11yKeystroke; + +typedef struct _MetaA11yKeyGrabber +{ + MetaA11yManager *manager; + GDBusConnection *connection; + char *bus_name; + guint bus_name_watcher_id; + gboolean grab_all; + GArray *modifiers; + GArray *keystrokes; +} MetaA11yKeyGrabber; + typedef struct _MetaA11yManager { GObject parent; MetaBackend *backend; + guint dbus_name_id; + MetaDBusKeyboardMonitor *keyboard_monitor_skeleton; + + GList *key_grabbers; + GHashTable *grabbed_keypresses; + GHashTable *all_grabbed_modifiers; } MetaA11yManager; G_DEFINE_TYPE (MetaA11yManager, meta_a11y_manager, G_TYPE_OBJECT) +static void +key_grabber_free (MetaA11yKeyGrabber *grabber) +{ + if (grabber->bus_name_watcher_id) + { + g_bus_unwatch_name (grabber->bus_name_watcher_id); + grabber->bus_name_watcher_id = 0; + } + + g_clear_pointer (&grabber->keystrokes, g_array_unref); + g_clear_pointer (&grabber->modifiers, g_array_unref); + g_clear_object (&grabber->connection); + g_clear_pointer (&grabber->bus_name, g_free); + + g_free (grabber); +} + +static void +rebuild_all_grabbed_modifiers (MetaA11yManager *a11y_manager, + MetaA11yKeyGrabber *ignored_grabber) +{ + GList *l; + int i; + + g_hash_table_remove_all (a11y_manager->all_grabbed_modifiers); + + for (l = a11y_manager->key_grabbers; l; l = l->next) + { + MetaA11yKeyGrabber *grabber = l->data; + + if (grabber == ignored_grabber) + continue; + + for (i = 0; i < grabber->modifiers->len; i++) + { + uint32_t modifier_keysym = g_array_index (grabber->modifiers, uint32_t, i); + g_hash_table_add (a11y_manager->all_grabbed_modifiers, + GUINT_TO_POINTER (modifier_keysym)); + } + } +} + +static void +key_grabber_bus_name_vanished_callback (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + MetaA11yKeyGrabber *grabber = user_data; + MetaA11yManager *a11y_manager = grabber->manager; + + grabber->manager->key_grabbers = + g_list_remove (grabber->manager->key_grabbers, grabber); + + if (grabber->modifiers) + { + rebuild_all_grabbed_modifiers (a11y_manager, grabber); + g_signal_emit (grabber->manager, signals[A11Y_MODIFIERS_CHANGED], 0); + } + + key_grabber_free (grabber); +} + +static MetaA11yKeyGrabber * +ensure_key_grabber (MetaA11yManager *a11y_manager, + GDBusMethodInvocation *invocation) +{ + GDBusConnection *connection = + g_dbus_method_invocation_get_connection (invocation); + const char *sender = g_dbus_method_invocation_get_sender (invocation); + MetaA11yKeyGrabber *grabber; + GList *l; + + for (l = a11y_manager->key_grabbers; l; l = l->next) + { + grabber = l->data; + + if (g_strcmp0 (grabber->bus_name, sender) == 0) + return grabber; + } + + grabber = g_new0 (MetaA11yKeyGrabber, 1); + grabber->manager = a11y_manager; + grabber->bus_name = g_strdup (sender); + grabber->connection = g_object_ref (connection); + + grabber->bus_name_watcher_id = + g_bus_watch_name_on_connection (connection, + grabber->bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + key_grabber_bus_name_vanished_callback, + grabber, + NULL); + + a11y_manager->key_grabbers = + g_list_prepend (a11y_manager->key_grabbers, grabber); + + return grabber; +} + +static gboolean +handle_grab_keyboard (MetaDBusKeyboardMonitor *skeleton, + GDBusMethodInvocation *invocation, + MetaA11yManager *a11y_manager) +{ + MetaA11yKeyGrabber *grabber; + + grabber = ensure_key_grabber (a11y_manager, invocation); + grabber->grab_all = TRUE; + meta_dbus_keyboard_monitor_complete_grab_keyboard (skeleton, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_ungrab_keyboard (MetaDBusKeyboardMonitor *skeleton, + GDBusMethodInvocation *invocation, + MetaA11yManager *a11y_manager) +{ + MetaA11yKeyGrabber *grabber; + + grabber = ensure_key_grabber (a11y_manager, invocation); + grabber->grab_all = FALSE; + meta_dbus_keyboard_monitor_complete_ungrab_keyboard (skeleton, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_set_key_grabs (MetaDBusKeyboardMonitor *skeleton, + GDBusMethodInvocation *invocation, + GVariant *modifiers, + GVariant *keystrokes, + MetaA11yManager *a11y_manager) +{ + MetaA11yKeyGrabber *grabber; + GVariantIter iter; + uint32_t modifier_keysym; + MetaA11yKeystroke keystroke; + + grabber = ensure_key_grabber (a11y_manager, invocation); + + g_clear_pointer (&grabber->modifiers, g_array_unref); + g_clear_pointer (&grabber->keystrokes, g_array_unref); + grabber->modifiers = g_array_new (FALSE, FALSE, sizeof (uint32_t)); + grabber->keystrokes = g_array_new (FALSE, FALSE, sizeof (MetaA11yKeystroke)); + + g_variant_iter_init (&iter, modifiers); + while (g_variant_iter_next (&iter, "u", &modifier_keysym)) + g_array_append_val (grabber->modifiers, modifier_keysym); + + g_variant_iter_init (&iter, keystrokes); + while (g_variant_iter_next (&iter, "(uu)", &keystroke.keysym, + &keystroke.modifiers)) + g_array_append_val (grabber->keystrokes, keystroke); + + rebuild_all_grabbed_modifiers (a11y_manager, NULL); + g_signal_emit (a11y_manager, signals[A11Y_MODIFIERS_CHANGED], 0); + meta_dbus_keyboard_monitor_complete_set_key_grabs (skeleton, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +on_bus_acquired (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + MetaA11yManager *manager = user_data; + + manager->keyboard_monitor_skeleton = meta_dbus_keyboard_monitor_skeleton_new (); + + g_signal_connect (manager->keyboard_monitor_skeleton, "handle-grab-keyboard", + G_CALLBACK (handle_grab_keyboard), manager); + g_signal_connect (manager->keyboard_monitor_skeleton, "handle-ungrab-keyboard", + G_CALLBACK (handle_ungrab_keyboard), manager); + g_signal_connect (manager->keyboard_monitor_skeleton, "handle-set-key-grabs", + G_CALLBACK (handle_set_key_grabs), manager); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager->keyboard_monitor_skeleton), + connection, + "/org/freedesktop/a11y/Manager", + NULL); +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + g_debug ("Acquired name %s", name); +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + g_debug ("Lost or failed to acquire name %s", name); +} + +static void +meta_a11y_manager_finalize (GObject *object) +{ + MetaA11yManager *a11y_manager = META_A11Y_MANAGER (object); + + g_list_free_full (a11y_manager->key_grabbers, + (GDestroyNotify) key_grabber_free); + g_clear_object (&a11y_manager->keyboard_monitor_skeleton); + g_clear_pointer (&a11y_manager->grabbed_keypresses, g_hash_table_destroy); + g_clear_pointer (&a11y_manager->all_grabbed_modifiers, g_hash_table_destroy); + g_bus_unown_name (a11y_manager->dbus_name_id); + + G_OBJECT_CLASS (meta_a11y_manager_parent_class)->finalize (object); +} + static void meta_a11y_manager_set_property (GObject *object, guint prop_id, @@ -71,12 +310,47 @@ meta_a11y_manager_set_property (GObject *object, } } +static void +meta_a11y_manager_constructed (GObject *object) +{ + MetaA11yManager *a11y_manager = META_A11Y_MANAGER (object); + MetaContext *context; + + g_assert (a11y_manager->backend); + context = meta_backend_get_context (a11y_manager->backend); + + a11y_manager->grabbed_keypresses = g_hash_table_new (NULL, NULL); + a11y_manager->all_grabbed_modifiers = g_hash_table_new (NULL, NULL); + + a11y_manager->dbus_name_id = + g_bus_own_name (G_BUS_TYPE_SESSION, + "org.freedesktop.a11y.Manager", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + (meta_context_is_replacing (context) ? + G_BUS_NAME_OWNER_FLAGS_REPLACE : + G_BUS_NAME_OWNER_FLAGS_NONE), + on_bus_acquired, + on_name_acquired, + on_name_lost, + a11y_manager, + NULL); +} + static void meta_a11y_manager_class_init (MetaA11yManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = meta_a11y_manager_finalize; object_class->set_property = meta_a11y_manager_set_property; + object_class->constructed = meta_a11y_manager_constructed; + + signals[A11Y_MODIFIERS_CHANGED] = + g_signal_new ("a11y-modifiers-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); props[PROP_BACKEND] = g_param_spec_object ("backend", NULL, NULL,