From ee7d1dc52c5c58f204f3c88a027e830eeb1e5c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Tue, 25 Mar 2025 12:10:28 +0100 Subject: [PATCH] Backport Accessibility manager patches Resolves: RHEL-82072 --- a11y-manager.patch | 2157 ++++++++++++++++++++++++++++++++++++++++++++ mutter.spec | 3 + 2 files changed, 2160 insertions(+) create mode 100644 a11y-manager.patch diff --git a/a11y-manager.patch b/a11y-manager.patch new file mode 100644 index 0000000..a1a4904 --- /dev/null +++ b/a11y-manager.patch @@ -0,0 +1,2157 @@ +From cc92000f1f4ae6c7fcee330ffd38842365bdf811 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 18:36:15 +0100 +Subject: [PATCH 01/14] data: Introduce the DBus interface description for the + a11y manager + +This object will for now only provide a way for assistive technologies +to receive keyboard events, however it is expected that it will be used for +the new a11y communication protocol in the future. + +Part-of: +(cherry picked from commit 84fc62a2806b893b215fcfb287390ced07a98aa7) +--- + data/dbus-interfaces/org.freedesktop.a11y.xml | 92 +++++++++++++++++++ + src/meson.build | 5 + + 2 files changed, 97 insertions(+) + create mode 100644 data/dbus-interfaces/org.freedesktop.a11y.xml + +diff --git a/data/dbus-interfaces/org.freedesktop.a11y.xml b/data/dbus-interfaces/org.freedesktop.a11y.xml +new file mode 100644 +index 0000000000..c60480e89c +--- /dev/null ++++ b/data/dbus-interfaces/org.freedesktop.a11y.xml +@@ -0,0 +1,92 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/src/meson.build b/src/meson.build +index e3c92aaf27..6c19300b8f 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -947,6 +947,11 @@ if mutter_private_enum_sources.length() > 0 + endif + + dbus_interfaces = [ ++ { ++ 'name': 'meta-dbus-a11y', ++ 'interface': 'org.freedesktop.a11y.xml', ++ 'prefix': 'org.freedesktop.a11y', ++ }, + { + 'name': 'meta-dbus-display-config', + 'interface': 'org.gnome.Mutter.DisplayConfig.xml', +-- +2.44.0.501.g19981daefd.dirty + + +From 7c4e773276b9cd87373e4231db371849157db325 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 18:37:28 +0100 +Subject: [PATCH 02/14] clutter: Add ClutterEventFlag to notify a11y modifier + click + +Use a Clutter event flag to communicate the the fact that the event +is an a11y modifier first click. The accessibility modifiers will +require special handling in the input and main threads. + +Part-of: +(cherry picked from commit d3a0bbdf524f160b683f5db261382c306e71c58d) +--- + clutter/clutter/clutter-enums.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/clutter/clutter/clutter-enums.h b/clutter/clutter/clutter-enums.h +index 9bd508fb67..451e1b8c67 100644 +--- a/clutter/clutter/clutter-enums.h ++++ b/clutter/clutter/clutter-enums.h +@@ -519,6 +519,7 @@ typedef enum /*< flags prefix=CLUTTER_EVENT >*/ + CLUTTER_EVENT_FLAG_RELATIVE_MOTION = 1 << 3, + CLUTTER_EVENT_FLAG_GRAB_NOTIFY = 1 << 4, + CLUTTER_EVENT_FLAG_POINTER_EMULATED = 1 << 5, ++ CLUTTER_EVENT_FLAG_A11Y_MODIFIER_FIRST_CLICK = 1 << 6, + } ClutterEventFlags; + + /** +-- +2.44.0.501.g19981daefd.dirty + + +From 7971ccea8aa76745165cdf44f61a19b5f82a17b7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 18:52:00 +0100 +Subject: [PATCH 03/14] backends/native: Handle a11y modifier presses in + MetaSeatImpl + +The state handling about whether the a11y modifier is a first press (so +could be consumed for other actions), or results in the modifier action +(e.g. caps lock) is performed in the input thread. This information will +be propagated through the CLUTTER_EVENT_FLAG_A11Y_MODIFIER_FIRST_CLICK +flag in the related key events. + +Carlos Garnacho: Drop synchronous wait for configuration changes + +Part-of: +(cherry picked from commit d86796413929bd5d5fb59cb3c2a9ab9d714f70cc) +--- + src/backends/native/meta-seat-impl.c | 133 ++++++++++++++++++++++++++- + src/backends/native/meta-seat-impl.h | 4 + + 2 files changed, 134 insertions(+), 3 deletions(-) + +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index c1e46a0502..0bdf1606fc 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -103,6 +103,14 @@ static guint signals[N_SIGNALS] = { 0 }; + typedef struct _MetaSeatImplPrivate + { + GHashTable *device_files; ++ ++ struct { ++ GHashTable *grabbed_modifiers; ++ GHashTable *pressed_modifiers; ++ uint32_t last_keysym; ++ uint32_t last_keysym_time; ++ gboolean saw_first_release; ++ } a11y; + } MetaSeatImplPrivate; + + static void meta_seat_impl_initable_iface_init (GInitableIface *iface); +@@ -389,6 +397,65 @@ emit_signal (MetaSeatImpl *seat_impl, + (GDestroyNotify) signal_data_free); + } + ++static gboolean ++is_a11y_modifier_first_click (MetaSeatImpl *seat_impl, ++ uint32_t keysym, ++ uint32_t event_time, ++ gboolean is_press) ++{ ++ MetaSeatImplPrivate *priv = meta_seat_impl_get_instance_private (seat_impl); ++ gboolean is_same_keysym = keysym == priv->a11y.last_keysym; ++ gboolean event_soon_enough = ++ event_time - priv->a11y.last_keysym_time < seat_impl->repeat_delay; ++ gboolean is_grabbed_modifier = ++ g_hash_table_contains (priv->a11y.grabbed_modifiers, GUINT_TO_POINTER (keysym)); ++ ++ priv->a11y.last_keysym = keysym; ++ priv->a11y.last_keysym_time = event_time; ++ ++ /* This is not an event for a grabbed modifier */ ++ if (!is_grabbed_modifier) ++ return FALSE; ++ ++ if (!is_press && g_hash_table_contains (priv->a11y.pressed_modifiers, ++ GUINT_TO_POINTER (keysym))) ++ { ++ g_hash_table_remove (priv->a11y.pressed_modifiers, ++ GUINT_TO_POINTER (keysym)); ++ /* This is a release event for a previously pressed modifier */ ++ return FALSE; ++ } ++ ++ if (is_same_keysym && event_soon_enough) ++ { ++ if (is_press && priv->a11y.saw_first_release) ++ { ++ priv->a11y.saw_first_release = FALSE; ++ g_hash_table_add (priv->a11y.pressed_modifiers, ++ GUINT_TO_POINTER (keysym)); ++ ++ /* This is the second press event and it is on time, process ++ * it normally ++ */ ++ return FALSE; ++ } ++ else ++ { ++ priv->a11y.saw_first_release = TRUE; ++ /* This is the first release event, wait for the second press event */ ++ return TRUE; ++ } ++ } ++ else ++ { ++ /* This is either a different modifier, the first press ++ * event, or not on time to progress ++ */ ++ priv->a11y.saw_first_release = FALSE; ++ return TRUE; ++ } ++} ++ + void + meta_seat_impl_notify_key_in_impl (MetaSeatImpl *seat_impl, + ClutterInputDevice *device, +@@ -401,6 +468,8 @@ meta_seat_impl_notify_key_in_impl (MetaSeatImpl *seat_impl, + ClutterEventFlags flags = CLUTTER_EVENT_NONE; + enum xkb_state_component changed_state; + uint32_t keycode; ++ uint32_t keysym; ++ gboolean should_ignore; + + if (state != AUTOREPEAT_VALUE) + { +@@ -421,6 +490,16 @@ meta_seat_impl_notify_key_in_impl (MetaSeatImpl *seat_impl, + flags = CLUTTER_EVENT_FLAG_REPEATED; + } + ++ keycode = meta_xkb_evdev_to_keycode (key); ++ keysym = xkb_state_key_get_one_sym (seat_impl->xkb, keycode); ++ ++ should_ignore = is_a11y_modifier_first_click (seat_impl, ++ keysym, ++ time_us / 1000, ++ state); ++ if (should_ignore) ++ flags |= CLUTTER_EVENT_FLAG_A11Y_MODIFIER_FIRST_CLICK; ++ + event = meta_key_event_new_from_evdev (device, + seat_impl->core_keyboard, + flags, +@@ -428,11 +507,9 @@ meta_seat_impl_notify_key_in_impl (MetaSeatImpl *seat_impl, + seat_impl->button_state, + time_us, key, state); + +- keycode = meta_xkb_evdev_to_keycode (key); +- + /* We must be careful and not pass multiple releases to xkb, otherwise it gets + confused and locks the modifiers */ +- if (state != AUTOREPEAT_VALUE) ++ if (!should_ignore && state != AUTOREPEAT_VALUE) + { + changed_state = xkb_state_update_key (seat_impl->xkb, keycode, + state ? XKB_KEY_DOWN : XKB_KEY_UP); +@@ -2938,6 +3015,9 @@ input_thread (MetaSeatImpl *seat_impl) + NULL, + (GDestroyNotify) meta_device_file_release); + ++ priv->a11y.grabbed_modifiers = g_hash_table_new (NULL, NULL); ++ priv->a11y.pressed_modifiers = g_hash_table_new (NULL, NULL); ++ + seat_impl->input_settings = meta_input_settings_native_new_in_impl (seat_impl); + g_signal_connect_object (seat_impl->input_settings, "kbd-a11y-changed", + G_CALLBACK (kbd_a11y_changed_cb), seat_impl, 0); +@@ -3099,6 +3179,9 @@ destroy_in_impl (GTask *task) + + g_clear_pointer (&priv->device_files, g_hash_table_destroy); + ++ g_clear_pointer (&priv->a11y.grabbed_modifiers, g_hash_table_destroy); ++ g_clear_pointer (&priv->a11y.pressed_modifiers, g_hash_table_destroy); ++ + g_main_loop_quit (seat_impl->input_loop); + g_task_return_boolean (task, TRUE); + +@@ -3820,6 +3903,50 @@ meta_seat_impl_set_viewports (MetaSeatImpl *seat_impl, + g_cond_clear (&data.cond); + } + ++static gboolean ++set_a11y_modifiers (GTask *task) ++{ ++ MetaSeatImpl *seat_impl = g_task_get_source_object (task); ++ MetaSeatImplPrivate *priv = meta_seat_impl_get_instance_private (seat_impl); ++ GArray *modifiers = g_task_get_task_data (task); ++ int i; ++ ++ g_hash_table_remove_all (priv->a11y.grabbed_modifiers); ++ ++ for (i = 0; i < modifiers->len; i++) ++ { ++ uint32_t keysym; ++ ++ keysym = g_array_index (modifiers, uint32_t, i); ++ g_hash_table_add (priv->a11y.grabbed_modifiers, ++ GUINT_TO_POINTER (keysym)); ++ } ++ ++ g_task_return_boolean (task, TRUE); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++void ++meta_seat_impl_set_a11y_modifiers (MetaSeatImpl *seat_impl, ++ const uint32_t *modifiers, ++ int n_modifiers) ++{ ++ g_autoptr (GTask) task = NULL; ++ GArray *modifiers_copy; ++ ++ g_return_if_fail (META_IS_SEAT_IMPL (seat_impl)); ++ ++ modifiers_copy = g_array_new (FALSE, FALSE, sizeof (uint32_t)); ++ g_array_append_vals (modifiers_copy, modifiers, n_modifiers); ++ ++ task = g_task_new (seat_impl, NULL, NULL, NULL); ++ g_task_set_task_data (task, modifiers_copy, ++ (GDestroyNotify) g_array_unref); ++ meta_seat_impl_run_input_task (seat_impl, task, ++ (GSourceFunc) set_a11y_modifiers); ++} ++ + MetaSeatImpl * + meta_seat_impl_new (MetaSeatNative *seat_native, + const char *seat_id, +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index d95fb7176b..7d85fc6fc7 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -263,3 +263,7 @@ void meta_seat_impl_add_virtual_input_device (MetaSeatImpl *seat_impl, + + void meta_seat_impl_remove_virtual_input_device (MetaSeatImpl *seat_impl, + ClutterInputDevice *device); ++ ++void meta_seat_impl_set_a11y_modifiers (MetaSeatImpl *seat_impl, ++ const uint32_t *modifiers, ++ int n_modifiers); +-- +2.44.0.501.g19981daefd.dirty + + +From 4615e7ce13211ee7aea8aa83f265a4318356495e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 18:58:41 +0100 +Subject: [PATCH 04/14] backends/native: Plumb a11y modifiers through + MetaSeatNative + +These modifiers will be set by the backend from the main thread, and +need to be handled specially for them to be usable as both modifier +buttons, and their own regular action. + +Carlos Garnacho: pass modifiers as array+lenght instead of hashtable. + +Part-of: +(cherry picked from commit 8c52e243c03c03c4e1fe0c13c730fd24d84a40a6) +--- + src/backends/native/meta-seat-native.c | 8 ++++++++ + src/backends/native/meta-seat-native.h | 4 ++++ + 2 files changed, 12 insertions(+) + +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index d912875684..ed3842bcf1 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -656,6 +656,14 @@ meta_seat_native_set_viewports (MetaSeatNative *seat, + meta_seat_impl_set_viewports (seat->impl, viewports); + } + ++void ++meta_seat_native_set_a11y_modifiers (MetaSeatNative *seat, ++ const uint32_t *modifiers, ++ int n_modifiers) ++{ ++ meta_seat_impl_set_a11y_modifiers (seat->impl, modifiers, n_modifiers); ++} ++ + void + meta_seat_native_run_impl_task (MetaSeatNative *seat, + GSourceFunc dispatch_func, +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index e82d0043be..cccfa6ce63 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -137,3 +137,7 @@ void meta_seat_native_run_impl_task (MetaSeatNative *seat, + GSourceFunc dispatch_func, + gpointer user_data, + GDestroyNotify destroy_notify); ++ ++void meta_seat_native_set_a11y_modifiers (MetaSeatNative *seat, ++ const uint32_t *modifiers, ++ int n_modifiers); +-- +2.44.0.501.g19981daefd.dirty + + +From 893688f075578a5d8f660094105a53d20b1143fb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 19:01:26 +0100 +Subject: [PATCH 05/14] backends: Add MetaA11yManager + +This a11y manager will handle key event emission to screen +readers and other ATs. This initial commit only introduces +the object. + +Carlos Garnacho: Make the object take a ::backend property + +Part-of: +(cherry picked from commit c0c452445236d98fc4297603f269938ba255a206) +--- + src/backends/meta-a11y-manager.c | 102 +++++++++++++++++++++++++++++++ + src/backends/meta-a11y-manager.h | 29 +++++++++ + src/meson.build | 2 + + 3 files changed, 133 insertions(+) + create mode 100644 src/backends/meta-a11y-manager.c + create mode 100644 src/backends/meta-a11y-manager.h + +diff --git a/src/backends/meta-a11y-manager.c b/src/backends/meta-a11y-manager.c +new file mode 100644 +index 0000000000..22de01030d +--- /dev/null ++++ b/src/backends/meta-a11y-manager.c +@@ -0,0 +1,102 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++/* ++ * Copyright 2024 GNOME Foundation ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ */ ++ ++#include "config.h" ++ ++#include "backends/meta-a11y-manager.h" ++#include "meta/meta-backend.h" ++#include "meta/meta-context.h" ++#include "meta/util.h" ++ ++#include "meta-dbus-a11y.h" ++ ++enum ++{ ++ A11Y_MODIFIERS_CHANGED, ++ N_SIGNALS ++}; ++ ++static guint signals[N_SIGNALS]; ++ ++enum ++{ ++ PROP_0, ++ PROP_BACKEND, ++ N_PROPS, ++}; ++ ++static GParamSpec *props[N_PROPS]; ++ ++typedef struct _MetaA11yManager ++{ ++ GObject parent; ++ MetaBackend *backend; ++} MetaA11yManager; ++ ++G_DEFINE_TYPE (MetaA11yManager, meta_a11y_manager, G_TYPE_OBJECT) ++ ++static void ++meta_a11y_manager_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ MetaA11yManager *a11y_manager = META_A11Y_MANAGER (object); ++ ++ switch (prop_id) ++ { ++ case PROP_BACKEND: ++ a11y_manager->backend = g_value_get_object (value); ++ break; ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ break; ++ } ++} ++ ++static void ++meta_a11y_manager_class_init (MetaA11yManagerClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->set_property = meta_a11y_manager_set_property; ++ ++ props[PROP_BACKEND] = ++ g_param_spec_object ("backend", NULL, NULL, ++ META_TYPE_BACKEND, ++ G_PARAM_WRITABLE | ++ G_PARAM_CONSTRUCT_ONLY | ++ G_PARAM_STATIC_STRINGS); ++ ++ g_object_class_install_properties (object_class, N_PROPS, props); ++} ++ ++static void ++meta_a11y_manager_init (MetaA11yManager *a11y_manager) ++{ ++} ++ ++MetaA11yManager * ++meta_a11y_manager_new (MetaBackend *backend) ++{ ++ return g_object_new (META_TYPE_A11Y_MANAGER, ++ "backend", backend, ++ NULL); ++} +diff --git a/src/backends/meta-a11y-manager.h b/src/backends/meta-a11y-manager.h +new file mode 100644 +index 0000000000..df5c9a5d21 +--- /dev/null ++++ b/src/backends/meta-a11y-manager.h +@@ -0,0 +1,29 @@ ++/* ++ * Copyright 2024 GNOME Foundation ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ */ ++ ++#pragma once ++ ++#include ++ ++#include "backends/meta-backend-types.h" ++#include "clutter/clutter.h" ++ ++#define META_TYPE_A11Y_MANAGER (meta_a11y_manager_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaA11yManager, meta_a11y_manager, META, A11Y_MANAGER, GObject) ++ ++MetaA11yManager * meta_a11y_manager_new (MetaBackend *backend); +diff --git a/src/meson.build b/src/meson.build +index 6c19300b8f..c57fd935f6 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -193,6 +193,8 @@ mutter_sources = [ + 'backends/edid.h', + 'backends/edid-parse.c', + 'backends/gsm-inhibitor-flag.h', ++ 'backends/meta-a11y-manager.c', ++ 'backends/meta-a11y-manager.h', + 'backends/meta-backend.c', + 'backends/meta-backend-private.h', + 'backends/meta-barrier.c', +-- +2.44.0.501.g19981daefd.dirty + + +From 3976dc0c8accf9fcd2968e5980408fca32589a69 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 19:03:10 +0100 +Subject: [PATCH 06/14] backends: Manage MetaA11yManager in MetaBackend + +The MetaBackend will own the MetaA11yManager, being actually +put to use in backend implementations. + +Part-of: +(cherry picked from commit f4ce1e8a462b96e207ff0ba21040c50e91c62cc2) +--- + src/backends/meta-backend-private.h | 3 +++ + src/backends/meta-backend.c | 18 ++++++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index 1d2af368bd..411a0a5d57 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -28,6 +28,7 @@ + #include "meta/meta-backend.h" + #include "meta/meta-idle-monitor.h" + #include "meta/meta-orientation-manager.h" ++#include "backends/meta-a11y-manager.h" + #include "backends/meta-backend-types.h" + #include "backends/meta-cursor-renderer.h" + #include "backends/meta-egl.h" +@@ -159,6 +160,8 @@ MetaScreenCast * meta_backend_get_screen_cast (MetaBackend *backend); + + MetaInputCapture * meta_backend_get_input_capture (MetaBackend *backend); + ++MetaA11yManager * meta_backend_get_a11y_manager (MetaBackend *backend); ++ + gboolean meta_backend_grab_device (MetaBackend *backend, + int device_id, + uint32_t timestamp); +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index cdca232abc..2cdc7edf6c 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -52,6 +52,7 @@ + + #include + ++#include "backends/meta-a11y-manager.h" + #include "backends/meta-barrier-private.h" + #include "backends/meta-color-manager-private.h" + #include "backends/meta-cursor-renderer.h" +@@ -154,6 +155,7 @@ struct _MetaBackendPrivate + MetaRemoteDesktop *remote_desktop; + #endif + MetaInputCapture *input_capture; ++ MetaA11yManager *a11y_manager; + + #ifdef HAVE_LIBWACOM + WacomDeviceDatabase *wacom_db; +@@ -226,6 +228,7 @@ meta_backend_dispose (GObject *object) + g_clear_object (&priv->input_capture); + g_clear_object (&priv->dbus_session_watcher); + g_clear_object (&priv->remote_access_controller); ++ g_clear_object (&priv->a11y_manager); + g_clear_object (&priv->dnd); + + #ifdef HAVE_LIBWACOM +@@ -607,6 +610,8 @@ meta_backend_real_post_init (MetaBackend *backend) + priv->remote_access_controller, + META_DBUS_SESSION_MANAGER (priv->input_capture)); + ++ priv->a11y_manager = meta_a11y_manager_new (backend); ++ + if (!meta_monitor_manager_is_headless (priv->monitor_manager)) + init_pointer_position (backend); + +@@ -1548,6 +1553,19 @@ meta_backend_get_remote_access_controller (MetaBackend *backend) + return priv->remote_access_controller; + } + ++/** ++ * meta_backend_get_a11y_manager: ++ * ++ * Returns: (transfer none): the #MetaA11yManager ++ */ ++MetaA11yManager * ++meta_backend_get_a11y_manager (MetaBackend *backend) ++{ ++ MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); ++ ++ return priv->a11y_manager; ++} ++ + /** + * meta_backend_is_rendering_hardware_accelerated: + * @backend: A #MetaBackend +-- +2.44.0.501.g19981daefd.dirty + + +From b6e5ec24b95f0b389b18927af0956f3b59d7b21d 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 07/14] 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: +(cherry picked from commit 0bcda4ecf47f04cb76fb642f05d498d1dd941e15) +--- + 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 22de01030d..5f8d5b1ee4 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, +-- +2.44.0.501.g19981daefd.dirty + + +From c60b8ee816991f6ca0ccd6e1c66b19a649a563df Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 19:17:08 +0100 +Subject: [PATCH 08/14] backends: Add MetaA11yManager method to get configured + a11y modifiers + +The ::a11y-modifiers-change method allows tracking for changes in the +configured modifiers, add this method to allow backends to get the +modifiers so that they can be passed along the lower layers. + +Carlos Garnacho: Turn into a method instead of a signal argument, turn +into an array+length instead of a hashtable. + +Part-of: +(cherry picked from commit 1498724774976fad1dbd4e5bc210205cef1faf9d) +--- + src/backends/meta-a11y-manager.c | 24 ++++++++++++++++++++++++ + src/backends/meta-a11y-manager.h | 3 +++ + 2 files changed, 27 insertions(+) + +diff --git a/src/backends/meta-a11y-manager.c b/src/backends/meta-a11y-manager.c +index 5f8d5b1ee4..647962c866 100644 +--- a/src/backends/meta-a11y-manager.c ++++ b/src/backends/meta-a11y-manager.c +@@ -374,3 +374,27 @@ meta_a11y_manager_new (MetaBackend *backend) + "backend", backend, + NULL); + } ++ ++uint32_t * ++meta_a11y_manager_get_modifier_keysyms (MetaA11yManager *a11y_manager, ++ int *n_modifiers) ++{ ++ GArray *modifier_keysyms; ++ GHashTableIter iter; ++ gpointer key; ++ ++ modifier_keysyms = g_array_new (FALSE, FALSE, sizeof (uint32_t)); ++ ++ g_hash_table_iter_init (&iter, a11y_manager->all_grabbed_modifiers); ++ while (g_hash_table_iter_next (&iter, &key, NULL)) ++ { ++ uint32_t keysym = GPOINTER_TO_UINT (key); ++ ++ g_array_append_val (modifier_keysyms, keysym); ++ } ++ ++ if (n_modifiers) ++ *n_modifiers = modifier_keysyms->len; ++ ++ return (uint32_t *) g_array_free (modifier_keysyms, FALSE); ++} +diff --git a/src/backends/meta-a11y-manager.h b/src/backends/meta-a11y-manager.h +index df5c9a5d21..df07c94de2 100644 +--- a/src/backends/meta-a11y-manager.h ++++ b/src/backends/meta-a11y-manager.h +@@ -27,3 +27,6 @@ + G_DECLARE_FINAL_TYPE (MetaA11yManager, meta_a11y_manager, META, A11Y_MANAGER, GObject) + + MetaA11yManager * meta_a11y_manager_new (MetaBackend *backend); ++ ++uint32_t * meta_a11y_manager_get_modifier_keysyms (MetaA11yManager *a11y_manager, ++ int *n_modifiers); +-- +2.44.0.501.g19981daefd.dirty + + +From 3ce02680be1fea0c488c9681e87dfafac315fc06 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 19:03:50 +0100 +Subject: [PATCH 09/14] backends/native: Propagate a11y modifiers from + MetaA11yManager + +Listen to changes in MetaA11yManager configured modifiers, and propagate +these along to the MetaSeatNative. This lets the backend keep track of +configuration changes in a11y modifiers. + +Part-of: +(cherry picked from commit 45f6bed7809dadb40df98308b9041fe08b453871) +--- + src/backends/native/meta-backend-native.c | 25 +++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index ee02b2783a..8732f3f7d2 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -38,6 +38,7 @@ + + #include + ++#include "backends/meta-a11y-manager.h" + #include "backends/meta-color-manager.h" + #include "backends/meta-cursor-tracker-private.h" + #include "backends/meta-idle-manager.h" +@@ -183,6 +184,23 @@ update_viewports (MetaBackend *backend) + g_object_unref (viewports); + } + ++static void ++on_a11y_modifiers_changed (MetaA11yManager *a11y_manager, ++ MetaBackend *backend) ++{ ++ MetaSeatNative *seat; ++ ClutterBackend *clutter_backend; ++ g_autofree uint32_t *modifiers; ++ int n_modifiers; ++ ++ clutter_backend = meta_backend_get_clutter_backend (backend); ++ seat = META_SEAT_NATIVE (clutter_backend_get_default_seat (clutter_backend)); ++ modifiers = meta_a11y_manager_get_modifier_keysyms (a11y_manager, ++ &n_modifiers); ++ ++ meta_seat_native_set_a11y_modifiers (seat, modifiers, n_modifiers); ++} ++ + static void + meta_backend_native_post_init (MetaBackend *backend) + { +@@ -191,6 +209,7 @@ meta_backend_native_post_init (MetaBackend *backend) + meta_backend_native_get_instance_private (backend_native); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); ++ MetaA11yManager *a11y_manager = meta_backend_get_a11y_manager (backend); + + META_BACKEND_CLASS (meta_backend_native_parent_class)->post_init (backend); + +@@ -200,6 +219,12 @@ meta_backend_native_post_init (MetaBackend *backend) + g_signal_connect_swapped (monitor_manager, "monitors-changed-internal", + G_CALLBACK (update_viewports), backend); + update_viewports (backend); ++ ++ g_signal_connect_object (a11y_manager, ++ "a11y-modifiers-changed", ++ G_CALLBACK (on_a11y_modifiers_changed), ++ backend, ++ G_CONNECT_DEFAULT); + } + + static MetaBackendCapabilities +-- +2.44.0.501.g19981daefd.dirty + + +From b03c4412be7fec86f43c975e418556f17398fd75 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Wed, 5 Feb 2025 19:16:41 +0100 +Subject: [PATCH 10/14] backends: Add method to pass key events to screen + readers + +Accessibility shortcuts and keyboard grabs need to be consumed, +and key events propagated to screen readers. Add a function that +does it all at once. + +Part-of: +(cherry picked from commit 800981c40c4f18f496a79227b3d3d5f0cdc195c9) +--- + src/backends/meta-a11y-manager.c | 148 +++++++++++++++++++++++++++++++ + src/backends/meta-a11y-manager.h | 3 + + 2 files changed, 151 insertions(+) + +diff --git a/src/backends/meta-a11y-manager.c b/src/backends/meta-a11y-manager.c +index 647962c866..a6ec4f6f3a 100644 +--- a/src/backends/meta-a11y-manager.c ++++ b/src/backends/meta-a11y-manager.c +@@ -27,6 +27,12 @@ + + #include "meta-dbus-a11y.h" + ++#define MOUSE_BUTTONS_MASK (CLUTTER_BUTTON1_MASK | \ ++ CLUTTER_BUTTON2_MASK | \ ++ CLUTTER_BUTTON3_MASK | \ ++ CLUTTER_BUTTON4_MASK | \ ++ CLUTTER_BUTTON5_MASK) ++ + enum + { + A11Y_MODIFIERS_CHANGED, +@@ -375,6 +381,148 @@ meta_a11y_manager_new (MetaBackend *backend) + NULL); + } + ++static gboolean ++should_grab_keypress (MetaA11yManager *a11y_manager, ++ MetaA11yKeyGrabber *grabber, ++ uint32_t keysym, ++ ClutterModifierType modifiers) ++{ ++ int i; ++ ++ if (grabber->grab_all) ++ return TRUE; ++ ++ if (grabber->modifiers) ++ { ++ for (i = 0; i < grabber->modifiers->len; i++) ++ { ++ uint32_t modifier_keysym; ++ ++ modifier_keysym = g_array_index (grabber->modifiers, uint32_t, i); ++ ++ if (keysym == modifier_keysym || ++ g_hash_table_contains (a11y_manager->grabbed_keypresses, ++ GUINT_TO_POINTER (modifier_keysym))) ++ return TRUE; ++ } ++ } ++ ++ if (grabber->keystrokes) ++ { ++ for (i = 0; i < grabber->keystrokes->len; i++) ++ { ++ MetaA11yKeystroke *keystroke = ++ &(g_array_index (grabber->keystrokes, MetaA11yKeystroke, i)); ++ ++ if (keysym == keystroke->keysym && modifiers == keystroke->modifiers) ++ return TRUE; ++ } ++ } ++ ++ return FALSE; ++} ++ ++static gboolean ++is_grabbed_modifier_key (MetaA11yManager *a11y_manager, ++ uint32_t keysym) ++{ ++ return g_hash_table_contains (a11y_manager->all_grabbed_modifiers, ++ GUINT_TO_POINTER (keysym)); ++} ++ ++static void ++notify_client (MetaA11yManager *a11y_manager, ++ MetaA11yKeyGrabber *key_grabber, ++ gboolean released, ++ ClutterModifierType state, ++ uint32_t keysym, ++ uint32_t unichar, ++ uint32_t keycode) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (!g_dbus_connection_emit_signal (key_grabber->connection, ++ key_grabber->bus_name, ++ "/org/freedesktop/a11y/Manager", ++ "org.freedesktop.a11y.KeyboardMonitor", ++ "KeyEvent", ++ g_variant_new ("(buuuq)", ++ released, ++ state, ++ keysym, ++ unichar, ++ (uint16_t) keycode), ++ &error)) ++ g_warning ("Could not emit a11y KeyEvent: %s", error->message); ++} ++ ++gboolean ++meta_a11y_manager_notify_clients (MetaA11yManager *a11y_manager, ++ const ClutterEvent *event) ++{ ++ gboolean a11y_grabbed = FALSE; ++ gboolean released = clutter_event_type (event) == CLUTTER_KEY_RELEASE; ++ /* A grabbed modifier is a11y grabbed if it was not double pressed, otherwise we process it normally */ ++ gboolean is_ignorable = ++ !!(clutter_event_get_flags (event) & ++ CLUTTER_EVENT_FLAG_A11Y_MODIFIER_FIRST_CLICK); ++ /* The Clutter event modifiers mask includes mouse buttons as well, ++ * but they're not expected by ATs, so we filter them out. ++ */ ++ uint32_t keysym = clutter_event_get_key_symbol (event); ++ uint32_t unichar = clutter_event_get_key_unicode (event); ++ uint32_t keycode = clutter_event_get_key_code (event); ++ ClutterModifierType state; ++ GList *l; ++ ++ state = clutter_event_get_state (event) & ~MOUSE_BUTTONS_MASK; ++ ++ for (l = a11y_manager->key_grabbers; l; l = l->next) ++ { ++ MetaA11yKeyGrabber *grabber = l->data; ++ ++ if (should_grab_keypress (a11y_manager, grabber, keysym, state)) ++ { ++ notify_client (a11y_manager, grabber, released, ++ state, keysym, unichar, keycode); ++ } ++ } ++ ++ if (is_grabbed_modifier_key (a11y_manager, keysym) && !is_ignorable) ++ return FALSE; ++ ++ if (released) ++ { ++ if (g_hash_table_contains (a11y_manager->grabbed_keypresses, ++ GUINT_TO_POINTER (keysym))) ++ { ++ g_hash_table_remove (a11y_manager->grabbed_keypresses, ++ GUINT_TO_POINTER (keysym)); ++ a11y_grabbed = TRUE; ++ } ++ } ++ else ++ { ++ if (g_hash_table_contains (a11y_manager->grabbed_keypresses, ++ GUINT_TO_POINTER (keysym))) ++ a11y_grabbed = TRUE; ++ ++ for (l = a11y_manager->key_grabbers; l; l = l->next) ++ { ++ MetaA11yKeyGrabber *grabber = l->data; ++ ++ if (should_grab_keypress (a11y_manager, grabber, keysym, state)) ++ { ++ g_hash_table_add (a11y_manager->grabbed_keypresses, ++ GUINT_TO_POINTER (keysym)); ++ a11y_grabbed = TRUE; ++ } ++ } ++ } ++ ++ return a11y_grabbed; ++} ++ + uint32_t * + meta_a11y_manager_get_modifier_keysyms (MetaA11yManager *a11y_manager, + int *n_modifiers) +diff --git a/src/backends/meta-a11y-manager.h b/src/backends/meta-a11y-manager.h +index df07c94de2..754cb65394 100644 +--- a/src/backends/meta-a11y-manager.h ++++ b/src/backends/meta-a11y-manager.h +@@ -28,5 +28,8 @@ G_DECLARE_FINAL_TYPE (MetaA11yManager, meta_a11y_manager, META, A11Y_MANAGER, GO + + MetaA11yManager * meta_a11y_manager_new (MetaBackend *backend); + ++gboolean meta_a11y_manager_notify_clients (MetaA11yManager *a11y_manager, ++ const ClutterEvent *event); ++ + uint32_t * meta_a11y_manager_get_modifier_keysyms (MetaA11yManager *a11y_manager, + int *n_modifiers); +-- +2.44.0.501.g19981daefd.dirty + + +From 921a010f93b4b89d50b7ba97666af367bd8719a6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= +Date: Fri, 7 Feb 2025 14:21:17 +0100 +Subject: [PATCH 11/14] backends: Add a11y interface methods to subscribe to + key event input + +This adds a pair of methods to signal an interest in receiving +all key events without grabbing them, e. g. the previously expected behavior +by screen readers. + +Part-of: +(cherry picked from commit 20c9b8cf0c85dabbe73b5b0ce155729a92f47bb0) +--- + data/dbus-interfaces/org.freedesktop.a11y.xml | 31 +++++++++++- + src/backends/meta-a11y-manager.c | 47 ++++++++++++++++++- + 2 files changed, 75 insertions(+), 3 deletions(-) + +diff --git a/data/dbus-interfaces/org.freedesktop.a11y.xml b/data/dbus-interfaces/org.freedesktop.a11y.xml +index c60480e89c..4f1d210423 100644 +--- a/data/dbus-interfaces/org.freedesktop.a11y.xml ++++ b/data/dbus-interfaces/org.freedesktop.a11y.xml +@@ -16,7 +16,7 @@ + GrabKeyboard: + + Starts grabbing all key events. The client receives the events +- through the KeyEvent signal as always, but the events aren't handled ++ through the KeyEvent signal, and in addition, the events aren't handled + normally by the compositor. This includes changes to the state + of toggles like Caps Lock, Num Lock, and Scroll Lock. + +@@ -33,9 +33,36 @@ + + After calling this method, the key grabs specified in the last call + to SetKeyGrabs, if any, are still in effect. ++ Also, the client will still receive key events through the KeyEvent ++ signal, if it has called WatchKeyboard. + --> + + ++ ++ ++ ++ ++ ++ +