Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,980 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "adapter_native.h"
|
||||
|
||||
static gchar *cthulhu_capture_text(AtspiAccessible *source, gchar **beforeCaret, gint *startOffset);
|
||||
static void cthulhu_capture_document_metrics(AtspiAccessible *source, cthulhu_snapshot *snapshot);
|
||||
static gchar *cthulhu_table_cell_label(AtspiAccessible *source, guint depth, guint *remaining);
|
||||
|
||||
// Some toolkits expose an edit field's label only as a relation, leaving its
|
||||
// accessible name empty. Follow direct targets only, with a shared query and
|
||||
// output budget; never infer a label from neighbouring controls or field text.
|
||||
static gchar *cthulhu_text_control_label(AtspiAccessible *source) {
|
||||
GArray *relations = atspi_accessible_get_relation_set(source, NULL);
|
||||
if (relations == NULL) return NULL;
|
||||
GString *label = g_string_new("");
|
||||
guint remaining = 16;
|
||||
for (guint i = 0; i < MIN(relations->len, 32) && remaining > 0 && label->len < 4096; i++) {
|
||||
AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
|
||||
if (atspi_relation_get_relation_type(relation) != ATSPI_RELATION_LABELLED_BY) continue;
|
||||
gint count = atspi_relation_get_n_targets(relation);
|
||||
for (gint j = 0; j < count && remaining > 0 && label->len < 4096; j++) {
|
||||
remaining--;
|
||||
AtspiAccessible *target = atspi_relation_get_target(relation, j);
|
||||
if (target == NULL) continue;
|
||||
AtspiRole role = atspi_accessible_get_role(target, NULL);
|
||||
gchar *text = NULL;
|
||||
if (role != ATSPI_ROLE_PASSWORD_TEXT) {
|
||||
text = atspi_accessible_get_name(target, NULL);
|
||||
if ((text == NULL || *text == '\0') &&
|
||||
(role == ATSPI_ROLE_LABEL || role == ATSPI_ROLE_STATIC)) {
|
||||
g_free(text);
|
||||
AtspiText *labelText = atspi_accessible_get_text(target);
|
||||
if (labelText != NULL) {
|
||||
gint characterCount = atspi_text_get_character_count(labelText, NULL);
|
||||
text = characterCount > 0 ? atspi_text_get_text(labelText, 0, MIN(characterCount, 1024), NULL) : NULL;
|
||||
g_object_unref(labelText);
|
||||
} else {
|
||||
text = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text != NULL && *g_strstrip(text) != '\0') {
|
||||
if (label->len != 0) g_string_append_c(label, ' ');
|
||||
const gchar *end = NULL;
|
||||
g_utf8_validate(text, MIN(strlen(text), 4096 - label->len), &end);
|
||||
g_string_append_len(label, text, end - text);
|
||||
}
|
||||
g_free(text);
|
||||
g_object_unref(target);
|
||||
}
|
||||
}
|
||||
for (guint i = 0; i < relations->len; i++)
|
||||
g_object_unref(g_array_index(relations, AtspiRelation *, i));
|
||||
g_array_unref(relations);
|
||||
return g_string_free(label, FALSE);
|
||||
}
|
||||
|
||||
// Collect only the selected row. Walking an entire table on every arrow key
|
||||
// would block input in large application tables.
|
||||
gchar *cthulhu_table_context(AtspiAccessible *source, gint *rows, gint *columns,
|
||||
gchar **tableName, gchar **tablePath,
|
||||
gint *rowIndex, gint *columnIndex, gboolean *guiTable) {
|
||||
AtspiAccessible *tableAccessible = NULL;
|
||||
gint row = -1;
|
||||
gint column = -1;
|
||||
if (atspi_accessible_get_role(source, NULL) == ATSPI_ROLE_TABLE) {
|
||||
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);
|
||||
if (cell != NULL) {
|
||||
tableAccessible = atspi_table_cell_get_table(cell, NULL);
|
||||
atspi_table_cell_get_position(cell, &row, &column, NULL);
|
||||
g_object_unref(cell);
|
||||
}
|
||||
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))) {
|
||||
tableAccessible = parent;
|
||||
gint index = atspi_accessible_get_index_in_parent(source, NULL);
|
||||
AtspiTable *parentTable = atspi_accessible_get_table_iface(parent);
|
||||
if (parentTable != NULL) {
|
||||
row = atspi_table_get_row_at_index(parentTable, index, NULL);
|
||||
column = atspi_table_get_column_at_index(parentTable, index, NULL);
|
||||
g_object_unref(parentTable);
|
||||
}
|
||||
} else if (parent != NULL) {
|
||||
g_object_unref(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tableAccessible == NULL) return NULL;
|
||||
*guiTable = TRUE;
|
||||
AtspiAccessible *ancestor = g_object_ref(tableAccessible);
|
||||
for (gint depth = 0; depth < 16 && ancestor != NULL; depth++) {
|
||||
AtspiRole role = atspi_accessible_get_role(ancestor, NULL);
|
||||
if (role == ATSPI_ROLE_DOCUMENT_WEB || role == ATSPI_ROLE_DOCUMENT_FRAME ||
|
||||
role == ATSPI_ROLE_DOCUMENT_TEXT || role == ATSPI_ROLE_DOCUMENT_SPREADSHEET) {
|
||||
*guiTable = FALSE;
|
||||
g_object_unref(ancestor);
|
||||
ancestor = NULL;
|
||||
break;
|
||||
}
|
||||
AtspiAccessible *parent = atspi_accessible_get_parent(ancestor, NULL);
|
||||
g_object_unref(ancestor);
|
||||
ancestor = parent;
|
||||
}
|
||||
if (ancestor != NULL) g_object_unref(ancestor);
|
||||
AtspiTable *table = atspi_accessible_get_table_iface(tableAccessible);
|
||||
if (table == NULL) {
|
||||
g_object_unref(tableAccessible);
|
||||
return NULL;
|
||||
}
|
||||
*rows = atspi_table_get_n_rows(table, NULL);
|
||||
*columns = atspi_table_get_n_columns(table, NULL);
|
||||
*tableName = atspi_accessible_get_name(tableAccessible, NULL);
|
||||
*tablePath = g_strdup(cthulhu_accessible_path(tableAccessible));
|
||||
*rowIndex = row;
|
||||
*columnIndex = column;
|
||||
GString *result = g_string_new(NULL);
|
||||
if (row >= 0 && row < *rows && *columns > 0 && *columns <= 32) {
|
||||
guint remaining = 32;
|
||||
for (gint column = 0; column < *columns; column++) {
|
||||
AtspiAccessible *item = atspi_table_get_accessible_at(table, row, column, NULL);
|
||||
if (item == NULL) 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) {
|
||||
g_free(name);
|
||||
name = cthulhu_table_cell_label(item, 0, &remaining);
|
||||
}
|
||||
if (name != NULL && *name != '\0' && result->len + strlen(name) < 4096) {
|
||||
if (result->len > 0) g_string_append(result, ", ");
|
||||
g_string_append(result, name);
|
||||
}
|
||||
g_free(name);
|
||||
g_object_unref(item);
|
||||
}
|
||||
}
|
||||
g_object_unref(table);
|
||||
g_object_unref(tableAccessible);
|
||||
return g_string_free(result, FALSE);
|
||||
}
|
||||
|
||||
static gboolean cthulhu_capture_sound_rect(AtspiAccessible *source, gint *x, gint *y,
|
||||
gint *width, gint *height) {
|
||||
AtspiStateSet *states = atspi_accessible_get_state_set(source);
|
||||
if (states == NULL) return FALSE;
|
||||
gboolean available = atspi_state_set_contains(states, ATSPI_STATE_VISIBLE) &&
|
||||
atspi_state_set_contains(states, ATSPI_STATE_SHOWING);
|
||||
g_object_unref(states);
|
||||
if (!available) return FALSE;
|
||||
AtspiComponent *component = atspi_accessible_get_component_iface(source);
|
||||
if (component == NULL) return FALSE;
|
||||
GError *error = NULL;
|
||||
AtspiRect *rect = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, &error);
|
||||
g_object_unref(component);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
if (rect != NULL) g_free(rect);
|
||||
return FALSE;
|
||||
}
|
||||
if (rect == NULL || rect->width <= 0 || rect->height <= 0 ||
|
||||
rect->x == G_MININT || rect->y == G_MININT) {
|
||||
if (rect != NULL) g_free(rect);
|
||||
return FALSE;
|
||||
}
|
||||
*x = rect->x;
|
||||
*y = rect->y;
|
||||
*width = rect->width;
|
||||
*height = rect->height;
|
||||
g_free(rect);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void cthulhu_capture_sound_geometry(AtspiAccessible *source, cthulhu_snapshot *snapshot) {
|
||||
snapshot->objectGeometryKnown = cthulhu_capture_sound_rect(
|
||||
source, &snapshot->objectX, &snapshot->objectY,
|
||||
&snapshot->objectWidth, &snapshot->objectHeight);
|
||||
if (!snapshot->objectGeometryKnown) return;
|
||||
|
||||
AtspiAccessible *current = g_object_ref(source);
|
||||
for (gint depth = 0; current != NULL && depth < 256; depth++) {
|
||||
GError *error = NULL;
|
||||
AtspiRole role = atspi_accessible_get_role(current, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
g_object_unref(current);
|
||||
return;
|
||||
}
|
||||
if (role == ATSPI_ROLE_DIALOG || role == ATSPI_ROLE_FRAME || role == ATSPI_ROLE_WINDOW) {
|
||||
snapshot->referenceGeometryKnown = cthulhu_capture_sound_rect(
|
||||
current, &snapshot->referenceX, &snapshot->referenceY,
|
||||
&snapshot->referenceWidth, &snapshot->referenceHeight);
|
||||
g_object_unref(current);
|
||||
return;
|
||||
}
|
||||
AtspiAccessible *parent = atspi_accessible_get_parent(current, &error);
|
||||
g_object_unref(current);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
if (parent != NULL) g_object_unref(parent);
|
||||
return;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
if (current != NULL) g_object_unref(current);
|
||||
}
|
||||
|
||||
static void cthulhu_capture_value(AtspiAccessible *source, cthulhu_snapshot *snapshot) {
|
||||
GError *error = NULL;
|
||||
AtspiRole role = atspi_accessible_get_role(source, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
switch (role) {
|
||||
case ATSPI_ROLE_COMBO_BOX: {
|
||||
// Many applications expose the selected item only through Selection.
|
||||
// Prefer an already supplied description so well-behaved controls do
|
||||
// not pay another synchronous AT-SPI round trip on every focus event.
|
||||
if (snapshot->description != NULL && *snapshot->description != '\0') {
|
||||
return;
|
||||
}
|
||||
AtspiSelection *selection = atspi_accessible_get_selection_iface(source);
|
||||
if (selection == NULL) return;
|
||||
AtspiAccessible *selected = atspi_selection_get_selected_child(selection, 0, &error);
|
||||
g_object_unref(selection);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
if (selected != NULL) g_object_unref(selected);
|
||||
return;
|
||||
}
|
||||
if (selected == NULL) return;
|
||||
snapshot->valueText = atspi_accessible_get_name(selected, &error);
|
||||
g_object_unref(selected);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
snapshot->valueText = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ATSPI_ROLE_DIAL:
|
||||
case ATSPI_ROLE_PROGRESS_BAR:
|
||||
case ATSPI_ROLE_SCROLL_BAR:
|
||||
case ATSPI_ROLE_SLIDER:
|
||||
break;
|
||||
case ATSPI_ROLE_SPIN_BUTTON:
|
||||
if (snapshot->text != NULL && *snapshot->text != '\0') {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ATSPI_ROLE_SPLIT_PANE:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
AtspiValue *value = atspi_accessible_get_value_iface(source);
|
||||
if (value == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
error = NULL;
|
||||
snapshot->valueText = atspi_value_get_text(value, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
error = NULL;
|
||||
}
|
||||
|
||||
snapshot->currentValue = atspi_value_get_current_value(value, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
error = NULL;
|
||||
g_object_unref(value);
|
||||
return;
|
||||
}
|
||||
snapshot->minimumValue = atspi_value_get_minimum_value(value, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
error = NULL;
|
||||
g_object_unref(value);
|
||||
return;
|
||||
}
|
||||
snapshot->maximumValue = atspi_value_get_maximum_value(value, &error);
|
||||
if (error == NULL) {
|
||||
snapshot->valueKnown = TRUE;
|
||||
} else {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
snapshot->minimumIncrement = atspi_value_get_minimum_increment(value, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
snapshot->minimumIncrement = (snapshot->maximumValue - snapshot->minimumValue) / 100.0;
|
||||
}
|
||||
g_object_unref(value);
|
||||
}
|
||||
|
||||
// GTK tree rows can be unnamed while their renderer cells hold the visible
|
||||
// label (including file names in Thunar's details view). Bound the lookup so
|
||||
// a large or broken table cannot stall the event callback.
|
||||
static gchar *cthulhu_table_cell_label(AtspiAccessible *source, guint depth, guint *remaining) {
|
||||
if (source == NULL || *remaining == 0) return NULL;
|
||||
(*remaining)--;
|
||||
if (depth != 0) {
|
||||
GError *error = NULL;
|
||||
gchar *name = atspi_accessible_get_name(source, &error);
|
||||
if (error != NULL) g_clear_error(&error);
|
||||
if (name != NULL && *g_strstrip(name) != '\0') return name;
|
||||
g_free(name);
|
||||
}
|
||||
if (depth >= 2) return NULL;
|
||||
GError *error = NULL;
|
||||
gint count = atspi_accessible_get_child_count(source, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
return NULL;
|
||||
}
|
||||
for (gint index = 0; index < count && index < 12 && *remaining != 0; index++) {
|
||||
AtspiAccessible *child = atspi_accessible_get_child_at_index(source, index, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
continue;
|
||||
}
|
||||
if (child == NULL) continue;
|
||||
gchar *name = cthulhu_table_cell_label(child, depth + 1, remaining);
|
||||
g_object_unref(child);
|
||||
if (name != NULL) return name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Document cells can contain several text/link children. Collect their labels
|
||||
// within a fixed budget, rather than the GUI row helper's first-label shortcut.
|
||||
static gchar *cthulhu_document_cell_label(AtspiAccessible *source, guint depth, guint *remaining) {
|
||||
if (!source || !*remaining || depth > 8) return NULL;
|
||||
(*remaining)--;
|
||||
gchar *name = atspi_accessible_get_name(source, NULL);
|
||||
if (name && *g_strstrip(name)) return name;
|
||||
g_free(name);
|
||||
GString *text = g_string_new(NULL);
|
||||
gint count = atspi_accessible_get_child_count(source, NULL);
|
||||
for (gint i = 0; i < count && *remaining; i++) {
|
||||
AtspiAccessible *child = atspi_accessible_get_child_at_index(source, i, NULL);
|
||||
if (!child) continue;
|
||||
gchar *label = cthulhu_document_cell_label(child, depth + 1, remaining);
|
||||
g_object_unref(child);
|
||||
if (label && *label) {
|
||||
if (text->len) g_string_append_c(text, ' ');
|
||||
g_string_append(text, label);
|
||||
}
|
||||
g_free(label);
|
||||
}
|
||||
return g_string_free(text, FALSE);
|
||||
}
|
||||
|
||||
// Header arrays own their accessible references. Copy labels before release.
|
||||
static gchar *cthulhu_table_headers(GPtrArray *headers) {
|
||||
if (!headers) return NULL;
|
||||
GString *text = g_string_new(NULL);
|
||||
guint remaining = 32;
|
||||
for (guint i = 0; i < headers->len && remaining; i++) {
|
||||
gchar *label = cthulhu_document_cell_label(g_ptr_array_index(headers, i), 0, &remaining);
|
||||
if (label && *label) {
|
||||
if (text->len) g_string_append(text, ", ");
|
||||
g_string_append(text, label);
|
||||
}
|
||||
g_free(label);
|
||||
}
|
||||
g_ptr_array_unref(headers);
|
||||
return g_string_free(text, FALSE);
|
||||
}
|
||||
|
||||
cthulhu_snapshot cthulhu_capture_snapshot(AtspiAccessible *source, gboolean captureText,
|
||||
gboolean captureCaretCharacter,
|
||||
gboolean captureDocumentMetadata,
|
||||
gboolean captureSoundGeometry,
|
||||
gboolean captureApplicationName) {
|
||||
cthulhu_snapshot snapshot = {0};
|
||||
if (source == NULL) {
|
||||
return snapshot;
|
||||
}
|
||||
snapshot.path = ATSPI_OBJECT(source)->path;
|
||||
if (ATSPI_OBJECT(source)->app != NULL) {
|
||||
snapshot.busName = ATSPI_OBJECT(source)->app->bus_name;
|
||||
if (captureApplicationName) {
|
||||
GError *applicationError = NULL;
|
||||
AtspiAccessible *application = atspi_accessible_get_application(source, &applicationError);
|
||||
if (applicationError != NULL) {
|
||||
g_clear_error(&applicationError);
|
||||
}
|
||||
if (application != NULL) {
|
||||
snapshot.applicationName = atspi_accessible_get_name(application, &applicationError);
|
||||
if (applicationError != NULL) {
|
||||
g_clear_error(&applicationError);
|
||||
}
|
||||
g_object_unref(application);
|
||||
}
|
||||
}
|
||||
}
|
||||
AtspiStateSet *states = atspi_accessible_get_state_set(source);
|
||||
if (states != NULL) {
|
||||
snapshot.statesKnown = TRUE;
|
||||
snapshot.visible = atspi_state_set_contains(states, ATSPI_STATE_VISIBLE);
|
||||
snapshot.showing = atspi_state_set_contains(states, ATSPI_STATE_SHOWING);
|
||||
snapshot.active = atspi_state_set_contains(states, ATSPI_STATE_ACTIVE);
|
||||
snapshot.autocomplete = atspi_state_set_contains(states, ATSPI_STATE_SUPPORTS_AUTOCOMPLETION) && atspi_state_set_contains(states, ATSPI_STATE_SINGLE_LINE);
|
||||
snapshot.focusable = atspi_state_set_contains(states, ATSPI_STATE_FOCUSABLE);
|
||||
snapshot.focused = atspi_state_set_contains(states, ATSPI_STATE_FOCUSED);
|
||||
snapshot.hasPopup = atspi_state_set_contains(states, ATSPI_STATE_HAS_POPUP);
|
||||
snapshot.sensitiveKnown = TRUE;
|
||||
snapshot.checkable = atspi_state_set_contains(states, ATSPI_STATE_CHECKABLE);
|
||||
snapshot.checked = atspi_state_set_contains(states, ATSPI_STATE_CHECKED) || atspi_state_set_contains(states, ATSPI_STATE_PRESSED);
|
||||
snapshot.defunct = atspi_state_set_contains(states, ATSPI_STATE_DEFUNCT);
|
||||
snapshot.editable = atspi_state_set_contains(states, ATSPI_STATE_EDITABLE);
|
||||
snapshot.expandable = atspi_state_set_contains(states, ATSPI_STATE_EXPANDABLE) || atspi_state_set_contains(states, ATSPI_STATE_EXPANDED);
|
||||
snapshot.expanded = atspi_state_set_contains(states, ATSPI_STATE_EXPANDED);
|
||||
snapshot.indeterminate = atspi_state_set_contains(states, ATSPI_STATE_INDETERMINATE);
|
||||
snapshot.invalid = atspi_state_set_contains(states, ATSPI_STATE_INVALID_ENTRY) || atspi_state_set_contains(states, ATSPI_STATE_INVALID);
|
||||
snapshot.multiselectable = atspi_state_set_contains(states, ATSPI_STATE_MULTISELECTABLE);
|
||||
snapshot.readOnly = atspi_state_set_contains(states, ATSPI_STATE_READ_ONLY);
|
||||
snapshot.required = atspi_state_set_contains(states, ATSPI_STATE_REQUIRED);
|
||||
snapshot.selected = atspi_state_set_contains(states, ATSPI_STATE_SELECTED);
|
||||
snapshot.sensitive = atspi_state_set_contains(states, ATSPI_STATE_SENSITIVE);
|
||||
snapshot.visited = atspi_state_set_contains(states, ATSPI_STATE_VISITED);
|
||||
g_object_unref(states);
|
||||
}
|
||||
GError *error = NULL;
|
||||
snapshot.name = atspi_accessible_get_name(source, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
error = NULL;
|
||||
}
|
||||
snapshot.role = atspi_accessible_get_role_name(source, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
error = NULL;
|
||||
}
|
||||
if (!snapshot.defunct && (snapshot.name == NULL || *snapshot.name == '\0') &&
|
||||
(g_strcmp0(snapshot.role, "text") == 0 || g_strcmp0(snapshot.role, "entry") == 0 ||
|
||||
g_strcmp0(snapshot.role, "password text") == 0)) {
|
||||
g_free(snapshot.name);
|
||||
snapshot.name = cthulhu_text_control_label(source);
|
||||
}
|
||||
cthulhu_capture_menu_checkability(source, &snapshot);
|
||||
if (!snapshot.defunct && (g_strcmp0(snapshot.role, "list") == 0 ||
|
||||
g_strcmp0(snapshot.role, "list box") == 0)) {
|
||||
snapshot.childCount = atspi_accessible_get_child_count(source, &error);
|
||||
snapshot.childCountKnown = error == NULL && snapshot.childCount >= 0;
|
||||
g_clear_error(&error);
|
||||
}
|
||||
if (!snapshot.defunct && !captureDocumentMetadata && g_strcmp0(snapshot.role, "table cell") == 0 &&
|
||||
(snapshot.name == NULL || *snapshot.name == '\0')) {
|
||||
guint remaining = 20;
|
||||
gchar *label = cthulhu_table_cell_label(source, 0, &remaining);
|
||||
if (label != NULL) {
|
||||
g_free(snapshot.name);
|
||||
snapshot.name = label;
|
||||
}
|
||||
}
|
||||
snapshot.description = atspi_accessible_get_description(source, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
}
|
||||
if (captureDocumentMetadata) {
|
||||
cthulhu_capture_document_metrics(source, &snapshot);
|
||||
}
|
||||
if (captureSoundGeometry) {
|
||||
cthulhu_capture_sound_geometry(source, &snapshot);
|
||||
}
|
||||
// Copy only semantic attribute fields used by navigation; arbitrary
|
||||
// browser attributes can include URLs and unrelated document data.
|
||||
error = NULL;
|
||||
GHashTable *attributes = NULL;
|
||||
gboolean captureSetPosition = !snapshot.defunct && cthulhu_has_set_position(snapshot.role);
|
||||
if (captureDocumentMetadata || g_strcmp0(snapshot.role, "heading") == 0 || captureSetPosition) {
|
||||
attributes = atspi_accessible_get_attributes(source, &error);
|
||||
}
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
if (captureSetPosition) cthulhu_capture_set_position(source, attributes, &snapshot);
|
||||
if (attributes != NULL) {
|
||||
if (captureDocumentMetadata) {
|
||||
snapshot.explicitName = g_strcmp0(g_hash_table_lookup(attributes, "explicit-name"), "true") == 0;
|
||||
const gchar *xmlRoles = g_hash_table_lookup(attributes, "xml-roles");
|
||||
if (xmlRoles != NULL) {
|
||||
snapshot.xmlRoles = g_strdup(xmlRoles);
|
||||
}
|
||||
const gchar *htmlTag = g_hash_table_lookup(attributes, "tag");
|
||||
if (htmlTag != NULL) snapshot.htmlTag = g_strdup(htmlTag);
|
||||
const gchar *elementID = g_hash_table_lookup(attributes, "id");
|
||||
if (elementID != NULL) {
|
||||
snapshot.elementID = g_strdup(elementID);
|
||||
}
|
||||
const gchar *containerLive = g_hash_table_lookup(attributes, "container-live");
|
||||
if (containerLive != NULL && (g_strcmp0(containerLive, "off") == 0 ||
|
||||
g_strcmp0(containerLive, "polite") == 0 ||
|
||||
g_strcmp0(containerLive, "assertive") == 0)) {
|
||||
snapshot.liveRegion = TRUE;
|
||||
}
|
||||
const gchar *layoutGuess = g_hash_table_lookup(attributes, "layout-guess");
|
||||
if (g_strcmp0(layoutGuess, "true") == 0) {
|
||||
snapshot.layoutTable = TRUE;
|
||||
}
|
||||
}
|
||||
if (g_strcmp0(snapshot.role, "heading") == 0) {
|
||||
const gchar *level = g_hash_table_lookup(attributes, "level");
|
||||
if (level != NULL && *level >= '1' && *level <= '9') {
|
||||
gchar *end = NULL;
|
||||
gint64 number = g_ascii_strtoll(level, &end, 10);
|
||||
if (end != NULL && *end == '\0' && number <= G_MAXINT) {
|
||||
snapshot.headingLevel = (gint)number;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attributes != NULL) g_hash_table_unref(attributes);
|
||||
}
|
||||
if (captureDocumentMetadata && (g_strcmp0(snapshot.role, "table") == 0 || g_strcmp0(snapshot.role, "tree table") == 0)) {
|
||||
AtspiTable *table = atspi_accessible_get_table_iface(source);
|
||||
if (table != NULL) {
|
||||
error = NULL;
|
||||
snapshot.tableRowCount = atspi_table_get_n_rows(table, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
snapshot.tableColumnCount = atspi_table_get_n_columns(table, NULL);
|
||||
g_object_unref(table);
|
||||
}
|
||||
}
|
||||
if (captureDocumentMetadata && (g_strcmp0(snapshot.role, "table cell") == 0 ||
|
||||
g_strcmp0(snapshot.role, "column header") == 0 || g_strcmp0(snapshot.role, "row header") == 0)) {
|
||||
if (!snapshot.name || !*snapshot.name) {
|
||||
guint remaining = 64;
|
||||
gchar *label = cthulhu_document_cell_label(source, 0, &remaining);
|
||||
g_free(snapshot.name);
|
||||
snapshot.name = label;
|
||||
}
|
||||
AtspiTableCell *cell = atspi_accessible_get_table_cell(source);
|
||||
if (cell) {
|
||||
error = NULL;
|
||||
snapshot.tableRowIndex = snapshot.tableColumnIndex = -1;
|
||||
atspi_table_cell_get_row_column_span(cell, &snapshot.tableRowIndex, &snapshot.tableColumnIndex,
|
||||
&snapshot.tableRowSpan, &snapshot.tableColumnSpan, &error);
|
||||
snapshot.tablePositionKnown = !error && snapshot.tableRowIndex >= 0 && snapshot.tableColumnIndex >= 0 &&
|
||||
snapshot.tableRowSpan > 0 && snapshot.tableColumnSpan > 0;
|
||||
g_clear_error(&error);
|
||||
snapshot.tableRowHeaders = cthulhu_table_headers(atspi_table_cell_get_row_header_cells(cell, NULL));
|
||||
snapshot.tableColumnHeaders = cthulhu_table_headers(atspi_table_cell_get_column_header_cells(cell, NULL));
|
||||
g_object_unref(cell);
|
||||
}
|
||||
}
|
||||
AtspiAction *action = captureDocumentMetadata ? atspi_accessible_get_action_iface(source) : NULL;
|
||||
if (action != NULL) {
|
||||
error = NULL;
|
||||
gint actionCount = atspi_action_get_n_actions(action, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
for (gint index = 0; index < actionCount; index++) {
|
||||
error = NULL;
|
||||
gchar *actionName = atspi_action_get_name(action, index, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
// Web providers use different action names for activation. Keep
|
||||
// this list aligned with activateNativeDocumentObject so browse
|
||||
// Enter can reach objects exposing click-ancestor or press.
|
||||
if (actionName != NULL &&
|
||||
(g_ascii_strcasecmp(actionName, "click") == 0 ||
|
||||
g_ascii_strcasecmp(actionName, "click-ancestor") == 0 ||
|
||||
g_ascii_strcasecmp(actionName, "press") == 0 ||
|
||||
g_ascii_strcasecmp(actionName, "jump") == 0 ||
|
||||
g_ascii_strcasecmp(actionName, "open") == 0 ||
|
||||
g_ascii_strcasecmp(actionName, "activate") == 0)) {
|
||||
snapshot.supportsClick = TRUE;
|
||||
g_free(actionName);
|
||||
break;
|
||||
}
|
||||
g_free(actionName);
|
||||
}
|
||||
g_object_unref(action);
|
||||
}
|
||||
// Text and caret capture make synchronous AT-SPI calls. Focus events for
|
||||
// ordinary controls do not use that text in presentation, so avoid stalling
|
||||
// the GLib event loop for each Tab stop. Focusable text controls include
|
||||
// read-only entries: their current line is still part of focus speech.
|
||||
// Caret events also need text when they originate in static web content.
|
||||
gboolean focusableText = snapshot.focusable &&
|
||||
(g_strcmp0(snapshot.role, "text") == 0 || g_strcmp0(snapshot.role, "entry") == 0);
|
||||
if (captureText && g_strcmp0(snapshot.role, "password text") != 0 &&
|
||||
(snapshot.editable || focusableText || g_strcmp0(snapshot.role, "terminal") == 0 || captureCaretCharacter)) {
|
||||
snapshot.text = cthulhu_capture_text(source, &snapshot.textBeforeCaret, &snapshot.textStartOffset);
|
||||
}
|
||||
if (captureCaretCharacter && g_strcmp0(snapshot.role, "password text") != 0) {
|
||||
snapshot.caretCharacter = cthulhu_capture_caret_unit(source, ATSPI_TEXT_GRANULARITY_CHAR);
|
||||
}
|
||||
cthulhu_capture_value(source, &snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
static gchar *cthulhu_capture_text(AtspiAccessible *source, gchar **beforeCaret, gint *startOffset) {
|
||||
*beforeCaret = NULL;
|
||||
AtspiText *text = atspi_accessible_get_text(source);
|
||||
if (text == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
GError *error = NULL;
|
||||
gint caretOffset = atspi_text_get_caret_offset(text, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
g_object_unref(text);
|
||||
return NULL;
|
||||
}
|
||||
AtspiTextRange *range = cthulhu_text_get_string_at_offset(
|
||||
text, caretOffset, ATSPI_TEXT_GRANULARITY_LINE, &error);
|
||||
// AXText.get_line_at_offset retries Chromium's invalid end-of-text
|
||||
// range. A valid empty range is a real blank line and must stay empty.
|
||||
if (error == NULL && range != NULL && caretOffset > 0) {
|
||||
gboolean retry = range->start_offset > caretOffset;
|
||||
if (range->start_offset == -1 && range->end_offset == -1) {
|
||||
gint count = atspi_text_get_character_count(text, &error);
|
||||
retry = error == NULL && caretOffset == count;
|
||||
}
|
||||
if (retry) {
|
||||
g_boxed_free(atspi_text_range_get_type(), range);
|
||||
range = cthulhu_text_get_string_at_offset(
|
||||
text, caretOffset - 1, ATSPI_TEXT_GRANULARITY_LINE, &error);
|
||||
}
|
||||
}
|
||||
g_object_unref(text);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
if (range != NULL) g_boxed_free(atspi_text_range_get_type(), range);
|
||||
return NULL;
|
||||
}
|
||||
if (range == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
gchar *content = g_strdup(range->content != NULL ? range->content : "");
|
||||
*startOffset = range->start_offset;
|
||||
gint relativeCaret = caretOffset - range->start_offset;
|
||||
glong characterCount = g_utf8_strlen(content, -1);
|
||||
if (relativeCaret < 0) {
|
||||
relativeCaret = 0;
|
||||
} else if (relativeCaret > characterCount) {
|
||||
relativeCaret = (gint)characterCount;
|
||||
}
|
||||
*beforeCaret = g_utf8_substring(content, 0, relativeCaret);
|
||||
g_free(range->content);
|
||||
g_free(range);
|
||||
return content;
|
||||
}
|
||||
|
||||
static gchar *cthulhu_label_text(AtspiAccessible *source) {
|
||||
AtspiText *text = atspi_accessible_get_text(source);
|
||||
if (text != NULL) {
|
||||
GError *error = NULL;
|
||||
gint characterCount = atspi_text_get_character_count(text, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
g_object_unref(text);
|
||||
return NULL;
|
||||
}
|
||||
gchar *content = atspi_text_get_text(text, 0, characterCount, &error);
|
||||
g_object_unref(text);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
return NULL;
|
||||
}
|
||||
return content != NULL ? content : g_strdup("");
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
gchar *name = atspi_accessible_get_name(source, &error);
|
||||
if (error != NULL) g_error_free(error);
|
||||
return name != NULL ? name : g_strdup("");
|
||||
}
|
||||
|
||||
static void cthulhu_append_label_texts(AtspiAccessible *source, GString *contents,
|
||||
int *visited) {
|
||||
if (source == NULL || *visited >= 100 || contents->len >= 4096) return;
|
||||
(*visited)++;
|
||||
GError *error = NULL;
|
||||
AtspiRole role = atspi_accessible_get_role(source, &error);
|
||||
if (error != NULL) g_clear_error(&error);
|
||||
if (role == ATSPI_ROLE_LABEL) {
|
||||
gchar *text = cthulhu_label_text(source);
|
||||
if (text != NULL && *text != '\0') {
|
||||
if (contents->len != 0) g_string_append_c(contents, ' ');
|
||||
g_string_append(contents, text);
|
||||
}
|
||||
g_free(text);
|
||||
}
|
||||
error = NULL;
|
||||
gint childCount = atspi_accessible_get_child_count(source, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
for (gint index = 0; index < childCount && *visited < 100 && contents->len < 4096; index++) {
|
||||
error = NULL;
|
||||
AtspiAccessible *child = atspi_accessible_get_child_at_index(source, index, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
continue;
|
||||
}
|
||||
cthulhu_append_label_texts(child, contents, visited);
|
||||
if (child != NULL) g_object_unref(child);
|
||||
}
|
||||
}
|
||||
|
||||
gchar *cthulhu_collect_notification_text(AtspiAccessible *source) {
|
||||
if (source == NULL) return g_strdup("");
|
||||
GString *contents = g_string_new("");
|
||||
int visited = 0;
|
||||
cthulhu_append_label_texts(source, contents, &visited);
|
||||
return g_string_free(contents, FALSE);
|
||||
}
|
||||
|
||||
// YAD exposes prompts as labels beside buttons or a choice list. Keep the
|
||||
// capture within that small shape; ordinary frames contain unrelated labels.
|
||||
static gboolean cthulhu_append_prompt_labels(AtspiAccessible *source, GString *contents,
|
||||
gint *visited, gint *buttons, gint *labels, gint *choices) {
|
||||
if (source == NULL || *visited >= 100 || contents->len >= 4096) return FALSE;
|
||||
(*visited)++;
|
||||
GError *error = NULL;
|
||||
AtspiRole role = atspi_accessible_get_role(source, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
return FALSE;
|
||||
}
|
||||
if (role == ATSPI_ROLE_BUTTON) {
|
||||
(*buttons)++;
|
||||
return *buttons <= 4;
|
||||
}
|
||||
if (role == ATSPI_ROLE_LABEL || role == ATSPI_ROLE_STATIC) {
|
||||
AtspiStateSet *states = atspi_accessible_get_state_set(source);
|
||||
gboolean showing = states != NULL && atspi_state_set_contains(states, ATSPI_STATE_SHOWING);
|
||||
if (states != NULL) g_object_unref(states);
|
||||
if (!showing) return TRUE;
|
||||
gchar *label = cthulhu_label_text(source);
|
||||
if (label != NULL && *label != '\0') {
|
||||
if (contents->len != 0) g_string_append_c(contents, ' ');
|
||||
g_string_append(contents, label);
|
||||
(*labels)++;
|
||||
}
|
||||
g_free(label);
|
||||
return TRUE;
|
||||
}
|
||||
if (role == ATSPI_ROLE_ICON || role == ATSPI_ROLE_SCROLL_BAR) return TRUE;
|
||||
if (role == ATSPI_ROLE_TREE_TABLE || role == ATSPI_ROLE_TREE ||
|
||||
role == ATSPI_ROLE_TABLE || role == ATSPI_ROLE_LIST) {
|
||||
(*choices)++;
|
||||
return *choices <= 1;
|
||||
}
|
||||
if (role != ATSPI_ROLE_FRAME && role != ATSPI_ROLE_DIALOG &&
|
||||
role != ATSPI_ROLE_FILLER && role != ATSPI_ROLE_PANEL &&
|
||||
role != ATSPI_ROLE_SCROLL_PANE) return FALSE;
|
||||
gint childCount = atspi_accessible_get_child_count(source, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
return FALSE;
|
||||
}
|
||||
for (gint index = 0; index < childCount; index++) {
|
||||
AtspiAccessible *child = atspi_accessible_get_child_at_index(source, index, &error);
|
||||
if (error != NULL || child == NULL) {
|
||||
g_clear_error(&error);
|
||||
if (child != NULL) g_object_unref(child);
|
||||
return FALSE;
|
||||
}
|
||||
gboolean valid = cthulhu_append_prompt_labels(child, contents, visited, buttons, labels, choices);
|
||||
g_object_unref(child);
|
||||
if (!valid) return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gchar *cthulhu_collect_simple_prompt_text(AtspiAccessible *source) {
|
||||
if (source == NULL) return NULL;
|
||||
GString *contents = g_string_new("");
|
||||
gint visited = 0, buttons = 0, labels = 0, choices = 0;
|
||||
gboolean valid = cthulhu_append_prompt_labels(source, contents, &visited, &buttons, &labels, &choices);
|
||||
if (!valid || labels == 0 || (buttons == 0 && choices == 0)) {
|
||||
g_string_free(contents, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
return g_string_free(contents, FALSE);
|
||||
}
|
||||
|
||||
// Follow the same label/ancestor conventions used by GTK radio groups. A
|
||||
// named panel or filler is the group's question, not the option's own name.
|
||||
gchar *cthulhu_radio_group_name(AtspiAccessible *source) {
|
||||
GArray *relations = atspi_accessible_get_relation_set(source, NULL);
|
||||
if (relations != NULL) {
|
||||
for (guint i = 0; i < relations->len; i++) {
|
||||
AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
|
||||
if (atspi_relation_get_relation_type(relation) == ATSPI_RELATION_LABELLED_BY &&
|
||||
atspi_relation_get_n_targets(relation) > 0) {
|
||||
AtspiAccessible *label = atspi_relation_get_target(relation, 0);
|
||||
gchar *name = label != NULL ? cthulhu_label_text(label) : NULL;
|
||||
gchar *option = atspi_accessible_get_name(source, NULL);
|
||||
gboolean ownLabel = g_strcmp0(name, option) == 0;
|
||||
g_free(option);
|
||||
if (label != NULL) g_object_unref(label);
|
||||
for (guint j = 0; j < relations->len; j++)
|
||||
g_object_unref(g_array_index(relations, AtspiRelation *, j));
|
||||
g_array_unref(relations);
|
||||
if (name != NULL && *name != '\0' && !ownLabel) return name;
|
||||
g_free(name);
|
||||
relations = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (relations != NULL) {
|
||||
for (guint i = 0; i < relations->len; i++)
|
||||
g_object_unref(g_array_index(relations, AtspiRelation *, i));
|
||||
g_array_unref(relations);
|
||||
}
|
||||
}
|
||||
AtspiAccessible *current = atspi_accessible_get_parent(source, NULL);
|
||||
for (int depth = 0; current != NULL && depth < 16; depth++) {
|
||||
AtspiRole role = atspi_accessible_get_role(current, NULL);
|
||||
if (role == ATSPI_ROLE_FRAME || role == ATSPI_ROLE_DIALOG || role == ATSPI_ROLE_APPLICATION) {
|
||||
g_object_unref(current);
|
||||
return NULL;
|
||||
}
|
||||
if (role == ATSPI_ROLE_PANEL || role == ATSPI_ROLE_FILLER) {
|
||||
gchar *name = atspi_accessible_get_name(current, NULL);
|
||||
if (name != NULL && *name != '\0') {
|
||||
g_object_unref(current);
|
||||
return name;
|
||||
}
|
||||
g_free(name);
|
||||
}
|
||||
AtspiAccessible *parent = atspi_accessible_get_parent(current, NULL);
|
||||
g_object_unref(current);
|
||||
current = parent;
|
||||
}
|
||||
if (current != NULL) g_object_unref(current);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cthulhu_append_live_text(AtspiAccessible *source, GString *contents, int *visited) {
|
||||
if (source == NULL || *visited >= 100 || contents->len >= 4096) return;
|
||||
(*visited)++;
|
||||
GError *error = NULL;
|
||||
gint childCount = atspi_accessible_get_child_count(source, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
childCount = 0;
|
||||
}
|
||||
guint before = contents->len;
|
||||
for (gint index = 0; index < childCount && *visited < 100 && contents->len < 4096; index++) {
|
||||
AtspiAccessible *child = atspi_accessible_get_child_at_index(source, index, &error);
|
||||
if (error != NULL) g_clear_error(&error);
|
||||
if (child != NULL) {
|
||||
cthulhu_append_live_text(child, contents, visited);
|
||||
g_object_unref(child);
|
||||
}
|
||||
}
|
||||
if (contents->len == before) {
|
||||
gchar *text = cthulhu_label_text(source);
|
||||
if (text != NULL && *text != '\0') {
|
||||
if (contents->len != 0) g_string_append_c(contents, ' ');
|
||||
g_string_append(contents, text);
|
||||
}
|
||||
g_free(text);
|
||||
}
|
||||
}
|
||||
|
||||
gchar *cthulhu_live_content(AtspiAccessible *source) {
|
||||
GString *contents = g_string_new("");
|
||||
int visited = 0;
|
||||
cthulhu_append_live_text(source, contents, &visited);
|
||||
return g_string_free(contents, FALSE);
|
||||
}
|
||||
|
||||
gchar *cthulhu_capture_caret_unit(AtspiAccessible *source, AtspiTextGranularity unit) {
|
||||
AtspiText *text = atspi_accessible_get_text(source);
|
||||
if (text == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
GError *error = NULL;
|
||||
gint caretOffset = atspi_text_get_caret_offset(text, &error);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
g_object_unref(text);
|
||||
return NULL;
|
||||
}
|
||||
if (caretOffset < 0) {
|
||||
g_object_unref(text);
|
||||
return NULL;
|
||||
}
|
||||
AtspiTextRange *range = cthulhu_text_get_string_at_offset(
|
||||
text, caretOffset, unit, &error);
|
||||
g_object_unref(text);
|
||||
if (error != NULL) {
|
||||
g_error_free(error);
|
||||
if (range != NULL) g_boxed_free(atspi_text_range_get_type(), range);
|
||||
return NULL;
|
||||
}
|
||||
if (range == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
gchar *content = g_strdup(range->content != NULL ? range->content : "");
|
||||
g_free(range->content);
|
||||
g_free(range);
|
||||
return content;
|
||||
}
|
||||
|
||||
static void cthulhu_capture_document_metrics(AtspiAccessible *source, cthulhu_snapshot *snapshot) {
|
||||
const gchar *role = snapshot->role;
|
||||
/* Gecko can expose heading/link text directly without static children. */
|
||||
if (g_strcmp0(role, "paragraph") != 0 &&
|
||||
g_strcmp0(role, "heading") != 0 &&
|
||||
g_strcmp0(role, "link") != 0 &&
|
||||
g_strcmp0(role, "list item") != 0 &&
|
||||
g_strcmp0(role, "math") != 0 &&
|
||||
g_strcmp0(role, "static") != 0 &&
|
||||
g_strcmp0(role, "column header") != 0 &&
|
||||
g_strcmp0(role, "row header") != 0 &&
|
||||
g_strcmp0(role, "table cell") != 0 &&
|
||||
g_strcmp0(role, "table row") != 0 &&
|
||||
g_strcmp0(role, "text") != 0 &&
|
||||
g_strcmp0(role, "section") != 0 &&
|
||||
g_strcmp0(role, "article") != 0 &&
|
||||
g_strcmp0(role, "description term") != 0 &&
|
||||
g_strcmp0(role, "description value") != 0 &&
|
||||
g_strcmp0(role, "document email") != 0 &&
|
||||
g_strcmp0(role, "document frame") != 0 &&
|
||||
g_strcmp0(role, "document presentation") != 0 &&
|
||||
g_strcmp0(role, "document spreadsheet") != 0 &&
|
||||
g_strcmp0(role, "document text") != 0 &&
|
||||
g_strcmp0(role, "document web") != 0 &&
|
||||
g_strcmp0(role, "block quote") != 0 &&
|
||||
g_strcmp0(role, "description list") != 0 &&
|
||||
g_strcmp0(role, "form") != 0 &&
|
||||
g_strcmp0(role, "footer") != 0 &&
|
||||
g_strcmp0(role, "header") != 0 &&
|
||||
g_strcmp0(role, "landmark") != 0 &&
|
||||
g_strcmp0(role, "log") != 0 &&
|
||||
g_strcmp0(role, "list") != 0 &&
|
||||
g_strcmp0(role, "marquee") != 0 &&
|
||||
g_strcmp0(role, "panel") != 0 &&
|
||||
g_strcmp0(role, "table") != 0 &&
|
||||
g_strcmp0(role, "tree") != 0 &&
|
||||
g_strcmp0(role, "tree table") != 0) {
|
||||
return;
|
||||
}
|
||||
AtspiText *text = atspi_accessible_get_text(source);
|
||||
if (text == NULL) {
|
||||
return;
|
||||
}
|
||||
GError *error = NULL;
|
||||
gint characterCount = atspi_text_get_character_count(text, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
g_object_unref(text);
|
||||
return;
|
||||
}
|
||||
snapshot->characterCount = characterCount;
|
||||
if (characterCount > 75) {
|
||||
error = NULL;
|
||||
gchar *contents = atspi_text_get_text(text, 0, characterCount, &error);
|
||||
if (error != NULL) {
|
||||
g_clear_error(&error);
|
||||
} else if (contents != NULL) {
|
||||
for (const gchar *cursor = contents; *cursor != '\0'; cursor = g_utf8_next_char(cursor)) {
|
||||
if (g_utf8_get_char(cursor) == 0xFFFC) {
|
||||
snapshot->embeddedObjectCount++;
|
||||
}
|
||||
}
|
||||
g_free(contents);
|
||||
}
|
||||
}
|
||||
g_object_unref(text);
|
||||
}
|
||||
Reference in new issue
Block a user