diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index ab0ad9a..b8a0a2f 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -10,7 +10,7 @@ AM_CPPFLAGS = \ @EFL_CFLAGS@ client_modulesdir = $(libdir)/clouseau/modules/client -client_modules_LTLIBRARIES = canvas_checker.la +client_modules_LTLIBRARIES = canvas_checker.la focus_graph_checker.la canvas_checker_la_SOURCES = client/canvas_checker.c @@ -18,3 +18,9 @@ canvas_checker_la_LDFLAGS = -module -avoid-version -rdynamic canvas_checker_la_DEPENDENCIES = $(top_builddir)/config.h canvas_checker_la_LIBADD = @EFL_LIBS@ +focus_graph_checker_la_SOURCES = client/focus_graph_checker.c + +focus_graph_checker_la_LDFLAGS = -module -avoid-version -rdynamic +focus_graph_checker_la_DEPENDENCIES = $(top_builddir)/config.h +focus_graph_checker_checker_la_LIBADD = @EFL_LIBS@ + diff --git a/src/modules/client/focus_graph_checker.c b/src/modules/client/focus_graph_checker.c new file mode 100644 index 0000000..fb09cc9 --- /dev/null +++ b/src/modules/client/focus_graph_checker.c @@ -0,0 +1,384 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +static int _focus_checker_dom = -1; + +//critical errors are errors which can make the checks produce wrong results +#define CRIT(...) EINA_LOG_DOM_CRIT(_focus_checker_dom, __VA_ARGS__) +//errors are reporting failures of the test +#define ERR(...) EINA_LOG_DOM_ERR(_focus_checker_dom, __VA_ARGS__) +#define WRN(...) EINA_LOG_DOM_WARN(_focus_checker_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_focus_checker_dom, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_focus_checker_dom, __VA_ARGS__) + +EAPI const char *clouseau_module_name = "Focus Graph Checker"; + +typedef struct { + Eina_List *right; + Eina_List *left; + Eina_List *top; + Eina_List *down; + void *next; + void *prev; + void *manager; + void *redirect; +} Focus_Relations; + +typedef struct _Focus_Manager Focus_Manager; + +struct _Focus_Manager { + void *ptr; + void *first_child; + Eina_Hash *objects; + Eina_List *redirects; + struct { + Focus_Manager *parent; + int depth; + } manager_tree_check; //only valid in execution of _managers_link_check(); +}; + +static Eina_Hash *managers = NULL; + +static Focus_Manager* +_manager_get(void *manager) +{ + Focus_Manager *m; + + m = eina_hash_find(managers, &manager); + if (!m) + { + m = calloc(1, sizeof(Focus_Manager)); + + m->ptr = manager; + m->objects = eina_hash_pointer_new(NULL); + eina_hash_add(managers, &manager, m); + } + + return m; +} + +static void +_focus_manager_redirect_add(Focus_Manager *m, void *redirect) +{ + m->redirects = eina_list_append(m->redirects, redirect); +} + +static void +_pointer_free(void *data) +{ + free(data); +} + +static Eina_Bool +_init(void) +{ + eina_init(); + _focus_checker_dom = eina_log_domain_register("Focus Checker", EINA_COLOR_CYAN); + eina_log_domain_level_set("Focus Checker", EINA_LOG_LEVEL_INFO); + managers = eina_hash_pointer_new(_pointer_free); + return EINA_TRUE; +} + +static void +_shutdown(void) +{ + eina_log_domain_unregister(_focus_checker_dom); + eina_shutdown(); +} + +static Eina_List* +_list_ptr_get(Efl_Dbg_Info *info) +{ + Eina_Value_List list; + Efl_Dbg_Info *data; + Eina_List *n, *result = NULL; + + if (eina_value_type_get(&info->value) != EINA_VALUE_TYPE_LIST) + { + CRIT("Expected list type"); + return NULL; + } + + eina_value_pget(&info->value, &list); + + EINA_LIST_FOREACH(list.list, n, data) + { + unsigned long long ptr; + + eina_value_get(&data->value, &ptr); + + result = eina_list_append(result, (void*)ptr); + } + + return result; +} + +static Focus_Relations* +_tree_it_convert(Clouseau_Tree_Item *it) +{ + Focus_Relations *ret; + Efl_Dbg_Info *widget, *focus, *next, *prev, + *right, *left, *top, *down, + *manager, *redirect; + + clouseau_tree_item_from_legacy_convert(it); + + widget = clouseau_eo_info_find(it->new_eo_info , "Elm_Widget"); + if (!widget) + { + return NULL; + } + + focus = clouseau_eo_info_find(widget, "Focus"); + if (!focus) + { + return NULL; + } + + next = clouseau_eo_info_find(focus, "next"); + prev = clouseau_eo_info_find(focus, "prev"); + right = clouseau_eo_info_find(focus, "right"); + left = clouseau_eo_info_find(focus, "left"); + top = clouseau_eo_info_find(focus, "top"); + down = clouseau_eo_info_find(focus, "down"); + manager = clouseau_eo_info_find(focus, "manager"); + redirect = clouseau_eo_info_find(focus, "redirect"); + + if (!next || !prev || !right || !left || !top || !down || !manager || !redirect) + { + CRIT("Widget %p did not present the full set of properties.", (void*)it->ptr); + + return NULL; + } + ret = calloc(1, sizeof(Focus_Relations)); + + eina_value_get(&next->value, &ret->next); + eina_value_get(&prev->value, &ret->prev); + ret->right = _list_ptr_get(right); + ret->left = _list_ptr_get(left); + ret->top = _list_ptr_get(top); + ret->down = _list_ptr_get(down); + eina_value_get(&manager->value, &ret->manager); + eina_value_get(&redirect->value, &ret->redirect); + return ret; +} + +static void +_add(Clouseau_Tree_Item *it) +{ + Clouseau_Tree_Item *treeit; + Focus_Relations *rel; + Focus_Manager *m; + Eina_List *n; + + rel = _tree_it_convert(it); + + if (rel) + { + m = _manager_get(rel->manager); + if (!m->first_child) + m->first_child = (void*)it->ptr; + if (rel->redirect) + _focus_manager_redirect_add(m, rel->redirect); + eina_hash_add(m->objects, &it->ptr, rel); + } + + EINA_LIST_FOREACH(it->children , n, treeit) + { + _add(treeit); + } +} + +/* + * Check that all widgets are linked to each other + * + * This is working by duplicating the hash of objects,. + * Exploring a item means that all the right left top + * down next and prev directions are added into the set of "need explotation". + * At first one random element is added to the set of need exploration. + * Then remove each time a element from the set, if the element is still + * in the duplicated hash, the element is explored, if not the element is ignored. + * + * If the set is now empty and the hash has still elements there must be a set + * of not linked elements to the other elements. + */ +Eina_Bool +_duplicate(const Eina_Hash *hash, const void *key, void *data, void *fdata) +{ + eina_hash_add(fdata, key, data); + + return EINA_TRUE; +} + +Eina_Bool +_count(const Eina_Hash *hash, const void *key, void *data, void *fdata) +{ + (*((int*)fdata))++; + return EINA_TRUE; +} + +static void +_manager_check(Focus_Manager *manager) +{ + Eina_Hash *dup; + Eina_Iterator *itr; + void *ptr; + Eina_List *runner = NULL, *n, *n_next; + + dup = eina_hash_pointer_new(NULL); + eina_hash_foreach(manager->objects, _duplicate, dup); + + runner = eina_list_append(runner, manager->first_child); + + do + { + void *ptr; + Focus_Relations *rel; + + ptr = eina_list_data_get(runner); + runner = eina_list_remove(runner, ptr); + + rel = eina_hash_find(manager->objects, &ptr); + if (!rel) + { + ERR("In manager %p %p is used in a reference, but not part of the manager", manager->ptr , ptr); + continue; + } + + rel = eina_hash_find(dup, &ptr); + if (!rel) + { + continue; + } + + eina_hash_del_by_key(dup, &ptr); + + + runner = eina_list_merge(runner, rel->left); + runner = eina_list_merge(runner, rel->right); + runner = eina_list_merge(runner, rel->top); + runner = eina_list_merge(runner, rel->down); + runner = eina_list_append(runner, rel->next); + runner = eina_list_append(runner, rel->prev); + + } while(eina_list_count(runner) != 0); + + //if we are done and sonething is left in the hash we have a not connected subset in the manager. BAD + int count; + + eina_hash_foreach(dup, _count, &count); + + if (count > 0) + { + ERR("Manager %p has a disconnected set of widgets", manager->ptr); + /* FIXME print all the children */ + } + else + INF("Manager %p is checked and fine", manager->ptr); +} + +/** + * Functions to check that the with redirect created objects are a tree + * + * This works by iterating 3 times throuw the elements, at first, we are setting the parent of each child object + * In the second stage we are searching for the element with no parent, which is the root. + * And we ensure that there is just one root. + * In the last stage we are calculating the depth of each manager, if in some element a other depth is laready + * calculated we know that there is at least one cycle. + */ + +static Eina_Bool +_depth_calc(Focus_Manager *m, int depth) +{ + Eina_List *n; + Focus_Manager *m2; + Eina_Bool suc = EINA_TRUE; + + if (m->manager_tree_check.depth != 0) + { + ERR("Manager %p is part of a cycle in the tree!", m->ptr); + return EINA_FALSE; + } + + m->manager_tree_check.depth = depth; + + EINA_LIST_FOREACH(m->redirects, n, m2) + { + if (!_depth_calc(m2, depth + 1)) + suc = EINA_FALSE; + } + return suc; +} + +static void +_managers_link_check(void) +{ + Focus_Manager *root = NULL, *m; + Eina_Hash *manager_cache; + Eina_Iterator *itr; + + //check if this things contains cycles + manager_cache = eina_hash_pointer_new(NULL); + + //First stage set parents; + itr = eina_hash_iterator_data_new(managers); + EINA_ITERATOR_FOREACH(itr, m) + { + Focus_Manager *m2; + Eina_List *n; + + //now we are setting the children + EINA_LIST_FOREACH(m->redirects, n, m2) + { + m2->manager_tree_check.parent = m; + } + } + eina_iterator_free(itr); + + //second stage, search for the root + itr = eina_hash_iterator_data_new(managers); + EINA_ITERATOR_FOREACH(itr, m) + { + if (!m->manager_tree_check.parent && !root) + root = m; + else if (!m->manager_tree_check.parent && root) + ERR("Error, there is already a root manager (%p), but %p does not have any parents", root->ptr, m->ptr); + } + eina_iterator_free(itr); + + //thirds stage, check that this is really a tree + if (_depth_calc(root, 1)) + INF("All managers are linked in the form of a tree"); +} + +EAPI void +clouseau_client_module_run(Eina_List *tree) +{ + Clouseau_Tree_Item *treeit; + Eina_List *n; + Eina_Iterator *managers_itr; + Focus_Manager *manager; + + //move everything into a pointer hash + EINA_LIST_FOREACH(tree, n, treeit) + _add(treeit); + + //check that there are no islands in the graph of a manager + managers_itr = eina_hash_iterator_data_new(managers); + EINA_ITERATOR_FOREACH(managers_itr, manager) + { + _manager_check(manager); + } + + //check that all managers are linked to at least one other manager + _managers_link_check(); + +} + +EINA_MODULE_INIT(_init); +EINA_MODULE_SHUTDOWN(_shutdown);