Added printing supported actions to at_spi2_tool
[platform/upstream/at-spi2-core.git] / test / at_spi2_tool.c
1 #include "atspi/atspi.h"
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <getopt.h>
6 #include <stdbool.h>
7 #include <gio/gio.h>
8 #include <assert.h>
9 #include <stdint.h>
10
11 #define ERROR_STATE -1
12 #define SAFE_BUFFER_SIZE 2048
13 #define CHECK_OUTPUT_WIDTH 42
14 #define MINIMAL_MODULE_WIDTH 3
15 #define ATTR_WIDTH 50
16 #define NUMBER_WIDTH 6
17 #define ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))
18 #define COLUMN_NO 3
19 #define FLAG_NO 3
20 #define DUMP 0
21 #define CHECK 1
22 #define FIRST_MATCH 2
23 #define RELATION_TABLE_COLUMN_COUNT 7
24 #define ATTRIBUTE_TABLE_COLUMN_COUNT 3
25 #define ACTIONS_TABLE_COLUMN_COUNT 2
26 #define RELATION_TABLE_COLUMN_WIDTH 20
27 #define ATTRIBUTE_TABLE_BASE_COLUMN_WIDTH 20
28 #define ACTIONS_TABLE_BASE_COLUMN_WIDTH 20
29 #define VERSION "1.1"
30
31 static unsigned indent_width = 2;
32 static int module_name_limit = -1;
33
34 static const char* atspi_state_names[] = {
35         [ATSPI_STATE_INVALID] = "INVALID",
36         [ATSPI_STATE_ACTIVE] = "ACTIVE",
37         [ATSPI_STATE_ARMED] = "ARMED",
38         [ATSPI_STATE_BUSY] =  "BUSY",
39         [ATSPI_STATE_CHECKED] = "CHECKED",
40         [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
41         [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
42         [ATSPI_STATE_EDITABLE] = "EDITABLE",
43         [ATSPI_STATE_ENABLED] = "ENABLED",
44         [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
45         [ATSPI_STATE_EXPANDED] = "EXPANDED",
46         [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
47         [ATSPI_STATE_FOCUSED] = "FOCUSED",
48         [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
49         [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
50         [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
51         [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
52         [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
53         [ATSPI_STATE_OPAQUE] = "OPAQUE",
54         [ATSPI_STATE_PRESSED] = "PRESSED",
55         [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
56         [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
57         [ATSPI_STATE_SELECTED] = "SELECTED",
58         [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
59         [ATSPI_STATE_SHOWING] = "SHOWING",
60         [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
61         [ATSPI_STATE_STALE] = "STALE",
62         [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
63         [ATSPI_STATE_VERTICAL] = "VERTICAL",
64         [ATSPI_STATE_VISIBLE] = "VISIBLE",
65         [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
66         [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
67         [ATSPI_STATE_REQUIRED] = "REQUIRED",
68         [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
69         [ATSPI_STATE_ANIMATED] = "ANIMATED",
70         [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
71         [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
72         [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
73         [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
74         [ATSPI_STATE_VISITED] = "VISITED",
75         [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
76         [ATSPI_STATE_MODAL] = "MODAL",
77         [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
78         [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
79         [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
80         [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
81         [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
82 };
83
84 typedef struct _Box_Size {
85         char *x;
86         char *y;
87         char *height;
88         char *width;
89 } Box_Size;
90
91 static char *_multiply_string(char c, unsigned number)
92 {
93         char *result = (char *)calloc(1, number + 1);
94
95         if (result == NULL)
96                 return result;
97
98         memset(result, c, number);
99
100         return result;
101 }
102
103 static void _print_module_legend()
104 {
105         printf("\n[[node],[node role name],[attributes list],[x,y,width,height],[node name],[states list][relations]\n\n");
106 }
107
108 static void _print_atspi_states_legend()
109 {
110         _print_module_legend();
111
112         int array_len = ARRAY_SIZE(atspi_state_names);
113         int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
114         for (int line_idx = 0; line_idx < line_count; line_idx++) {
115                 if ((line_idx < line_count - 1) || (array_len % COLUMN_NO == 0)) {
116                         printf("[%d]\t%-24s[%d]\t%-24s[%d]\t%s\n",
117                                         line_idx * COLUMN_NO,
118                                         atspi_state_names[line_idx * COLUMN_NO],
119                                         (line_idx * COLUMN_NO) + 1,
120                                         atspi_state_names[(line_idx * COLUMN_NO) + 1],
121                                         (line_idx * COLUMN_NO) + 2,
122                                         atspi_state_names[(line_idx * COLUMN_NO) + 2]);
123                 } else if (array_len % COLUMN_NO == 2) {
124                         printf("[%d]\t%-24s[%d]\t%s\n",
125                                         line_idx * COLUMN_NO,
126                                         atspi_state_names[line_idx * COLUMN_NO],
127                                         (line_idx * COLUMN_NO) + 1,
128                                         atspi_state_names[(line_idx * COLUMN_NO) + 1]);
129                 } else {
130                         printf("[%d]\t%s\n", line_idx * COLUMN_NO, atspi_state_names[line_idx * COLUMN_NO]);
131                 }
132         }
133 }
134
135 static void _set_indent(int indent_size)
136 {
137         if (indent_size < 0) {
138                 fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
139                 return;
140         }
141
142         indent_width = indent_size;
143 }
144
145 static void _set_module_length_limit(int limit)
146 {
147         if (limit < MINIMAL_MODULE_WIDTH) {
148                 fprintf(stderr, "WARNING: Invalid maximum field size. Default value retained.\n");
149                 return;
150         }
151
152         module_name_limit = limit;
153 }
154
155 static int _int_sort_function(gconstpointer a, gconstpointer b)
156 {
157         int int_a = GPOINTER_TO_INT (a);
158         int int_b = GPOINTER_TO_INT (b);
159
160         return int_a - int_b;
161 }
162
163 static void _truncate_string(char *string, int length_limit)
164 {
165         if (length_limit < MINIMAL_MODULE_WIDTH || !string)
166                 return;
167
168         int len = strlen(string);
169
170         if (len > length_limit)
171                 memcpy(string + length_limit - 3, "...", 4);
172 }
173
174 static size_t _combine_strings(char **destination, const char *source)
175 {
176         if (!source || !*source || !destination)
177                 return 0;
178
179         size_t old_len = *destination ? strlen(*destination) : 0;
180         size_t source_len = strlen(source);
181         size_t len = old_len + source_len;
182         char *buf = realloc(*destination, (len + 1) * sizeof(char));
183
184         if (!buf) {
185                 fprintf(stderr, "_combine_strings: allocation failed");
186                 return old_len;
187         }
188
189         memcpy(buf + old_len, source, source_len + 1);
190
191         *destination = buf;
192
193         return len;
194 }
195
196 static Box_Size *_get_box_size(AtspiAccessible *node)
197 {
198         Box_Size *box_size = (Box_Size *)malloc(sizeof(Box_Size));
199         if (box_size == NULL)
200                 return NULL;
201
202         char *x = NULL;
203         char *y = NULL;
204         char *width = NULL;
205         char *height = NULL;
206
207         AtspiComponent *component = atspi_accessible_get_component_iface(node);
208         AtspiRect *extent = component ? atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL) : NULL;
209
210         if (extent == NULL) {
211                 _combine_strings(&x, "_");
212                 _combine_strings(&y, "_");
213                 _combine_strings(&width, "_");
214                 _combine_strings(&height, "_");
215         } else {
216                 char temp_buff[NUMBER_WIDTH];
217
218                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->x);
219                 _combine_strings(&x, temp_buff);
220
221                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->y);
222                 _combine_strings(&y, temp_buff);
223
224                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->width);
225                 _combine_strings(&width, temp_buff);
226
227                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->height);
228                 _combine_strings(&height, temp_buff);
229
230                 g_object_unref(component);
231                 g_free(extent);
232         }
233
234         box_size->x = x;
235         box_size->y = y;
236         box_size->width = width;
237         box_size->height = height;
238
239         return box_size;
240 }
241
242 static char *_get_states(AtspiAccessible *node, int length_limit)
243 {
244         AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
245         GArray *states = atspi_state_set_get_states(node_state_set);
246         if (!states) {
247                 g_clear_object(&node_state_set);
248                 return NULL;
249         }
250         g_array_sort(states, _int_sort_function);
251
252         AtspiStateType state_type;
253         char *state_string = NULL;
254
255         for (int i = 0; i < states->len; i++) {
256                 state_type = g_array_index(states, AtspiStateType, i);
257
258                 char node_state_str[27] = "";
259                 strncat(node_state_str, "(", sizeof(node_state_str) - strlen(node_state_str) - 1);
260                 strncat(node_state_str, atspi_state_names[state_type], sizeof(node_state_str) - strlen(node_state_str) - 1);
261                 strncat(node_state_str, ")", sizeof(node_state_str) - strlen(node_state_str) - 1);
262
263                 _combine_strings(&state_string, node_state_str);
264         }
265
266         g_array_free(states, 0);
267         g_clear_object(&node_state_set);
268
269         _truncate_string(state_string, length_limit);
270
271         return state_string;
272 }
273
274 static char *_get_attributes(AtspiAccessible *node, int length_limit, bool *attributes_are_too_long)
275 {
276         GHashTable *attributes = atspi_accessible_get_attributes(node, NULL);
277
278         if (!attributes)
279                 return NULL;
280
281         GHashTableIter attributes_iter;
282         gpointer attr_key;
283         gpointer attr_value;
284         g_hash_table_iter_init (&attributes_iter, attributes);
285         char *result = NULL;
286         while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
287                 char attributes_string[SAFE_BUFFER_SIZE];
288                 int ret = snprintf(attributes_string, SAFE_BUFFER_SIZE, "(%s=%s)", (char *)attr_key, (char *)attr_value);
289                 if (ret >= SAFE_BUFFER_SIZE)
290                         fprintf(stderr, "\n%s, %s %d: generated string is too long. Buffer overflow\n", __FILE__, __FUNCTION__, __LINE__);
291
292                 _combine_strings(&result, attributes_string);
293         }
294         g_hash_table_unref(attributes);
295
296         int real_truncate_size = (length_limit < ATTR_WIDTH && length_limit > MINIMAL_MODULE_WIDTH) ? length_limit : ATTR_WIDTH;
297         if (result && attributes_are_too_long && strlen(result) > real_truncate_size)
298                 *attributes_are_too_long = true;
299
300         _truncate_string(result, real_truncate_size);
301         return result;
302 }
303
304 static char *_get_info(AtspiAccessible *node, int length_limit, bool *attributes_are_too_long, bool *app_has_relations, bool *app_has_actions)
305 {
306         if (!node)
307                 return NULL;
308
309         char *node_name = atspi_accessible_get_name(node, NULL);
310         char *node_role_name = atspi_accessible_get_role_name(node, NULL);
311         char *unique_id = atspi_accessible_get_unique_id(node, NULL);
312         char *path = atspi_accessible_get_path(node, NULL);
313         unsigned long long eo_ptr = 0;
314         sscanf(path, "%llu", &eo_ptr);
315
316         char *attributes = _get_attributes(node, length_limit, attributes_are_too_long);
317         Box_Size *box_size = _get_box_size(node);
318         char *states = _get_states(node, length_limit);
319
320         GArray *relations = atspi_accessible_get_relation_set(node, NULL);
321         bool current_node_has_relations = (relations && relations->len);
322
323         bool current_node_has_actions = false;
324
325         AtspiAction* action_if = atspi_accessible_get_action_iface (node);
326         if (action_if)
327         {
328                 gint no_actions = atspi_action_get_n_actions (action_if, NULL);
329                 if (no_actions > 0) current_node_has_actions = true;
330         }
331
332         char result[SAFE_BUFFER_SIZE];
333         int ret = snprintf(result, SAFE_BUFFER_SIZE, "[[%s(%p)],[%s],[%s],[%s,%s,%s,%s],[%s],[%s],[%s]]",
334                                                 unique_id, (uintptr_t)eo_ptr,
335                                                 node_role_name,
336                                                 attributes,
337                                                 box_size ? box_size->x : "nil",
338                                                 box_size ? box_size->y : "nil",
339                                                 box_size ? box_size->width : "nil",
340                                                 box_size ? box_size->height : "nil",
341                                                 node_name,
342                                                 states,
343                                                 current_node_has_relations ? "*" : "");
344
345         if (ret >= SAFE_BUFFER_SIZE)
346                 fprintf(stderr, "\n%s, %s %d: generated string is too long. Buffer overflow\n", __FILE__, __FUNCTION__, __LINE__);
347
348         if (current_node_has_relations)
349                 *app_has_relations = true;
350
351         if (current_node_has_actions)
352                 *app_has_actions = true;
353
354         free(node_name);
355         free(node_role_name);
356         free(unique_id);
357         free(path);
358         free(attributes);
359         if (box_size) {
360                 free(box_size->width);
361                 free(box_size->height);
362                 free(box_size);
363         }
364         free(states);
365         if (relations)
366                 g_array_free(relations, TRUE);
367
368         return g_strdup(result);
369 }
370
371 static void _test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
372 {
373         int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
374         AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
375
376         char output[CHECK_OUTPUT_WIDTH];
377         if (parent_index != parent_candidate_index || parent_candidate != parent) {
378                 char parent_status[NUMBER_WIDTH];
379
380                 if (parent == NULL)
381                         strncpy(parent_status, "_", NUMBER_WIDTH);
382                 else
383                         snprintf(parent_status, NUMBER_WIDTH, "%d", parent_index);
384
385                 char *parent_unique_id = atspi_accessible_get_unique_id(parent, NULL);
386                 char *parent_candidate_unique_id = atspi_accessible_get_unique_id(parent_candidate, NULL);
387                 snprintf(output, CHECK_OUTPUT_WIDTH, "[FAIL<%d,%s><%s,%s>]", parent_candidate_index, parent_status,
388                                 parent_candidate_unique_id, parent_unique_id);
389                 free(parent_unique_id);
390                 free(parent_candidate_unique_id);
391
392         } else {
393                 snprintf(output, CHECK_OUTPUT_WIDTH, "[OK]");
394         }
395
396         printf("%-*s\t", CHECK_OUTPUT_WIDTH, output);
397 }
398
399 static int _print_atspi_tree_verify_maybe_r(int indent_number, AtspiAccessible *object, bool check_integrity, int length_limit,
400                                                 bool *attributes_are_too_long, bool *app_has_relations, bool *app_has_actions)
401 {
402         char *indent = _multiply_string(' ', indent_number*indent_width);
403         if (indent != NULL) {
404                 printf("%s", indent);
405                 free(indent);
406         }
407
408         char *node_info = _get_info(object, length_limit, attributes_are_too_long, app_has_relations, app_has_actions);
409         if (node_info != NULL) {
410                 printf("%s\n", node_info);
411                 free(node_info);
412         }
413
414         int count = atspi_accessible_get_child_count(object, NULL);
415         for (int i = 0; i < count; i++) {
416                 AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
417                 if (child) {
418                         if (check_integrity)
419                                 _test_atspi_parent_child_relation(child, object, i);
420
421                         _print_atspi_tree_verify_maybe_r(indent_number + 1, child, check_integrity, length_limit, attributes_are_too_long, app_has_relations, app_has_actions);
422                 }
423         }
424         return 0;
425 }
426
427 void _print_help()
428 {
429         printf("AT-SPI2-CORE-UTIL\n\n");
430         printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
431         printf("OPTION LIST:\n");
432         printf("-h, --help\t\tshow this message\n");
433         printf("-v, --version\t\tshow actual version of tool\n");
434         printf("-g, --show-legend\tprint AT-SPI state legend\n");
435         printf("-l, --list-apps\t\tlist all applications of desktop\n");
436         printf("-a, --at-spi-client <true|false>\tenable/disable org.a11y.Status.IsEnabled property\n");
437         printf("-s, --sleep <N>\tsleep N seconds\n");
438         printf("-d, --tree-dump\t\tdump tree for selected application\n");
439         printf("-c, --tree-check\tcheck tree for selected application\n");
440         printf("-f, --first-match\tperform dump or check only for first matching application\n");
441         printf("-i, --indent\t\tset indentation size, default value stands at 2\n");
442         printf("-t, --truncate\t\tset maximum single field size, default value stands at 30\n");
443         printf("\nEXAMPLE:\n");
444         printf("\tat_spi2_tool -i3 -d quickpanel\n");
445         printf("\t  show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
446         printf("\tat_spi2_tool -i1 -c starter\n");
447         printf("\t  show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
448 }
449
450 void _print_version()
451 {
452         printf("AT-SPI2-CORE-UTIL v%s\n", VERSION);
453 }
454
455 static void _print_horizontal_line_in_relations_table() {
456         for (int i = 0; i < RELATION_TABLE_COLUMN_COUNT; i++) {
457                 for (int j = 0; j < RELATION_TABLE_COLUMN_WIDTH; j++)
458                         printf("-");
459                 printf("+");
460         }
461         printf("\n");
462 }
463
464 static char *_get_unique_id_of_object_in_relation(AtspiRelation *relation) {
465         if (!relation)
466                 return NULL;
467
468         gint last_index = atspi_relation_get_n_targets(relation) - 1;
469         AtspiAccessible *target = atspi_relation_get_target(relation, last_index);
470         return atspi_accessible_get_unique_id(target, NULL);
471 }
472
473 static void _print_relations_for_object(AtspiAccessible *node) {
474         GArray *relations = atspi_accessible_get_relation_set(node, NULL);
475         if (!relations)
476                 return;
477
478         if (!relations->len) {
479                 g_array_free(relations, FALSE);
480                 return;
481         }
482
483         char **table = calloc(RELATION_TABLE_COLUMN_COUNT, sizeof(char *));
484         if (!table) {
485                 fprintf(stderr, "Calloc failed. Can't alloc memory for object relations string\n");
486                 return;
487         }
488
489         table[0] = atspi_accessible_get_unique_id(node, NULL);
490         for (int i = 0; i < relations->len; i++) {
491                 AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
492                 AtspiRelationType type = atspi_relation_get_relation_type(relation);
493                 int idx;
494                 switch (type) {
495                         case ATSPI_RELATION_CONTROLLER_FOR:     idx = 1; break;
496                         case ATSPI_RELATION_CONTROLLED_BY:      idx = 2; break;
497                         case ATSPI_RELATION_FLOWS_TO:           idx = 3; break;
498                         case ATSPI_RELATION_FLOWS_FROM:         idx = 4; break;
499                         case ATSPI_RELATION_DESCRIBED_BY:       idx = 5; break;
500                         default: idx = 0;
501                 }
502
503                 if (idx > 0)
504                         table[idx] = _get_unique_id_of_object_in_relation(relation);
505                 else {
506                         char buf[16];
507                         snprintf(buf, sizeof(buf), " [%d]", type);
508                         _combine_strings(&table[RELATION_TABLE_COLUMN_COUNT - 1], buf);
509                 }
510         }
511
512         for (int i = 0; i < RELATION_TABLE_COLUMN_COUNT; i++) {
513                 printf("%*s|", RELATION_TABLE_COLUMN_WIDTH, table[i] ? table[i] : "");
514                 free(table[i]);
515         }
516         free(table);
517
518         printf("\n");
519         _print_horizontal_line_in_relations_table();
520         if (relations)
521                 g_array_free(relations, TRUE);
522 }
523
524 typedef void (*print_information_about_object_function)(AtspiAccessible *);
525
526 static void _iterate_over_tree(print_information_about_object_function func, AtspiAccessible *node) {
527         func(node);
528
529         int count = atspi_accessible_get_child_count(node, NULL);
530         for (int i = 0; i < count; i++) {
531                 AtspiAccessible *child = atspi_accessible_get_child_at_index(node, i, NULL);
532                 if (child)
533                         _iterate_over_tree(func, child);
534         }
535 }
536
537 static void _print_relations_for_objects_in_tree(AtspiAccessible *node) {
538         _iterate_over_tree(_print_relations_for_object, node);
539 }
540
541 static void _print_header_for_relation_table() {
542         char *table[] = {"OBJECT", "CONTROLLER_FOR", "CONTROLLED_BY", "FLOWS_TO", "FLOWS_FROM", "DESCRIBED_BY", "OTHER RELATION [ID]"};
543         assert(ARRAY_SIZE(table) == RELATION_TABLE_COLUMN_COUNT);
544
545         _print_horizontal_line_in_relations_table();
546
547         for (int i = 0; i < RELATION_TABLE_COLUMN_COUNT; i++)
548                 printf("%*s|", RELATION_TABLE_COLUMN_WIDTH , table[i]);
549
550         printf("\n");
551         _print_horizontal_line_in_relations_table();
552 }
553
554 static void _print_relations_table(AtspiAccessible *node) {
555         printf("\nRELATIONS TABLE\n");
556         _print_header_for_relation_table();
557         _print_relations_for_objects_in_tree(node);
558 }
559
560 static void _print_horizontal_line_in_actions_table() {
561         int size_factor = 1;
562         for (int i = 0; i < ACTIONS_TABLE_COLUMN_COUNT; i++) {
563                 if (i == ACTIONS_TABLE_COLUMN_COUNT - 1)
564                         size_factor = 4;
565                 for (int j = 0; j < ACTIONS_TABLE_BASE_COLUMN_WIDTH * size_factor; j++)
566                         printf("-");
567                 printf("+");
568         }
569         printf("\n");
570 }
571
572 static void _print_actions_for_object(AtspiAccessible *node) {
573         char *unique_id = atspi_accessible_get_unique_id(node, NULL);
574         AtspiAction* action_if = atspi_accessible_get_action_iface (node);
575         if (action_if) {
576                 gint no_actions = atspi_action_get_n_actions (action_if, NULL);
577                 for (int i=0; i< no_actions; i++) {
578                         gchar * ith_action = atspi_action_get_action_name (action_if, i, NULL);
579                         printf("%*s|", ACTIONS_TABLE_BASE_COLUMN_WIDTH, unique_id ? unique_id : "");
580                         printf("%*s|\n", ACTIONS_TABLE_BASE_COLUMN_WIDTH * 4, ith_action ? (char *) ith_action : "");
581                         g_free(ith_action);
582                 }
583                 _print_horizontal_line_in_actions_table();
584         }
585         free(unique_id);
586 }
587
588 static void _print_actions_for_objects_in_tree(AtspiAccessible *node) {
589         _iterate_over_tree(_print_actions_for_object, node);
590 }
591
592 static void _print_header_for_actions_table() {
593         char *table[] = {"OBJECT", "SUPPORTED ACTION"};
594         assert(ARRAY_SIZE(table) == ACTIONS_TABLE_COLUMN_COUNT);
595
596         _print_horizontal_line_in_actions_table();
597
598         int size_factor = 1;
599         for (int i = 0; i < ACTIONS_TABLE_COLUMN_COUNT; i++) {
600                 if (i == ACTIONS_TABLE_COLUMN_COUNT - 1)
601                          size_factor = 4;
602                 printf("%*s|", ACTIONS_TABLE_BASE_COLUMN_WIDTH * size_factor , table[i]);
603         }
604
605         printf("\n");
606         _print_horizontal_line_in_actions_table();
607 }
608
609 static void _print_actions_table(AtspiAccessible *node) {
610         printf("\nSUPPPORTED ACTIONS TABLE\n");
611         _print_header_for_actions_table();
612         _print_actions_for_objects_in_tree(node);
613 }
614
615 static void _print_horizontal_line_in_attributes_table() {
616         int size_factor = 1;
617         for (int i = 0; i < ATTRIBUTE_TABLE_COLUMN_COUNT; i++) {
618                 if (i == ATTRIBUTE_TABLE_COLUMN_COUNT - 1)
619                         size_factor = 4;
620                 for (int j = 0; j < ATTRIBUTE_TABLE_BASE_COLUMN_WIDTH * size_factor; j++)
621                         printf("-");
622                 printf("+");
623         }
624         printf("\n");
625 }
626
627 static void _print_attributes_for_object(AtspiAccessible *node) {
628         GHashTable *attributes = atspi_accessible_get_attributes(node, NULL);
629
630         if (!attributes)
631                 return;
632
633         char *unique_id = atspi_accessible_get_unique_id(node, NULL);
634         GHashTableIter attributes_iter;
635         gpointer attr_key;
636         gpointer attr_value;
637
638         g_hash_table_iter_init (&attributes_iter, attributes);
639         while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
640                 printf("%*s|", ATTRIBUTE_TABLE_BASE_COLUMN_WIDTH, unique_id ? unique_id : "");
641                 printf("%*s|", ATTRIBUTE_TABLE_BASE_COLUMN_WIDTH, attr_key ? (char *) attr_key : "");
642                 printf("%*s|\n", ATTRIBUTE_TABLE_BASE_COLUMN_WIDTH * 4, attr_value ? (char *) attr_value : "");
643         }
644
645         if (g_hash_table_size (attributes))
646                 _print_horizontal_line_in_attributes_table();
647
648         g_hash_table_unref(attributes);
649         free(unique_id);
650 }
651
652 static void _print_attributes_for_objects_in_tree(AtspiAccessible *node) {
653         _iterate_over_tree(_print_attributes_for_object, node);
654 }
655
656 static void _print_header_for_attributes_table() {
657         char *table[] = {"OBJECT", "ATTRIBUTE KEY", "ATTRIBUTE VALUE"};
658         assert(ARRAY_SIZE(table) == ATTRIBUTE_TABLE_COLUMN_COUNT);
659
660         _print_horizontal_line_in_attributes_table();
661
662         int size_factor = 1;
663         for (int i = 0; i < ATTRIBUTE_TABLE_COLUMN_COUNT; i++) {
664                 if (i == ATTRIBUTE_TABLE_COLUMN_COUNT - 1)
665                         size_factor = 4;
666                 printf("%*s|", ATTRIBUTE_TABLE_BASE_COLUMN_WIDTH * size_factor , table[i]);
667         }
668
669         printf("\n");
670         _print_horizontal_line_in_attributes_table();
671 }
672
673 static void _print_attributes_table(AtspiAccessible *node) {
674         printf("\nATTRIBUTES TABLE\n");
675         _print_header_for_attributes_table();
676         _print_attributes_for_objects_in_tree(node);
677 }
678
679 static void _atspi_tree_traverse(const char *app_name, bool dump, bool check, bool first_match, int length_limit)
680 {
681
682         AtspiAccessible *desktop = atspi_get_desktop(0);
683         if (!desktop) {
684                 fprintf(stderr, "atspi_get_desktop failed\n");
685                 return;
686         }
687
688         int count = atspi_accessible_get_child_count(desktop, NULL);
689         bool app_name_matched = false;
690
691         for (int i = 0; i < count; i++) {
692                 AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
693                 if (child == NULL) {
694                         fprintf(stderr, "\n%s, %s %d: Null application occured. Results may be misleading.\n", __FILE__, __FUNCTION__, __LINE__);
695                         continue;
696                 }
697
698                 char *name = atspi_accessible_get_name(child, NULL);
699                 bool attributes_are_too_long = false;
700                 bool app_has_relations = false;
701                 bool app_has_actions = false;
702
703                 if (!dump && !check)
704                         printf("%s\n", name);
705
706                 if ((check || dump) && name && app_name && !strcmp(name, app_name)) {
707                         app_name_matched = true;
708
709                         _print_module_legend();
710
711                         if (check)
712                                 _test_atspi_parent_child_relation(child, desktop, i);
713
714                         _print_atspi_tree_verify_maybe_r(0, child, check, length_limit, &attributes_are_too_long, &app_has_relations, &app_has_actions);
715
716                         if (app_has_relations)
717                                 _print_relations_table(child);
718
719                         if (attributes_are_too_long)
720                                 _print_attributes_table(child);
721
722                         if (app_has_actions)
723                                 _print_actions_table(child);
724
725                         if (first_match) {
726                                 free(name);
727                                 break;
728                         } else {
729                                 printf("\n");
730                         }
731                 }
732                 free(name);
733         }
734
735         if (!app_name_matched && (dump || check))
736                 fprintf(stderr, "There is no application with name: %s. Try again.\n", app_name);
737
738         g_object_unref(desktop);
739 }
740
741 static void _at_spi_client_enable(gboolean enabled)
742 {
743         static GDBusProxy *proxy = NULL; //we keep proxy (dbus connection) until program exits
744         GVariant *result;
745         GVariant *enabled_variant;
746         GError *error = NULL;
747         GDBusProxyFlags flags = G_DBUS_PROXY_FLAGS_NONE;
748
749
750         if (!proxy) {
751                 proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
752                                         flags,
753                                         NULL, /* GDBusInterfaceInfo */
754                                         "org.a11y.Bus",
755                                         "/org/a11y/bus",
756                                         "org.freedesktop.DBus.Properties",
757                                         NULL, /* GCancellable */
758                                         &error);
759                 if (error) {
760                         fprintf(stderr, "Failed to create proxy object for '/org/a11y/bus': %s\n", error->message);
761                         g_error_free(error);
762                         return;
763                 }
764         }
765
766         enabled_variant = g_variant_new_boolean(enabled);
767         result = g_dbus_proxy_call_sync(proxy,
768                                         "Set",
769                                         g_variant_new ("(ssv)",  "org.a11y.Status", "IsEnabled", enabled_variant),
770                                         G_DBUS_CALL_FLAGS_NONE,
771                                         -1,
772                                         NULL,
773                                         &error);
774         if (enabled_variant)
775                 g_variant_unref(enabled_variant);
776         if (result)
777                 g_variant_unref(result);
778
779         if (error) {
780                 fprintf(stderr, "Fail to call org.freedesktop.DBus.Properties.Set: %s\n", error->message);
781                 g_error_free(error);
782         }
783 }
784
785 static void _run_command(int argc, char *argv[])
786 {
787         struct option long_options[] = {
788                 {"help", no_argument, 0, 'h'},
789                 {"version", no_argument, 0, 'v'},
790                 {"show-legend", no_argument, 0, 'g'},
791                 {"list-apps", no_argument, 0, 'l'},
792                 {"at-spi-client", no_argument, 0, 'a'},
793                 {"sleep", required_argument, 0, 's'},
794                 {"tree-dump", required_argument, 0, 'd'},
795                 {"tree-check", required_argument, 0, 'c'},
796                 {"first-match", no_argument, 0, 'f'},
797                 {"indent", required_argument, 0, 'i'},
798                 {"truncate", required_argument, 0, 't'},
799         };
800
801         int command = 0;
802         int option_index = 0;
803         bool traverse_flags[FLAG_NO] = {false};
804         char *app_name = NULL;
805         gboolean enable_at_spi_client;
806
807         while (TRUE) {
808                 command = getopt_long(argc, argv, "hvgla:s:d:c:ft:i:", long_options, &option_index);
809
810                 if (command == ERROR_STATE)
811                         break;
812
813                 switch (command) {
814                 case 'h':
815                         _print_help();
816                         break;
817
818                 case 'v':
819                         _print_version();
820                         break;
821
822                 case 'g':
823                         _print_atspi_states_legend();
824                         break;
825
826                 case 'l':
827                         _atspi_tree_traverse(NULL, false, false, false, module_name_limit);
828                         break;
829
830                 case 'a':
831                         enable_at_spi_client = TRUE;
832                         if (optarg[0] == 'f' || optarg[0] == '0')
833                                 enable_at_spi_client = FALSE;
834
835                         _at_spi_client_enable(enable_at_spi_client);
836                         break;
837
838                 case 's':
839                         sleep(atoi(optarg));
840                         break;
841
842                 case 'd':
843                         traverse_flags[DUMP] = true;
844                         app_name = optarg;
845                         break;
846
847                 case 'c':
848                         traverse_flags[CHECK] = true;
849                         app_name = optarg;
850                         break;
851
852                 case 'f':
853                         traverse_flags[FIRST_MATCH] = true;
854                         break;
855
856                 case 'i':
857                         _set_indent(atoi(optarg));
858                         break;
859
860                 case 't':
861                         _set_module_length_limit(atoi(optarg));
862                         break;
863
864                 case '?':
865                         fprintf(stderr, "Invalid parameter. Use: \"--help\"\n");
866                         break;
867
868                 default:
869                         abort();
870                 }
871         }
872
873         if (traverse_flags[DUMP] || traverse_flags[CHECK])
874                 _atspi_tree_traverse(app_name, traverse_flags[DUMP], traverse_flags[CHECK], traverse_flags[FIRST_MATCH], module_name_limit);
875 }
876
877 int main(int argc, char *argv[])
878 {
879         if (argc < 2) {
880                 printf("Arguments required. Type %s --help.\n", argv[0]);
881                 return -1;
882         }
883
884         int result = atspi_init();
885         if (result != 0)
886         {
887                 fprintf(stderr, "Unable to initilize AT-SPI infrastructure, atspi_init failed\n");
888                 return -1;
889         }
890
891         _run_command(argc, argv);
892
893         return 0;
894 }