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