Add navigation helper functions for screen-reader and friends 06/117306/24
authorRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Fri, 3 Mar 2017 15:29:55 +0000 (16:29 +0100)
committerRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Wed, 14 Jun 2017 09:58:46 +0000 (11:58 +0200)
This patch adds GetNavigableAtPoint accessibility interface - the function finds accessibility object at given coordinates.
This massively (10-15 times) reduce amount of IPC calls (and time spent) in typical screen-reader scenario.

Change-Id: Iff5820e280bef64414d0f07e9e2d5927a29bae4d

src/lib/elm_atspi_bridge.c

index 49a844212a4e86a2445529e483ae3017ddc7a107..f872a7304198240b26ed45d44e350b77986ca751 100644 (file)
@@ -178,6 +178,10 @@ static Eina_Bool _on_object_add(void *data, Eo *obj, const Eo_Event_Description
 static Eina_Bool _on_object_del(void *data, Eo *obj, const Eo_Event_Description *event EINA_UNUSED, void *event_info EINA_UNUSED);
 static void _plug_connect(Eldbus_Connection *conn, Eo *proxy);
 static void _socket_ifc_create(Eldbus_Connection *conn, Eo *proxy);
+static void _object_get_bus_name_and_path(Eo *bridge, Eo *obj, const char **bus_name, const char **path);
+// TIZEN_ONLY(20170310) - implementation of get object under coordinates for accessibility
+static Eo *_calculate_navigable_accessible_at_point(Eo *any_object, Eina_Bool coord_type, int x, int y);
+//
 
 typedef struct {
      const Eo_Event_Description *desc;
@@ -519,13 +523,27 @@ static AtspiRelationType _elm_relation_to_atspi_relation(Elm_Atspi_Relation_Type
    return ATSPI_RELATION_NULL;
 }
 
+static Elm_Atspi_Relation_Type _atspi_relation_to_elm_relation(AtspiRelationType type)
+{
+   unsigned int i;
+   for(i = 0; i < sizeof(elm_relation_to_atspi_relation_mapping) / sizeof(elm_relation_to_atspi_relation_mapping[0]); ++i)
+     {
+       if (elm_relation_to_atspi_relation_mapping[i] == (int)type) return (Elm_Atspi_Relation_Type)i;
+     }
+   return ELM_ATSPI_RELATION_NULL;
+}
+
+static AtspiRole _elm_role_to_atspi_role(Elm_Atspi_Role role)
+{
+   return role > ELM_ATSPI_ROLE_LAST_DEFINED ? ATSPI_ROLE_LAST_DEFINED : elm_roles_to_atspi_roles[role][1];
+}
+
 static Eldbus_Message *
 _accessible_get_role(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg)
 {
    const char *obj_path = eldbus_message_path_get(msg);
    Eo *bridge = eldbus_service_object_data_get(iface, ELM_ATSPI_BRIDGE_CLASS_NAME);
    Eo *obj = _bridge_object_from_path(bridge, obj_path);
-   AtspiRole atspi_role = ATSPI_ROLE_INVALID;
    Elm_Atspi_Role role;
 
    ELM_ATSPI_OBJ_CHECK_OR_RETURN_DBUS_ERROR(obj, ELM_INTERFACE_ATSPI_ACCESSIBLE_MIXIN, msg);
@@ -535,7 +553,7 @@ _accessible_get_role(const Eldbus_Service_Interface *iface, const Eldbus_Message
    Eldbus_Message *ret = eldbus_message_method_return_new(msg);
    EINA_SAFETY_ON_NULL_RETURN_VAL(ret, NULL);
 
-   atspi_role = role > ELM_ATSPI_ROLE_LAST_DEFINED ? ATSPI_ROLE_LAST_DEFINED : elm_roles_to_atspi_roles[role][1];
+   AtspiRole atspi_role = _elm_role_to_atspi_role(role);
    eldbus_message_arguments_append(ret, "u", atspi_role);
    return ret;
 }
@@ -904,12 +922,69 @@ _accessible_gesture_do(const Eldbus_Service_Interface *iface, const Eldbus_Messa
    gesture_info.event_time = event_time;
    eo_do(obj, result = elm_interface_atspi_accessible_gesture_do(gesture_info));
    eldbus_message_arguments_append(ret, "b", result);
+   return ret;
+}
+//
+
+// TIZEN_ONLY(20170310) - implementation of get object under coordinates for accessibility
+static Eldbus_Message *
+_accessible_get_navigable_at_point(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg)
+{
+   const char *obj_path = eldbus_message_path_get(msg);
+   Eo *bridge = eldbus_service_object_data_get(iface, ELM_ATSPI_BRIDGE_CLASS_NAME);
+   // TIZEN_ONLY(20160705) - enable atspi_proxy to work
+   ELM_ATSPI_BRIDGE_DATA_GET_OR_RETURN_VAL(bridge, pd, NULL);
+   //
+   Eo *obj = _bridge_object_from_path(bridge, obj_path);
+   int x, y;
+   Eldbus_Message *ret;
+   AtspiCoordType coord_type;
+   Eldbus_Message_Iter *iter;
+
+   ELM_ATSPI_OBJ_CHECK_OR_RETURN_DBUS_ERROR(obj, ELM_INTERFACE_ATSPI_ACCESSIBLE_MIXIN, msg);
+
+   // TIZEN_ONLY(20161213) - do not response if ecore evas is obscured
+   const Ecore_Evas *ee = ecore_evas_ecore_evas_get(evas_object_evas_get(obj));
+   if (ecore_evas_obscured_get(ee))
+     return eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.Failed", "ecore evas is obscured.");
+   //
 
+   if (!eldbus_message_arguments_get(msg, "iiu", &x, &y, &coord_type))
+     return eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.InvalidArgs", "Invalid index type.");
+
+   // TIZEN_ONLY(20160705) - enable atspi_proxy to work
+   Evas_Object *top = elm_object_top_widget_get(obj);
+   int sx = 0;
+   int sy = 0;
+   eo_do(top, elm_interface_atspi_component_socket_offset_get(&sx, &sy));
+   x = x - sx;
+   y = y - sy;
+   //
+
+   ret = eldbus_message_method_return_new(msg);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(ret, NULL);
+
+   iter = eldbus_message_iter_get(ret);
+
+   Eina_Bool type = coord_type == ATSPI_COORD_TYPE_SCREEN ? EINA_TRUE : EINA_FALSE;
+   Eo *accessible = _calculate_navigable_accessible_at_point(obj, type, x, y);
+   _bridge_iter_object_reference_append(bridge, iter, accessible);
+   _bridge_object_register(bridge, accessible);
+
+   const char *obj_bus_name = NULL, *ret_bus_name = NULL;
+   _object_get_bus_name_and_path(bridge, obj, &obj_bus_name, NULL);
+   if (accessible) _object_get_bus_name_and_path(bridge, accessible, &ret_bus_name, NULL);
+
+   unsigned char recurse = obj_bus_name && ret_bus_name && strcmp(obj_bus_name, ret_bus_name) != 0;
+   eldbus_message_iter_basic_append(iter, 'y', recurse);
    return ret;
 }
 //
 
 static const Eldbus_Method accessible_methods[] = {
+// TIZEN_ONLY(20170310) - implementation of get object under coordinates for accessibility
+   { "GetNavigableAtPoint", ELDBUS_ARGS({"i", "x"}, {"i", "y"}, {"u", "coord_type"}), ELDBUS_ARGS({"(so)", "accessible"}), _accessible_get_navigable_at_point, 0 },
+//
    { "GetChildAtIndex", ELDBUS_ARGS({"i", "index"}), ELDBUS_ARGS({"(so)", "Accessible"}), _accessible_child_at_index, 0 },
    { "GetChildren", NULL, ELDBUS_ARGS({"a(so)", "children"}), _accessible_get_children, 0 },
    { "GetIndexInParent", NULL, ELDBUS_ARGS({"i", "index"}), _accessible_get_index_in_parent, 0 },
@@ -3276,38 +3351,38 @@ static const Eldbus_Service_Interface_Desc collection_iface_desc = {
 };
 
 static void
-_bridge_iter_object_reference_append(Eo *bridge, Eldbus_Message_Iter *iter, const Eo *obj)
+_object_get_bus_name_and_path(Eo *bridge, Eo *obj, const char **bus_name, const char **path)
 {
    ELM_ATSPI_BRIDGE_DATA_GET_OR_RETURN(bridge, pd);
-   Eldbus_Message_Iter *iter_struct = eldbus_message_iter_container_new(iter, 'r', NULL);
-   EINA_SAFETY_ON_NULL_RETURN(iter);
 
    // TIZEN_ONLY(20160705) - enable atspi_proxy to work
    if (eo_isa(obj, ELM_ATSPI_PROXY_CLASS))
      {
         const char *pbus = "", *ppath = ATSPI_DBUS_PATH_NULL;
         eo_do(obj, elm_obj_atspi_proxy_address_get(&pbus, &ppath));
-        if (!pbus || !ppath)
+        if (pbus && ppath)
           {
-             DBG("Invalid proxy address! Address not set before connecting/listening. Or after proxy is removed.");
-             const char *path = _bridge_path_from_object(bridge, obj);
-             eldbus_message_iter_basic_append(iter_struct, 's', eldbus_connection_unique_name_get(pd->a11y_bus));
-             eldbus_message_iter_basic_append(iter_struct, 'o', path);
-          }
-        else
-          {
-             eldbus_message_iter_basic_append(iter_struct, 's', pbus);
-             eldbus_message_iter_basic_append(iter_struct, 'o', ppath);
+            if (bus_name) *bus_name = pbus;
+            if (path) *path = ppath;
+             return;
           }
+       DBG("Invalid proxy address! Address not set before connecting/listening. Or after proxy is removed.");
      }
-   else
-     {
-        const char *path = _bridge_path_from_object(bridge, obj);
-        eldbus_message_iter_basic_append(iter_struct, 's', eldbus_connection_unique_name_get(pd->a11y_bus));
-        eldbus_message_iter_basic_append(iter_struct, 'o', path);
-     }
+   if (bus_name) *bus_name = eldbus_connection_unique_name_get(pd->a11y_bus);
+   if (path) *path = _bridge_path_from_object(bridge, obj);
    //
+}
+
+static void
+_bridge_iter_object_reference_append(Eo *bridge, Eldbus_Message_Iter *iter, const Eo *obj)
+{
+   EINA_SAFETY_ON_NULL_RETURN(iter);
 
+   const char *pbus = "", *ppath = ATSPI_DBUS_PATH_NULL;
+   _object_get_bus_name_and_path(bridge, obj, &pbus, &ppath);
+   Eldbus_Message_Iter *iter_struct = eldbus_message_iter_container_new(iter, 'r', NULL);
+   eldbus_message_iter_basic_append(iter_struct, 's', pbus);
+   eldbus_message_iter_basic_append(iter_struct, 'o', ppath);
    eldbus_message_iter_container_close(iter, iter_struct);
 }
 
@@ -3388,14 +3463,14 @@ _cache_item_reference_append_cb(Eo *bridge, Eo *data, Eldbus_Message_Iter *iter_
   Eina_List *children_list = NULL, *l;
   Eo *child;
 
-  //TIZEN_ONLY(20150709) Do not register children of MANAGES_DESCENDATS objects 
+  //TIZEN_ONLY(20150709) Do not register children of MANAGES_DESCENDATS objects
   Elm_Atspi_State_Set ss;
   eo_do(data, ss = elm_interface_atspi_accessible_state_set_get());
   //
   iter_sub_array = eldbus_message_iter_container_new(iter_struct, 'a', "(so)");
   EINA_SAFETY_ON_NULL_GOTO(iter_sub_array, fail);
 
-  //TIZEN_ONLY(20150709) Do not register children of MANAGES_DESCENDATS objects 
+  //TIZEN_ONLY(20150709) Do not register children of MANAGES_DESCENDATS objects
   if (!STATE_TYPE_GET(ss, ELM_ATSPI_STATE_MANAGES_DESCENDANTS))
     {
        eo_do(data, children_list = elm_interface_atspi_accessible_children_get());
@@ -3403,7 +3478,7 @@ _cache_item_reference_append_cb(Eo *bridge, Eo *data, Eldbus_Message_Iter *iter_
        EINA_LIST_FOREACH(children_list, l, child)
           _bridge_iter_object_reference_append(bridge, iter_sub_array, child);
 
-  //TIZEN_ONLY(20150709) Do not register children of MANAGES_DESCENDATS objects 
+  //TIZEN_ONLY(20150709) Do not register children of MANAGES_DESCENDATS objects
        eina_list_free(children_list);
     }
   //
@@ -3586,6 +3661,259 @@ _component_get_accessible_at_point(const Eldbus_Service_Interface *iface EINA_UN
    return ret;
 }
 
+// TIZEN_ONLY(20170310) - implementation of get object under coordinates for accessibility
+static AtspiRole _object_get_role_impl(void *ptr)
+{
+   Elm_Atspi_Role role;
+   Eo *obj = (Eo*)ptr;
+   eo_do(obj, role = elm_interface_atspi_accessible_role_get());
+   return _elm_role_to_atspi_role(role);
+}
+
+static uint64_t _object_get_state_set_impl(void *ptr)
+{
+   Elm_Atspi_State_Set states;
+   Eo *obj = (Eo*)ptr;
+   eo_do(obj, states = elm_interface_atspi_accessible_state_set_get());
+   return _elm_atspi_state_set_to_atspi_state_set(states);
+}
+
+static void *_get_object_in_relation_by_type_impl(void *ptr, AtspiRelationType type)
+{
+   if (ptr)
+     {
+       const Eo *source = ptr;
+       Elm_Atspi_Relation_Set relations;
+       Elm_Atspi_Relation_Type expected_relation_type = _atspi_relation_to_elm_relation(type);
+       eo_do(source, relations = elm_interface_atspi_accessible_relation_set_get());
+       Elm_Atspi_Relation *rel;
+       Eina_List *l;
+       EINA_LIST_FOREACH(relations, l, rel)
+         {
+           if (rel->type == expected_relation_type) return rel->objects ? rel->objects->data : NULL;
+         }
+     }
+   return NULL;
+}
+
+static unsigned char _object_is_zero_size_impl(void *ptr)
+{
+   int x, y, w, h;
+   Eo *obj = (Eo*)ptr;
+   eo_do(obj, elm_interface_atspi_component_extents_get(EINA_TRUE, &x, &y, &w, &h));
+   return w == 0 || h == 0;
+}
+
+unsigned char _object_is_scrollable_impl(void *ptr)
+{
+   Eo *obj = (Eo*)ptr;
+   return eo_isa(obj, ELM_INTERFACE_SCROLLABLE_MIXIN);
+}
+
+void *_get_parent_impl(void *ptr)
+{
+   Eo *obj = (Eo*)ptr;
+   if (elm_widget_is(obj)) return elm_widget_parent_get(obj);
+   return elm_widget_parent_widget_get(obj);
+}
+
+void *_object_at_point_get_impl(void *ptr, int x, int y, unsigned char coordinates_are_screen_based)
+{
+   Eo *obj = (Eo*)ptr, *target;
+   eo_do(obj, target = elm_interface_atspi_component_accessible_at_point_get(coordinates_are_screen_based, x, y));
+   return target;
+}
+
+unsigned char _object_contains_impl(void *ptr, int x, int y, unsigned char coordinates_are_screen_based)
+{
+   Eo *obj = (Eo*)ptr;
+   Eina_Bool return_value;
+   eo_do(obj, return_value = elm_interface_atspi_component_contains(coordinates_are_screen_based, x, y));
+   return return_value ? 1 : 0;
+}
+
+unsigned char _object_is_proxy_impl(void *ptr)
+{
+   Eo *obj = (Eo*)ptr;
+   return eo_isa(obj, ELM_ATSPI_PROXY_CLASS) ? 1 : 0;
+}
+typedef struct {
+   AtspiRole (*object_get_role)(void *ptr);
+   uint64_t (*object_get_state_set)(void *ptr);
+   void *(*get_object_in_relation_by_type)(void *ptr, AtspiRelationType type);
+   unsigned char (*object_is_zero_size)(void *ptr);
+   void *(*get_parent)(void *ptr);
+   unsigned char (*object_is_scrollable)(void *ptr);
+   void *(*object_at_point_get)(void *ptr, int x, int y, unsigned char coordinates_are_screen_based);
+   unsigned char (*object_contains)(void *ptr, int x, int y, unsigned char coordinates_are_screen_based);
+   unsigned char (*object_is_proxy)(void *ptr);
+} accessibility_navigation_pointer_table;
+
+static unsigned char _accept_object_check_role(accessibility_navigation_pointer_table *table, void *obj)
+{
+   AtspiRole role = table->object_get_role(obj);
+   switch (role)
+     {
+       case ATSPI_ROLE_APPLICATION:
+       case ATSPI_ROLE_FILLER:
+       case ATSPI_ROLE_SCROLL_PANE:
+       case ATSPI_ROLE_SPLIT_PANE:
+       case ATSPI_ROLE_WINDOW:
+       case ATSPI_ROLE_IMAGE:
+       case ATSPI_ROLE_LIST:
+       case ATSPI_ROLE_ICON:
+       case ATSPI_ROLE_TOOL_BAR:
+       case ATSPI_ROLE_REDUNDANT_OBJECT:
+       case ATSPI_ROLE_COLOR_CHOOSER:
+       case ATSPI_ROLE_PANEL:
+       case ATSPI_ROLE_TREE_TABLE:
+       case ATSPI_ROLE_PAGE_TAB_LIST:
+       case ATSPI_ROLE_PAGE_TAB:
+       case ATSPI_ROLE_SPIN_BUTTON:
+       case ATSPI_ROLE_INPUT_METHOD_WINDOW:
+       case ATSPI_ROLE_EMBEDDED:
+       case ATSPI_ROLE_INVALID:
+       case ATSPI_ROLE_NOTIFICATION:
+         return 0;
+       default:
+         break;
+     }
+   return 1;
+}
+
+static unsigned char _state_set_is_set(uint64_t state_set, AtspiStateType state)
+{
+   return (state_set & ((uint64_t)1 << (unsigned int)state)) != 0;
+}
+
+static unsigned char _object_is_item(accessibility_navigation_pointer_table *table, void *obj)
+{
+   AtspiRole role = table->object_get_role(obj);
+   return role == ATSPI_ROLE_LIST_ITEM || role == ATSPI_ROLE_MENU_ITEM;
+}
+
+static unsigned char _object_is_highlightable(accessibility_navigation_pointer_table *table, void *obj)
+{
+   uint64_t state_set = table->object_get_state_set(obj);
+   return _state_set_is_set(state_set, ATSPI_STATE_HIGHLIGHTABLE);
+}
+
+static unsigned char _object_is_showing(accessibility_navigation_pointer_table *table, void *obj)
+{
+   uint64_t state_set = table->object_get_state_set(obj);
+   return _state_set_is_set(state_set, ATSPI_STATE_SHOWING);
+}
+
+static unsigned char _object_is_collapsed(accessibility_navigation_pointer_table *table, void *obj)
+{
+   uint64_t state_set = table->object_get_state_set(obj);
+   return
+      _state_set_is_set(state_set, ATSPI_STATE_EXPANDABLE) &&
+      !_state_set_is_set(state_set, ATSPI_STATE_EXPANDED);
+}
+
+static unsigned char _object_has_modal_state(accessibility_navigation_pointer_table *table, void *obj)
+{
+   uint64_t state_set = table->object_get_state_set(obj);
+   return _state_set_is_set(state_set, ATSPI_STATE_MODAL);
+}
+
+static unsigned char _object_is_zero_size(accessibility_navigation_pointer_table *table, void *obj)
+{
+   return table->object_is_zero_size(obj);
+}
+
+static void *_get_scrollable_parent(accessibility_navigation_pointer_table *table, void *obj)
+{
+   while(obj)
+     {
+       obj = table->get_parent(obj);
+       if (obj && table->object_is_scrollable(obj)) return obj;
+     }
+   return NULL;
+}
+static unsigned char _accept_object(accessibility_navigation_pointer_table *table, void *obj)
+{
+   if (!obj) return 0;
+   if (!_accept_object_check_role(table, obj)) return 0;
+   if (table->get_object_in_relation_by_type(obj, ATSPI_RELATION_CONTROLLED_BY) != NULL) return 0;
+   if (!_object_is_highlightable(table, obj)) return 0;
+
+   if (_get_scrollable_parent(table, obj) != NULL)
+     {
+       void *parent = table->get_parent(obj);
+
+       if (parent)
+         {
+           return !_object_is_item(table, obj) || !_object_is_collapsed(table, parent);
+         }
+     }
+   else
+     {
+       if (_object_is_zero_size(table, obj)) return 0;
+       if (!_object_is_showing(table, obj)) return 0;
+     }
+   return 1;
+}
+
+static void *_calculate_navigable_accessible_at_point_impl(accessibility_navigation_pointer_table *table,
+          void *root, int x, int y, unsigned char coordinates_are_screen_based)
+{
+   if (!root) return NULL;
+
+   void *return_value = NULL;
+   while (1)
+     {
+       void *target = table->object_at_point_get(root, x, y, coordinates_are_screen_based);
+       if (!target) break;
+
+       // always return proxy, so atspi lib can call on it again
+       if (table->object_is_proxy(target)) return target;
+
+       root = target;
+       void *relation_obj = table->get_object_in_relation_by_type(root, ATSPI_RELATION_CONTROLLED_BY);
+       unsigned char contains = 0;
+       if (relation_obj)
+         {
+           contains = table->object_contains(relation_obj, x, y, coordinates_are_screen_based);
+           if (contains) root = relation_obj;
+         }
+
+       if (_accept_object(table, root))
+         {
+           return_value = root;
+           if (contains) break;
+         }
+     }
+
+   if (return_value && _object_has_modal_state(table, return_value)) return_value = NULL;
+   return return_value;
+}
+
+accessibility_navigation_pointer_table construct_accessibility_navigation_pointer_table(void)
+{
+   accessibility_navigation_pointer_table table;
+#define INIT(n) table.n = _## n ## _impl
+   INIT(object_get_role);
+   INIT(object_get_state_set);
+   INIT(get_object_in_relation_by_type);
+   INIT(object_is_zero_size);
+   INIT(get_parent);
+   INIT(object_is_scrollable);
+   INIT(object_at_point_get);
+   INIT(object_contains);
+   INIT(object_is_proxy);
+#undef INIT
+   return table;
+}
+static Eo *_calculate_navigable_accessible_at_point(Eo *root, Eina_Bool coord_type, int x, int y)
+{
+   accessibility_navigation_pointer_table table = construct_accessibility_navigation_pointer_table();
+   Eo *result = (Eo*)_calculate_navigable_accessible_at_point_impl(&table, root, x, y, coord_type ? 1 : 0);
+   return result;
+}
+//
+
 static Eldbus_Message *
 _component_get_extents(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg)
 {