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
This commit is contained in:
Tom Hacohen 2011-08-24 14:06:04 +00:00
parent fbc00c7026
commit a87f4db1de
13 changed files with 583 additions and 0 deletions

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
Tom Hacohen <tom@stosb.com>

25
COPYING Normal file
View File

@ -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.

0
ChangeLog Normal file
View File

10
Makefile.am Normal file
View File

@ -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

0
NEWS Normal file
View File

4
README Normal file
View File

@ -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.

9
TODO Normal file
View File

@ -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?

14
autogen.sh Executable file
View File

@ -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

43
configure.ac Normal file
View File

@ -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 <stdlib.h>],
[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
])

4
make.sh Executable file
View File

@ -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`

4
src/Makefile.am Normal file
View File

@ -0,0 +1,4 @@
MAINTAINERCLEANFILES = Makefile.in
SUBDIRS = lib

21
src/lib/Makefile.am Normal file
View File

@ -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@

448
src/lib/libclouseau.c Normal file
View File

@ -0,0 +1,448 @@
#define _GNU_SOURCE 1
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <Eina.h>
#include <Ecore.h>
#include <Edje.h>
#include <Evas.h>
#include <Elementary.h>
#define ELM_INTERNAL_API_ARGESFSDFEFC
#include <elm_widget.h>
#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;
}