Filter out stale accessibility events.
This commit is contained in:
1 parent
9e15f8652d
commit
f23e89ed93
10 files changed
+469
-2
No files matched your search
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "adapter_native.h"
|
||||
|
||||
extern void cthulhuAtspiSelectionCallback(AtspiEvent *event, void *userData, uint64_t epoch);
|
||||
|
||||
// This check uses only libatspi's cache. Unknown metadata retains the normal
|
||||
// query path; a stale non-FOCUSED state alone never rejects an explicit claim.
|
||||
gboolean cthulhu_accessible_known_dead(AtspiAccessible *source) {
|
||||
if (source == NULL) return TRUE;
|
||||
if (source->states != NULL && atspi_state_set_contains(source->states, ATSPI_STATE_DEFUNCT)) return TRUE;
|
||||
return (source->cached_properties & ATSPI_CACHE_ROLE) && source->role == ATSPI_ROLE_INVALID;
|
||||
}
|
||||
|
||||
gboolean cthulhu_gtk_table(AtspiAccessible *source, gboolean queryToolkit) {
|
||||
if (source == NULL || !(source->cached_properties & ATSPI_CACHE_ROLE)) return FALSE;
|
||||
if (source->role != ATSPI_ROLE_TABLE && source->role != ATSPI_ROLE_TREE_TABLE &&
|
||||
source->role != ATSPI_ROLE_LIST && source->role != ATSPI_ROLE_TREE) return FALSE;
|
||||
AtspiApplication *application = ATSPI_OBJECT(source)->app;
|
||||
// libatspi caches toolkit identity per application. Seed that cache on
|
||||
// the first table selection if focus capture has not needed it yet.
|
||||
if (queryToolkit && application != NULL && application->toolkit_name == NULL) {
|
||||
gchar *toolkit = atspi_accessible_get_toolkit_name(source, NULL);
|
||||
g_free(toolkit);
|
||||
}
|
||||
if (application == NULL || g_ascii_strcasecmp(application->toolkit_name != NULL ? application->toolkit_name : "", "gtk") != 0) return FALSE;
|
||||
if (source->role == ATSPI_ROLE_TABLE || source->role == ATSPI_ROLE_TREE_TABLE) return TRUE;
|
||||
if ((source->role != ATSPI_ROLE_LIST && source->role != ATSPI_ROLE_TREE) ||
|
||||
!(source->cached_properties & ATSPI_CACHE_INTERFACES)) return FALSE;
|
||||
AtspiTable *table = atspi_accessible_get_table_iface(source);
|
||||
if (table == NULL) return FALSE;
|
||||
g_object_unref(table);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// All queue state and boxed events belong exclusively to the GLib owner.
|
||||
// Only one table can be pending. A different table or another live event
|
||||
// flushes it first, preserving ordering across focus/lifecycle boundaries.
|
||||
void cthulhu_flush_selection(cthulhu_callback_data *data) {
|
||||
if (data->selectionIdle != 0) {
|
||||
g_source_remove(data->selectionIdle);
|
||||
data->selectionIdle = 0;
|
||||
}
|
||||
AtspiEvent *event = data->pendingSelection;
|
||||
uint64_t epoch = data->selectionEpoch;
|
||||
data->pendingSelection = NULL;
|
||||
if (event == NULL) return;
|
||||
cthulhuAtspiSelectionCallback(event, data, epoch);
|
||||
g_boxed_free(ATSPI_TYPE_EVENT, event);
|
||||
}
|
||||
|
||||
void cthulhu_flush_other_selection(cthulhu_callback_data *data, AtspiAccessible *source) {
|
||||
if (data->pendingSelection != NULL && data->pendingSelection->source != source) {
|
||||
cthulhu_flush_selection(data);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean cthulhu_selection_idle(gpointer userData) {
|
||||
cthulhu_callback_data *data = userData;
|
||||
data->selectionIdle = 0;
|
||||
cthulhu_flush_selection(data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void cthulhu_defer_selection(cthulhu_callback_data *data, AtspiEvent *event, uint64_t epoch) {
|
||||
cthulhu_flush_other_selection(data, event->source);
|
||||
if (data->pendingSelection != NULL) g_boxed_free(ATSPI_TYPE_EVENT, data->pendingSelection);
|
||||
data->pendingSelection = g_boxed_copy(ATSPI_TYPE_EVENT, event);
|
||||
data->selectionEpoch = epoch;
|
||||
if (data->selectionIdle == 0) {
|
||||
// No timer/debounce delay: read the latest selection when the ready
|
||||
// provider signals have drained. Ordinary arrows still flush on their
|
||||
// following live focus/descendant event, or at the next idle turn.
|
||||
data->selectionIdle = g_idle_add(cthulhu_selection_idle, data);
|
||||
}
|
||||
}
|
||||
|
||||
void cthulhu_clear_selection(cthulhu_callback_data *data) {
|
||||
if (data == NULL) return;
|
||||
if (data->selectionIdle != 0) {
|
||||
g_source_remove(data->selectionIdle);
|
||||
data->selectionIdle = 0;
|
||||
}
|
||||
if (data->pendingSelection != NULL) {
|
||||
g_boxed_free(ATSPI_TYPE_EVENT, data->pendingSelection);
|
||||
data->pendingSelection = NULL;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user