// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include "adapter_native.h" // Owned by the native accessible, on the AT-SPI GLib thread. No accessible // references or application labels are retained. Destruction frees the data; // semantic identity changes and explicit capability removal retire it too. 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); } void cthulhu_note_menu_checkability(AtspiAccessible *source) { if (source != NULL && GPOINTER_TO_INT(g_object_get_data(G_OBJECT(source), menu_checkability_key)) != CHECKABILITY_EXPLICIT) g_object_set_data(G_OBJECT(source), menu_checkability_key, GINT_TO_POINTER(CHECKABILITY_INFERRED)); } void cthulhu_capture_menu_checkability(AtspiAccessible *source, cthulhu_snapshot *snapshot) { if (snapshot->defunct || g_strcmp0(snapshot->role, "menu item") != 0) { cthulhu_forget_menu_checkability(source); return; } if (!snapshot->statesKnown) return; gint evidence = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(source), menu_checkability_key)); if (snapshot->checkable) { g_object_set_data(G_OBJECT(source), menu_checkability_key, GINT_TO_POINTER(CHECKABILITY_EXPLICIT)); } else if (snapshot->checked || snapshot->indeterminate) { cthulhu_note_menu_checkability(source); snapshot->checkable = TRUE; } else if (evidence == CHECKABILITY_INFERRED) { // Providers such as Qt 5 omit CHECKABLE in both states. Once CHECKED // or a checked-state event proves this object's capability, retain it // when the value becomes false. Absence alone proves nothing. snapshot->checkable = TRUE; } else { // A provider which supplied CHECKABLE can explicitly withdraw it. cthulhu_forget_menu_checkability(source); } }