1 #include "atspi/atspi.h"
10 #define SAFE_BUFFER_SIZE 2048
11 #define CHECK_OUTPUT_WIDTH 42
12 #define MINIMAL_MODULE_WIDTH 3
14 #define NUMBER_WIDTH 6
15 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
21 #define RELATION_TABLE_COLUMN_COUNT 7
22 #define RELATION_COLUMN_WIDTH 20
25 static unsigned indent_width = 2;
26 static int module_name_limit = -1;
28 static const char* atspi_state_names[] = {
29 [ATSPI_STATE_INVALID] = "INVALID",
30 [ATSPI_STATE_ACTIVE] = "ACTIVE",
31 [ATSPI_STATE_ARMED] = "ARMED",
32 [ATSPI_STATE_BUSY] = "BUSY",
33 [ATSPI_STATE_CHECKED] = "CHECKED",
34 [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
35 [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
36 [ATSPI_STATE_EDITABLE] = "EDITABLE",
37 [ATSPI_STATE_ENABLED] = "ENABLED",
38 [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
39 [ATSPI_STATE_EXPANDED] = "EXPANDED",
40 [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
41 [ATSPI_STATE_FOCUSED] = "FOCUSED",
42 [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
43 [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
44 [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
45 [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
46 [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
47 [ATSPI_STATE_OPAQUE] = "OPAQUE",
48 [ATSPI_STATE_PRESSED] = "PRESSED",
49 [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
50 [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
51 [ATSPI_STATE_SELECTED] = "SELECTED",
52 [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
53 [ATSPI_STATE_SHOWING] = "SHOWING",
54 [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
55 [ATSPI_STATE_STALE] = "STALE",
56 [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
57 [ATSPI_STATE_VERTICAL] = "VERTICAL",
58 [ATSPI_STATE_VISIBLE] = "VISIBLE",
59 [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
60 [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
61 [ATSPI_STATE_REQUIRED] = "REQUIRED",
62 [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
63 [ATSPI_STATE_ANIMATED] = "ANIMATED",
64 [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
65 [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
66 [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
67 [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
68 [ATSPI_STATE_VISITED] = "VISITED",
69 [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
70 [ATSPI_STATE_MODAL] = "MODAL",
71 [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
72 [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
73 [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
74 [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
75 [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
78 typedef struct _Box_Size {
85 static char *_multiply_string(char c, unsigned number)
87 char *result = (char *)calloc(1, number + 1);
92 memset(result, c, number);
97 static void _print_module_legend()
99 printf("\n[[node],[node role name],[attributes list],[x,y,width,height],[node name],[states list][relations]\n\n");
102 static void _print_atspi_states_legend()
104 _print_module_legend();
106 int array_len = ARRAY_SIZE(atspi_state_names);
107 int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
108 for (int line_idx = 0; line_idx < line_count; line_idx++) {
109 if ((line_idx < line_count - 1) || (array_len % COLUMN_NO == 0)) {
110 printf("[%d]\t%-24s[%d]\t%-24s[%d]\t%s\n",
111 line_idx * COLUMN_NO,
112 atspi_state_names[line_idx * COLUMN_NO],
113 (line_idx * COLUMN_NO) + 1,
114 atspi_state_names[(line_idx * COLUMN_NO) + 1],
115 (line_idx * COLUMN_NO) + 2,
116 atspi_state_names[(line_idx * COLUMN_NO) + 2]);
117 } else if (array_len % COLUMN_NO == 2) {
118 printf("[%d]\t%-24s[%d]\t%s\n",
119 line_idx * COLUMN_NO,
120 atspi_state_names[line_idx * COLUMN_NO],
121 (line_idx * COLUMN_NO) + 1,
122 atspi_state_names[(line_idx * COLUMN_NO) + 1]);
124 printf("[%d]\t%s\n", line_idx * COLUMN_NO, atspi_state_names[line_idx * COLUMN_NO]);
129 static void _set_indent(int indent_size)
131 if (indent_size < 0) {
132 fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
136 indent_width = indent_size;
139 static void _set_module_length_limit(int limit)
141 if (limit < MINIMAL_MODULE_WIDTH) {
142 fprintf(stderr, "WARNING: Invalid maximum field size. Default value retained.\n");
146 module_name_limit = limit;
149 static int _int_sort_function(gconstpointer a, gconstpointer b)
151 int int_a = GPOINTER_TO_INT (a);
152 int int_b = GPOINTER_TO_INT (b);
154 return int_a - int_b;
157 static void _truncate_string(char *string, int length_limit)
159 if (length_limit < MINIMAL_MODULE_WIDTH || !string)
162 int len = strlen(string);
164 if (len > length_limit)
165 memcpy(string + length_limit - 3, "...", 4);
168 static size_t _combine_strings(char **destination, const char *source)
170 if (!source || !*source || !destination)
173 size_t old_len = *destination ? strlen(*destination) : 0;
174 size_t source_len = strlen(source);
175 size_t len = old_len + source_len;
176 char *buf = realloc(*destination, (len + 1) * sizeof(char));
179 fprintf(stderr, "_combine_strings: allocation failed");
183 memcpy(buf + old_len, source, source_len + 1);
190 static Box_Size *_get_box_size(AtspiAccessible *node)
192 Box_Size *box_size = (Box_Size *)malloc(sizeof(Box_Size));
193 if (box_size == NULL)
201 AtspiComponent *component = atspi_accessible_get_component_iface(node);
202 AtspiRect *extent = component ? atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL) : NULL;
204 if (extent == NULL) {
205 _combine_strings(&x, "_");
206 _combine_strings(&y, "_");
207 _combine_strings(&width, "_");
208 _combine_strings(&height, "_");
210 char temp_buff[NUMBER_WIDTH];
212 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->x);
213 _combine_strings(&x, temp_buff);
215 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->y);
216 _combine_strings(&y, temp_buff);
218 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->width);
219 _combine_strings(&width, temp_buff);
221 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->height);
222 _combine_strings(&height, temp_buff);
224 g_object_unref(component);
230 box_size->width = width;
231 box_size->height = height;
236 static char *_get_states(AtspiAccessible *node, int length_limit)
238 AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
239 GArray *states = atspi_state_set_get_states(node_state_set);
240 if (!states) return NULL;
241 g_array_sort(states, _int_sort_function);
243 AtspiStateType state_type;
244 char *state_string = NULL;
246 for (int i = 0; i < states->len; i++) {
247 state_type = g_array_index(states, AtspiStateType, i);
249 char node_state_str[8];
250 snprintf(node_state_str, 8, "(%d)", state_type);
251 _combine_strings(&state_string, node_state_str);
254 g_array_free(states, 0);
255 g_object_unref(node_state_set);
257 _truncate_string(state_string, length_limit);
262 static char *_get_attributes(AtspiAccessible *node, int length_limit)
264 GHashTable *attributes = atspi_accessible_get_attributes(node, NULL);
269 GHashTableIter attributes_iter;
272 g_hash_table_iter_init (&attributes_iter, attributes);
274 while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
275 char attributes_string[ATTR_WIDTH];
276 int ret = snprintf(attributes_string, ATTR_WIDTH, "(%s=%s)", (char *)attr_key, (char *)attr_value);
277 if (ret >= ATTR_WIDTH)
278 fprintf(stderr, "\n_get_attributes: generated string is too long. Buffer overflow\n");
280 _combine_strings(&result, attributes_string);
282 g_hash_table_unref(attributes);
284 _truncate_string(result, length_limit);
288 static char *_get_info(AtspiAccessible *node, int length_limit)
293 char *node_name = atspi_accessible_get_name(node, NULL);
294 char *node_role_name = atspi_accessible_get_role_name(node, NULL);
295 char *path = atspi_accessible_get_path(node, NULL);
297 char *attributes = _get_attributes(node, length_limit);
298 Box_Size *box_size = _get_box_size(node);
299 char *states = _get_states(node, length_limit);
301 GArray *relations = atspi_accessible_get_relation_set(node, NULL);
303 char result[SAFE_BUFFER_SIZE];
304 int ret = snprintf(result, SAFE_BUFFER_SIZE, "[[%s],[%s],[%s],[%s,%s,%s,%s],[%s],[%s],[%s]]",
314 (relations && relations->len) ? "*" : "");
316 if (ret >= SAFE_BUFFER_SIZE)
317 fprintf(stderr, "\n%s, %s %s: generated string is too long. Buffer overflow\n", __FILE__, __FUNCTION__, __LINE__);
320 free(node_role_name);
324 free(box_size->width);
325 free(box_size->height);
329 g_array_free(relations, TRUE);
331 return g_strdup(result);
334 static void _test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
336 int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
337 AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
339 char output[CHECK_OUTPUT_WIDTH];
340 if (parent_index != parent_candidate_index || parent_candidate != parent) {
341 char parent_status[NUMBER_WIDTH];
344 strncpy(parent_status, "_", NUMBER_WIDTH);
346 snprintf(parent_status, NUMBER_WIDTH, "%d", parent_index);
348 char *parent_path = atspi_accessible_get_path(parent, NULL);
349 char *parent_candidate_path = atspi_accessible_get_path(parent_candidate, NULL);
350 snprintf(output, CHECK_OUTPUT_WIDTH, "[FAIL<%d,%s><%s,%s>]", parent_candidate_index, parent_status,
351 parent_candidate_path, parent_path);
353 free(parent_candidate_path);
356 snprintf(output, CHECK_OUTPUT_WIDTH, "[OK]");
359 printf("%-*s\t", CHECK_OUTPUT_WIDTH, output);
362 static int _print_atspi_tree_verify_maybe_r(int indent_number, AtspiAccessible *object, bool check_integrity, int length_limit)
364 char *indent = _multiply_string(' ', indent_number*indent_width);
365 if (indent != NULL) {
366 printf("%s", indent);
370 char *node_info = _get_info(object, length_limit);
371 if (node_info != NULL) {
372 printf("%s\n", node_info);
376 int count = atspi_accessible_get_child_count(object, NULL);
377 for (int i = 0; i < count; i++) {
378 AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
381 _test_atspi_parent_child_relation(child, object, i);
383 _print_atspi_tree_verify_maybe_r(indent_number + 1, child, check_integrity, length_limit);
391 printf("AT-SPI2-CORE-UTIL\n\n");
392 printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
393 printf("OPTION LIST:\n");
394 printf("-h, --help\t\tshow this message\n");
395 printf("-v, --version\t\tshow actual version of tool\n");
396 printf("-g, --show-legend\tprint AT-SPI state legend\n");
397 printf("-l, --list-apps\t\tlist all applications of desktop\n");
398 printf("-d, --tree-dump\t\tdump tree for selected application\n");
399 printf("-c, --tree-check\tcheck tree for selected application\n");
400 printf("-f, --first-match\tperform dump or check only for first matching application\n");
401 printf("-i, --indent\t\tset indentation size, default value stands at 2\n");
402 printf("-t, --truncate\t\tset maximum single field size, default value stands at 30\n");
403 printf("\nEXAMPLE:\n");
404 printf("\tat_spi2_tool -i3 -d quickpanel\n");
405 printf("\t show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
406 printf("\tat_spi2_tool -i1 -c starter\n");
407 printf("\t show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
410 void _print_version()
412 printf("AT-SPI2-CORE-UTIL v%s\n", VERSION);
415 static void _print_horizontal_line_in_relation_table() {
416 for (int i = 0; i < RELATION_TABLE_COLUMN_COUNT; i++) {
417 for (int j = 0; j < RELATION_COLUMN_WIDTH; j++)
424 static void _print_legend_for_relation_table() {
425 char *table[] = {"OBJECT", "CONTROLLER_FOR", "CONTROLLED_BY", "FLOWS_TO", "FLOWS_FROM", "DESCRIBED_BY", "OTHER RELATION [ID]"};
426 assert(sizeof(table) / sizeof(table[0]) == RELATION_TABLE_COLUMN_COUNT);
427 for(int i = 0; i < RELATION_TABLE_COLUMN_COUNT; i++)
428 printf("%*s|", RELATION_COLUMN_WIDTH , table[i]);
430 _print_horizontal_line_in_relation_table();
433 static char *_get_path_to_object_in_relation(AtspiRelation *relation) {
436 gint last_index = atspi_relation_get_n_targets(relation) - 1;
437 AtspiAccessible *target = atspi_relation_get_target(relation, last_index);
438 return atspi_accessible_get_path(target, NULL);
441 static void _print_relations_for_object(AtspiAccessible *node) {
442 GArray *relations = atspi_accessible_get_relation_set(node, NULL);
446 if (relations->len) {
447 int size = RELATION_TABLE_COLUMN_COUNT * sizeof(char *);
448 char **table = malloc(size);
449 memset(table, 0, size);
450 table[0] = atspi_accessible_get_path(node, NULL);
452 for (int i = 0; i < relations->len; i++) {
453 AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
454 AtspiRelationType type = atspi_relation_get_relation_type(relation);
456 case ATSPI_RELATION_CONTROLLER_FOR:
457 table[1] = _get_path_to_object_in_relation(relation);
459 case ATSPI_RELATION_CONTROLLED_BY:
460 table[2] = _get_path_to_object_in_relation(relation);
462 case ATSPI_RELATION_FLOWS_TO:
463 table[3] = _get_path_to_object_in_relation(relation);
465 case ATSPI_RELATION_FLOWS_FROM:
466 table[4] = _get_path_to_object_in_relation(relation);
468 case ATSPI_RELATION_DESCRIBED_BY:
469 table[5] = _get_path_to_object_in_relation(relation);
472 snprintf(buf, 8, " [%d]", type);
473 _combine_strings(&table[RELATION_TABLE_COLUMN_COUNT - 1], buf);
477 for (int i = 0; i < RELATION_TABLE_COLUMN_COUNT; i++) {
478 printf("%*s|", RELATION_COLUMN_WIDTH, table[i] ? table[i] : "");
484 _print_horizontal_line_in_relation_table();
486 g_array_free(relations, TRUE);
488 int count = atspi_accessible_get_child_count(node, NULL);
489 for (int i = 0; i < count; i++) {
490 AtspiAccessible *child = atspi_accessible_get_child_at_index(node, i, NULL);
492 _print_relations_for_object(child);
496 static void _atspi_tree_traverse(AtspiAccessible *desktop, const char *app_name, bool dump, bool check, bool first_match, int length_limit)
498 int count = atspi_accessible_get_child_count(desktop, NULL);
499 bool app_name_matched = false;
500 for (int i = 0; i < count; i++) {
501 AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
503 fprintf(stderr, "\n%s, %s %s: Null child occured. Results may be misleading.\n", __FILE__, __FUNCTION__, __LINE__);
507 char *name = atspi_accessible_get_name(child, NULL);
510 printf("%s\n", name);
512 if ((check || dump) && name && !strcmp(name, app_name)) {
513 app_name_matched = true;
515 _print_module_legend();
518 _test_atspi_parent_child_relation(child, desktop, i);
520 _print_atspi_tree_verify_maybe_r(0, child, check, length_limit);
527 _print_legend_for_relation_table();
528 _print_relations_for_object(child);
535 if (!app_name_matched && (dump || check))
536 fprintf(stderr, "There is no application with name: %s. Try again.\n", app_name);
539 static void _run_command(int argc, char *argv[], AtspiAccessible *desktop)
541 struct option long_options[] = {
542 {"help", no_argument, 0, 'h'},
543 {"version", no_argument, 0, 'v'},
544 {"show-legend", no_argument, 0, 'g'},
545 {"list-apps", no_argument, 0, 'l'},
546 {"tree-dump", required_argument, 0, 'd'},
547 {"tree-check", required_argument, 0, 'c'},
548 {"first-match", no_argument, 0, 'f'},
549 {"indent", required_argument, 0, 'i'},
550 {"truncate", required_argument, 0, 't'},
554 int option_index = 0;
555 bool traverse_flags[FLAG_NO] = {false};
556 char *app_name = NULL;
559 command = getopt_long(argc, argv, "hvgld:c:ft:i:", long_options, &option_index);
561 if (command == ERROR_STATE)
574 _print_atspi_states_legend();
578 _atspi_tree_traverse(desktop, NULL, false, false, false, module_name_limit);
582 traverse_flags[DUMP] = true;
587 traverse_flags[CHECK] = true;
592 traverse_flags[FIRST_MATCH] = true;
596 _set_indent(atoi(optarg));
600 _set_module_length_limit(atoi(optarg));
604 fprintf(stderr, "Invalid parameter. Use: \"--help\"\n");
612 if (traverse_flags[DUMP] || traverse_flags[CHECK])
613 _atspi_tree_traverse(desktop, app_name, traverse_flags[DUMP], traverse_flags[CHECK], traverse_flags[FIRST_MATCH], module_name_limit);
616 int main(int argc, char *argv[])
619 printf("Arguments required. Type %s --help.\n", argv[0]);
623 int result = atspi_init();
626 fprintf(stderr, "Unable to initilize AT-SPI infrastructure, atspi_init failed\n");
630 AtspiAccessible *desktop = atspi_get_desktop(0);
632 fprintf(stderr, "atspi_get_desktop failed\n");
636 _run_command(argc, argv, desktop);