018acc7e922dc76c082bfe048ac1797953e05525
[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
8 #define ERROR_STATE -1
9 #define SAFE_BUFFER_SIZE 2048
10 #define CHECK_OUTPUT_WIDTH 42
11 #define MINIMAL_MODULE_WIDTH 3
12 #define ATTR_WIDTH 50
13 #define NUMBER_WIDTH 6
14 #define ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))
15 #define COLUMN_NO 3
16 #define FLAG_NO 3
17 #define DUMP 0
18 #define CHECK 1
19 #define FIRST_MATCH 2
20 #define VERSION "1.1"
21
22 static unsigned indent_width = 2;
23 static int module_name_limit = -1;
24
25 static const char* atspi_state_names[] = {
26         [ATSPI_STATE_INVALID] = "INVALID",
27         [ATSPI_STATE_ACTIVE] = "ACTIVE",
28         [ATSPI_STATE_ARMED] = "ARMED",
29         [ATSPI_STATE_BUSY] =  "BUSY",
30         [ATSPI_STATE_CHECKED] = "CHECKED",
31         [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
32         [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
33         [ATSPI_STATE_EDITABLE] = "EDITABLE",
34         [ATSPI_STATE_ENABLED] = "ENABLED",
35         [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
36         [ATSPI_STATE_EXPANDED] = "EXPANDED",
37         [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
38         [ATSPI_STATE_FOCUSED] = "FOCUSED",
39         [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
40         [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
41         [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
42         [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
43         [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
44         [ATSPI_STATE_OPAQUE] = "OPAQUE",
45         [ATSPI_STATE_PRESSED] = "PRESSED",
46         [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
47         [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
48         [ATSPI_STATE_SELECTED] = "SELECTED",
49         [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
50         [ATSPI_STATE_SHOWING] = "SHOWING",
51         [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
52         [ATSPI_STATE_STALE] = "STALE",
53         [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
54         [ATSPI_STATE_VERTICAL] = "VERTICAL",
55         [ATSPI_STATE_VISIBLE] = "VISIBLE",
56         [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
57         [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
58         [ATSPI_STATE_REQUIRED] = "REQUIRED",
59         [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
60         [ATSPI_STATE_ANIMATED] = "ANIMATED",
61         [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
62         [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
63         [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
64         [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
65         [ATSPI_STATE_VISITED] = "VISITED",
66         [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
67         [ATSPI_STATE_MODAL] = "MODAL",
68         [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
69         [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
70         [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
71         [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
72         [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
73 };
74
75 typedef struct _Box_Size {
76         char *x;
77         char *y;
78         char *height;
79         char *width;
80 } Box_Size;
81
82 char *_strdup(const char *c)
83 {
84         char *result = (char *)calloc(1, strlen(c) + 1);
85         return result ? strcpy(result, c) : result;
86 }
87
88 static char *_multiply_string(char c, unsigned number)
89 {
90         char *result = (char *)calloc(1, number + 1);
91
92         if (result == NULL)
93                 return result;
94
95         memset(result, c, number);
96
97         return result;
98 }
99
100 static void _print_module_legend()
101 {
102         printf("\n[[node],[node role name],[attributes list],[x,y,width,height],[node name],[states list][flow to node, flow from node]\n\n");
103 }
104
105 static void _print_atspi_states_legend()
106 {
107         _print_module_legend();
108
109         int array_len = ARRAY_SIZE(atspi_state_names);
110         int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
111         for (int line_idx = 0; line_idx < line_count; line_idx++) {
112                 if ((line_idx < line_count - 1) || (array_len % COLUMN_NO == 0)) {
113                         printf("[%d]\t%-24s[%d]\t%-24s[%d]\t%s\n",
114                                         line_idx * COLUMN_NO,
115                                         atspi_state_names[line_idx * COLUMN_NO],
116                                         (line_idx * COLUMN_NO) + 1,
117                                         atspi_state_names[(line_idx * COLUMN_NO) + 1],
118                                         (line_idx * COLUMN_NO) + 2,
119                                         atspi_state_names[(line_idx * COLUMN_NO) + 2]);
120                 } else if (array_len % COLUMN_NO == 2) {
121                         printf("[%d]\t%-24s[%d]\t%s\n",
122                                         line_idx * COLUMN_NO,
123                                         atspi_state_names[line_idx * COLUMN_NO],
124                                         (line_idx * COLUMN_NO) + 1,
125                                         atspi_state_names[(line_idx * COLUMN_NO) + 1]);
126                 } else {
127                         printf("[%d]\t%s\n", line_idx * COLUMN_NO, atspi_state_names[line_idx * COLUMN_NO]);
128                 }
129         }
130 }
131
132 static void _set_indent(int indent_size)
133 {
134         if (indent_size < 0) {
135                 fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
136                 return;
137         }
138
139         indent_width = indent_size;
140 }
141
142 static void _set_module_length_limit(int limit)
143 {
144         if (limit < MINIMAL_MODULE_WIDTH) {
145                 fprintf(stderr, "WARNING: Invalid maximum field size. Default value retained.\n");
146                 return;
147         }
148
149         module_name_limit = limit;
150 }
151
152 static int _int_sort_function(gconstpointer a, gconstpointer b)
153 {
154         int int_a = GPOINTER_TO_INT (a);
155         int int_b = GPOINTER_TO_INT (b);
156
157         return int_a - int_b;
158 }
159
160 static void _truncate_string(char *string, int length_limit)
161 {
162         if (length_limit < MINIMAL_MODULE_WIDTH || !string)
163                 return;
164
165         int len = strlen(string);
166
167         if (len > length_limit)
168                 memcpy(string + length_limit - 3, "...", 4);
169 }
170
171 static size_t _combine_strings(char **destination, const char *source)
172 {
173         if (!source || !*source || !destination)
174                 return 0;
175
176         size_t old_len = *destination ? strlen(*destination) : 0;
177         size_t source_len = strlen(source);
178         size_t len = old_len + source_len;
179         char *buf = realloc(*destination, (len + 1) * sizeof(char));
180
181         if (!buf) {
182                 fprintf(stderr, "_combine_strings: allocation failed");
183                 return old_len;
184         }
185
186         memcpy(buf + old_len, source, source_len + 1);
187
188         *destination = buf;
189
190         return len;
191 }
192
193 static Box_Size *_get_box_size(AtspiAccessible *node)
194 {
195         Box_Size *box_size = (Box_Size *)malloc(sizeof(Box_Size));
196         if (box_size == NULL)
197                 return NULL;
198
199         char *x = NULL;
200         char *y = NULL;
201         char *width = NULL;
202         char *height = NULL;
203
204         AtspiComponent *component = atspi_accessible_get_component_iface(node);
205         AtspiRect *extent = component ? atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL) : NULL;
206
207         if (extent == NULL) {
208                 _combine_strings(&x, "_");
209                 _combine_strings(&y, "_");
210                 _combine_strings(&width, "_");
211                 _combine_strings(&height, "_");
212         } else {
213                 char temp_buff[NUMBER_WIDTH];
214
215                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->x);
216                 _combine_strings(&x, temp_buff);
217
218                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->y);
219                 _combine_strings(&y, temp_buff);
220
221                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->width);
222                 _combine_strings(&width, temp_buff);
223
224                 snprintf(temp_buff, NUMBER_WIDTH, "%d", extent->height);
225                 _combine_strings(&height, temp_buff);
226
227                 g_object_unref(component);
228                 g_free(extent);
229         }
230
231         box_size->x = x;
232         box_size->y = y;
233         box_size->width = width;
234         box_size->height = height;
235
236         return box_size;
237 }
238
239 static char *_get_states(AtspiAccessible *node, int length_limit)
240 {
241         AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
242         GArray *states = atspi_state_set_get_states(node_state_set);
243         if (!states) return NULL;
244         g_array_sort(states, _int_sort_function);
245
246         AtspiStateType state_type;
247         char *state_string = NULL;
248
249         for (int i = 0; i < states->len; i++) {
250                 state_type = g_array_index(states, AtspiStateType, i);
251
252                 char node_state_str[8];
253                 sprintf(node_state_str, "(%d)", state_type);
254                 _combine_strings(&state_string, node_state_str);
255         }
256
257         g_array_free(states, 0);
258         g_object_unref(node_state_set);
259
260         _truncate_string(state_string, length_limit);
261
262         return state_string;
263 }
264
265 static char *_get_attributes(AtspiAccessible *node, int length_limit)
266 {
267         GHashTable *attributes = atspi_accessible_get_attributes(node, NULL);
268
269         if (!attributes)
270                 return NULL;
271
272         GHashTableIter attributes_iter;
273         gpointer attr_key;
274         gpointer attr_value;
275         g_hash_table_iter_init (&attributes_iter, attributes);
276         char *result = NULL;
277         while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
278                 char attributes_string[ATTR_WIDTH];
279                 int ret = snprintf(attributes_string, ATTR_WIDTH, "(%s=%s)", (char *)attr_key, (char *)attr_value);
280                 if (ret >= ATTR_WIDTH)
281                         fprintf(stderr, "\n_get_attributes: generated string is too long. Buffer overflow\n");
282
283                 _combine_strings(&result, attributes_string);
284         }
285         g_hash_table_unref(attributes);
286
287         _truncate_string(result, length_limit);
288         return result;
289 }
290
291 static char *_get_object_in_relation(AtspiAccessible *source, AtspiRelationType search_type)
292 {
293         if (source == NULL)
294                 return "";
295
296         GArray *relations = atspi_accessible_get_relation_set(source, NULL);
297
298         if (relations == NULL)
299                 return "";
300
301         AtspiAccessible *ret = NULL;
302         for (int i = 0; i < relations->len; ++i) {
303                 AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
304                 AtspiRelationType type = atspi_relation_get_relation_type(relation);
305
306                 if (type == search_type) {
307                         ret = atspi_relation_get_target(relation, 0);
308                         break;
309                 }
310         }
311
312         g_array_free(relations, TRUE);
313
314         return ret ? atspi_accessible_get_path(ret, NULL) : g_strdup("");
315 }
316
317 static char *_get_info(AtspiAccessible *node, int length_limit)
318 {
319         if (!node)
320                 return NULL;
321
322         char *node_name = atspi_accessible_get_name(node, NULL);
323         char *node_role_name = atspi_accessible_get_role_name(node, NULL);
324         char *path = atspi_accessible_get_path(node, NULL);
325
326         char *attributes = _get_attributes(node, length_limit);
327         Box_Size *box_size = _get_box_size(node);
328         char *states = _get_states(node, length_limit);
329
330         char *flows_to = _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO);
331         char *flows_from = _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
332
333         char result[SAFE_BUFFER_SIZE];
334         int ret = snprintf(result, SAFE_BUFFER_SIZE, "[[%s],[%s],[%s],[%s,%s,%s,%s],[%s],[%s],[%s,%s]]",
335                                                 path,
336                                                 node_role_name,
337                                                 attributes,
338                                                 box_size->x,
339                                                 box_size->y,
340                                                 box_size->width,
341                                                 box_size->height,
342                                                 node_name,
343                                                 states,
344                                                 flows_to,
345                                                 flows_from);
346
347         if (ret >= SAFE_BUFFER_SIZE)
348                 fprintf(stderr, "\n%s, %s %s: generated string is too long. Buffer overflow\n", __FILE__, __FUNCTION__, __LINE__);
349
350         free(node_name);
351         free(node_role_name);
352         free(path);
353         free(attributes);
354         if (box_size) {
355                 free(box_size->width);
356                 free(box_size->height);
357                 free(box_size);
358         }
359         free(states);
360         free(flows_to);
361         free(flows_from);
362
363         return _strdup(result);
364 }
365
366 static void _test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
367 {
368         int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
369         AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
370
371         char output[CHECK_OUTPUT_WIDTH];
372         if (parent_index != parent_candidate_index || parent_candidate != parent) {
373                 char parent_status[NUMBER_WIDTH];
374
375                 if (parent == NULL)
376                         strcpy(parent_status, "_");
377                 else
378                         snprintf(parent_status, NUMBER_WIDTH, "%d", parent_index);
379
380                 char *parent_path = atspi_accessible_get_path(parent, NULL);
381                 char *parent_candidate_path = atspi_accessible_get_path(parent_candidate, NULL);
382                 snprintf(output, CHECK_OUTPUT_WIDTH, "[FAIL<%d,%s><%s,%s>]", parent_candidate_index, parent_status,
383                                 parent_candidate_path, parent_path);
384                 free(parent_path);
385                 free(parent_candidate_path);
386
387         } else {
388                 snprintf(output, CHECK_OUTPUT_WIDTH, "[OK]");
389         }
390
391         printf("%-*s\t", CHECK_OUTPUT_WIDTH, output);
392 }
393
394 static int _print_atspi_tree_verify_maybe_r(int indent_number, AtspiAccessible *object, bool check_integrity, int length_limit)
395 {
396         char *indent = _multiply_string(' ', indent_number*indent_width);
397         if (indent != NULL) {
398                 printf("%s", indent);
399                 free(indent);
400         }
401
402         char *node_info = _get_info(object, length_limit);
403         if (node_info != NULL) {
404                 printf("%s\n", node_info);
405                 free(node_info);
406         }
407
408         int count = atspi_accessible_get_child_count(object, NULL);
409         for (int i = 0; i < count; i++) {
410                 AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
411                 if (child) {
412                         if (check_integrity)
413                                 _test_atspi_parent_child_relation(child, object, i);
414
415                         _print_atspi_tree_verify_maybe_r(indent_number + 1, child, check_integrity, length_limit);
416                 }
417         }
418         return 0;
419 }
420
421 void _print_help()
422 {
423         printf("AT-SPI2-CORE-UTIL\n\n");
424         printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
425         printf("OPTION LIST:\n");
426         printf("-h, --help\t\tshow this message\n");
427         printf("-v, --version\t\tshow actual version of tool\n");
428         printf("-g, --show-legend\tprint AT-SPI state legend\n");
429         printf("-l, --list-apps\t\tlist all applications of desktop\n");
430         printf("-d, --tree-dump\t\tdump tree for selected application\n");
431         printf("-c, --tree-check\tcheck tree for selected application\n");
432         printf("-f, --first-match\tperform dump or check only for first matching application\n");
433         printf("-i, --indent\t\tset indentation size, default value stands at 2\n");
434         printf("-t, --truncate\t\tset maximum single field size, default value stands at 30\n");
435         printf("\nEXAMPLE:\n");
436         printf("\tat_spi2_tool -i3 -d quickpanel\n");
437         printf("\t  show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
438         printf("\tat_spi2_tool -i1 -c starter\n");
439         printf("\t  show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
440 }
441 void _print_version()
442 {
443         printf("AT-SPI2-CORE-UTIL v%s\n", VERSION);
444 }
445
446 static void _atspi_tree_traverse(AtspiAccessible *desktop, const char *app_name, bool dump, bool check, bool first_match, int length_limit)
447 {
448         int count = atspi_accessible_get_child_count(desktop, NULL);
449         bool app_name_matched = false;
450         for (int i = 0; i < count; i++) {
451                 AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
452                 if (child == NULL) {
453                         fprintf(stderr, "\n%s, %s %s: Null child occured. Results may be misleading.\n", __FILE__, __FUNCTION__, __LINE__);
454                         continue;
455                 }
456
457                 char *name = atspi_accessible_get_name(child, NULL);
458
459                 if (!dump && !check)
460                         printf("%s\n", name);
461
462                 if ((check || dump) && name && !strcmp(name, app_name)) {
463                         app_name_matched = true;
464
465                         _print_module_legend();
466
467                         if (check)
468                                 _test_atspi_parent_child_relation(child, desktop, i);
469
470                         _print_atspi_tree_verify_maybe_r(0, child, check, length_limit);
471
472                         if (first_match) {
473                                 free(name);
474                                 break;
475                         } else
476                                 printf("\n");
477
478                         free(name);
479                 }
480         }
481
482         if (!app_name_matched && (dump || check))
483                 fprintf(stderr, "There is no application with name: %s. Try again.\n", app_name);
484 }
485
486 static void _run_command(int argc, char *argv[], AtspiAccessible *desktop)
487 {
488         struct option long_options[] = {
489                 {"help", no_argument, 0, 'h'},
490                 {"version", no_argument, 0, 'v'},
491                 {"show-legend", no_argument, 0, 'g'},
492                 {"list-apps", no_argument, 0, 'l'},
493                 {"tree-dump", required_argument, 0, 'd'},
494                 {"tree-check", required_argument, 0, 'c'},
495                 {"first-match", no_argument, 0, 'f'},
496                 {"indent", required_argument, 0, 'i'},
497                 {"truncate", required_argument, 0, 't'},
498         };
499
500         int command = 0;
501         int option_index = 0;
502         bool traverse_flags[FLAG_NO] = {false};
503         char *app_name = NULL;
504
505         while (TRUE) {
506                 command = getopt_long(argc, argv, "hvgld:c:ft:i:", long_options, &option_index);
507
508                 if (command == ERROR_STATE)
509                         break;
510
511                 switch (command) {
512                 case 'h':
513                         _print_help();
514                         break;
515
516                 case 'v':
517                         _print_version();
518                         break;
519
520                 case 'g':
521                         _print_atspi_states_legend();
522                         break;
523
524                 case 'l':
525                         _atspi_tree_traverse(desktop, NULL, false, false, false, module_name_limit);
526                         break;
527
528                 case 'd':
529                         traverse_flags[DUMP] = true;
530                         app_name = optarg;
531                         break;
532
533                 case 'c':
534                         traverse_flags[CHECK] = true;
535                         app_name = optarg;
536                         break;
537
538                 case 'f':
539                         traverse_flags[FIRST_MATCH] = true;
540                         break;
541
542                 case 'i':
543                         _set_indent(atoi(optarg));
544                         break;
545
546                 case 't':
547                         _set_module_length_limit(atoi(optarg));
548                         break;
549
550                 case '?':
551                         fprintf(stderr, "Invalid parameter. Use: \"--help\"\n");
552                         break;
553
554                 default:
555                         abort();
556                 }
557         }
558
559         if (traverse_flags[DUMP] || traverse_flags[CHECK])
560                 _atspi_tree_traverse(desktop, app_name, traverse_flags[DUMP], traverse_flags[CHECK], traverse_flags[FIRST_MATCH], module_name_limit);
561 }
562
563 int main(int argc, char *argv[])
564 {
565         if (argc < 2) {
566                 printf("Arguments required. Type %s --help.\n", argv[0]);
567                 return -1;
568         }
569
570         int result = atspi_init();
571         if (result != 0)
572         {
573                 fprintf(stderr, "Unable to initilize AT-SPI infrastructure, atspi_init failed\n");
574                 return -1;
575         }
576
577         AtspiAccessible *desktop = atspi_get_desktop(0);
578         if (!desktop) {
579                 fprintf(stderr, "atspi_get_desktop failed\n");
580                 return -1;
581         }
582
583         _run_command(argc, argv, desktop);
584
585         return 0;
586 }