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