From a87f4db1de0c0def0e32b575de98eafb70f3495f Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Wed, 24 Aug 2011 14:06:04 +0000 Subject: [PATCH] Clouseau: Everyone welcome clouseau, a tool to debug UI applications. It's inspired (and currently not very different) by Android's hierarchy viewer but it'll hopefully grow to be more than that. It currently lets you walk the evas object tree, query some evas object parameters and highlight the object on the canvas. It currently does not take clipping into account, so you only see the actual object size, and you can't see the clip frame just yet. Read the README for a quick how to. It's very basic atm, read the TODO if you care about future plans, this is more of a PoC with some useful pieces of code than anything else, but still, it's more than usable (I wrote it cause I needed it). Feel free to contribute/sumbit suggestions. SVN revision: 62740 --- AUTHORS | 1 + COPYING | 25 +++ ChangeLog | 0 Makefile.am | 10 + NEWS | 0 README | 4 + TODO | 9 + autogen.sh | 14 ++ configure.ac | 43 ++++ make.sh | 4 + src/Makefile.am | 4 + src/lib/Makefile.am | 21 ++ src/lib/libclouseau.c | 448 ++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 583 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100755 make.sh create mode 100644 src/Makefile.am create mode 100644 src/lib/Makefile.am create mode 100644 src/lib/libclouseau.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..ee22837 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Tom Hacohen diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..189a6b6 --- /dev/null +++ b/COPYING @@ -0,0 +1,25 @@ +Copyright notice for Clouseau: + +Copyright (C) 2000-2011 Tom Hacohen and various contributors (see AUTHORS) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..5209bd8 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,10 @@ + +MAINTAINERCLEANFILES = Makefile.in aclocal.m4 config.guess \ + config.h.in config.sub configure install-sh \ + depcomp libtool missing + +EXTRA_DIST = README AUTHORS COPYING + +SUBDIRS = src + +ACLOCAL_AMFLAGS = -I m4 diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..26a214f --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +Just run your wanted ecore/elm based app with this lib in LD_PRELOAD, e.g: +LD_PRELOAD=/usr/lib/clouseau/libclouseau.so elementary_test + +Click "Load" and the rest is straightforward. diff --git a/TODO b/TODO new file mode 100644 index 0000000..f04e124 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +* Make it an elm module (or setting an env var without a module?) so I'll be able to remotely connect to elm apps. +* Split UI and actual program code (better to do the previous bullet first on the bullet above). +* Add "grab screenshot" button. +* Add a clipping range rectangle. +* Add special per object info, for example edje part names? + +* Things to show per-object + * clipping info. + * callback list? diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..cf63ef1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +rm -rf autom4te.cache +rm -f aclocal.m4 ltmain.sh + +echo "Running aclocal..." ; aclocal -I m4 $ACLOCAL_FLAGS || exit 1 +echo "Running autoheader..." ; autoheader || exit 1 +echo "Running autoconf..." ; autoconf || exit 1 +echo "Running libtoolize..." ; (libtoolize --copy --automake || glibtoolize --automake) || exit 1 +echo "Running automake..." ; automake --add-missing --copy --gnu || exit 1 + +if [ -z "$NOCONFIGURE" ]; then + ./configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..84412eb --- /dev/null +++ b/configure.ac @@ -0,0 +1,43 @@ +AC_INIT(clouseau, 0.1.0, tom@stosb.com) +AC_PREREQ(2.52) +AC_CONFIG_SRCDIR(configure.ac) +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE(1.6 dist-bzip2) +AM_CONFIG_HEADER(config.h) + +AC_PROG_LIBTOOL + +dnl Checking for __attribute__ support +AC_MSG_CHECKING([for __attribute__]) +AC_CACHE_VAL(_cv_have___attribute__, + [ + AC_TRY_COMPILE([#include ], + [int func(int x); int foo(int x __attribute__ ((unused))) { exit(1); }], + [_cv_have___attribute__="yes"], + [_cv_have___attribute__="no"]) + ] +) + +if test "x${_cv_have___attribute__}" = "xyes" ; then + AC_DEFINE(HAVE___ATTRIBUTE__, 1, [Define to 1 if your compiler has __attribute__]) +fi +AC_MSG_RESULT(${_cv_have___attribute__}) + +PKG_CHECK_MODULES(EFL, + [ + elementary >= 0.7.0 + evas >= 1.0.0 + ] +) + +AC_CHECK_LIB(dl,dlopen) +AC_CHECK_FUNCS(dlopen dlerror) + +AC_OUTPUT([ + Makefile + src/Makefile + src/lib/Makefile + ]) diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..d1f4e60 --- /dev/null +++ b/make.sh @@ -0,0 +1,4 @@ +#!/bin/sh +gcc -Wall -Wextra -fPIC -rdynamic -g3 -O0 -c libclouseau.c `pkg-config --libs --cflags elementary ecore evas` +gcc -g3 -O0 -shared -Wl,-soname,libclouseau.so.1 -o libclouseau.so.1.0.1 libclouseau.o -lc -ldl `pkg-config --libs --cflags elementary` + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..a587cac --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,4 @@ + +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = lib diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 0000000..93739e5 --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,21 @@ +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I$(top_srcdir)/src/include \ +-I$(top_builddir)/src/include \ +-DPACKAGE_BIN_DIR=\"$(bindir)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +-DPACKAGE_DATA_DIR=\"$(datadir)/$(PACKAGE)\" \ +@EFL_CFLAGS@ + +EXTRA_DIST = + +pkgdir = $(libdir)/clouseau +pkg_LTLIBRARIES = libclouseau.la + +libclouseau_la_SOURCES = libclouseau.c + +libclouseau_la_LDFLAGS = -module -avoid-version -rdynamic +libclouseau_la_DEPENDENCIES = $(top_builddir)/config.h +libclouseau_la_LIBADD = @EFL_LIBS@ +libclouseau_la_CFLAGS = @EFL_CFLAGS@ diff --git a/src/lib/libclouseau.c b/src/lib/libclouseau.c new file mode 100644 index 0000000..e2ea5c0 --- /dev/null +++ b/src/lib/libclouseau.c @@ -0,0 +1,448 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ELM_INTERNAL_API_ARGESFSDFEFC +#include + +#include "config.h" + +#ifdef HAVE___ATTRIBUTE__ +# define __UNUSED__ __attribute__((unused)) +#else +# define __UNUSED__ +#endif + +static Elm_Genlist_Item_Class itc, itc_ee; +static Eina_Bool _lib_init = EINA_FALSE; +static Eina_List *tree = NULL; +static Evas_Object *prop_list; + +static void libclouseau_highlight(Evas_Object *addr); +static Eina_Bool libclouseau_highlight_fade(void *rv); + +typedef struct _Tree_Item Tree_Item; +struct _Tree_Item +{ + Tree_Item *parent; + Eina_List *children; + union { + Ecore_Evas *ee; + Evas_Object *obj; + } data; +}; + +static void +_item_tree_item_free(Tree_Item *parent) +{ + Tree_Item *treeit; + + EINA_LIST_FREE(parent->children, treeit) + { + _item_tree_item_free(treeit); + } + + free(parent); +} + +static void +_item_tree_free(void) +{ + Tree_Item *treeit; + + EINA_LIST_FREE(tree, treeit) + { + _item_tree_item_free(treeit); + } +} + +static char * +item_label_get(void *data, Evas_Object *obj __UNUSED__, + const char *part __UNUSED__) +{ + Tree_Item *treeit = data; + char buf[256]; + if (elm_widget_is(treeit->data.obj)) + { + snprintf(buf, sizeof(buf), "%p %s (%s)", treeit->data.obj, + elm_widget_type_get(treeit->data.obj), + evas_object_type_get(treeit->data.obj)); + } + else + { + snprintf(buf, sizeof(buf), "%p %s", treeit->data.obj, + evas_object_type_get(treeit->data.obj)); + } + return strdup(buf); +} + +static char * +item_ee_label_get(void *data, Evas_Object *obj __UNUSED__, + const char *part __UNUSED__) +{ + Tree_Item *treeit = data; + char buf[256]; + snprintf(buf, sizeof(buf), "%p %s", treeit->data.ee, + ecore_evas_title_get(treeit->data.ee)); + return strdup(buf); +} + +static void +_gl_selected(void *data __UNUSED__, Evas_Object *pobj __UNUSED__, + void *event_info) +{ + elm_list_clear(prop_list); + + /* If not an object, return. */ + if (!elm_genlist_item_parent_get(event_info)) + return; + + Tree_Item *treeit = elm_genlist_item_data_get(event_info); + Evas_Object *obj = treeit->data.obj; + libclouseau_highlight(obj); + /* Populate properties list */ + { + char buf[1024]; + Eina_Bool visibility; + Evas_Coord x, y, w, h; + double dx, dy; + + visibility = evas_object_visible_get(obj); + snprintf(buf, sizeof(buf), "Visibility: %d", (int) visibility); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + snprintf(buf, sizeof(buf), "Layer: %hd", + evas_object_layer_get(obj)); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + evas_object_geometry_get(obj, &x, &y, &w, &h); + snprintf(buf, sizeof(buf), "Position: %d %d", x, y); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + snprintf(buf, sizeof(buf), "Size: %d %d", w, h); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + +#if 0 + if (evas_object_clip_get(obj)) + { + evas_object_geometry_get( + evas_object_clip_get(obj), &x, &y, &w, &h); + snprintf(buf, sizeof(buf), "Clipper position: %d %d", x, y); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + snprintf(buf, sizeof(buf), "Clipper size: %d %d", w, h); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + } +#endif + + evas_object_size_hint_min_get(obj, &w, &h); + snprintf(buf, sizeof(buf), "Min size: %d %d", w, h); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + evas_object_size_hint_max_get(obj, &w, &h); + snprintf(buf, sizeof(buf), "Max size: %d %d", w, h); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + evas_object_size_hint_request_get(obj, &w, &h); + snprintf(buf, sizeof(buf), "Request size: %d %d", w, h); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + evas_object_size_hint_align_get(obj, &dx, &dy); + snprintf(buf, sizeof(buf), "Align: %.6lg %.6lg", dx, dy); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + evas_object_size_hint_weight_get(obj, &dx, &dy); + snprintf(buf, sizeof(buf), "Weight: %.6lg %.6lg", dx, dy); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + + /* Handle color */ + { + int r, g, b, a; + evas_object_color_get(obj, &r, &g, &b, &a); + snprintf(buf, sizeof(buf), "Color: %d %d %d %d", r, g, b, a); + elm_list_item_append(prop_list, buf, NULL, NULL, NULL, NULL); + } + elm_list_go(prop_list); + } +} + +static void +gl_exp(void *data __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info) +{ + Elm_Genlist_Item *it = event_info; + Evas_Object *gl = elm_genlist_item_genlist_get(it); + Tree_Item *parent = elm_genlist_item_data_get(it); + Tree_Item *treeit; + Eina_List *itr; + + EINA_LIST_FOREACH(parent->children, itr, treeit) + { + Elm_Genlist_Item_Flags iflag = (treeit->children) ? + ELM_GENLIST_ITEM_SUBITEMS : ELM_GENLIST_ITEM_NONE; + elm_genlist_item_append(gl, &itc, treeit, it, iflag, + _gl_selected, NULL); + } +} + +static void +gl_con(void *data __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info) +{ + Elm_Genlist_Item *it = event_info; + elm_genlist_item_subitems_clear(it); +} + +static void +gl_exp_req(void *data __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info) +{ + Elm_Genlist_Item *it = event_info; + elm_genlist_item_expanded_set(it, EINA_TRUE); +} + +static void +gl_con_req(void *data __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info) +{ + Elm_Genlist_Item *it = event_info; + elm_genlist_item_expanded_set(it, EINA_FALSE); +} + +static void +libclouseau_item_add(Evas_Object *o, Evas_Object *gl, Tree_Item *parent) +{ + Eina_List *children, *tmp; + Evas_Object *child; + Tree_Item *treeit; + + treeit = calloc(1, sizeof(*treeit)); + treeit->data.obj = o; + treeit->parent = parent; + parent->children = eina_list_append(parent->children, treeit); + + children = evas_object_smart_members_get(o); + EINA_LIST_FOREACH(children, tmp, child){ + libclouseau_item_add(child, gl, treeit); + } +} + +static void +_load_list(Evas_Object *gl) +{ + Eina_List *os, *ot; + Evas_Object *o; + Eina_List *ees,*l; + Ecore_Evas *ee, *this_ee; + + elm_genlist_clear(gl); + _item_tree_free(); + + ees = ecore_evas_ecore_evas_list_get(); + + this_ee = ecore_evas_ecore_evas_get( + evas_object_evas_get(elm_object_top_widget_get(gl))); + + EINA_LIST_FOREACH(ees, l, ee) + { + if (this_ee == ee) + continue; + Tree_Item *treeit; + + Evas *e; + int w, h; + e = ecore_evas_get(ee); + evas_output_size_get(e, &w, &h); + + treeit = calloc(1, sizeof(*treeit)); + treeit->data.ee = ee; + + tree = eina_list_append(tree, treeit); + + os = evas_objects_in_rectangle_get(e, SHRT_MIN, SHRT_MIN, + USHRT_MAX, USHRT_MAX, EINA_TRUE, EINA_TRUE); + EINA_LIST_FOREACH(os, ot, o){ + libclouseau_item_add(o, gl, treeit); + } + + /* Insert the base ee items */ + { + Elm_Genlist_Item_Flags glflag = (treeit->children) ? + ELM_GENLIST_ITEM_SUBITEMS : ELM_GENLIST_ITEM_NONE; + elm_genlist_item_append(gl, &itc_ee, treeit, NULL, + glflag, NULL, NULL); + } + } +} + +static void +_bt_clicked(void *data, Evas_Object *obj, void *event_info __UNUSED__) +{ + elm_object_text_set(obj, "Reload"); + _load_list(data); +} + +void +libclouseau_init(void) +{ + Evas_Object *win, *bg, *panes, *bx, *bt; + + win = elm_win_add(NULL, PACKAGE_NAME, ELM_WIN_BASIC); + elm_win_autodel_set(win, EINA_TRUE); + elm_win_title_set(win, PACKAGE_NAME); + + bg = elm_bg_add(win); + elm_win_resize_object_add(win, bg); + evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(bg); + + bx = elm_box_add(win); + evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_win_resize_object_add(win, bx); + evas_object_show(bx); + + bt = elm_button_add(win); + evas_object_size_hint_align_set(bt, 0.0, 0.5); + elm_object_text_set(bt, "Load"); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + + panes = elm_panes_add(win); + evas_object_size_hint_weight_set(panes, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(panes, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(bx, panes); + evas_object_show(panes); + + /* The list main */ + { + Evas_Object *gl; + + gl = elm_genlist_add(panes); + evas_object_size_hint_align_set(gl, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(gl, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_panes_content_left_set(panes, gl); + evas_object_show(gl); + + evas_object_smart_callback_add(bt, "clicked", _bt_clicked, gl); + + itc_ee.item_style = "default"; + itc_ee.func.label_get = item_ee_label_get; + itc_ee.func.icon_get = NULL; + itc_ee.func.state_get = NULL; + itc_ee.func.del = NULL; + + itc.item_style = "default"; + itc.func.label_get = item_label_get; + itc.func.icon_get = NULL; + itc.func.state_get = NULL; + itc.func.del = NULL; + + evas_object_smart_callback_add(gl, "expand,request", gl_exp_req, gl); + evas_object_smart_callback_add(gl, "contract,request", gl_con_req, gl); + evas_object_smart_callback_add(gl, "expanded", gl_exp, gl); + evas_object_smart_callback_add(gl, "contracted", gl_con, gl); + evas_object_smart_callback_add(gl, "selected", _gl_selected, NULL); + } + + /* Properties list */ + { + prop_list = elm_list_add(panes); + evas_object_size_hint_align_set(prop_list, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_weight_set(prop_list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + + elm_panes_content_right_set(panes, prop_list); + evas_object_show(prop_list); + } + + evas_object_resize(win, 500, 500); + evas_object_show(win); +} + +/* Hook on the main loop + * We only do something here if we didn't already go into elm_init, + * which probably means we are not using elm. */ +void +ecore_main_loop_begin(void) +{ + Eina_Bool _is_init = _lib_init; + void (*_ecore_main_loop_begin)(void) = + dlsym(RTLD_NEXT, "ecore_main_loop_begin"); + + if (!_is_init) + { + /* Make sure we init elementary, wouldn't be needed once we + * take away the ui to another proc. */ + elm_init(0, NULL); + + libclouseau_init(); + } + + _lib_init = EINA_TRUE; + _ecore_main_loop_begin(); + + return; +} + + +/* HIGHLIGHT code. */ + +/* The color of the highlight */ +enum { + HIGHLIGHT_R = 255, + HIGHLIGHT_G = 128, + HIGHLIGHT_B = 128, + HIGHLIGHT_A = 255, + + /* How much padding around the highlight box. + * Currently we don't want any. */ + PADDING = 0, +}; + +static void +libclouseau_highlight(Evas_Object *obj) +{ + Evas *e; + Evas_Object *r; + int x, y, w, h; + + e = evas_object_evas_get(obj); + if (!e) return; + + evas_object_geometry_get(obj, &x, &y, &w, &h); + + r = evas_object_rectangle_add(e); + evas_object_move(r, x - PADDING, y - PADDING); + evas_object_resize(r, w + (2 * PADDING), h + (2 * PADDING)); + evas_object_color_set(r, HIGHLIGHT_R, HIGHLIGHT_G, HIGHLIGHT_B, + HIGHLIGHT_A); + evas_object_show(r); + ecore_timer_add(0.1, libclouseau_highlight_fade, r); +} + +static Eina_Bool +libclouseau_highlight_fade(void *_rect) +{ + Evas_Object *rect = _rect; + int r, g, b, a; + double na; + + evas_object_color_get(rect, &r, &g, &b, &a); + if (a < 20) + { + evas_object_del(rect); + return EINA_FALSE; + } + + na = a - 20; + r = na / a * r; + g = na / a * g; + b = na / a * b; + evas_object_color_set(rect, r, g, b, na); + + return EINA_TRUE; +}