Tool for checking AT-SPI tree integrity v0.5 42/88542/6 accepted/tizen/common/20160927.152731 accepted/tizen/ivi/20160927.231039 accepted/tizen/mobile/20160927.230902 accepted/tizen/tv/20160927.230943 accepted/tizen/wearable/20160927.231013 submit/tizen/20160927.065504 submit/tizen/20160927.065746
authorLukasz Wlazly <l.wlazly@partner.samsung.com>
Mon, 19 Sep 2016 14:01:38 +0000 (16:01 +0200)
committerLukasz Wlazly <l.wlazly@partner.samsung.com>
Mon, 26 Sep 2016 07:50:41 +0000 (09:50 +0200)
Tool is placed in /bin/
To check AT-SPI tree user "owner" must be set:
su - owner

Change-Id: I5da4bcaba19547695ae46242f5bbf2dbce7c411c

packaging/at-spi2-core.spec
test/Makefile.am
test/at_spi2_tool.c [new file with mode: 0644]

index ad24f8d..472f089 100644 (file)
@@ -82,6 +82,7 @@ cp %{SOURCE1001} .
 %install
 rm -rf %{buildroot}
 find %{buildroot} -name '*.la' -or -name '*.a' | xargs rm -f
+
 %make_install
 %find_lang %{name}
 
@@ -97,6 +98,8 @@ rm -fr %{buildroot}
 %files -f %{name}.lang
 %manifest %{name}.manifest
 %defattr(-,root,root)
+%{_bindir}/at_spi2_tool
+
 %doc AUTHORS README
 %license COPYING
 %{_libexecdir}/at-spi2/at-spi-bus-launcher
index 7af2ee0..f233c36 100644 (file)
@@ -1,8 +1,10 @@
 LDADD = $(top_builddir)/atspi/libatspi.la
-noinst_PROGRAMS = memory
-memory_SOURCES = memory.c
-memory_CPPFLAGS = -I$(top_srcdir) -I$(top_builddir) -I$(top_builddir)/atspi
-memory_CFLAGS = $(GLIB_CFLAGS)         $(GOBJ_LIBS) $(DBUS_CFLAGS)
-memory_LDFLAGS = 
+bin_PROGRAMS = at_spi2_tool
+at_spi2_tool_SOURCES = at_spi2_tool.c
+at_spi2_tool_CPPFLAGS = -I$(top_srcdir) -I$(top_builddir) -I$(top_builddir)/atspi
+at_spi2_tool_CFLAGS = $(GLIB_CFLAGS) --std=c99 $(GOBJ_LIBS) $(DBUS_CFLAGS)
+at_spi2_tool_LDFLAGS =
+
+
 
 -include $(top_srcdir)/git.mk
