Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "adapter_native.h"
|
||||
|
||||
gboolean cthulhu_has_set_position(const gchar *role) {
|
||||
return g_strcmp0(role, "list item") == 0 || g_strcmp0(role, "tree item") == 0 ||
|
||||
g_strcmp0(role, "menu item") == 0 || g_strcmp0(role, "check menu item") == 0 ||
|
||||
g_strcmp0(role, "radio menu item") == 0 || g_strcmp0(role, "page tab") == 0 ||
|
||||
g_strcmp0(role, "radio button") == 0;
|
||||
}
|
||||
|
||||
static gint cthulhu_positive_integer(const gchar *value) {
|
||||
if (value == NULL || *value < '0' || *value > '9') return 0;
|
||||
gchar *end = NULL;
|
||||
gint64 number = g_ascii_strtoll(value, &end, 10);
|
||||
if (*end != '\0' || number <= 0 || number > G_MAXINT) return 0;
|
||||
return (gint)number;
|
||||
}
|
||||
|
||||
static gboolean cthulhu_menu_item(AtspiRole role) {
|
||||
return role == ATSPI_ROLE_MENU_ITEM || role == ATSPI_ROLE_CHECK_MENU_ITEM ||
|
||||
role == ATSPI_ROLE_RADIO_MENU_ITEM;
|
||||
}
|
||||
|
||||
static gboolean cthulhu_group_member(AtspiRelation *relation, AtspiAccessible *child) {
|
||||
for (gint index = 0; index < atspi_relation_get_n_targets(relation); index++) {
|
||||
AtspiAccessible *target = atspi_relation_get_target(relation, index);
|
||||
gboolean matches = target != NULL &&
|
||||
g_strcmp0(cthulhu_accessible_bus_name(target), cthulhu_accessible_bus_name(child)) == 0 &&
|
||||
g_strcmp0(cthulhu_accessible_path(target), cthulhu_accessible_path(child)) == 0;
|
||||
if (target != NULL) g_object_unref(target);
|
||||
if (matches) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void cthulhu_capture_set_position(AtspiAccessible *source, GHashTable *attributes,
|
||||
cthulhu_snapshot *snapshot) {
|
||||
const gchar *position = attributes == NULL ? NULL : g_hash_table_lookup(attributes, "posinset");
|
||||
const gchar *size = attributes == NULL ? NULL : g_hash_table_lookup(attributes, "setsize");
|
||||
// Explicit provider values describe virtualized and hierarchical sets.
|
||||
// Partial, unknown (-1), or invalid values must not become sibling guesses.
|
||||
if (position != NULL || size != NULL) {
|
||||
gint parsedPosition = cthulhu_positive_integer(position);
|
||||
gint parsedSize = cthulhu_positive_integer(size);
|
||||
if (parsedPosition > 0 && parsedPosition <= parsedSize) {
|
||||
snapshot->positionInSet = parsedPosition;
|
||||
snapshot->setSize = parsedSize;
|
||||
}
|
||||
return;
|
||||
}
|
||||
GError *error = NULL;
|
||||
AtspiAccessible *parent = atspi_accessible_get_parent(source, &error);
|
||||
if (error != NULL || parent == NULL) {
|
||||
g_clear_error(&error);
|
||||
if (parent != NULL) g_object_unref(parent);
|
||||
return;
|
||||
}
|
||||
AtspiRole sourceRole = atspi_accessible_get_role(source, &error);
|
||||
GArray *relations = NULL;
|
||||
AtspiRelation *memberRelation = NULL;
|
||||
if (error != NULL) goto cleanup;
|
||||
if (sourceRole == ATSPI_ROLE_RADIO_BUTTON) {
|
||||
relations = atspi_accessible_get_relation_set(source, &error);
|
||||
if (error != NULL) goto cleanup;
|
||||
for (guint index = 0; relations != NULL && index < relations->len; index++) {
|
||||
AtspiRelation *relation = g_array_index(relations, AtspiRelation *, index);
|
||||
if (atspi_relation_get_relation_type(relation) == ATSPI_RELATION_MEMBER_OF) {
|
||||
memberRelation = relation;
|
||||
if (atspi_relation_get_n_targets(relation) > 128) goto cleanup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
AtspiRole parentRole = atspi_accessible_get_role(parent, &error);
|
||||
if (error != NULL) goto cleanup;
|
||||
// A flattened tree does not reliably describe logical sibling sets.
|
||||
if (sourceRole == ATSPI_ROLE_TREE_ITEM) goto cleanup;
|
||||
if (sourceRole == ATSPI_ROLE_LIST_ITEM && parentRole != ATSPI_ROLE_LIST && parentRole != ATSPI_ROLE_LIST_BOX)
|
||||
goto cleanup;
|
||||
if (sourceRole == ATSPI_ROLE_PAGE_TAB && parentRole != ATSPI_ROLE_PAGE_TAB_LIST) goto cleanup;
|
||||
if (cthulhu_menu_item(sourceRole) && parentRole != ATSPI_ROLE_MENU && parentRole != ATSPI_ROLE_MENU_BAR)
|
||||
goto cleanup;
|
||||
gint count = atspi_accessible_get_child_count(parent, &error);
|
||||
if (error != NULL) goto cleanup;
|
||||
if (count <= 0) goto cleanup;
|
||||
gint index = atspi_accessible_get_index_in_parent(source, &error);
|
||||
if (error != NULL || index < 0 || index >= count) goto cleanup;
|
||||
// Lists and tab lists expose their items in child order, including large
|
||||
// sets. This uses a fixed number of queries rather than a sibling walk.
|
||||
if (sourceRole == ATSPI_ROLE_LIST_ITEM || sourceRole == ATSPI_ROLE_PAGE_TAB) {
|
||||
snapshot->positionInSet = index + 1;
|
||||
snapshot->setSize = count;
|
||||
goto cleanup;
|
||||
}
|
||||
// Menus contain separators; radio containers can contain labels and
|
||||
// unrelated controls. Count only item peers, with bounded native work.
|
||||
if (count > 128) goto cleanup;
|
||||
gint itemPosition = 0, setSize = 0;
|
||||
for (gint childIndex = 0; childIndex < count; childIndex++) {
|
||||
AtspiAccessible *child = atspi_accessible_get_child_at_index(parent, childIndex, &error);
|
||||
if (error != NULL || child == NULL) {
|
||||
if (child != NULL) g_object_unref(child);
|
||||
goto cleanup;
|
||||
}
|
||||
AtspiRole childRole = atspi_accessible_get_role(child, &error);
|
||||
gboolean member = memberRelation == NULL || cthulhu_group_member(memberRelation, child);
|
||||
g_object_unref(child);
|
||||
if (error != NULL) goto cleanup;
|
||||
if (!member) continue;
|
||||
if (childRole != sourceRole && !(cthulhu_menu_item(sourceRole) && cthulhu_menu_item(childRole))) continue;
|
||||
setSize++;
|
||||
if (childIndex == index) itemPosition = setSize;
|
||||
}
|
||||
snapshot->positionInSet = itemPosition;
|
||||
snapshot->setSize = setSize;
|
||||
cleanup:
|
||||
if (relations != NULL) {
|
||||
for (guint index = 0; index < relations->len; index++)
|
||||
g_object_unref(g_array_index(relations, AtspiRelation *, index));
|
||||
g_array_unref(relations);
|
||||
}
|
||||
g_clear_error(&error);
|
||||
g_object_unref(parent);
|
||||
}
|
||||
Reference in new issue
Block a user