THREE_FINGERS_FLICK_DOWN,
ONE_FINGER_SINGLE_TAP,
ONE_FINGER_DOUBLE_TAP,
+ TWO_FINGERS_FLICK_UP,
+ TWO_FINGERS_FLICK_LEFT,
+ TWO_FINGERS_FLICK_RIGHT,
+ TWO_FINGERS_FLICK_DOWN,
GESTURES_COUNT,
};
typedef enum _Gesture Gesture;
--- /dev/null
+#ifndef SMART_NAVI_OBJECT_CACHE_H_H
+#define SMART_NAVI_OBJECT_CACHE_H_H
+
+#include <atspi/atspi.h>
+
+typedef struct {
+ // Boundaries of object taken from atspi_component_get_extents function.
+ // NULL if object do not implemetn AtspiComponent interface
+ AtspiRect *bounds;
+} ObjectCache;
+
+typedef void (*ObjectCacheReadyCb)(void *data);
+
+/**
+ * @brief Recursivly build ObjectCache structures for root Accessible
+ * object from and its descendants. (Children's children also, etc.)
+ *
+ * @param root starting object.
+ *
+ * @remarks This function may block main-loop for significant ammount of time.
+ * Flushes all previously cached items.
+ */
+void object_cache_build(AtspiAccessible *root);
+
+/**
+ * @brief Recursivly build ObjectCache structures for ever Accessible
+ * object from root and its all descendants.
+ *
+ * @param root starting object.
+ * @param bulk_size Ammount of AtspiAccessible that will be cached on every
+ * ecore_main_loop entering into 'idle' state.
+ * @param cb function to be called after creating cache is done.
+ * @param user_data passed to cb
+ *
+ * @remarks Flushes all previously cached items.
+ */
+void object_cache_build_async(AtspiAccessible *root, int bulk_size, ObjectCacheReadyCb cb, void *user_data);
+
+/**
+ * @brief Gets and ObjectCache item from accessible object
+ *
+ * @param obj AtspiAccessible
+ *
+ * @remarks If obj is not in cache function will block main loop and build ObjectCache
+ * struct.
+ */
+const ObjectCache *object_cache_get(AtspiAccessible *obj);
+
+/**
+ * @brief Shoutdown cache.
+ *
+ * Function will free all gathered caches.
+ *
+ * @remarks Use it after You have used any of above object_cache_* functions.
+ */
+void object_cache_shutdown(void);
+
+#endif /* end of include guard: OBJECT_CACHE_H_H */
--- /dev/null
+#ifndef SMART_NAVI_PIVOT_CHOOSER_H_
+#define SMART_NAVI_PIVOT_CHOOSER_H_
+
+#include <atspi/atspi.h>
+
+/**
+ * @brief Some heuristic choosing candidate to reacieve highlight.
+ *
+ * @param win Accessibility search tree object root.
+ *
+ * @return Highlight candidate
+ */
+AtspiAccessible *pivot_chooser_pivot_get(AtspiAccessible *win);
+
+#endif /* end of include guard: PIVOT_CHOOSER_H_ */
--- /dev/null
+#ifndef SMART_NAVI_POSITION_SORT_H_
+#define SMART_NAVI_POSITION_SORT_H_
+
+#include <atspi/atspi.h>
+#include <Eina.h>
+
+
+/**
+ * @brief Sort objects by position they appear on screen
+ *
+ * @ret list List of lists
+ */
+Eina_List *position_sort(const Eina_List *obj);
+
+#endif /* end of include guard: POSITION_SORT_H_ */
--- /dev/null
+#ifndef SMART_NAVI_STRUCTURAL_NAVI_H
+#define SMART_NAVI_STRUCTURAL_NAVI_H_
+
+#include <atspi/atspi.h>
+
+AtspiAccessible *structural_navi_same_level_next(AtspiAccessible *current);
+AtspiAccessible *structural_navi_same_level_prev(AtspiAccessible *current);
+
+AtspiAccessible *structural_navi_level_up(AtspiAccessible *current);
+AtspiAccessible *structural_navi_level_down(AtspiAccessible *current);
+
+AtspiAccessible *structural_navi_app_chain_next(AtspiAccessible *current);
+AtspiAccessible *structural_navi_app_chain_prev(AtspiAccessible *current);
+
+#endif /* end of include guard: STRUCTURAL_NAVI_KML0ETIJ */
if(!strcmp("OneFingerDoubleTap", gesture_name))
return ONE_FINGER_DOUBLE_TAP;
+ if(!strcmp("TwoFingersFlickLeft", gesture_name))
+ return TWO_FINGERS_FLICK_LEFT;
+
+ if(!strcmp("TwoFingersFlickRight", gesture_name))
+ return TWO_FINGERS_FLICK_RIGHT;
+
+ if(!strcmp("TwoFingersFlickUp", gesture_name))
+ return TWO_FINGERS_FLICK_UP;
+
+ if(!strcmp("TwoFingersFlickDown", gesture_name))
+ return TWO_FINGERS_FLICK_DOWN;
+
return GESTURES_COUNT;
}
#include "gesture_tracker.h"
#include "window_tracker.h"
#include "keyboard_tracker.h"
+#include "pivot_chooser.h"
+#include "structural_navi.h"
+#include "object_cache.h"
#define QUICKPANEL_DOWN TRUE
#define QUICKPANEL_UP FALSE
static AtspiAccessible *previous_obj;
static AtspiAccessible *current_obj;
static AtspiAccessible *top_window;
+static AtspiScrollable *scrolled_obj;
+static Eina_Bool _window_cache_builded;
+
+static void
+_current_highlight_object_set(AtspiAccessible *obj)
+{
+ if (!obj)
+ {
+ atspi_component_clear_highlight(top_window);
+ return;
+ }
+ if (current_obj == obj)
+ {
+ DEBUG("Object already highlighted");
+ return;
+ }
+ if (obj && ATSPI_IS_COMPONENT(obj))
+ {
+ atspi_component_clear_highlight(top_window);
+ atspi_component_grab_highlight(obj, NULL);
+ current_obj = obj;
+ DEBUG("New highlighted object: %s, role: %s",
+ atspi_accessible_get_name(obj, NULL),
+ atspi_accessible_get_role_name(obj, NULL));
+ }
+ else
+ DEBUG("Unable to highlight object");
+}
void test_debug(AtspiAccessible *current_widget)
{
int height = 0, width = 0, xpos1 = 0, ypos1 = 0, xpos2 = 0, ypos2 = 0;
- object_get_wh(app, &width, &height);
- object_get_x_y(app, &xpos1, &ypos1);
+ object_get_wh(app, &width, &height); object_get_x_y(app, &xpos1, &ypos1);
gboolean is_visile = atspi_state_set_contains(state_set, state);
}
static void _focus_widget(Gesture_Info *info)
-{/*
+{
AtspiAccessible *target_widget = NULL;
AtspiComponent *target_component = NULL;
AtspiComponent *window_component = NULL;
return;
target_widget = atspi_component_get_accessible_at_point(window_component, info->x_begin, info->y_begin, ATSPI_COORD_TYPE_WINDOW, NULL);
- if(!target_widget)
- return;
-
- target_component = atspi_accessible_get_component(target_widget);
- if(!target_component)
- return;
+ if (target_widget) _current_highlight_object_set(target_widget);
else
- {
- if (atspi_component_grab_focus(target_component, NULL) == TRUE)
- {
- ERROR("Focus was changed\n");
- current_obj = target_widget;
- border_set(current_obj);
- }
- }
-*/
- AtspiAccessible *current_widget = get_nearest_widget(top_window, info->x_begin, info->y_begin, -1);
- if(current_widget)
- {
- current_obj = current_widget;
- AtspiComponent *focus_component = atspi_accessible_get_component(current_widget);
-
- if (atspi_component_grab_highlight(focus_component, NULL))
- ERROR("Highlighted\n");
-
- }
+ DEBUG("NO widget under (%d, %d) found",
+ info->x_begin, info->y_begin);
}
static AtspiAccessible *_focused_next_widget(void)
static void _focus_next(void)
{
- current_obj = _focused_next_widget();
+ AtspiAccessible *obj = structural_navi_same_level_next(current_obj);
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Next widget not found. Abort");
}
static void _focus_prev(void)
{
- current_obj = _focused_prev_widget();
+ AtspiAccessible *obj = structural_navi_same_level_prev(current_obj);
+ if (obj)
+ _current_highlight_object_set(obj);
+ else
+ DEBUG("Previous widget not found. Abort");
}
static void _value_inc_widget(void)
if(!strcmp(roleName, "entry"))
{
focus_component = atspi_accessible_get_component(current_widget);
- if (focus_component != NULL)
+ if (focus_component != NULL)
{
- if (atspi_component_grab_focus(focus_component, NULL) == TRUE)
- ERROR("Entry activated\n");
- return;
- }
+ if (atspi_component_grab_focus(focus_component, NULL) == TRUE)
+ ERROR("Entry activated\n");
+ return;
+ }
}
AtspiAction *action;
const char *clas = NULL;
int ret;
- ERROR(quickpanel_switch ? "QUICKPANEL STATE ON" : "QUICKPANEL STATE OFF");
+ ERROR(quickpanel_switch ? "QUICKPANEL STATE ON" : "QUICKPANEL STATE OFF");
Ecore_X_Illume_Quickpanel_State state;
ecore_main_loop_iterate();
}
+/**
+ * @brief Gets 'deepest' Scrollable accessible containing (x,y) point
+ */
+static AtspiScrollable*
+_find_scrollable_ancestor_at_xy(int x, int y)
+{
+ AtspiAccessible *ret = NULL;
+ AtspiRect *rect;
+ GError *error = NULL;
+
+ if (!top_window || !ATSPI_IS_COMPONENT(top_window))
+ {
+ DEBUG("No active window detected or no AtspiComponent interface available");
+ return NULL;
+ }
+
+ rect = atspi_component_get_extents(ATSPI_COMPONENT(top_window), ATSPI_COORD_TYPE_SCREEN, &error);
+ if (!rect || error)
+ {
+ ERROR("Unable to fetch window screen coordinates");
+ if (error) g_error_free(error);
+ return NULL;
+ }
+
+ // Scroll must originate within window borders
+ if ((x < rect->x) || (x > rect->x + rect->width) ||
+ (y < rect->y) || (y > rect->y + rect->height))
+ {
+ DEBUG("Scroll don't start within active window borders");
+ g_free(rect);
+ return NULL;
+ }
+
+ ret = atspi_component_get_accessible_at_point(ATSPI_COMPONENT(top_window), x, y, ATSPI_COORD_TYPE_SCREEN, &error);
+ if (!ret || error)
+ {
+ ERROR("Unable to get accessible objct at (%d, %d) screen coordinates.", x, y);
+ if (error) g_error_free(error);
+ return NULL;
+ }
+
+ // find accessible object with Scrollable interface
+ while (ret && (ret != top_window))
+ {
+ DEBUG("Testing for scrollability: %s %s",
+ atspi_accessible_get_name(ret, NULL), atspi_accessible_get_role_name(ret, NULL));
+ if (atspi_accessible_get_scrollable(ret))
+ {
+ DEBUG("Scrollable widget found at (%d, %d), name: %s, role: %s", x, y,
+ atspi_accessible_get_name(ret, NULL), atspi_accessible_get_role_name(ret, NULL));
+ return ATSPI_SCROLLABLE(ret);
+ }
+ ret = atspi_accessible_get_parent(ret, &error);
+ if (error)
+ {
+ ERROR("Unable to fetch AT-SPI parent");
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static void _widget_scroll_begin(Gesture_Info *gi)
+{
+ GError *error = NULL;
+
+ if (scrolled_obj)
+ {
+ ERROR("Scrolling context active when initializing new scrolling context! This should never happen.");
+ ERROR("Force reset of current scrolling context...");
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_END, gi->x_begin, gi->y_begin, &error);
+ if (error)
+ {
+ ERROR("Failed to reset scroll context.");
+ g_error_free(error);
+ scrolled_obj = NULL;
+ error = NULL;
+ }
+ }
+
+ scrolled_obj = _find_scrollable_ancestor_at_xy(gi->x_begin, gi->y_begin);
+
+ if (!scrolled_obj)
+ {
+ DEBUG("No scrollable widget found at (%d, %d) coordinates", gi->x_begin, gi->y_begin);
+ return;
+ }
+
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_START, gi->x_begin, gi->y_begin, &error);
+ if (error)
+ {
+ ERROR("Failed to initialize scroll operation");
+ scrolled_obj = NULL;
+ }
+}
+
+static void _widget_scroll_continue(Gesture_Info *gi)
+{
+ if (!scrolled_obj)
+ {
+ DEBUG("Scrolling context not initialized!");
+ return;
+ }
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_CONTINUE, gi->x_begin, gi->y_begin, NULL);
+}
+
+static void _widget_scroll_end(Gesture_Info *gi)
+{
+ if (!scrolled_obj)
+ {
+ ERROR("Scrolling context not initialized!");
+ return;
+ }
+
+ atspi_scrollable_scroll_after_pointer(scrolled_obj, ATSPI_SCROLL_POINTER_END, gi->x_begin, gi->y_begin, NULL);
+ scrolled_obj = NULL;
+}
+
+static void _goto_children_widget(void)
+{
+ AtspiAccessible *obj;
+ if (!current_obj)
+ {
+ DEBUG("No current object is set. Aborting diving into children structure");
+ return;
+ }
+ obj = structural_navi_level_down(current_obj);
+ if (obj) _current_highlight_object_set(obj);
+}
+
+static void _escape_children_widget(void)
+{
+ AtspiAccessible *obj;
+ if (!current_obj)
+ {
+ DEBUG("No current object is set. Aborting escaping from children structure");
+ return;
+ }
+ obj = structural_navi_level_up(current_obj);
+ if (obj) _current_highlight_object_set(obj);
+}
+
+static void _widget_scroll(Gesture_Info *gi)
+{
+ switch (gi->state)
+ {
+ case 0:
+ _widget_scroll_begin(gi);
+ break;
+ case 1:
+ _widget_scroll_continue(gi);
+ break;
+ case 2:
+ _widget_scroll_end(gi);
+ break;
+ default:
+ ERROR("Unrecognized gesture state: %d", gi->state);
+ }
+}
+
static void on_gesture_detected(void *data, Gesture_Info *info)
{
switch(info->type)
_focus_widget(info);
break;
case TWO_FINGERS_HOVER:
- ERROR("SCROLLING!");
+ _widget_scroll(info);
break;
case ONE_FINGER_FLICK_LEFT:
_focus_prev();
case ONE_FINGER_DOUBLE_TAP:
_activate_widget();
break;
- case THREE_FINGERS_FLICK_LEFT:
- // not implemented ??
+ case TWO_FINGERS_FLICK_LEFT:
+ _escape_children_widget();
break;
- case THREE_FINGERS_FLICK_RIGHT:
- // not implemented ??
+ case TWO_FINGERS_FLICK_RIGHT:
+ _goto_children_widget();
break;
case THREE_FINGERS_FLICK_DOWN:
_quickpanel_change_state(QUICKPANEL_DOWN);
_quickpanel_change_state(QUICKPANEL_UP);
break;
default:
- ERROR("Function not implemented for gesture type :%d", info->type);
+ DEBUG("Gesture type %d not handled in switch", info->type);
}
}
static void _get_first_focusable_widget(AtspiAccessible* obj)
{
- AtspiAccessible *current_widget = NULL;
- AtspiStateSet *state_set = NULL;
- AtspiComponent *focus_component = NULL;
- int i;
-
- gint nChild;
- gchar *winName = NULL;
- gchar *widgetName = NULL;
- gchar *roleName = NULL;
-
- if (obj == NULL)
- {
- ERROR("No window on top");
- return;
- }
-
- winName = atspi_accessible_get_name(obj, NULL);
- ERROR("Win name: %s\n", winName);
+ AtspiAccessible *current_widget = NULL;
+ AtspiStateSet *state_set = NULL;
+ AtspiComponent *focus_component = NULL;
+ int i;
- nChild = atspi_accessible_get_child_count(obj, NULL);
- ERROR("Child count of top win: %d\n", nChild);
+ gint nChild;
+ gchar *winName = NULL;
+ gchar *widgetName = NULL;
+ gchar *roleName = NULL;
- for (i = 0; i < nChild; i++)
- {
- current_widget = atspi_accessible_get_child_at_index(obj, i, NULL);
- widgetName = atspi_accessible_get_name(current_widget, NULL);
- ERROR("Widget name: %s\n", widgetName);
+ if (obj == NULL)
+ {
+ ERROR("No window on top");
+ return;
+ }
- roleName = atspi_accessible_get_role_name(current_widget, NULL);
- ERROR("Widget role: %s\n", roleName);
+ winName = atspi_accessible_get_name(obj, NULL);
+ ERROR("Win name: %s\n", winName);
- state_set = atspi_accessible_get_state_set(current_widget);
- if (ATSPI_IS_STATE_SET(state_set))
- {
+ nChild = atspi_accessible_get_child_count(obj, NULL);
+ ERROR("Child count of top win: %d\n", nChild);
- if (atspi_state_set_contains(ATSPI_STATE_SET(state_set),ATSPI_STATE_FOCUSABLE))
- {
- ERROR("FOCUSABLE widget name: %s \n", widgetName);
+ for (i = 0; i < nChild; i++)
+ {
+ current_widget = atspi_accessible_get_child_at_index(obj, i, NULL);
+ widgetName = atspi_accessible_get_name(current_widget, NULL);
+ ERROR("Widget name: %s\n", widgetName);
- current_obj = current_widget;
+ roleName = atspi_accessible_get_role_name(current_widget, NULL);
+ ERROR("Widget role: %s\n", roleName);
- focus_component = atspi_accessible_get_component(current_widget);
- if (focus_component != NULL)
- {
- ERROR("Focus component is not null\n");
- if (atspi_component_grab_highlight(focus_component, NULL))
- ERROR("Highlighted!\n");
- return;
- }
- }
- }
- }
+ state_set = atspi_accessible_get_state_set(current_widget);
+ if (ATSPI_IS_STATE_SET(state_set))
+ {
- ERROR("Not found any focusable widget");
+ focus_component = atspi_accessible_get_component(current_widget);
+ if (focus_component != NULL)
+ {
+ ERROR("Focus component is not null\n");
+ if (atspi_component_grab_highlight(focus_component, NULL))
+ ERROR("Highlighted!\n");
+ return;
+ }
+ }
+ }
+
+ ERROR("Not found any focusable widget");
- return;
+ return;
}
+static void
+_on_cache_builded(void *data)
+{
+ DEBUG("Cache builded");
+ _window_cache_builded = EINA_TRUE;
+}
static void on_window_activate(void *data, AtspiAccessible *window)
{
if(top_window)
{
ERROR("Window name: %s", atspi_accessible_get_name(window, NULL));
- _get_first_focusable_widget(top_window);
+ current_obj = pivot_chooser_pivot_get(top_window);
+ if (!current_obj)
+ {
+ ERROR("Unable to find pivot widget");
+ return;
+ }
+ else
+ {
+ DEBUG("Found pivot widget: name: %s, role: %s",
+ atspi_accessible_get_name(current_obj, NULL),
+ atspi_accessible_get_role_name(current_obj, NULL));
+ }
+ _window_cache_builded = EINA_FALSE;
+ object_cache_build_async(window, 5, _on_cache_builded, NULL);
}
else
{
ERROR("No top window found!");
top_window = NULL;
current_obj = NULL;
+ scrolled_obj = NULL;
}
}
--- /dev/null
+#include <Eina.h>
+#include <Ecore.h>
+
+#include "object_cache.h"
+#include "logger.h"
+
+static Eina_Hash *cache;
+static Ecore_Idler *idler;
+static Eina_List *toprocess;
+static void *user_data;
+static ObjectCacheReadyCb callback;
+static int bulk;
+
+static void
+_cache_item_free_cb(void *data)
+{
+ ObjectCache *co = data;
+ g_free(co->bounds);
+ free(co);
+}
+
+static void
+_list_obj_unref_and_free(Eina_List *toprocess)
+{
+ AtspiAccessible *obj;
+ EINA_LIST_FREE(toprocess, obj)
+ g_object_unref(obj);
+}
+
+static void
+_object_cache_free_internal(void)
+{
+ if (idler)
+ {
+ ecore_idler_del(idler);
+ idler = NULL;
+ }
+ if (toprocess)
+ {
+ _list_obj_unref_and_free(toprocess);
+ toprocess = NULL;
+ }
+ if (cache)
+ {
+ eina_hash_free(cache);
+ cache = NULL;
+ }
+}
+
+/**
+ * Returnes a list of all accessible object implementing AtkCompoment interface
+ * Eina_List should be free with eina_list_free function.
+ * Every AtspiAccessible in list should be unrefed with g_object_unref.
+ */
+static Eina_List*
+_cache_candidates_list_prepare(AtspiAccessible *root)
+{
+ Eina_List *toprocess = NULL, *ret = NULL;
+ int n, i;
+
+ if (!root) return NULL;
+
+ // Keep ref counter +1 on every object in returned list
+ g_object_ref(root);
+ toprocess = eina_list_append(toprocess, root);
+
+ while (toprocess)
+ {
+ AtspiAccessible *obj = eina_list_data_get(toprocess);
+ toprocess = eina_list_remove_list(toprocess, toprocess);
+ n = atspi_accessible_get_child_count(obj, NULL);
+ for (i = 0; i < n; i++)
+ {
+ AtspiAccessible *cand = atspi_accessible_get_child_at_index(obj, i, NULL);
+ toprocess = eina_list_append(toprocess, cand);
+ }
+ ret = eina_list_append(ret, obj);
+ }
+
+ return ret;
+}
+
+static ObjectCache*
+_cache_item_construct(AtspiAccessible *obj)
+{
+ ObjectCache *ret;
+ AtspiComponent *comp;
+
+ ret = calloc(1, sizeof(ObjectCache));
+ if (!ret)
+ {
+ ERROR("out-of memory");
+ return NULL;
+ }
+
+ if ((comp = atspi_accessible_get_component(obj)) != NULL)
+ ret->bounds = atspi_component_get_extents(comp, ATSPI_COORD_TYPE_SCREEN, NULL);
+
+ return ret;
+}
+
+static Eina_List*
+_cache_item_n_cache(Eina_List *objs, int count)
+{
+ int i = 0;
+ Eina_List *ret = objs;
+ ObjectCache *oc;
+ AtspiAccessible *obj;
+
+ for (i = 0; (i < count) && ret; i++)
+ {
+ obj = eina_list_data_get(ret);
+ ret = eina_list_remove_list(ret, ret);
+
+ oc = _cache_item_construct(obj);
+ if (!oc) break;
+
+ eina_hash_add(cache, obj, oc);
+ g_object_unref(obj);
+ }
+
+ return ret;
+}
+
+static Eina_Hash*
+_object_cache_new(void)
+{
+ return eina_hash_pointer_new(_cache_item_free_cb);
+}
+
+void
+object_cache_build(AtspiAccessible *root)
+{
+ Eina_List *objs;
+
+ _object_cache_free_internal();
+ cache = _object_cache_new();
+ if (!cache)
+ {
+ ERROR("ObjectCache: hash table creation failed");
+ return;
+ }
+
+ objs = _cache_candidates_list_prepare(root);
+ _cache_item_n_cache(objs, eina_list_count(objs));
+
+ return;
+}
+
+static Eina_Bool
+_do_cache(void *data)
+{
+ toprocess = _cache_item_n_cache(toprocess, bulk);
+ idler = NULL;
+
+ if (toprocess)
+ idler = ecore_idler_add(_do_cache, NULL);
+ else if (callback)
+ {
+ callback(user_data);
+ }
+
+ return EINA_FALSE;
+}
+
+void
+object_cache_build_async(AtspiAccessible *root, int bulk_size, ObjectCacheReadyCb cb, void *ud)
+{
+ Eina_List *objs;
+
+ _object_cache_free_internal();
+
+ callback = cb;
+ user_data = ud;
+ bulk = bulk_size;
+
+ cache = _object_cache_new();
+ if (!cache)
+ {
+ ERROR("ObjectCache: hash table creation failed");
+ return;
+ }
+
+ objs = _cache_candidates_list_prepare(root);
+ idler = ecore_idler_add(_do_cache, objs);
+
+ return;
+}
+
+void
+object_cache_shutdown(void)
+{
+ _object_cache_free_internal();
+}
+
+const ObjectCache*
+object_cache_get(AtspiAccessible *obj)
+{
+ ObjectCache *ret = NULL;
+ if (!cache)
+ {
+ cache = _object_cache_new();
+ if (!cache)
+ {
+ ERROR("ObjectCache: hash table creation failed");
+ return NULL;
+ }
+ }
+ else
+ ret = eina_hash_find(cache, obj);
+
+ if (!ret && !idler)
+ {
+ // fallback to blocking d-bus call
+ ret = _cache_item_construct(obj);
+ eina_hash_add(cache, obj, ret);
+ }
+
+ return ret;
+}
--- /dev/null
+#include <atspi/atspi.h>
+#include "logger.h"
+#include <Eina.h>
+
+
+/**
+ * @brief Finds first leaf in object hierarchy with given states,
+ * starting from object given as parent.
+ *
+ * This heuristic assumes that focused element have focused
+ * parent widgets.
+ */
+static AtspiAccessible*
+_pivot_with_state_top_down_find(AtspiAccessible *parent, AtspiStateType type)
+{
+ AtspiAccessible *ret = NULL;
+ AtspiStateSet *states;
+ int i;
+
+ states = atspi_accessible_get_state_set(parent);
+ if (!states || atspi_state_set_contains(states, type))
+ {
+ int n = atspi_accessible_get_child_count(parent, NULL);
+ if (n == 0) ret = parent;
+ for (i = 0; i < n; i++)
+ {
+ AtspiAccessible *child = atspi_accessible_get_child_at_index(parent, i, NULL);
+ if (!child) continue;
+
+ ret = _pivot_with_state_top_down_find(child, type);
+
+ g_object_unref(child);
+
+ if (ret) break;
+ }
+ }
+ g_object_unref(states);
+
+ return ret;
+}
+
+/**
+ * @brief Finds first leaf descendant of given object with state @p type
+ */
+static AtspiAccessible*
+_pivot_with_state_flat_find(AtspiAccessible *parent, AtspiStateType type)
+{
+ Eina_List *candidates = NULL, *queue = NULL;
+
+ // ref object to keep same ref count
+ g_object_ref(parent);
+ queue = eina_list_append(queue, parent);
+
+ while (queue)
+ {
+ AtspiAccessible *obj = eina_list_data_get(queue);
+ queue = eina_list_remove_list(queue, queue);
+
+ int n = atspi_accessible_get_child_count(obj, NULL);
+ if (n == 0)
+ candidates = eina_list_append(candidates, obj);
+ else
+ {
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ AtspiAccessible *child = atspi_accessible_get_child_at_index(obj, i, NULL);
+ if (child)
+ queue = eina_list_append(queue, child);
+ }
+ g_object_unref(obj);
+ }
+ }
+
+ // FIXME sort by (x,y) first ??
+ while (candidates)
+ {
+ AtspiAccessible *obj = eina_list_data_get(candidates);
+ candidates = eina_list_remove_list(candidates, candidates);
+
+ AtspiStateSet *states = atspi_accessible_get_state_set(obj);
+ if (states && atspi_state_set_contains(states, type))
+ {
+ g_object_unref(states);
+ g_object_unref(obj);
+ eina_list_free(candidates);
+
+ return obj;
+ }
+
+ g_object_unref(states);
+ g_object_unref(obj);
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Purpose of this methods is to find first visible object in
+ * hierarchy
+ */
+AtspiAccessible *pivot_chooser_pivot_get(AtspiAccessible *win)
+{
+ AtspiAccessible *ret;
+
+ if (atspi_accessible_get_role(win, NULL) != ATSPI_ROLE_WINDOW)
+ {
+ ERROR("Pivot search entry point must be a Window!");
+ return NULL;
+ }
+
+ DEBUG("Finding SHOWING widget using top-down method.");
+ ret = _pivot_with_state_top_down_find(win, ATSPI_STATE_SHOWING);
+ if (ret) return ret;
+
+ DEBUG("Finding SHOWING widget using top-down method.");
+ ret = _pivot_with_state_flat_find(win, ATSPI_STATE_SHOWING);
+ if (ret) return ret;
+
+ DEBUG("Finding FOCUSED widget using top-down method.");
+ ret = _pivot_with_state_top_down_find(win, ATSPI_STATE_FOCUSED);
+ if (ret) return ret;
+
+ DEBUG("Finding FOCUSED widget using flat search method.");
+ ret = _pivot_with_state_flat_find(win, ATSPI_STATE_FOCUSED);
+ if (ret) return ret;
+
+ return NULL;
+}
--- /dev/null
+#include "logger.h"
+#include "object_cache.h"
+#include "position_sort.h"
+
+
+static int
+_sort_by_y_cb(const void *a, const void *b)
+{
+ AtspiAccessible *objA, *objB;
+ const ObjectCache *cA, *cB;
+
+ objA = (AtspiAccessible*)a;
+ objB = (AtspiAccessible*)b;
+
+ cA = object_cache_get(objA);
+ cB = object_cache_get(objB);
+
+ return cA->bounds->y < cB->bounds->y;
+}
+
+static int
+_sort_by_x_cb(const void *a, const void *b)
+{
+ AtspiAccessible *objA, *objB;
+ const ObjectCache *cA, *cB;
+
+ objA = (AtspiAccessible*)a;
+ objB = (AtspiAccessible*)b;
+
+ cA = object_cache_get(objA);
+ cB = object_cache_get(objB);
+
+ return cA->bounds->x < cB->bounds->x;
+}
+
+static Eina_List*
+_get_zones(const Eina_List *objs)
+{
+ Eina_List *candidates = NULL, *l;
+ AtspiAccessible *obj;
+ const ObjectCache *oc;
+
+ EINA_LIST_FOREACH(objs, l, obj)
+ {
+ if (atspi_accessible_get_component(obj))
+ {
+ oc = object_cache_get(obj);
+ // some objects may implement AtspiCompoment interface, however
+ // they do not have valid sizes.
+ if ((oc->bounds->width < 0) || oc->bounds->height < 0)
+ continue;
+ candidates = eina_list_append(candidates, obj);
+ }
+ }
+
+ // Sort object by y - coordinate
+ return eina_list_sort(candidates, 0, _sort_by_y_cb);
+}
+
+static Eina_List*
+_get_lines(const Eina_List *objs)
+{
+ Eina_List *l, *line = NULL, *lines = NULL;
+ AtspiAccessible *obj;
+ const ObjectCache *line_beg;
+
+ EINA_LIST_FOREACH(objs, l, obj)
+ {
+ if (!line) {
+ // set first object in line
+ line = eina_list_append(line, obj);
+ line_beg = object_cache_get(obj);
+ continue;
+ }
+
+ const ObjectCache *oc = object_cache_get(obj);
+ // Object are considered as present in same line, if
+ // its y coordinate begins maximum 25% below
+ // y coordinate of first object in line.
+ if ((line_beg->bounds->y + 0.25 * line_beg->bounds->height) >
+ oc->bounds->y)
+ {
+ line = eina_list_append(line, obj);
+ continue;
+ }
+ lines = eina_list_append(lines, line);
+ line = NULL;
+ }
+
+ return lines;
+}
+
+Eina_List *position_sort(const Eina_List *objs)
+{
+ Eina_List *l, *line, *zones, *lines = NULL;
+
+ // Get list of objects occupying place on the screen
+ zones = _get_zones(objs);
+
+ // Cluster all zones into lines - verticaly
+ lines = _get_lines(zones);
+
+ // sort all zones in line - horizontaly
+ EINA_LIST_FOREACH(lines, l, line)
+ {
+ line = eina_list_sort(line, 0, _sort_by_x_cb);
+ eina_list_data_set(l, line);
+ }
+
+ if (zones) eina_list_free(zones);
+
+ return lines;
+}
--- /dev/null
+#include "structural_navi.h"
+#include "logger.h"
+
+AtspiAccessible *structural_navi_same_level_next(AtspiAccessible *current)
+{
+ AtspiAccessible *parent;
+ AtspiRole role;
+ int id, n;
+
+ parent = atspi_accessible_get_parent(current, NULL);
+ if (!parent) return NULL;
+
+ role = atspi_accessible_get_role(parent, NULL);
+ if (role != ATSPI_ROLE_DESKTOP_FRAME)
+ {
+ n = atspi_accessible_get_child_count(parent, NULL);
+ id = atspi_accessible_get_index_in_parent(current, NULL);
+ if (id == (n - 1)) DEBUG("End of children structure list reached.");
+ id = (id + 1) == n ? id : id + 1;
+ return atspi_accessible_get_child_at_index(parent, id, NULL);
+ }
+ return NULL;
+}
+
+AtspiAccessible *structural_navi_same_level_prev(AtspiAccessible *current)
+{
+ AtspiAccessible *parent;
+ AtspiRole role;
+ int id;
+
+ parent = atspi_accessible_get_parent(current, NULL);
+ if (!parent) return NULL;
+
+ role = atspi_accessible_get_role(parent, NULL);
+ if (role != ATSPI_ROLE_DESKTOP_FRAME)
+ {
+ id = atspi_accessible_get_index_in_parent(current, NULL);
+ if (id == 0) DEBUG("Begining of children structure list reached.");
+ id = id == 0 ? id : id - 1;
+ return atspi_accessible_get_child_at_index(parent, id, NULL);
+ }
+ return NULL;
+}
+
+AtspiAccessible *structural_navi_level_up(AtspiAccessible *current)
+{
+ AtspiAccessible *parent;
+ AtspiRole role;
+
+ parent = atspi_accessible_get_parent(current, NULL);
+ if (!parent) return NULL;
+
+ role = atspi_accessible_get_role(parent, NULL);
+ if (role != ATSPI_ROLE_DESKTOP_FRAME)
+ {
+ return parent;
+ }
+ return NULL;
+}
+
+AtspiAccessible *structural_navi_level_down(AtspiAccessible *current)
+{
+ int n = atspi_accessible_get_child_count(current, NULL);
+ if (!n) return NULL;
+
+ return atspi_accessible_get_child_at_index(current, 0, NULL);
+}
+
+static AtspiAccessible*
+_navi_app_chain_next(AtspiAccessible *current, AtspiRelationType search_type)
+{
+ GArray *relations;
+ AtspiAccessible *ret = NULL;
+ AtspiRelation *relation;
+ AtspiRelationType type;
+ int i;
+
+ relations = atspi_accessible_get_relation_set(current, NULL);
+
+ for (i = 0; i < relations->len; i++)
+ {
+ relation = g_array_index (relations, AtspiRelation*, i);
+ type = atspi_relation_get_relation_type(relation);
+
+ if (type == search_type)
+ {
+ ret = atspi_relation_get_target(relation, 0);
+ break;
+ }
+ }
+
+ g_array_free(relations, TRUE);
+ return ret;
+}
+
+AtspiAccessible *structural_navi_app_chain_next(AtspiAccessible *current)
+{
+ return _navi_app_chain_next(current, ATSPI_RELATION_FLOWS_TO);
+}
+
+AtspiAccessible *structural_navi_app_chain_prev(AtspiAccessible *current)
+{
+ return _navi_app_chain_next(current, ATSPI_RELATION_FLOWS_FROM);
+}