clutter: Always snoop key events for a11y

In the case a11y is required, the screen reader is very much
interested in getting an uninterrupted flow of key events. It attempts
so by setting a ::captured-event callback on the ClutterStage, but
that falls short with our MetaDisplay event handler, as clutter events
can be stopped before a11y gets a chance to see them.

This kind of selective amnesia wrt key events is not new, in X11 those
go unheard of by the WM as long as a client is focused and no grabs hold,
so it is clients' responsibility to talk with AT bridge.

This commit doesn't yet change that for X11, but we can do this right
away from the compositor on Wayland, and without any chance to be
tampered by clients.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1328>
This commit is contained in:
Carlos Garnacho 2020-06-23 16:04:40 +02:00 committed by Marge Bot
parent 7bb0055acd
commit f2154ceaad
3 changed files with 13 additions and 113 deletions

View File

@ -60,17 +60,6 @@ static const gchar * cally_util_get_toolkit_name (void);
static const gchar * cally_util_get_toolkit_version (void); static const gchar * cally_util_get_toolkit_version (void);
/* private */ /* private */
static void cally_util_simulate_snooper_install (void);
static void cally_util_simulate_snooper_remove (void);
static gboolean cally_key_snooper (ClutterActor *actor,
ClutterEvent *event,
gpointer user_data);
static void cally_util_stage_added_cb (ClutterStageManager *stage_manager,
ClutterStage *stage,
gpointer data);
static void cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
ClutterStage *stage,
gpointer data);
static gboolean notify_hf (gpointer key, static gboolean notify_hf (gpointer key,
gpointer value, gpointer value,
gpointer data); gpointer data);
@ -153,12 +142,8 @@ cally_util_add_key_event_listener (AtkKeySnoopFunc listener,
CallyKeyEventInfo *event_info = NULL; CallyKeyEventInfo *event_info = NULL;
if (!key_listener_list) if (!key_listener_list)
{
key_listener_list = g_hash_table_new_full (NULL, NULL, NULL, g_free); key_listener_list = g_hash_table_new_full (NULL, NULL, NULL, g_free);
cally_util_simulate_snooper_install ();
}
event_info = g_new (CallyKeyEventInfo, 1); event_info = g_new (CallyKeyEventInfo, 1);
event_info->listener = listener; event_info->listener = listener;
event_info->func_data = data; event_info->func_data = data;
@ -179,75 +164,11 @@ cally_util_remove_key_event_listener (guint remove_listener)
{ {
g_hash_table_destroy (key_listener_list); g_hash_table_destroy (key_listener_list);
key_listener_list = NULL; key_listener_list = NULL;
cally_util_simulate_snooper_remove ();
} }
} }
/* ------------------------------ PRIVATE FUNCTIONS ------------------------- */ /* ------------------------------ PRIVATE FUNCTIONS ------------------------- */
/* Trying to emulate gtk_key_snooper install (a kind of wrapper). This
could be implemented without it, but I will maintain it in this
way, so if in the future clutter implements it natively it would be
easier the transition */
static void
cally_util_simulate_snooper_install (void)
{
ClutterStageManager *stage_manager = NULL;
ClutterStage *stage = NULL;
GSList *stage_list = NULL;
GSList *iter = NULL;
stage_manager = clutter_stage_manager_get_default ();
stage_list = clutter_stage_manager_list_stages (stage_manager);
for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
{
stage = CLUTTER_STAGE (iter->data);
g_signal_connect (G_OBJECT (stage), "captured-event",
G_CALLBACK (cally_key_snooper), NULL);
}
g_signal_connect (G_OBJECT (stage_manager), "stage-added",
G_CALLBACK (cally_util_stage_added_cb), cally_key_snooper);
g_signal_connect (G_OBJECT (stage_manager), "stage-removed",
G_CALLBACK (cally_util_stage_removed_cb), cally_key_snooper);
g_slist_free (stage_list);
}
static void
cally_util_simulate_snooper_remove (void)
{
ClutterStageManager *stage_manager = NULL;
ClutterStage *stage = NULL;
GSList *stage_list = NULL;
GSList *iter = NULL;
gint num = 0;
stage_manager = clutter_stage_manager_get_default ();
stage_list = clutter_stage_manager_list_stages (stage_manager);
for (iter = stage_list; iter != NULL; iter = g_slist_next (iter))
{
stage = CLUTTER_STAGE (iter->data);
num += g_signal_handlers_disconnect_by_func (stage, cally_key_snooper, NULL);
}
g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
G_CALLBACK (cally_util_stage_added_cb),
cally_key_snooper);
g_signal_handlers_disconnect_by_func (G_OBJECT (stage_manager),
G_CALLBACK (cally_util_stage_removed_cb),
cally_key_snooper);
#ifdef CALLY_DEBUG
g_print ("Number of snooper callbacks disconnected: %i\n", num);
#endif
}
static AtkKeyEventStruct * static AtkKeyEventStruct *
atk_key_event_from_clutter_event_key (ClutterKeyEvent *clutter_event, atk_key_event_from_clutter_event_key (ClutterKeyEvent *clutter_event,
gunichar password_char) gunichar password_char)
@ -384,59 +305,34 @@ check_key_visibility (ClutterEvent *event)
return DEFAULT_PASSWORD_CHAR; return DEFAULT_PASSWORD_CHAR;
} }
static gboolean gboolean
cally_key_snooper (ClutterActor *actor, cally_snoop_key_event (ClutterKeyEvent *key)
ClutterEvent *event,
gpointer user_data)
{ {
ClutterEvent *event = (ClutterEvent *) key;
AtkKeyEventStruct *key_event = NULL; AtkKeyEventStruct *key_event = NULL;
gint consumed = 0; gboolean consumed = FALSE;
gunichar password_char = 0; gunichar password_char = 0;
/* filter key events */ /* filter key events */
if ((event->type != CLUTTER_KEY_PRESS) && (event->type != CLUTTER_KEY_RELEASE)) if ((event->type != CLUTTER_KEY_PRESS) && (event->type != CLUTTER_KEY_RELEASE))
{ return FALSE;
return FALSE;
}
password_char = check_key_visibility (event);
if (key_listener_list) if (key_listener_list)
{ {
GHashTable *new_hash = g_hash_table_new (NULL, NULL); GHashTable *new_hash = g_hash_table_new (NULL, NULL);
g_hash_table_foreach (key_listener_list, insert_hf, new_hash); g_hash_table_foreach (key_listener_list, insert_hf, new_hash);
key_event = atk_key_event_from_clutter_event_key ((ClutterKeyEvent *)event, password_char = check_key_visibility (event);
password_char); key_event = atk_key_event_from_clutter_event_key (key, password_char);
/* func data is inside the hash table */ /* func data is inside the hash table */
consumed = g_hash_table_foreach_steal (new_hash, notify_hf, key_event); consumed = g_hash_table_foreach_steal (new_hash, notify_hf, key_event) > 0;
g_hash_table_destroy (new_hash); g_hash_table_destroy (new_hash);
g_free (key_event->string); g_free (key_event->string);
g_free (key_event); g_free (key_event);
} }
return (consumed ? 1 : 0); return consumed;
}
static void
cally_util_stage_added_cb (ClutterStageManager *stage_manager,
ClutterStage *stage,
gpointer data)
{
GCallback cally_key_snooper_cb = G_CALLBACK (data);
g_signal_connect (G_OBJECT (stage), "captured-event", cally_key_snooper_cb, NULL);
}
static void
cally_util_stage_removed_cb (ClutterStageManager *stage_manager,
ClutterStage *stage,
gpointer data)
{
GCallback cally_key_snooper_cb = G_CALLBACK (data);
g_signal_handlers_disconnect_by_func (stage, cally_key_snooper_cb, NULL);
} }
void void

View File

@ -79,6 +79,8 @@ GType cally_util_get_type (void) G_GNUC_CONST;
void _cally_util_override_atk_util (void); void _cally_util_override_atk_util (void);
gboolean cally_snoop_key_event (ClutterKeyEvent *key);
G_END_DECLS G_END_DECLS
#endif /* __CALLY_UTIL_H__ */ #endif /* __CALLY_UTIL_H__ */

View File

@ -762,6 +762,8 @@ static inline void
process_key_event (ClutterEvent *event, process_key_event (ClutterEvent *event,
ClutterInputDevice *device) ClutterInputDevice *device)
{ {
cally_snoop_key_event ((ClutterKeyEvent *) event);
if (_clutter_event_process_filters (event)) if (_clutter_event_process_filters (event))
return; return;