Tool for checking AT-SPI tree integrity v0.5
[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 ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))
11 #define COLUMN_NO 3
12
13 static unsigned indent_width = 2;
14
15 static const char* atspi_state_names[] = {
16         [ATSPI_STATE_INVALID] = "INVALID",
17         [ATSPI_STATE_ACTIVE] = "ACTIVE",
18         [ATSPI_STATE_ARMED] = "ARMED",
19         [ATSPI_STATE_BUSY] =  "BUSY",
20         [ATSPI_STATE_CHECKED] = "CHECKED",
21         [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
22         [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
23         [ATSPI_STATE_EDITABLE] = "EDITABLE",
24         [ATSPI_STATE_ENABLED] = "ENABLED",
25         [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
26         [ATSPI_STATE_EXPANDED] = "EXPANDED",
27         [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
28         [ATSPI_STATE_FOCUSED] = "FOCUSED",
29         [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
30         [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
31         [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
32         [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
33         [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
34         [ATSPI_STATE_OPAQUE] = "OPAQUE",
35         [ATSPI_STATE_PRESSED] = "PRESSED",
36         [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
37         [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
38         [ATSPI_STATE_SELECTED] = "SELECTED",
39         [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
40         [ATSPI_STATE_SHOWING] = "SHOWING",
41         [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
42         [ATSPI_STATE_STALE] = "STALE",
43         [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
44         [ATSPI_STATE_VERTICAL] = "VERTICAL",
45         [ATSPI_STATE_VISIBLE] = "VISIBLE",
46         [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
47         [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
48         [ATSPI_STATE_REQUIRED] = "REQUIRED",
49         [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
50         [ATSPI_STATE_ANIMATED] = "ANIMATED",
51         [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
52         [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
53         [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
54         [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
55         [ATSPI_STATE_VISITED] = "VISITED",
56         [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
57         [ATSPI_STATE_MODAL] = "MODAL",
58         [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
59         [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
60         [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
61         [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
62         [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
63 };
64
65 typedef struct _StringAccumulator {
66         char *string;
67         int length;
68 } StringAccumulator;
69
70 char *strdup(const char *c) {
71         char *result = (char *)calloc(1, strlen(c) + 1);
72         return result ? strcpy(result, c) : result;
73 }
74
75 static char *multiply_string(char c, unsigned number) {
76         char *result = (char *)calloc(1, number + 1);
77
78         if (result == NULL)
79                 return result;
80
81         memset(result, c, number);
82
83         return result;
84 }
85
86 static void _print_atspi_states_legend() {
87         int array_len = ARRAY_SIZE(atspi_state_names);
88         int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
89         for (int line_idx = 0; line_idx < line_count; line_idx++) {
90                 if ((line_idx < line_count - 1) || (array_len % 3 == 0)) {
91                         printf("[%d]\t%-24s[%d]\t%-24s[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3], (line_idx * 3) + 1, atspi_state_names[(line_idx * 3) + 1], (line_idx * 3) + 2, atspi_state_names[(line_idx * 3) + 2]);
92                 } else if (array_len % 3 == 2) {
93                         printf("[%d]\t%-24s[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3], (line_idx * 3) + 1, atspi_state_names[(line_idx * 3) + 1]);
94                 } else {
95                         printf("[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3]);
96                 }
97         }
98 }
99
100 static void set_indent(int indent_size)
101 {
102         if (indent_size < 0) {
103                 fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
104                 return;
105         }
106
107         indent_width = indent_size;
108 }
109
110 static int _int_sort_function(gconstpointer a, gconstpointer b)
111 {
112         int int_a = GPOINTER_TO_INT (a);
113         int int_b = GPOINTER_TO_INT (b);
114
115         return int_a - int_b;
116 }
117
118 static int _accumulate_string(const char *string, StringAccumulator *accumulator) {
119         if (!accumulator)
120                 return FALSE;
121
122         if (string) {
123                 int length = accumulator->length + strlen(string);
124                 char *rebuffer = realloc(accumulator->string, length + 1);
125                 if (rebuffer) {
126                         strcpy(rebuffer + accumulator->length, string);
127                         accumulator->string = rebuffer;
128                         accumulator->length = length;
129                 } else {
130                         return FALSE;
131                 }
132         }
133         return TRUE;
134 }
135
136 static char *get_info(AtspiAccessible *node) {
137         if (!node)
138                 return NULL;
139
140         int node_width = -1;
141         int node_height = -1;
142         char *node_name = atspi_accessible_get_name(node, NULL);
143         char *node_role_name = atspi_accessible_get_role_name(node, NULL);
144         AtspiComponent *component = atspi_accessible_get_component_iface(node);
145
146         AtspiRect *extent = NULL;
147         if (component != NULL){
148                 extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
149
150                 if (extent != NULL) {
151                         node_width = extent->width;
152                         node_height = extent->height;
153                 }
154         }
155
156         AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
157         GArray *states = atspi_state_set_get_states(node_state_set);
158         g_array_sort(states, _int_sort_function);
159
160         AtspiStateType state;
161         StringAccumulator state_accumulator = {};
162         for (int i = 0; states && (i < states->len); i++) {
163                 state = g_array_index(states, AtspiStateType, i);
164                 char node_state_str[8];
165                 sprintf(node_state_str, "[%d]", state);
166                 _accumulate_string(node_state_str, &state_accumulator);
167         }
168         g_array_free(states, 0);
169
170         StringAccumulator attribute_accumulator = {};
171         GHashTable *attributes = NULL;
172         attributes = atspi_accessible_get_attributes(node, NULL);
173         if (attributes) {
174                 GHashTableIter attributes_iter;
175                 gpointer attr_key, attr_value;
176                 g_hash_table_iter_init (&attributes_iter, attributes);
177                 while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
178                         _accumulate_string("[", &attribute_accumulator);
179                         _accumulate_string((char*)attr_key, &attribute_accumulator);
180                         _accumulate_string("=", &attribute_accumulator);
181                         _accumulate_string((char*)attr_value, &attribute_accumulator);
182                         _accumulate_string("]", &attribute_accumulator);
183                 }
184                 g_hash_table_unref(attributes);
185         }
186
187         char result[SAFE_BUFFER_SIZE + 1];
188         snprintf(result, SAFE_BUFFER_SIZE, "[[%s],[%s],[%d,%d],[%s],[%s]]\n", node_role_name, attribute_accumulator.string, node_width, node_height, node_name, state_accumulator.string);
189
190         free(node_name);
191         free(node_role_name);
192
193         if (state_accumulator.string)
194                 g_free(state_accumulator.string);
195
196         if (attribute_accumulator.string)
197                 g_free(attribute_accumulator.string);
198
199         if (component)
200                 g_object_unref(component);
201
202         if (extent)
203                 g_free(extent);
204
205         if (node_state_set)
206                 g_object_unref(node_state_set);
207
208         return strdup(result);
209 }
210
211 static void test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
212 {
213         int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
214         AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
215         if (parent_index != parent_candidate_index || parent_candidate != parent) {
216                 printf("[FAIL<%d,", parent_candidate_index);
217
218                 if (parent == NULL)
219                         printf("_");
220                 else
221                         printf("%d", parent_index);
222
223                 printf(">]\t");
224
225         } else {
226                 printf("[OK]\t");
227         }
228 }
229
230 static int expand_r(int indent_number, AtspiAccessible *object, bool check_integrity)
231 {
232         char *indent = multiply_string(' ', indent_number*indent_width);
233         if (indent != NULL) {
234                 printf("%s", indent);
235                 free(indent);
236         }
237
238         char *node_info = get_info(object);
239         if (node_info != NULL) {
240                 printf("%s", node_info);
241                 free(node_info);
242         }
243
244         int count = atspi_accessible_get_child_count(object, NULL);
245         for (int i = 0; i < count; i++) {
246                 AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
247                 if (child) {
248                         if (check_integrity)
249                                 test_atspi_parent_child_relation(child, object, i);
250
251                         expand_r(indent_number + 1, child, check_integrity);
252                 }
253         }
254         return 0;
255 }
256
257 void print_help()
258 {
259         printf("AT-SPI2-CORE-UTIL\n\n");
260         printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
261         printf("OPTION LIST:\n");
262         printf("-h, --help\t\tshow this message\n");
263         printf("-g, --show-legend\tprint AT-SPI state legend\n");
264         printf("-l, --list-apps\t\tlist all applications of desktop\n");
265         printf("-d, --tree-dump\t\tdump tree for selected application\n");
266         printf("-c, --tree-check\tcheck tree for selected application\n");
267         printf("-i, --indent\t\tset indentation size\n");
268         printf("\nEXAMPLE:\n");
269         printf("\tat_spi2_tool -i3 -d quickpanel\n");
270         printf("\t  show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
271         printf("\tat_spi2_tool -i1 -c starter\n");
272         printf("\t  show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
273 }
274
275 static void atspi_tree_traverse(AtspiAccessible *desktop, const char *app_name, bool dump, bool check)
276 {
277         int count = atspi_accessible_get_child_count(desktop, NULL);
278         for (int i = 0; i < count; i++) {
279                 AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
280                 if (child) {
281                         char *name = atspi_accessible_get_name(child, NULL);
282
283                         if (!dump && !check)
284                                 printf("%s\n", name);
285
286                         if (dump && name && !strcmp(name, app_name)) {
287                                 expand_r(0, child, false);
288                                 break;
289                         }
290
291                         if (check && name && !strcmp(name, app_name)) {
292                                 test_atspi_parent_child_relation(child, desktop, i);
293                                 expand_r(0, child, true);
294                                 break;
295                         }
296                 }
297         }
298 }
299
300 static void run_command(int argc, char *argv[], AtspiAccessible *desktop)
301 {
302         struct option long_options[] = {
303                 {"help", no_argument, 0, 'h'},
304                 {"show-legend", no_argument, 0, 'g'},
305                 {"list-apps", no_argument, 0, 'l'},
306                 {"tree-dump", required_argument, 0, 'd'},
307                 {"tree-check", required_argument, 0, 'c'},
308                 {"indent", required_argument, 0, 'i'},
309         };
310
311         int command = 0;
312         int option_index = 0;
313
314         while (TRUE) {
315                 command = getopt_long(argc, argv, "hgld:c:i:", long_options, &option_index);
316
317                 if (command == ERROR_STATE)
318                         break;
319
320                 switch (command) {
321                 case 'h':
322                         print_help();
323                         break;
324
325                 case 'g':
326                         _print_atspi_states_legend();
327                         break;
328
329                 case 'l':
330                         atspi_tree_traverse(desktop, NULL, false, false);
331                         break;
332
333                 case 'd':
334                         atspi_tree_traverse(desktop, optarg, true, false);
335                         break;
336
337                 case 'c':
338                         atspi_tree_traverse(desktop, optarg, false, true);
339                         break;
340
341                 case 'i':
342                         set_indent(atoi(optarg));
343                         break;
344
345                 case '?':
346                         break;
347
348                 default:
349                         abort();
350                 }
351         }
352 }
353
354 int main(int argc, char *argv[])
355 {
356         if (argc < 2) {
357                 printf("Arguments required. Type %s --help.\n", argv[0]);
358                 return -1;
359         }
360
361         AtspiAccessible *desktop = atspi_get_desktop(0);
362
363         if (!desktop) {
364                 fprintf(stderr, "atspi_get_desktop failed\n");
365                 return 1;
366         }
367
368         atspi_init();
369
370         run_command(argc, argv, desktop);
371
372         return 0;
373 }