diff --git a/test/at_spi2_tool.c b/test/at_spi2_tool.c
new file mode 100644 (file)
index 0000000..f67700d
--- /dev/null
@@ -0,0 +1,373 @@
+#include "atspi/atspi.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdbool.h>
+
+#define ERROR_STATE -1
+#define SAFE_BUFFER_SIZE 255
+#define ARRAY_SIZE(x)  (sizeof(x) / sizeof((x)[0]))
+#define COLUMN_NO 3
+
+static unsigned indent_width = 2;
+
+static const char* atspi_state_names[] = {
+       [ATSPI_STATE_INVALID] = "INVALID",
+       [ATSPI_STATE_ACTIVE] = "ACTIVE",
+       [ATSPI_STATE_ARMED] = "ARMED",
+       [ATSPI_STATE_BUSY] =  "BUSY",
+       [ATSPI_STATE_CHECKED] = "CHECKED",
+       [ATSPI_STATE_COLLAPSED] = "COLLAPSED",
+       [ATSPI_STATE_DEFUNCT] = "DEFUNCT",
+       [ATSPI_STATE_EDITABLE] = "EDITABLE",
+       [ATSPI_STATE_ENABLED] = "ENABLED",
+       [ATSPI_STATE_EXPANDABLE] = "EXPANDABLE",
+       [ATSPI_STATE_EXPANDED] = "EXPANDED",
+       [ATSPI_STATE_FOCUSABLE] = "FOCUSABLE",
+       [ATSPI_STATE_FOCUSED] = "FOCUSED",
+       [ATSPI_STATE_HAS_TOOLTIP] = "HAS_TOOLTIP",
+       [ATSPI_STATE_HORIZONTAL] = "HORIZONTAL",
+       [ATSPI_STATE_ICONIFIED] = "ICONIFIED",
+       [ATSPI_STATE_MULTI_LINE] = "MULTI_LINE",
+       [ATSPI_STATE_MULTISELECTABLE] = "MULTISELECTABLE",
+       [ATSPI_STATE_OPAQUE] = "OPAQUE",
+       [ATSPI_STATE_PRESSED] = "PRESSED",
+       [ATSPI_STATE_RESIZABLE] = "RESIZABLE",
+       [ATSPI_STATE_SELECTABLE] = "SELECTABLE",
+       [ATSPI_STATE_SELECTED] = "SELECTED",
+       [ATSPI_STATE_SENSITIVE] = "SENSITIVE",
+       [ATSPI_STATE_SHOWING] = "SHOWING",
+       [ATSPI_STATE_SINGLE_LINE] = "SINGLE_LINE",
+       [ATSPI_STATE_STALE] = "STALE",
+       [ATSPI_STATE_TRANSIENT] = "TRANSIENT",
+       [ATSPI_STATE_VERTICAL] = "VERTICAL",
+       [ATSPI_STATE_VISIBLE] = "VISIBLE",
+       [ATSPI_STATE_MANAGES_DESCENDANTS] = "MANAGES_DESCENDANTS",
+       [ATSPI_STATE_INDETERMINATE] = "INDETERMINATE",
+       [ATSPI_STATE_REQUIRED] = "REQUIRED",
+       [ATSPI_STATE_TRUNCATED] = "TRUNCATED",
+       [ATSPI_STATE_ANIMATED] = "ANIMATED",
+       [ATSPI_STATE_INVALID_ENTRY] = "INVALID_ENTRY",
+       [ATSPI_STATE_SUPPORTS_AUTOCOMPLETION] = "SUPPORTS_AUTOCOMPLETION",
+       [ATSPI_STATE_SELECTABLE_TEXT] = "SELECTABLE_TEXT",
+       [ATSPI_STATE_IS_DEFAULT] = "IS_DEFAULT",
+       [ATSPI_STATE_VISITED] = "VISITED",
+       [ATSPI_STATE_CHECKABLE] = "CHECKABLE",
+       [ATSPI_STATE_MODAL] = "MODAL",
+       [ATSPI_STATE_HIGHLIGHTED] = "HIGHLIGHTED",
+       [ATSPI_STATE_HIGHLIGHTABLE] = "HIGHLIGHTABLE",
+       [ATSPI_STATE_HAS_POPUP] = "HAS_POPUP",
+       [ATSPI_STATE_READ_ONLY] = "READ_ONLY",
+       [ATSPI_STATE_LAST_DEFINED] = "LAST_DEFINED"
+};
+
+typedef struct _StringAccumulator {
+       char *string;
+       int length;
+} StringAccumulator;
+
+char *strdup(const char *c) {
+       char *result = (char *)calloc(1, strlen(c) + 1);
+       return result ? strcpy(result, c) : result;
+}
+
+static char *multiply_string(char c, unsigned number) {
+       char *result = (char *)calloc(1, number + 1);
+
+       if (result == NULL)
+               return result;
+
+       memset(result, c, number);
+
+       return result;
+}
+
+static void _print_atspi_states_legend() {
+       int array_len = ARRAY_SIZE(atspi_state_names);
+       int line_count = (array_len + COLUMN_NO - 1)/COLUMN_NO;
+       for (int line_idx = 0; line_idx < line_count; line_idx++) {
+               if ((line_idx < line_count - 1) || (array_len % 3 == 0)) {
+                       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]);
+               } else if (array_len % 3 == 2) {
+                       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]);
+               } else {
+                       printf("[%d]\t%s\n", line_idx * 3, atspi_state_names[line_idx * 3]);
+               }
+       }
+}
+
+static void set_indent(int indent_size)
+{
+       if (indent_size < 0) {
+               fprintf(stderr, "WARNING: Invalid indent size. Default value retained.\n");
+               return;
+       }
+
+       indent_width = indent_size;
+}
+
+static int _int_sort_function(gconstpointer a, gconstpointer b)
+{
+       int int_a = GPOINTER_TO_INT (a);
+       int int_b = GPOINTER_TO_INT (b);
+
+       return int_a - int_b;
+}
+
+static int _accumulate_string(const char *string, StringAccumulator *accumulator) {
+       if (!accumulator)
+               return FALSE;
+
+       if (string) {
+               int length = accumulator->length + strlen(string);
+               char *rebuffer = realloc(accumulator->string, length + 1);
+               if (rebuffer) {
+                       strcpy(rebuffer + accumulator->length, string);
+                       accumulator->string = rebuffer;
+                       accumulator->length = length;
+               } else {
+                       return FALSE;
+               }
+       }
+       return TRUE;
+}
+
+static char *get_info(AtspiAccessible *node) {
+       if (!node)
+               return NULL;
+
+       int node_width = -1;
+       int node_height = -1;
+       char *node_name = atspi_accessible_get_name(node, NULL);
+       char *node_role_name = atspi_accessible_get_role_name(node, NULL);
+       AtspiComponent *component = atspi_accessible_get_component_iface(node);
+
+       AtspiRect *extent = NULL;
+       if (component != NULL){
+               extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
+
+               if (extent != NULL) {
+                       node_width = extent->width;
+                       node_height = extent->height;
+               }
+       }
+
+       AtspiStateSet *node_state_set = atspi_accessible_get_state_set(node);
+       GArray *states = atspi_state_set_get_states(node_state_set);
+       g_array_sort(states, _int_sort_function);
+
+       AtspiStateType state;
+       StringAccumulator state_accumulator = {};
+       for (int i = 0; states && (i < states->len); i++) {
+               state = g_array_index(states, AtspiStateType, i);
+               char node_state_str[8];
+               sprintf(node_state_str, "[%d]", state);
+               _accumulate_string(node_state_str, &state_accumulator);
+       }
+       g_array_free(states, 0);
+
+       StringAccumulator attribute_accumulator = {};
+       GHashTable *attributes = NULL;
+       attributes = atspi_accessible_get_attributes(node, NULL);
+       if (attributes) {
+               GHashTableIter attributes_iter;
+               gpointer attr_key, attr_value;
+               g_hash_table_iter_init (&attributes_iter, attributes);
+               while (g_hash_table_iter_next (&attributes_iter, &attr_key, &attr_value)) {
+                       _accumulate_string("[", &attribute_accumulator);
+                       _accumulate_string((char*)attr_key, &attribute_accumulator);
+                       _accumulate_string("=", &attribute_accumulator);
+                       _accumulate_string((char*)attr_value, &attribute_accumulator);
+                       _accumulate_string("]", &attribute_accumulator);
+               }
+               g_hash_table_unref(attributes);
+       }
+
+       char result[SAFE_BUFFER_SIZE + 1];
+       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);
+
+       free(node_name);
+       free(node_role_name);
+
+       if (state_accumulator.string)
+               g_free(state_accumulator.string);
+
+       if (attribute_accumulator.string)
+               g_free(attribute_accumulator.string);
+
+       if (component)
+               g_object_unref(component);
+
+       if (extent)
+               g_free(extent);
+
+       if (node_state_set)
+               g_object_unref(node_state_set);
+
+       return strdup(result);
+}
+
+static void test_atspi_parent_child_relation(AtspiAccessible *obj, AtspiAccessible *parent_candidate, int parent_candidate_index)
+{
+       int parent_index = atspi_accessible_get_index_in_parent(obj, NULL);
+       AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+       if (parent_index != parent_candidate_index || parent_candidate != parent) {
+               printf("[FAIL<%d,", parent_candidate_index);
+
+               if (parent == NULL)
+                       printf("_");
+               else
+                       printf("%d", parent_index);
+
+               printf(">]\t");
+
+       } else {
+               printf("[OK]\t");
+       }
+}
+
+static int expand_r(int indent_number, AtspiAccessible *object, bool check_integrity)
+{
+       char *indent = multiply_string(' ', indent_number*indent_width);
+       if (indent != NULL) {
+               printf("%s", indent);
+               free(indent);
+       }
+
+       char *node_info = get_info(object);
+       if (node_info != NULL) {
+               printf("%s", node_info);
+               free(node_info);
+       }
+
+       int count = atspi_accessible_get_child_count(object, NULL);
+       for (int i = 0; i < count; i++) {
+               AtspiAccessible *child = atspi_accessible_get_child_at_index(object, i, NULL);
+               if (child) {
+                       if (check_integrity)
+                               test_atspi_parent_child_relation(child, object, i);
+
+                       expand_r(indent_number + 1, child, check_integrity);
+               }
+       }
+       return 0;
+}
+
+void print_help()
+{
+       printf("AT-SPI2-CORE-UTIL\n\n");
+       printf("USAGE: at_spi2_tool [OPTION] [PARAMETER] ...\n\n");
+       printf("OPTION LIST:\n");
+       printf("-h, --help\t\tshow this message\n");
+       printf("-g, --show-legend\tprint AT-SPI state legend\n");
+       printf("-l, --list-apps\t\tlist all applications of desktop\n");
+       printf("-d, --tree-dump\t\tdump tree for selected application\n");
+       printf("-c, --tree-check\tcheck tree for selected application\n");
+       printf("-i, --indent\t\tset indentation size\n");
+       printf("\nEXAMPLE:\n");
+       printf("\tat_spi2_tool -i3 -d quickpanel\n");
+       printf("\t  show AT-SPI tree for node \"quickpanel\" using three-space indentation\n");
+       printf("\tat_spi2_tool -i1 -c starter\n");
+       printf("\t  show AT-SPI tree with integrity test for node \"starter\" using one-space indentation\n");
+}
+
+static void atspi_tree_traverse(AtspiAccessible *desktop, const char *app_name, bool dump, bool check)
+{
+       int count = atspi_accessible_get_child_count(desktop, NULL);
+       for (int i = 0; i < count; i++) {
+               AtspiAccessible *child = atspi_accessible_get_child_at_index(desktop, i, NULL);
+               if (child) {
+                       char *name = atspi_accessible_get_name(child, NULL);
+
+                       if (!dump && !check)
+                               printf("%s\n", name);
+
+                       if (dump && name && !strcmp(name, app_name)) {
+                               expand_r(0, child, false);
+                               break;
+                       }
+
+                       if (check && name && !strcmp(name, app_name)) {
+                               test_atspi_parent_child_relation(child, desktop, i);
+                               expand_r(0, child, true);
+                               break;
+                       }
+               }
+       }
+}
+
+static void run_command(int argc, char *argv[], AtspiAccessible *desktop)
+{
+       struct option long_options[] = {
+               {"help", no_argument, 0, 'h'},
+               {"show-legend", no_argument, 0, 'g'},
+               {"list-apps", no_argument, 0, 'l'},
+               {"tree-dump", required_argument, 0, 'd'},
+               {"tree-check", required_argument, 0, 'c'},
+               {"indent", required_argument, 0, 'i'},
+       };
+
+       int command = 0;
+       int option_index = 0;
+
+       while (TRUE) {
+               command = getopt_long(argc, argv, "hgld:c:i:", long_options, &option_index);
+
+               if (command == ERROR_STATE)
+                       break;
+
+               switch (command) {
+               case 'h':
+                       print_help();
+                       break;
+
+               case 'g':
+                       _print_atspi_states_legend();
+                       break;
+
+               case 'l':
+                       atspi_tree_traverse(desktop, NULL, false, false);
+                       break;
+
+               case 'd':
+                       atspi_tree_traverse(desktop, optarg, true, false);
+                       break;
+
+               case 'c':
+                       atspi_tree_traverse(desktop, optarg, false, true);
+                       break;
+
+               case 'i':
+                       set_indent(atoi(optarg));
+                       break;
+
+               case '?':
+                       break;
+
+               default:
+                       abort();
+               }
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       if (argc < 2) {
+               printf("Arguments required. Type %s --help.\n", argv[0]);
+               return -1;
+       }
+
+       AtspiAccessible *desktop = atspi_get_desktop(0);
+
+       if (!desktop) {
+               fprintf(stderr, "atspi_get_desktop failed\n");
+               return 1;
+       }
+
+       atspi_init();
+
+       run_command(argc, argv, desktop);
+
+       return 0;
+}