Better handling of some qt and gtk applications. Used Audacious for testing.
This commit is contained in:
1 parent
300f4a7504
commit
bbe931335f
15 files changed
+507
-16
No files matched your search
@@ -65,7 +65,7 @@ gchar *cthulhu_table_context(AtspiAccessible *source, gint *rows, gint *columns,
|
||||
AtspiAccessible *tableAccessible = NULL;
|
||||
gint row = -1;
|
||||
gint column = -1;
|
||||
if (atspi_accessible_get_role(source, NULL) == ATSPI_ROLE_TABLE) {
|
||||
if (cthulhu_is_table_container(source)) {
|
||||
tableAccessible = g_object_ref(source);
|
||||
} else if (atspi_accessible_get_role(source, NULL) == ATSPI_ROLE_TABLE_CELL) {
|
||||
AtspiTableCell *cell = atspi_accessible_get_table_cell(source);
|
||||
@@ -76,8 +76,7 @@ gchar *cthulhu_table_context(AtspiAccessible *source, gint *rows, gint *columns,
|
||||
}
|
||||
if (tableAccessible == NULL) {
|
||||
AtspiAccessible *parent = atspi_accessible_get_parent(source, NULL);
|
||||
if (parent != NULL && (atspi_accessible_get_role(parent, NULL) == ATSPI_ROLE_TABLE ||
|
||||
cthulhu_is_gtk_list_table(parent))) {
|
||||
if (parent != NULL && cthulhu_is_table_container(parent)) {
|
||||
tableAccessible = parent;
|
||||
gint index = atspi_accessible_get_index_in_parent(source, NULL);
|
||||
AtspiTable *parentTable = atspi_accessible_get_table_iface(parent);
|
||||
@@ -125,6 +124,21 @@ gchar *cthulhu_table_context(AtspiAccessible *source, gint *rows, gint *columns,
|
||||
for (gint column = 0; column < *columns; column++) {
|
||||
AtspiAccessible *item = atspi_table_get_accessible_at(table, row, column, NULL);
|
||||
if (item == NULL) continue;
|
||||
// Qt keeps hidden columns in the Table interface. As in Orca's
|
||||
// get_showing_cells_in_same_row, GUI full-row reading includes
|
||||
// only showing cells. Document/spreadsheet capture keeps its
|
||||
// existing policy. Query fresh state after column visibility changes.
|
||||
if (*guiTable) {
|
||||
atspi_accessible_clear_cache_single(item);
|
||||
AtspiStateSet *states = atspi_accessible_get_state_set(item);
|
||||
gboolean showing = states != NULL &&
|
||||
atspi_state_set_contains(states, ATSPI_STATE_SHOWING);
|
||||
if (states != NULL) g_object_unref(states);
|
||||
if (!showing) {
|
||||
g_object_unref(item);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
gchar *name = atspi_accessible_get_name(item, NULL);
|
||||
if ((name == NULL || *name == '\0') && remaining != 0 &&
|
||||
atspi_accessible_get_role(item, NULL) == ATSPI_ROLE_TABLE_CELL) {
|
||||
|
||||
@@ -52,13 +52,13 @@ func selectNativeEventTarget(event *C.AtspiEvent, eventType string) (*C.AtspiAcc
|
||||
// A table's first selected child is its active child only when the
|
||||
// selection is unique. Multi-selection changes use their own events.
|
||||
targetRole := C.atspi_accessible_get_role(target, nil)
|
||||
listTable := targetRole == C.ATSPI_ROLE_LIST && C.cthulhu_is_gtk_list_table(target) != 0
|
||||
tableContainer := C.cthulhu_is_table_container(target) != 0
|
||||
// GTK notebooks report container focus before the active tab when an
|
||||
// X11 key grab ends. Resolve that container to its selected tab, as
|
||||
// with menus, so ordinary duplicate-focus handling preserves review.
|
||||
if targetRole == C.ATSPI_ROLE_MENU || targetRole == C.ATSPI_ROLE_PAGE_TAB_LIST || targetRole == C.ATSPI_ROLE_TABLE || listTable {
|
||||
if targetRole == C.ATSPI_ROLE_MENU || targetRole == C.ATSPI_ROLE_PAGE_TAB_LIST || tableContainer {
|
||||
var child *C.AtspiAccessible
|
||||
if targetRole == C.ATSPI_ROLE_TABLE || listTable {
|
||||
if tableContainer {
|
||||
child = C.cthulhu_table_selected_child(target)
|
||||
} else {
|
||||
child = C.cthulhu_selected_child(target)
|
||||
@@ -93,7 +93,7 @@ func selectNativeEventTarget(event *C.AtspiEvent, eventType string) (*C.AtspiAcc
|
||||
// child list. Tab creation/removal makes that list stale.
|
||||
C.atspi_accessible_clear_cache_single(event.source)
|
||||
}
|
||||
if C.atspi_accessible_get_role(event.source, nil) == C.ATSPI_ROLE_TABLE || C.cthulhu_is_gtk_list_table(event.source) != 0 {
|
||||
if C.cthulhu_is_table_container(event.source) != 0 {
|
||||
target = C.cthulhu_table_selected_child(event.source)
|
||||
} else {
|
||||
target = C.cthulhu_selected_child(event.source)
|
||||
@@ -433,6 +433,13 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
|
||||
adapter.forgetWindowDocuments(originID)
|
||||
}
|
||||
invalidated := adapter.applyTreeInvalidation(eventType, event)
|
||||
returnedMenuItem := adapter.returned_menu_item(event, eventType, originID)
|
||||
if returnedMenuItem != nil {
|
||||
defer C.g_object_unref(C.gpointer(returnedMenuItem))
|
||||
// Keep the original showing event and popup origin in diagnostics.
|
||||
// Only fresh focus on its parent supplies this normalized focus event.
|
||||
eventType, invalidated = FocusEvent, false
|
||||
}
|
||||
if selection := adapter.window_switcher_event(event, eventType); selection != nil {
|
||||
adapter.enqueueSecureEvent(*selection, secureEpoch)
|
||||
return
|
||||
@@ -500,8 +507,12 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
|
||||
return
|
||||
}
|
||||
snapshotStarted := time.Now()
|
||||
target := adapter.libreOfficeFocusTarget(event, eventType)
|
||||
ownedTarget := target != nil
|
||||
target := returnedMenuItem
|
||||
ownedTarget := false
|
||||
if target == nil {
|
||||
target = adapter.libreOfficeFocusTarget(event, eventType)
|
||||
ownedTarget = target != nil
|
||||
}
|
||||
if target == nil {
|
||||
target, ownedTarget = selectNativeEventTarget(event, eventType)
|
||||
}
|
||||
|
||||
@@ -157,20 +157,66 @@ gboolean cthulhu_is_gtk_list_table(AtspiAccessible *source) {
|
||||
return matches;
|
||||
}
|
||||
|
||||
// Qt item views use TREE even for flat, multi-column playlists. Require the
|
||||
// Table interface before applying row-selection policy to a tree.
|
||||
gboolean cthulhu_is_table_container(AtspiAccessible *source) {
|
||||
AtspiRole role = atspi_accessible_get_role(source, NULL);
|
||||
if (role == ATSPI_ROLE_TABLE) return TRUE;
|
||||
if (role == ATSPI_ROLE_LIST) return cthulhu_is_gtk_list_table(source);
|
||||
if (role != ATSPI_ROLE_TREE && role != ATSPI_ROLE_TREE_TABLE) return FALSE;
|
||||
AtspiTable *table = atspi_accessible_get_table_iface(source);
|
||||
if (table == NULL) return FALSE;
|
||||
g_object_unref(table);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Selection counts cells, so a single GTK row with two columns already has
|
||||
// two selected children. Query the table's selected rows before treating its
|
||||
// first selected cell as the focus target.
|
||||
AtspiAccessible *cthulhu_table_selected_child(AtspiAccessible *source) {
|
||||
// A menu/dialog can return focus without refreshing the cached child list.
|
||||
atspi_accessible_clear_cache_single(source);
|
||||
AtspiTable *table = atspi_accessible_get_table_iface(source);
|
||||
if (table == NULL) return cthulhu_selected_child(source);
|
||||
GError *error = NULL;
|
||||
gint count = atspi_table_get_n_selected_rows(table, &error);
|
||||
g_object_unref(table);
|
||||
gboolean ambiguous = error == NULL && count > 1;
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
g_clear_error(&error);
|
||||
}
|
||||
return ambiguous ? NULL : cthulhu_selected_child(source);
|
||||
AtspiAccessible *child = ambiguous ? NULL : cthulhu_selected_child(source);
|
||||
// Qt selects every cell in a row, including an unnamed status column.
|
||||
// Prefer the provider's focused cell in that same uniquely selected row
|
||||
// so returning from a menu restores a useful Braille/review target too.
|
||||
// Keep the existing target when no such evidence is available; never
|
||||
// search other rows or guess a focus target in a multiple selection.
|
||||
gint columns = atspi_table_get_n_columns(table, NULL);
|
||||
if (!ambiguous && count == 1 && columns > 0 && columns <= 32) {
|
||||
GArray *rows = atspi_table_get_selected_rows(table, NULL);
|
||||
if (rows != NULL && rows->len == 1) {
|
||||
gint row = g_array_index(rows, gint, 0);
|
||||
for (gint column = 0; column < columns; column++) {
|
||||
AtspiAccessible *item = atspi_table_get_accessible_at(table, row, column, NULL);
|
||||
if (item == NULL) continue;
|
||||
atspi_accessible_clear_cache_single(item);
|
||||
AtspiStateSet *states = atspi_accessible_get_state_set(item);
|
||||
gboolean focused = states != NULL &&
|
||||
atspi_state_set_contains(states, ATSPI_STATE_FOCUSED) &&
|
||||
atspi_state_set_contains(states, ATSPI_STATE_SHOWING) &&
|
||||
!atspi_state_set_contains(states, ATSPI_STATE_DEFUNCT);
|
||||
if (states != NULL) g_object_unref(states);
|
||||
if (focused) {
|
||||
if (child != NULL) g_object_unref(child);
|
||||
child = item;
|
||||
break;
|
||||
}
|
||||
g_object_unref(item);
|
||||
}
|
||||
}
|
||||
if (rows != NULL) g_array_unref(rows);
|
||||
}
|
||||
g_object_unref(table);
|
||||
return child;
|
||||
}
|
||||
|
||||
// Resolve the focused descendant at a window-activation boundary. Providers
|
||||
|
||||
@@ -10,6 +10,33 @@
|
||||
static const gchar *menu_checkability_key = "cthulhu-menu-checkability";
|
||||
enum { CHECKABILITY_INFERRED = 1, CHECKABILITY_EXPLICIT = 2 };
|
||||
|
||||
// Qt can hide a submenu on Left/Escape without emitting focus-gained for
|
||||
// its parent action. Require fresh provider focus and visibility on that
|
||||
// exact parent; closing an entire menu or switching away grants no focus.
|
||||
AtspiAccessible *cthulhu_returned_menu_item(AtspiAccessible *menu) {
|
||||
atspi_accessible_clear_cache_single(menu);
|
||||
AtspiStateSet *states = atspi_accessible_get_state_set(menu);
|
||||
gboolean hidden = states != NULL &&
|
||||
!atspi_state_set_contains(states, ATSPI_STATE_SHOWING) &&
|
||||
!atspi_state_set_contains(states, ATSPI_STATE_DEFUNCT);
|
||||
if (states != NULL) g_object_unref(states);
|
||||
if (!hidden) return NULL;
|
||||
AtspiAccessible *parent = atspi_accessible_get_parent(menu, NULL);
|
||||
if (parent == NULL) return NULL;
|
||||
atspi_accessible_clear_cache_single(parent);
|
||||
AtspiRole role = atspi_accessible_get_role(parent, NULL);
|
||||
states = atspi_accessible_get_state_set(parent);
|
||||
gboolean focused = states != NULL &&
|
||||
atspi_state_set_contains(states, ATSPI_STATE_FOCUSED) &&
|
||||
atspi_state_set_contains(states, ATSPI_STATE_SHOWING) &&
|
||||
!atspi_state_set_contains(states, ATSPI_STATE_DEFUNCT);
|
||||
if (states != NULL) g_object_unref(states);
|
||||
if (focused && (role == ATSPI_ROLE_MENU_ITEM || role == ATSPI_ROLE_CHECK_MENU_ITEM ||
|
||||
role == ATSPI_ROLE_RADIO_MENU_ITEM)) return parent;
|
||||
g_object_unref(parent);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cthulhu_forget_menu_checkability(AtspiAccessible *source) {
|
||||
if (source != NULL)
|
||||
g_object_set_data(G_OBJECT(source), menu_checkability_key, NULL);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package atspi
|
||||
|
||||
/*
|
||||
#include "adapter_native.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// A hiding submenu can return focus only from its most recently focused
|
||||
// direct item. Queries and the returned reference belong to the GLib owner;
|
||||
// the callback releases the reference after the ordinary foreground gates.
|
||||
func (adapter *Adapter) returned_menu_item(event *C.AtspiEvent, eventType string, origin ObjectID) *C.AtspiAccessible {
|
||||
if eventType != ShowingStateEvent || event.detail1 != 0 || len(adapter.nativeFocus) == 0 {
|
||||
return nil
|
||||
}
|
||||
role := C.atspi_accessible_get_role(event.source, nil)
|
||||
if role != C.ATSPI_ROLE_MENU && role != C.ATSPI_ROLE_POPUP_MENU {
|
||||
return nil
|
||||
}
|
||||
current := adapter.nativeFocus[len(adapter.nativeFocus)-1].source
|
||||
switch C.atspi_accessible_get_role(current, nil) {
|
||||
case C.ATSPI_ROLE_MENU_ITEM, C.ATSPI_ROLE_CHECK_MENU_ITEM, C.ATSPI_ROLE_RADIO_MENU_ITEM:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
parent := C.atspi_accessible_get_parent(current, nil)
|
||||
if parent == nil {
|
||||
return nil
|
||||
}
|
||||
match := nativeObjectID(parent) == origin
|
||||
C.g_object_unref(C.gpointer(parent))
|
||||
if !match {
|
||||
return nil
|
||||
}
|
||||
return C.cthulhu_returned_menu_item(event.source)
|
||||
}
|
||||
@@ -123,6 +123,8 @@ AtspiAccessible *cthulhu_event_active_descendant(AtspiEvent *event);
|
||||
AtspiAccessible *cthulhu_selected_child(AtspiAccessible *source);
|
||||
AtspiAccessible *cthulhu_table_selected_child(AtspiAccessible *source);
|
||||
gboolean cthulhu_is_gtk_list_table(AtspiAccessible *source);
|
||||
gboolean cthulhu_is_table_container(AtspiAccessible *source);
|
||||
AtspiAccessible *cthulhu_returned_menu_item(AtspiAccessible *menu);
|
||||
void cthulhu_refresh_frame_children(AtspiAccessible *window);
|
||||
AtspiAccessible *cthulhu_focused_descendant(AtspiAccessible *window);
|
||||
AtspiAccessible *cthulhu_showing_web_document(AtspiAccessible *window);
|
||||
|
||||
Reference in new issue
Block a user