Files
cthulhu/internal/atspi/adapter_menu_states.c

76 lines
3.5 KiB
C

// 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);
}
}