--- /dev/null
+#include "atspi/atspi.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdbool.h>
+
+#define ERROR_STATE -1
+#define SAFE_BUFFER_SIZE 255
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define COLUMN_NO 3
+
+static unsigned indent_width = 2;
+
+static const char* atspi_state_names[] = {
+ [ATSPI_STATE_INVALID] = "INVALID",
+ [ATSPI_STATE_ACTIVE] = "ACTIVE",
+ [ATSPI_STATE_ARMED] = "ARMED",
+ [ATSPI_STATE_BUSY] = "BUSY",
+ [ATSPI_STATE_CHECKED] = "CHECKED",
+ [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
+ [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
+ [ATSPI_STATE_EDITABLE] = "EDITABLE",
+ [ATSPI_STATE_ENABLED] = "ENABLED",
+ [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
+ [ATSPI_STATE_EXPANDED] = "EXPANDED",
+ [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
+ [ATSPI_STATE_FOCUSED] = "FOCUSED",
+ [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
+ [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
+ [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
+ [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
+ [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
+ [ATSPI_STATE_OPAQUE] = "OPAQUE",
+ [ATSPI_STATE_PRESSED] = "PRESSED",
+ [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
+ [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
+ [ATSPI_STATE_SELECTED] = "SELECTED",
+ [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
+ [ATSPI_STATE_SHOWING] = "SHOWING",
+ [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
+ [ATSPI_STATE_STALE] = "STALE",
+ [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
+ [ATSPI_STATE_VERTICAL] = "VERTICAL",
+ [ATSPI_STATE_VISIBLE] = "VISIBLE",
+ [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
+ [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
+ [ATSPI_STATE_REQUIRED] = "REQUIRED",
+ [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
+ [ATSPI_STATE_ANIMATED] = "ANIMATED",
+ [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
+ [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
+ [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
+ [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
+ [ATSPI_STATE_VISITED] = "VISITED",
+ [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
+ [ATSPI_STATE_MODAL] = "MODAL",
+ [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
+ [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
+ [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
+ [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
+ [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
+};
+
+typedef struct _StringAccumulator {
+ char *string;
+ int length;
+} StringAccumulator;
+
+char *strdup(const char *c) {
+ char *result = (char *)calloc(1, strlen(c) + 1);
+ return result ? strcpy(result, c) : result;
+}
+
+static char *multiply_string(char c, unsigned number) {
+ char *result = (char *)calloc(1, number + 1);
+
+ if (result == NULL)
+ return result;
+
+ memset(result, c, number);
+
+ return result;
+}
+
+static void _print_atspi_states_legend() {
+ int array_len = ARRAY_SIZE(atspi_state_names);
+ int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
+ for (int line_idx = 0; line_idx < line_count; line_idx++) {
+ if ((line_idx < line_count - 1) || (array_len % 3 == 0)) {
+ printf("[%d]\t%-24s[%d]\t%-24s[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3], (line_idx * 3) + 1, atspi_state_names[(line_idx * 3) + 1], (line_idx * 3) + 2, atspi_state_names[(line_idx * 3) + 2]);
+ } else if (array_len % 3 == 2) {
+ printf("[%d]\t%-24s[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3], (line_idx * 3) + 1, atspi_state_names[(line_idx * 3) + 1]);
+ } else {
+ printf("[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3]);
+ }
+ }
+}
+
+static void set_indent(int indent_size)
+{
+ if (indent_size < 0) {
+ fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
+ return;
+ }
+
+ indent_width = indent_size;
+}
+
+static int _int_sort_function(gconstpointer a, gconstpointer b)
+{
+ int int_a = GPOINTER_TO_INT (a);
+ int int_b = GPOINTER_TO_INT (b);
+
+ return int_a - int_b;
+}
+
+static int _accumulate_string(const char *string, StringAccumulator *accumulator) {
+ if (!accumulator)
+ return FALSE;
+
+ if (string) {
+ int length = accumulator->length + strlen(string);
+ char *rebuffer = realloc(accumulator->string, length + 1);
+ if (rebuffer) {
+ strcpy(rebuffer + accumulator->length, string);
+ accumulator->string = rebuffer;
+ accumulator->length = length;
+ } else {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static char *get_info(AtspiAccessible *node) {
+ if (!node)
+ return NULL;
+
+ int node_width = -1;
+ int node_height = -1;
+ char *node_name = atspi_accessible_get_name(node, NULL);
+ char *node_role_name = atspi_accessible_get_role_name(node, NULL);
+ AtspiComponent *component = atspi_accessible_get_component_iface(node);
+
+ AtspiRect *extent = NULL;
+ if (component != NULL){
+ extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
+
+ if (extent != NULL) {
+ node_width = extent->width;
+ node_height = extent->height;
+ }
+ }
+
+ AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
+ GArray *states = atspi_state_set_get_states(node_state_set);
+ g_array_sort(states, _int_sort_function);
+
+ AtspiStateType state;
+ StringAccumulator state_accumulator = {};
+ for (int i = 0; states && (i < states->len); i++) {
+ state = g_array_index(states, AtspiStateType, i);
+ char node_state_str[8];
+ sprintf(node_state_str, "[%d]", state);
+ _accumulate_string(node_state_str, &state_accumulator);
+ }
+ g_array_free(states, 0);
+
+ StringAccumulator attribute_accumulator = {};
+ GHashTable *attributes = NULL;
+ attributes = atspi_accessible_get_attributes(node, NULL);
+ if (attributes) {
+ GHashTableIter attributes_iter;
+ gpointer attr_key, attr_value;
+ g_hash_table_iter_init (&attributes_iter, attributes);
+ while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
+ _accumulate_string("[", &attribute_accumulator);
+ _accumulate_string((char*)attr_key, &attribute_accumulator);
+ _accumulate_string("=", &attribute_accumulator);
+ _accumulate_string((char*)attr_value, &attribute_accumulator);
+ _accumulate_string("]", &attribute_accumulator);
+ }
+ g_hash_table_unref(attributes);
+ }
+
+ char result[SAFE_BUFFER_SIZE + 1];
+ snprintf(result, SAFE_BUFFER_SIZE, "[[%s],[%s],[%d,%d],[%s],[%s]]\n", node_role_name, attribute_accumulator.string, node_width, node_height, node_name, state_accumulator.string);
+
+ free(node_name);
+ free(node_role_name);
+
+ if (state_accumulator.string)
+ g_free(state_accumulator.string);
+
+ if (attribute_accumulator.string)
+ g_free(attribute_accumulator.string);
+
+ if (component)
+ g_object_unref(component);
+
+ if (extent)
+ g_free(extent);
+
+ if (node_state_set)
+ g_object_unref(node_state_set);
+
+ return strdup(result);
+}
+
+static void test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
+{
+ int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ if (parent_index != parent_candidate_index || parent_candidate != parent) {
+ printf("[FAIL<%d,", parent_candidate_index);
+
+ if (parent == NULL)
+ printf("_");
+ else
+ printf("%d", parent_index);
+
+ printf(">]\t");
+
+ } else {
+ printf("[OK]\t");
+ }
+}
+
+static int expand_r(int indent_number, AtspiAccessible *object, bool check_integrity)
+{
+ char *indent = multiply_string(' ', indent_number*indent_width);
+ if (indent != NULL) {
+ printf("%s", indent);
+ free(indent);
+ }
+
+ char *node_info = get_info(object);
+ if (node_info != NULL) {
+ printf("%s", node_info);
+ free(node_info);
+ }
+
+ int count = atspi_accessible_get_child_count(object, NULL);
+ for (int i = 0; i < count; i++) {
+ AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
+ if (child) {
+ if (check_integrity)
+ test_atspi_parent_child_relation(child, object, i);
+
+ expand_r(indent_number + 1, child, check_integrity);
+ }
+ }
+ return 0;
+}
+
+void print_help()
+{
+ printf("AT-SPI2-CORE-UTIL\n\n");
+ printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
+ printf("OPTION LIST:\n");
+ printf("-h, --help\t\tshow this message\n");
+ printf("-g, --show-legend\tprint AT-SPI state legend\n");
+ printf("-l, --list-apps\t\tlist all applications of desktop\n");
+ printf("-d, --tree-dump\t\tdump tree for selected application\n");
+ printf("-c, --tree-check\tcheck tree for selected application\n");
+ printf("-i, --indent\t\tset indentation size\n");
+ printf("\nEXAMPLE:\n");
+ printf("\tat_spi2_tool -i3 -d quickpanel\n");
+ printf("\t show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
+ printf("\tat_spi2_tool -i1 -c starter\n");
+ printf("\t show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
+}
+
+static void atspi_tree_traverse(AtspiAccessible *desktop, const char *app_name, bool dump, bool check)
+{
+ int count = atspi_accessible_get_child_count(desktop, NULL);
+ for (int i = 0; i < count; i++) {
+ AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
+ if (child) {
+ char *name = atspi_accessible_get_name(child, NULL);
+
+ if (!dump && !check)
+ printf("%s\n", name);
+
+ if (dump && name && !strcmp(name, app_name)) {
+ expand_r(0, child, false);
+ break;
+ }
+
+ if (check && name && !strcmp(name, app_name)) {
+ test_atspi_parent_child_relation(child, desktop, i);
+ expand_r(0, child, true);
+ break;
+ }
+ }
+ }
+}
+
+static void run_command(int argc, char *argv[], AtspiAccessible *desktop)
+{
+ struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"show-legend", no_argument, 0, 'g'},
+ {"list-apps", no_argument, 0, 'l'},
+ {"tree-dump", required_argument, 0, 'd'},
+ {"tree-check", required_argument, 0, 'c'},
+ {"indent", required_argument, 0, 'i'},
+ };
+
+ int command = 0;
+ int option_index = 0;
+
+ while (TRUE) {
+ command = getopt_long(argc, argv, "hgld:c:i:", long_options, &option_index);
+
+ if (command == ERROR_STATE)
+ break;
+
+ switch (command) {
+ case 'h':
+ print_help();
+ break;
+
+ case 'g':
+ _print_atspi_states_legend();
+ break;
+
+ case 'l':
+ atspi_tree_traverse(desktop, NULL, false, false);
+ break;
+
+ case 'd':
+ atspi_tree_traverse(desktop, optarg, true, false);
+ break;
+
+ case 'c':
+ atspi_tree_traverse(desktop, optarg, false, true);
+ break;
+
+ case 'i':
+ set_indent(atoi(optarg));
+ break;
+
+ case '?':
+ break;
+
+ default:
+ abort();
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2) {
+ printf("Arguments required. Type %s --help.\n", argv[0]);
+ return -1;
+ }
+
+ AtspiAccessible *desktop = atspi_get_desktop(0);
+
+ if (!desktop) {
+ fprintf(stderr, "atspi_get_desktop failed\n");
+ return 1;
+ }
+
+ atspi_init();
+
+ run_command(argc, argv, desktop);
+
+ return 0;
+}