diff --git a/common/utils/T/tracer/Makefile b/common/utils/T/tracer/Makefile
index 093acfbe3c380d7274b82a649022d6a860715644..fab5012c2862bc56499cdf425f147515700dfa67 100644
--- a/common/utils/T/tracer/Makefile
+++ b/common/utils/T/tracer/Makefile
@@ -10,11 +10,14 @@ CFLAGS += -DT_USE_SHARED_MEMORY
 LIBS += -lrt
 
 PROG=tracer
-OBJS=main.o plot.o database.o forward.o
+OBJS=main.o plot.o database.o forward.o gui/gui.a
 
 $(PROG): $(OBJS)
 	$(CC) $(CFLAGS) -o $(PROG) $(OBJS) $(LIBS)
 
+gui/gui.a:
+	cd gui && make
+
 %.o: %.c
 	$(CC) $(CFLAGS) -c -o $@ $<
 
@@ -22,3 +25,4 @@ main.o: ../T_IDs.h ../T_defs.h
 
 clean:
 	rm -f *.o $(PROG) core
+	cd gui && make clean
diff --git a/common/utils/T/tracer/gui/Makefile b/common/utils/T/tracer/gui/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..13820114ddd69e0debe47f2f33aa4db0db98c22e
--- /dev/null
+++ b/common/utils/T/tracer/gui/Makefile
@@ -0,0 +1,17 @@
+CC=gcc
+CFLAGS=-Wall -g -pthread
+
+OBJS=init.o loop.o toplevel_window.o x.o container.o widget.o \
+     gui.o label.o event.o xy_plot.o text_list.o
+
+gui.a: $(OBJS)
+	ar cr gui.a $(OBJS)
+
+test: test.o gui.a
+	$(CC) -o test $(OBJS) test.o -lX11 -pthread -lm
+
+%.o: %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
+clean:
+	rm -f *.a *.o test
diff --git a/common/utils/T/tracer/gui/container.c b/common/utils/T/tracer/gui/container.c
new file mode 100644
index 0000000000000000000000000000000000000000..23f7a42295dd6f3a0bccaa1262776d79ed06b885
--- /dev/null
+++ b/common/utils/T/tracer/gui/container.c
@@ -0,0 +1,206 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+static void repack(gui *g, widget *_this)
+{
+printf("REPACK container %p\n", _this);
+  struct container_widget *this = _this;
+  this->hints_are_valid = 0;
+  return this->common.parent->repack(g, this->common.parent);
+}
+
+static void add_child(gui *g, widget *this, widget *child, int position)
+{
+printf("ADD_CHILD container\n");
+  widget_add_child_internal(g, this, child, position);
+}
+
+static void compute_vertical_hints(struct gui *g,
+    struct container_widget *this)
+{
+  struct widget_list *l;
+  int cwidth, cheight;
+  int allocated_width = 0, allocated_height = 0;
+
+  /* get largest width */
+  l = this->common.children;
+  while (l) {
+    l->item->hints(g, l->item, &cwidth, &cheight);
+    if (cwidth > allocated_width) allocated_width = cwidth;
+    allocated_height += cheight;
+    l = l->next;
+  }
+  this->hint_width = allocated_width;
+  this->hint_height = allocated_height;
+  this->hints_are_valid = 1;
+}
+
+static void compute_horizontal_hints(struct gui *g,
+    struct container_widget *this)
+{
+  struct widget_list *l;
+  int cwidth, cheight;
+  int allocated_width = 0, allocated_height = 0;
+
+  /* get largest height */
+  l = this->common.children;
+  while (l) {
+    l->item->hints(g, l->item, &cwidth, &cheight);
+    if (cheight > allocated_height) allocated_height = cheight;
+    allocated_width += cwidth;
+    l = l->next;
+  }
+  this->hint_width = allocated_width;
+  this->hint_height = allocated_height;
+  this->hints_are_valid = 1;
+}
+
+static void vertical_allocate(gui *_gui, widget *_this,
+    int x, int y, int width, int height)
+{
+printf("ALLOCATE container vertical %p\n", _this);
+  int cy = 0;
+  int cwidth, cheight;
+  struct gui *g = _gui;
+  struct container_widget *this = _this;
+  struct widget_list *l;
+
+  if (this->hints_are_valid == 1) goto hints_ok;
+
+  compute_vertical_hints(g, this);
+
+hints_ok:
+
+  this->common.x = x;
+  this->common.y = y;
+  this->common.width = width;
+  this->common.height = height;
+
+  /* allocate */
+  l = this->common.children;
+  while (l) {
+    l->item->hints(g, l->item, &cwidth, &cheight);
+    l->item->allocate(g, l->item, this->common.x, this->common.y + cy,
+        //this->hint_width, cheight);
+        width, cheight);
+    cy += cheight;
+    l = l->next;
+  }
+
+  if (cy != this->hint_height) ERR("reachable?\n");
+}
+
+static void horizontal_allocate(gui *_gui, widget *_this,
+    int x, int y, int width, int height)
+{
+printf("ALLOCATE container horizontal %p\n", _this);
+  int cx = 0;
+  int cwidth, cheight;
+  struct gui *g = _gui;
+  struct container_widget *this = _this;
+  struct widget_list *l;
+
+  if (this->hints_are_valid == 1) goto hints_ok;
+
+  compute_horizontal_hints(g, this);
+
+hints_ok:
+
+  this->common.x = x;
+  this->common.y = y;
+  this->common.width = width;
+  this->common.height = height;
+
+  /* allocate */
+  l = this->common.children;
+  while (l) {
+    l->item->hints(g, l->item, &cwidth, &cheight);
+    l->item->allocate(g, l->item, this->common.x + cx, this->common.y,
+        cwidth, this->hint_height);
+    cx += cwidth;
+    l = l->next;
+  }
+
+  if (cx != this->hint_width) ERR("reachable?\n");
+}
+
+static void vertical_hints(gui *_gui, widget *_w, int *width, int *height)
+{
+printf("HINTS container vertical %p\n", _w);
+  struct gui *g = _gui;
+  struct container_widget *this = _w;
+
+  if (this->hints_are_valid) {
+    *width = this->hint_width;
+    *height = this->hint_height;
+    return;
+  }
+
+  compute_vertical_hints(g, this);
+
+  *width = this->hint_width;
+  *height = this->hint_height;
+}
+
+static void horizontal_hints(gui *_gui, widget *_w, int *width, int *height)
+{
+printf("HINTS container horizontal %p\n", _w);
+  struct gui *g = _gui;
+  struct container_widget *this = _w;
+
+  if (this->hints_are_valid) {
+    *width = this->hint_width;
+    *height = this->hint_height;
+    return;
+  }
+
+  compute_horizontal_hints(g, this);
+
+  *width = this->hint_width;
+  *height = this->hint_height;
+}
+
+static void paint(gui *_gui, widget *_this)
+{
+printf("PAINT container\n");
+  struct gui *g = _gui;
+  struct widget *this = _this;
+  struct widget_list *l;
+
+  l = this->children;
+  while (l) {
+    l->item->paint(g, l->item);
+    l = l->next;
+  }
+}
+
+widget *new_container(gui *_gui, int vertical)
+{
+  struct gui *g = _gui;
+  struct container_widget *w;
+
+  glock(g);
+
+  w = new_widget(g, CONTAINER, sizeof(struct container_widget));
+
+  w->vertical = vertical;
+  w->hints_are_valid = 0;
+
+  w->common.paint     = paint;
+  w->common.add_child = add_child;
+  w->common.repack    = repack;
+
+  if (vertical) {
+    w->common.allocate  = vertical_allocate;
+    w->common.hints     = vertical_hints;
+  } else {
+    w->common.allocate  = horizontal_allocate;
+    w->common.hints     = horizontal_hints;
+  }
+
+  gunlock(g);
+
+  return w;
+}
diff --git a/common/utils/T/tracer/gui/event.c b/common/utils/T/tracer/gui/event.c
new file mode 100644
index 0000000000000000000000000000000000000000..fe7f9fbb47215c571115b8e3b9396f5d63c81d8f
--- /dev/null
+++ b/common/utils/T/tracer/gui/event.c
@@ -0,0 +1,165 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+/*****************************************************************/
+/*                       generic functions                       */
+/*****************************************************************/
+
+static void event_list_append(struct gui *g, struct event *e)
+{
+  struct event_list *new;
+
+  new = calloc(1, sizeof(struct event_list));
+  if (new == NULL) OOM;
+
+  new->item = e;
+
+  if (g->queued_events == NULL) {
+    g->queued_events = new;
+    new->last = new;
+    return;
+  }
+
+  g->queued_events->last->next = new;
+  g->queued_events->last = new;
+}
+
+static void free_event(struct event *e)
+{
+  switch (e->type) {
+  case REPACK: /* nothing */ break;
+  case DIRTY: /* nothing */ break;
+  }
+  free(e);
+}
+
+/*****************************************************************/
+/*                         sending events                        */
+/*****************************************************************/
+
+static event *new_event_repack(int id)
+{
+  struct repack_event *ret;
+  ret = calloc(1, sizeof(struct repack_event));
+  if (ret == NULL) OOM;
+  ret->id = id;
+  return ret;
+}
+
+static event *new_event_dirty(int id)
+{
+  struct dirty_event *ret;
+  ret = calloc(1, sizeof(struct dirty_event));
+  if (ret == NULL) OOM;
+  ret->id = id;
+  return ret;
+}
+
+void send_event(gui *_gui, enum event_type type, ...)
+{
+printf("send_event %d\n", type);
+  struct gui *g = _gui;
+  int do_write = 0;
+  va_list ap;
+  struct event *e;
+
+  if (g->queued_events == NULL) do_write = 1;
+
+  va_start(ap, type);
+
+  switch (type) {
+  case REPACK: {
+    int id;
+    id = va_arg(ap, int);
+    e = new_event_repack(id);
+    break;
+  }
+  case DIRTY: {
+    int id;
+    id = va_arg(ap, int);
+    e = new_event_dirty(id);
+    break;
+  }
+  }
+
+  va_end(ap);
+
+  e->type = type;
+
+  event_list_append(g, e);
+
+  if (do_write) {
+    char c = 1;
+    if (write(g->event_pipe[1], &c, 1) != 1)
+      ERR("error writing to pipe: %s\n", strerror(errno));
+  }
+}
+
+/*****************************************************************/
+/*                      processing events                        */
+/*****************************************************************/
+
+static void repack_event(struct gui *g, int id)
+{
+  struct widget *w = find_widget(g, id);
+  if (w == NULL) { WARN("widget id %d not found\n", id); return; }
+  w->repack(g, w);
+}
+
+/* TODO: put that function somewhere else? */
+static struct toplevel_window_widget *get_toplevel_window(struct widget *w)
+{
+  while (w != NULL) {
+    if (w->type == TOPLEVEL_WINDOW)
+      return (struct toplevel_window_widget *)w;
+    w = w->parent;
+  }
+  return NULL;
+}
+
+static void dirty_event(struct gui *g, int id)
+{
+  struct widget *w = find_widget(g, id);
+  struct toplevel_window_widget *win;
+  if (w == NULL) { WARN("widget id %d not found\n", id); return; }
+  win = get_toplevel_window(w);
+  if (win == NULL)
+    { WARN("widget id %d not contained in a window\n", id); return; }
+  g->xwin = win->x;
+  w->paint(g, w);
+  g->xwin = NULL;
+  g->repainted = 1;
+}
+
+static void process_event(struct gui *g, struct event *e)
+{
+printf("processing event type %d\n", e->type);
+  switch (e->type) {
+  case REPACK: repack_event(g, ((struct repack_event *)e)->id); break;
+  case DIRTY: dirty_event(g, ((struct dirty_event *)e)->id); break;
+  }
+}
+
+/* TODO: events' compression */
+void gui_events(gui *_gui)
+{
+  struct gui *g = _gui;
+
+printf("gui_events START: head %p\n", g->queued_events);
+
+  while (g->queued_events) {
+    struct event_list *cur = g->queued_events;
+    g->queued_events = cur->next;
+    if (g->queued_events) g->queued_events->last = cur->last;
+    process_event(g, cur->item);
+    free_event(cur->item);
+    free(cur);
+  }
+printf("gui_events DONE\n");
+}
diff --git a/common/utils/T/tracer/gui/gui.c b/common/utils/T/tracer/gui/gui.c
new file mode 100644
index 0000000000000000000000000000000000000000..55c7a3680aa04b23341cd2d6c123acf6e0a96cfe
--- /dev/null
+++ b/common/utils/T/tracer/gui/gui.c
@@ -0,0 +1,24 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+void glock(gui *_gui)
+{
+  struct gui *g = _gui;
+  if (pthread_mutex_lock(g->lock)) ERR("mutex error\n");
+}
+
+void gunlock(gui *_gui)
+{
+  struct gui *g = _gui;
+  if (pthread_mutex_unlock(g->lock)) ERR("mutex error\n");
+}
+
+int new_color(gui *_gui, char *color)
+{
+  struct gui *g = _gui;
+  return x_new_color(g->x, color);
+}
diff --git a/common/utils/T/tracer/gui/gui.h b/common/utils/T/tracer/gui/gui.h
new file mode 100644
index 0000000000000000000000000000000000000000..846b16457d2e892be9c33716a83254b1e64a032b
--- /dev/null
+++ b/common/utils/T/tracer/gui/gui.h
@@ -0,0 +1,36 @@
+#ifndef _GUI_H_
+#define _GUI_H_
+
+/* defines the public API of the GUI */
+
+typedef void gui;
+typedef void widget;
+
+#define HORIZONTAL 0
+#define VERTICAL   1
+
+gui *gui_init(void);
+
+/* position = -1 to put at the end */
+void widget_add_child(gui *gui, widget *parent, widget *child, int position);
+
+widget *new_toplevel_window(gui *gui, int width, int height, char *title);
+widget *new_container(gui *gui, int vertical);
+widget *new_label(gui *gui, const char *text);
+widget *new_xy_plot(gui *gui, int width, int height, char *label,
+    int vruler_width);
+widget *new_text_list(gui *_gui, int width, int nlines, int background_color);
+
+void xy_plot_set_range(gui *gui, widget *this,
+    float xmin, float xmax, float ymin, float ymax);
+
+void text_list_add(gui *gui, widget *this, const char *text, int position);
+
+void gui_loop(gui *gui);
+
+void glock(gui *gui);
+void gunlock(gui *gui);
+
+int new_color(gui *gui, char *color);
+
+#endif /* _GUI_H_ */
diff --git a/common/utils/T/tracer/gui/gui_defs.h b/common/utils/T/tracer/gui/gui_defs.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7032f95daac0c1b0c0fced830d280864b36dd81
--- /dev/null
+++ b/common/utils/T/tracer/gui/gui_defs.h
@@ -0,0 +1,178 @@
+#ifndef _GUI_DEFS_H_
+#define _GUI_DEFS_H_
+
+/* defines the private API of the GUI */
+
+/*************************************************************************/
+/*                            logging macros                             */
+/*************************************************************************/
+
+#define ERR(...) \
+  do { \
+    printf("%s:%d:%s: ERROR: ", __FILE__, __LINE__, __FUNCTION__); \
+    printf(__VA_ARGS__); \
+    abort(); \
+  } while (0)
+
+#define WARN(...) \
+  do { \
+    printf("%s:%d:%s: WARNING: ", __FILE__, __LINE__, __FUNCTION__); \
+    printf(__VA_ARGS__); \
+  } while (0)
+
+#define OOM ERR("out of memory\n")
+
+/*************************************************************************/
+/*                             widgets                                   */
+/*************************************************************************/
+
+enum widget_type {
+  TOPLEVEL_WINDOW, CONTAINER, TEXT_LIST, XY_PLOT, BUTTON, LABEL
+};
+
+struct widget_list;
+
+struct widget {
+  enum widget_type type;
+  int id;
+  int x;            /* allocated x after packing */
+  int y;            /* allocated y after packing */
+  int width;        /* allocated width after packing */
+  int height;       /* allocated height after packing */
+  struct widget_list *children;
+  struct widget *parent;
+  void (*repack)(gui *g, widget *this);
+  void (*add_child)(gui *g, widget *this, widget *child, int position);
+  void (*allocate)(gui *g, widget *this, int x, int y, int width, int height);
+  void (*hints)(gui *g, widget *this, int *width, int *height);
+  void (*paint)(gui *g, widget *this);
+};
+
+struct widget_list {
+  struct widget *item;
+  struct widget_list *next;
+  //struct widget_list *prev;  /* unused? */
+  struct widget_list *last;  /* valid only for the head of the list */
+};
+
+struct toplevel_window_widget {
+  struct widget common;
+  void *x;                /* opaque X data (type x_window), used in x.c */
+};
+
+struct container_widget {
+  struct widget common;
+  int vertical;
+  int hints_are_valid;     /* used to cache hints values */
+  int hint_width;          /* cached hint values - invalid if */
+  int hint_height;         /* repack_was_called == 1          */
+};
+
+struct text_list_widget {
+  struct widget common;
+  char **text;
+  int text_count;
+  int wanted_width;
+  int wanted_nlines;    /* number of lines of text the user wants to see */
+  int allocated_nlines; /* actual number of visible lines */
+  int starting_line;    /* points to the first visible line of text */
+  int line_height;
+  int baseline;
+  int background_color;
+};
+
+struct xy_plot_widget {
+  struct widget common;
+  float *x;
+  float *y;
+  int npoints;
+  char *label;
+  int label_width;
+  int label_height;
+  int label_baseline;
+  int vrule_width;       /* the width of the vertical ruler text zone */
+  float xmin, xmax;
+  float ymin, ymax;
+  int wanted_width;
+  int wanted_height;
+};
+
+struct button_widget {
+  struct widget common;
+};
+
+struct label_widget {
+  struct widget common;
+  const char *t;
+  int color;
+  int width;         /* as given by the graphic's backend */
+  int height;        /* as given by the graphic's backend */
+  int baseline;      /* as given by the graphic's backend */
+};
+
+/*************************************************************************/
+/*                             events                                    */
+/*************************************************************************/
+
+typedef void event;
+
+enum event_type {
+  DIRTY, REPACK
+};
+
+struct event {
+  enum event_type type;
+};
+
+struct event_list {
+  struct event *item;
+  struct event_list *next;
+  struct event_list *last;
+};
+
+struct dirty_event {
+  struct event common;
+  int id;
+};
+
+struct repack_event {
+  struct event common;
+  int id;
+};
+
+/*************************************************************************/
+/*                          main structure                               */
+/*************************************************************************/
+
+struct gui {
+  void                *lock;
+  void                *x; /* opaque X data (type x_connection), used in x.c */
+  struct widget_list  *toplevel;
+  struct event_list   *queued_events;
+  int                 event_pipe[2];
+  int                 next_id;         /* tells what is the ID of
+                                          the next created widget */
+  int                 repainted;       /* set to 1 when some widget has
+                                        * been repainted (TODO: can be any,
+                                        * to be optimized) */
+  void                *xwin;           /* set by a toplevel_window when
+                                        * it paints itself, to be used
+                                        * by its children */
+};
+
+/*************************************************************************/
+/*                            internal functions                         */
+/*************************************************************************/
+
+widget *new_widget(struct gui *g, enum widget_type type, int size);
+void widget_add_child_internal(
+    gui *_gui, widget *parent, widget *child, int position);
+
+const char *widget_name(enum widget_type type);
+
+void send_event(gui *gui, enum event_type type, ...);
+void gui_events(gui *gui);
+
+struct widget *find_widget(struct gui *g, int id);
+
+#endif /* _GUI_DEFS_H_ */
diff --git a/common/utils/T/tracer/gui/init.c b/common/utils/T/tracer/gui/init.c
new file mode 100644
index 0000000000000000000000000000000000000000..c708639674f88099dc1cdaf7c6df6ff96fa0ed11
--- /dev/null
+++ b/common/utils/T/tracer/gui/init.c
@@ -0,0 +1,29 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+gui *gui_init(void)
+{
+  struct gui *ret;
+
+  ret = calloc(1, sizeof(struct gui));
+  if (ret == NULL) OOM;
+
+  ret->lock = malloc(sizeof(pthread_mutex_t));
+  if (ret->lock == NULL) OOM;
+  if (pthread_mutex_init(ret->lock, NULL))
+    ERR("mutex initialization failed\n");
+
+  if (pipe(ret->event_pipe))
+    ERR("%s\n", strerror(errno));
+
+  ret->x = x_open();
+
+  return ret;
+}
diff --git a/common/utils/T/tracer/gui/label.c b/common/utils/T/tracer/gui/label.c
new file mode 100644
index 0000000000000000000000000000000000000000..be6c8794a07af1354382221590f7762655e01c02
--- /dev/null
+++ b/common/utils/T/tracer/gui/label.c
@@ -0,0 +1,46 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void paint(gui *_gui, widget *_w)
+{
+  struct gui *g = _gui;
+  struct label_widget *l = _w;
+printf("PAINT label '%s'\n", l->t);
+  x_draw_string(g->x, g->xwin, l->color,
+      l->common.x, l->common.y + l->baseline, l->t);
+}
+
+static void hints(gui *_gui, widget *_w, int *width, int *height)
+{
+  struct label_widget *l = _w;
+printf("HINTS label '%s'\n", l->t);
+  *width = l->width;
+  *height = l->height;
+}
+
+widget *new_label(gui *_gui, const char *label)
+{
+  struct gui *g = _gui;
+  struct label_widget *w;
+
+  glock(g);
+
+  w = new_widget(g, LABEL, sizeof(struct label_widget));
+
+  w->t = strdup(label);
+  if (w->t == NULL) OOM;
+  w->color = FOREGROUND_COLOR;
+
+  x_text_get_dimensions(g->x, label, &w->width, &w->height, &w->baseline);
+
+  w->common.paint = paint;
+  w->common.hints = hints;
+
+  gunlock(g);
+
+  return w;
+}
diff --git a/common/utils/T/tracer/gui/loop.c b/common/utils/T/tracer/gui/loop.c
new file mode 100644
index 0000000000000000000000000000000000000000..94b7fe54bd5f1cc436f8d05ebc7f6e51f639c464
--- /dev/null
+++ b/common/utils/T/tracer/gui/loop.c
@@ -0,0 +1,59 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <sys/select.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+void gui_loop(gui *_gui)
+{
+  struct gui *g = _gui;
+  int xfd;
+  int eventfd;
+  int maxfd;
+  fd_set rd;
+
+  xfd = x_connection_fd(g->x);
+  eventfd = g->event_pipe[0];
+
+  if (eventfd > xfd) maxfd = eventfd;
+  else               maxfd = xfd;
+
+  while (1) {
+    x_flush(g->x);
+    FD_ZERO(&rd);
+    FD_SET(xfd, &rd);
+    FD_SET(eventfd, &rd);
+    if (select(maxfd+1, &rd, NULL, NULL, NULL) == -1)
+      ERR("select: %s\n", strerror(errno));
+
+    glock(g);
+
+    if (FD_ISSET(xfd, &rd))
+      x_events(g);
+
+    if (FD_ISSET(eventfd, &rd)) {
+      char c[256];
+      if (read(eventfd, c, 256)); /* for no gcc warnings */
+    }
+
+    gui_events(g);
+
+    if (g->repainted) {
+      struct widget_list *cur;
+      g->repainted = 0;
+      cur = g->toplevel;
+      while (cur) {
+        struct toplevel_window_widget *w =
+            (struct toplevel_window_widget *)cur->item;
+        x_draw(g->x, w->x);
+        cur = cur->next;
+      }
+    }
+
+    gunlock(g);
+  }
+}
diff --git a/common/utils/T/tracer/gui/test.c b/common/utils/T/tracer/gui/test.c
new file mode 100644
index 0000000000000000000000000000000000000000..06e3b6309d2b81223675268ca9b92129573daf1d
--- /dev/null
+++ b/common/utils/T/tracer/gui/test.c
@@ -0,0 +1,46 @@
+#include "gui.h"
+
+int main(void)
+{
+  gui *g;
+  widget *w, *c1, *c2, *l, *plot, *tl;
+  int tlcol;
+
+  g = gui_init();
+
+  c1 = new_container(g, VERTICAL);
+  c2 = new_container(g, HORIZONTAL);
+
+  l = new_label(g, "this is a good label");
+  widget_add_child(g, c2, l, 0);
+  l = new_label(g, "this is another good label");
+  widget_add_child(g, c2, l, -1);
+
+  l = new_label(g, "OH! WHAT A LABEL!");
+  widget_add_child(g, c1, l, -1);
+
+  widget_add_child(g, c1, c2, 0);
+
+  plot = new_xy_plot(g, 100, 100, "xy plot test", 30);
+#if 0
+  c2 = new_container(g, HORIZONTAL);
+  widget_add_child(g, c2, plot, -1);
+  widget_add_child(g, c1, c2, -1);
+#else
+  widget_add_child(g, c1, plot, -1);
+#endif
+
+  tlcol = new_color(g, "#ddf");
+  tl = new_text_list(g, 300, 10, tlcol);
+  widget_add_child(g, c1, tl, -1);
+
+  text_list_add(g, tl, "hello", -1);
+  text_list_add(g, tl, "world", -1);
+
+  w = new_toplevel_window(g, 500, 400, "test window");
+  widget_add_child(g, w, c1, 0);
+
+  gui_loop(g);
+
+  return 0;
+}
diff --git a/common/utils/T/tracer/gui/text_list.c b/common/utils/T/tracer/gui/text_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..89b7e8822d8d427bee2f13bf65ba9f3d75c46a0a
--- /dev/null
+++ b/common/utils/T/tracer/gui/text_list.c
@@ -0,0 +1,95 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void paint(gui *_gui, widget *_this)
+{
+  struct gui *g = _gui;
+  struct text_list_widget *this = _this;
+  int i, j;
+printf("PAINT text_list %p xywh %d %d %d %d\n", _this, this->common.x, this->common.y, this->common.width, this->common.height);
+  x_fill_rectangle(g->x, g->xwin, this->background_color,
+      this->common.x, this->common.y,
+      this->common.width, this->common.height);
+  for (i = 0, j = this->starting_line;
+       i < this->allocated_nlines && j < this->text_count; i++, j++)
+    x_draw_string(g->x, g->xwin, FOREGROUND_COLOR,
+        this->common.x,
+        this->common.y + i * this->line_height + this->baseline,
+        this->text[j]);
+}
+
+static void hints(gui *_gui, widget *_w, int *width, int *height)
+{
+  struct text_list_widget *w = _w;
+  *width = w->wanted_width;
+  *height = w->wanted_nlines * w->line_height;
+printf("HINTS text_list wh %d %d\n", *width, *height);
+}
+
+static void allocate(
+    gui *gui, widget *_this, int x, int y, int width, int height)
+{
+  struct text_list_widget *this = _this;
+  this->common.x = x;
+  this->common.y = y;
+  this->common.width = width;
+  this->common.height = height;
+  this->allocated_nlines = height / this->line_height;
+printf("ALLOCATE text_list %p xywh %d %d %d %d nlines %d\n", this, x, y, width, height, this->allocated_nlines);
+}
+
+widget *new_text_list(gui *_gui, int width, int nlines, int bgcol)
+{
+  struct gui *g = _gui;
+  struct text_list_widget *w;
+  int dummy;
+
+  glock(g);
+
+  w = new_widget(g, TEXT_LIST, sizeof(struct text_list_widget));
+
+  w->wanted_nlines = nlines;
+  x_text_get_dimensions(g->x, ".", &dummy, &w->line_height, &w->baseline);
+  w->background_color = bgcol;
+  w->wanted_width = width;
+
+  w->common.paint = paint;
+  w->common.hints = hints;
+  w->common.allocate = allocate;
+
+  gunlock(g);
+
+  return w;
+}
+
+/*************************************************************************/
+/*                             public functions                          */
+/*************************************************************************/
+
+void text_list_add(gui *_gui, widget *_this, const char *text, int position)
+{
+  struct gui *g = _gui;
+  struct text_list_widget *this = _this;
+
+  glock(g);
+
+  if (position < 0) position = this->text_count;
+  if (position > this->text_count) position = this->text_count;
+
+  this->text_count++;
+  this->text = realloc(this->text, this->text_count * sizeof(char *));
+  if (this->text == NULL) OOM;
+
+  memmove(this->text + position + 1, this->text + position,
+          (this->text_count-1 - position) * sizeof(char *));
+
+  this->text[position] = strdup(text); if (this->text[position] == NULL) OOM;
+
+  send_event(g, DIRTY, this->common.id);
+
+  gunlock(g);
+}
diff --git a/common/utils/T/tracer/gui/toplevel_window.c b/common/utils/T/tracer/gui/toplevel_window.c
new file mode 100644
index 0000000000000000000000000000000000000000..b1dd251f6b4bd981386e87ca650dea18fad7d60f
--- /dev/null
+++ b/common/utils/T/tracer/gui/toplevel_window.c
@@ -0,0 +1,88 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+/**********************************************************************/
+/*                         callback functions                         */
+/**********************************************************************/
+
+static void repack(gui *g, widget *_this)
+{
+printf("REPACK toplevel_window\n");
+  struct toplevel_window_widget *this = _this;
+  if (this->common.children == NULL) ERR("toplevel window has no child\n");
+  if (this->common.children->next != NULL)
+    ERR("toplevel window has too much children\n");
+  this->common.children->item->allocate(g, this->common.children->item,
+      0 /* x */, 0 /* y */, this->common.width, this->common.height);
+  send_event(g, DIRTY, this->common.id);
+}
+
+static void add_child(gui *_gui, widget *_this, widget *child, int position)
+{
+printf("ADD_CHILD toplevel_window\n");
+  struct widget *this = _this;
+  if (this->children != NULL) {
+    WARN("toplevel window already has a child\n");
+    return;
+  }
+  if (position)
+    WARN("toplevel window doesn't care about 'position' "
+         "(you passed %d, you should always pass 0)\n",
+        position);
+  widget_add_child_internal(_gui, _this, child, 0); /* this does the REPACK */
+}
+
+/* called when the underlying window is resized by the user or the system */
+static void allocate(
+    gui *_gui, widget *_this, int x, int y, int width, int height)
+{
+printf("ALLOCATE toplevel_window\n");
+  struct toplevel_window_widget *this = _this;
+  this->common.width = width;
+  this->common.height = height;
+//  repack(_gui, _this);
+  send_event(_gui, REPACK, this->common.id);
+}
+
+static void paint(gui *_gui, widget *_this)
+{
+  struct gui *g = _gui;
+  struct toplevel_window_widget *this = _this;
+printf("PAINT toplevel_window (%d %d)\n", this->common.width, this->common.height);
+  x_fill_rectangle(g->x, this->x, BACKGROUND_COLOR,
+      0, 0, this->common.width, this->common.height);
+  g->xwin = this->x;
+  this->common.children->item->paint(_gui, this->common.children->item);
+  g->xwin = NULL;    /* TODO: remove? it's just in case */
+}
+
+/**********************************************************************/
+/*                              creation                              */
+/**********************************************************************/
+
+widget *new_toplevel_window(gui *_gui, int width, int height, char *title)
+{
+  struct gui *g = _gui;
+  struct toplevel_window_widget *w;
+
+  glock(g);
+
+  w = new_widget(g, TOPLEVEL_WINDOW, sizeof(struct toplevel_window_widget));
+
+  w->common.width  = width;
+  w->common.height = height;
+
+  w->x = x_create_window(g->x, width, height, title);
+
+  w->common.repack    = repack;
+  w->common.add_child = add_child;
+  w->common.allocate  = allocate;
+  w->common.paint     = paint;
+
+  gunlock(g);
+
+  return w;
+}
diff --git a/common/utils/T/tracer/gui/widget.c b/common/utils/T/tracer/gui/widget.c
new file mode 100644
index 0000000000000000000000000000000000000000..8304420aa13109598ca2fa95cbc049b4e31f6103
--- /dev/null
+++ b/common/utils/T/tracer/gui/widget.c
@@ -0,0 +1,216 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+static void default_repack(gui *gui, widget *_this);
+static void default_allocate(
+    gui *gui, widget *_this, int x, int y, int width, int height);
+static void default_add_child(
+    gui *_gui, widget *_this, widget *child, int position);
+static void default_hints(gui *g, widget *this, int *width, int *height);
+
+static void toplevel_list_append(struct gui *g, struct widget *e)
+{
+  struct widget_list *new;
+
+  new = calloc(1, sizeof(struct widget_list));
+  if (new == NULL) OOM;
+
+  new->item = e;
+
+  if (g->toplevel == NULL) {
+    g->toplevel = new;
+    new->last = new;
+    return;
+  }
+
+  g->toplevel->last->next = new;
+  g->toplevel->last = new;
+}
+
+widget *new_widget(struct gui *g, enum widget_type type, int size)
+{
+  struct widget *ret;
+
+  //glock(g);
+
+  ret = calloc(1, size);
+  if (ret == NULL) OOM;
+
+  ret->repack    = default_repack;
+  ret->add_child = default_add_child;
+  ret->allocate  = default_allocate;
+  ret->hints     = default_hints;
+  /* there is no default paint, on purpose */
+
+  ret->type      = type;
+  ret->id        = g->next_id;
+  g->next_id++;
+  ret->width     = 0;
+  ret->height    = 0;
+
+  /* add toplevel windows to g->toplevel */
+  if (type == TOPLEVEL_WINDOW)
+    toplevel_list_append(g, ret);
+
+  //gunlock(g);
+
+  return ret;
+}
+
+/*************************************************************************/
+/*                          internal functions                           */
+/*************************************************************************/
+
+void widget_add_child_internal(
+    gui *_gui, widget *parent, widget *child, int position)
+{
+  struct widget *p = parent;
+  struct widget *c = child;
+  struct widget_list *new;
+  struct widget_list *prev, *cur;
+  int i;
+
+  new = calloc(1, sizeof(struct widget_list));
+  if (new == NULL) OOM;
+
+  new->item = c;
+  c->parent = p;
+
+  prev = NULL;
+  cur = p->children;
+
+  for (i = 0; position < 0 || i < position; i++) {
+    if (cur == NULL) break;
+    prev = cur;
+    cur = cur->next;
+  }
+
+  /* TODO: warn/err if i != position+1? */
+
+  if (prev == NULL) {
+    /* new is at head */
+    new->next = p->children;
+    if (p->children != NULL) new->last = p->children->last;
+    else                     new->last = new;
+    p->children = new;
+    goto repack;
+  }
+
+  if (cur == NULL) {
+    /* new is at tail */
+    prev->next = new;
+    p->children->last = new;
+    goto repack;
+  }
+
+  /* new is between two existing items */
+  prev->next = new;
+  new->next = cur;
+
+repack:
+  send_event(_gui, REPACK, p->id);
+}
+
+/*************************************************************************/
+/*                           default functions                           */
+/*************************************************************************/
+
+static void default_repack(gui *gui, widget *_this)
+{
+  struct widget *this = _this;
+  return this->parent->repack(gui, this->parent);
+}
+
+static void default_add_child(
+    gui *_gui, widget *_this, widget *child, int position) 
+{
+  struct widget *this = _this;
+  WARN("cannot add child to widget %s\n", widget_name(this->type));
+}
+
+static void default_allocate(
+    gui *gui, widget *_this, int x, int y, int width, int height)
+{
+  struct widget *this = _this;
+  this->x = x;
+  this->y = y;
+  this->width = width;
+  this->height = height;
+}
+
+static void default_hints(gui *g, widget *this, int *width, int *height)
+{
+  *width = 1;
+  *height = 1;
+}
+
+/*************************************************************************/
+/*                             utils functions                           */
+/*************************************************************************/
+
+void widget_add_child(gui *_gui, widget *parent, widget *child, int position)
+{
+  struct widget *this = parent;
+  glock(_gui);
+  this->add_child(_gui, parent, child, position);
+  gunlock(_gui);
+}
+
+static const char *names[] = {
+  "TOPLEVEL_WINDOW", "CONTAINER", "TEXT_LIST", "XY_PLOT", "BUTTON", "LABEL"
+};
+const char *widget_name(enum widget_type type)
+{
+  switch (type) {
+  default: break;
+  case TOPLEVEL_WINDOW:
+  case CONTAINER:
+  case TEXT_LIST:
+  case XY_PLOT:
+  case BUTTON:
+  case LABEL:
+    return names[type];
+  }
+  return "UNKNOWN (error)";
+}
+
+/*************************************************************************/
+/*                             find a widget                             */
+/*************************************************************************/
+
+/* TODO: optimize traversal and also use a cache */
+struct widget *_find_widget(struct widget *c, int id)
+{
+  struct widget_list *l;
+  struct widget *ret;
+  if (c == NULL) return NULL;
+  if (c->id == id) return c;
+  l = c->children;
+  while (l) {
+    ret = _find_widget(l->item, id);
+    if (ret != NULL) return ret;
+    l = l->next;
+  }
+  return NULL;
+}
+
+struct widget *find_widget(struct gui *g, int id)
+{
+  struct widget_list *l;
+  struct widget *ret;
+
+  l = g->toplevel;
+
+  while (l) {
+    ret = _find_widget(l->item, id);
+    if (ret != NULL) return ret;
+    l = l->next;
+  }
+
+  return NULL;
+}
diff --git a/common/utils/T/tracer/gui/x.c b/common/utils/T/tracer/gui/x.c
new file mode 100644
index 0000000000000000000000000000000000000000..b498654fdb4110a8aadac2f3f96969238cbcc716
--- /dev/null
+++ b/common/utils/T/tracer/gui/x.c
@@ -0,0 +1,284 @@
+#include "x.h"
+#include "x_defs.h"
+#include "gui_defs.h"
+#include <X11/Xlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int x_connection_fd(x_connection *_x)
+{
+  struct x_connection *x = _x;
+  return ConnectionNumber(x->d);
+}
+
+static GC create_gc(Display *d, char *color)
+{
+  GC ret = XCreateGC(d, DefaultRootWindow(d), 0, NULL);
+  XGCValues gcv;
+  XColor rcol, scol;
+
+  XCopyGC(d, DefaultGC(d, DefaultScreen(d)), -1L, ret);
+  if (XAllocNamedColor(d, DefaultColormap(d, DefaultScreen(d)),
+                      color, &scol, &rcol)) {
+    gcv.foreground = scol.pixel;
+    XChangeGC(d, ret, GCForeground, &gcv);
+  } else ERR("X: could not allocate color '%s'\n", color);
+
+  return ret;
+}
+
+int x_new_color(x_connection *_x, char *color)
+{
+  struct x_connection *x = _x;
+  x->ncolors++;
+  x->colors = realloc(x->colors, x->ncolors * sizeof(GC));
+  if (x->colors == NULL) OOM;
+  x->colors[x->ncolors-1] = create_gc(x->d, color);
+  return x->ncolors - 1;
+}
+
+x_connection *x_open(void)
+{
+  struct x_connection *ret;
+
+  ret = calloc(1, sizeof(struct x_connection));
+  if (ret == NULL) OOM;
+
+  ret->d = XOpenDisplay(0);
+printf("XOpenDisplay display %p return x_connection %p\n", ret->d, ret);
+  if (ret->d == NULL) ERR("error calling XOpenDisplay: no X? you root?\n");
+
+  x_new_color(ret, "white");    /* background color */
+  x_new_color(ret, "black");    /* foreground color */
+
+  return ret;
+}
+
+x_window *x_create_window(x_connection *_x, int width, int height,
+    char *title)
+{
+  struct x_connection *x = _x;
+  struct x_window *ret;
+
+  ret = calloc(1, sizeof(struct x_window));
+  if (ret == NULL) OOM;
+
+  ret->w = XCreateSimpleWindow(x->d, DefaultRootWindow(x->d), 0, 0,
+      width, height, 0, WhitePixel(x->d, DefaultScreen(x->d)),
+      WhitePixel(x->d, DefaultScreen(x->d)));
+  ret->width = width;
+  ret->height = height;
+
+  XStoreName(x->d, ret->w, title);
+
+  ret->p = XCreatePixmap(x->d, ret->w, width, height,
+      DefaultDepth(x->d, DefaultScreen(x->d)));
+
+  /* enable backing store */
+  {
+    XSetWindowAttributes att;
+    att.backing_store = Always;
+    XChangeWindowAttributes(x->d, ret->w, CWBackingStore, &att);
+  }
+
+  XSelectInput(x->d, ret->w,
+      KeyPressMask      |
+      ButtonPressMask   |
+      ButtonReleaseMask |
+      PointerMotionMask |
+      ExposureMask      |
+      StructureNotifyMask);
+
+  XMapWindow(x->d, ret->w);
+
+#if 0
+  /* wait for window to be mapped */
+printf("wait for map\n");
+  while (1) {
+    XEvent ev;
+    //XWindowEvent(x->d, ret->w, StructureNotifyMask, &ev);
+    XWindowEvent(x->d, ret->w, ExposureMask, &ev);
+printf("got ev %d\n", ev.type);
+    //if (ev.type == MapNotify) break;
+    if (ev.type == Expose) break;
+  }
+printf("XXX create connection %p window %p (win id %d pixmap %d) w h %d %d\n", x, ret, (int)ret->w, (int)ret->p, width, height);
+#endif
+
+  return ret;
+}
+
+static struct toplevel_window_widget *find_x_window(struct gui *g, Window id)
+{
+  struct widget_list *cur;
+  struct toplevel_window_widget *w;
+  struct x_window *xw;
+  cur = g->toplevel;
+  while (cur) {
+    w = (struct toplevel_window_widget *)cur->item;
+    xw = w->x;
+    if (xw->w == id) return w;
+    cur = cur->next;
+  }
+  return NULL;
+}
+
+void x_events(gui *_gui)
+{
+  struct gui *g = _gui;
+  struct widget_list *cur;
+  struct x_connection *x = g->x;
+  struct toplevel_window_widget *w;
+
+printf("x_events START\n");
+  /* preprocessing (to "compress" events) */
+  cur = g->toplevel;
+  while (cur) {
+    struct x_window *xw;
+    w = (struct toplevel_window_widget *)cur->item;
+    xw = w->x;
+    xw->redraw = 0;
+    xw->repaint = 0;
+    xw->resize = 0;
+    cur = cur->next;
+  }
+
+  while (XPending(x->d)) {
+    XEvent ev;
+    XNextEvent(x->d, &ev);
+printf("XEV %d\n", ev.type);
+    switch (ev.type) {
+    case Expose:
+      if ((w = find_x_window(g, ev.xexpose.window)) != NULL) {
+        struct x_window *xw = w->x;
+        xw->redraw = 1;
+      }
+      break;
+    case ConfigureNotify:
+      if ((w = find_x_window(g, ev.xexpose.window)) != NULL) {
+        struct x_window *xw = w->x;
+        xw->resize = 1;
+        xw->new_width = ev.xconfigure.width;
+        xw->new_height = ev.xconfigure.height;
+        if (xw->new_width < 10) xw->new_width = 10;
+        if (xw->new_height < 10) xw->new_height = 10;
+printf("ConfigureNotify %d %d\n", ev.xconfigure.width, ev.xconfigure.height);
+      }
+      break;
+#if 0
+    case MapNotify:
+      if ((w = find_x_window(g, ev.xexpose.window)) != NULL) {
+        struct x_window *xw = w->x;
+        xw->repaint = 1;
+      }
+      break;
+#endif
+    default: WARN("TODO: X event type %d\n", ev.type); break;
+    }
+  }
+
+  /* postprocessing */
+printf("post processing\n");
+  cur = g->toplevel;
+  while (cur) {
+    struct toplevel_window_widget *w =
+        (struct toplevel_window_widget *)cur->item;
+    struct x_window *xw = w->x;
+    if (xw->resize) {
+printf("resize old %d %d new %d %d\n", xw->width, xw->height, xw->new_width, xw->new_height);
+      if (xw->width != xw->new_width || xw->height != xw->new_height) {
+        w->common.allocate(g, w, 0, 0, xw->new_width, xw->new_height);
+        xw->width = xw->new_width;
+        xw->height = xw->new_height;
+        XFreePixmap(x->d, xw->p);
+        xw->p = XCreatePixmap(x->d, xw->w, xw->width, xw->height,
+            DefaultDepth(x->d, DefaultScreen(x->d)));
+        //xw->repaint = 1;
+      }
+    }
+    if (xw->repaint) {
+      w->common.paint(g, w);
+      xw->redraw = 1;
+    }
+    if (xw->redraw) {
+      struct x_connection *x = g->x;
+printf("XCopyArea w h %d %d\n", xw->width, xw->height);
+      XCopyArea(x->d, xw->p, xw->w, x->colors[1],
+          0, 0, xw->width, xw->height, 0, 0);
+    }
+    cur = cur->next;
+  }
+printf("x_events DONE\n");
+}
+
+void x_flush(x_connection *_x)
+{
+  struct x_connection *x = _x;
+  XFlush(x->d);
+}
+
+void x_text_get_dimensions(x_connection *_c, const char *t,
+    int *width, int *height, int *baseline)
+{
+  struct x_connection *c = _c;
+  int dir;
+  int ascent;
+  int descent;
+  XCharStruct overall;
+
+  /* TODO: don't use XQueryTextExtents (X roundtrip) */
+  XQueryTextExtents(c->d, XGContextFromGC(c->colors[1]), t, strlen(t),
+      &dir, &ascent, &descent, &overall);
+
+//printf("dir %d ascent %d descent %d lbearing %d rbearing %d width %d ascent %d descent %d\n", dir, ascent, descent, overall.lbearing, overall.rbearing, overall.width, overall.ascent, overall.descent);
+
+  *width = overall.width;
+  *height = ascent + descent;
+  *baseline = ascent;
+}
+
+/***********************************************************************/
+/*                    public drawing functions                         */
+/***********************************************************************/
+
+void x_draw_line(x_connection *_c, x_window *_w, int color,
+    int x1, int y1, int x2, int y2)
+{
+  struct x_connection *c = _c;
+  struct x_window *w = _w;
+  XDrawLine(c->d, w->p, c->colors[color], x1, y1, x2, y2);
+}
+
+void x_draw_rectangle(x_connection *_c, x_window *_w, int color,
+    int x, int y, int width, int height)
+{
+  struct x_connection *c = _c;
+  struct x_window *w = _w;
+  XDrawRectangle(c->d, w->p, c->colors[color], x, y, width, height);
+}
+
+void x_fill_rectangle(x_connection *_c, x_window *_w, int color,
+    int x, int y, int width, int height)
+{
+  struct x_connection *c = _c;
+  struct x_window *w = _w;
+  XFillRectangle(c->d, w->p, c->colors[color], x, y, width, height);
+}
+
+void x_draw_string(x_connection *_c, x_window *_w, int color,
+    int x, int y, const char *t)
+{
+  struct x_connection *c = _c;
+  struct x_window *w = _w;
+  int tlen = strlen(t);
+  XDrawString(c->d, w->p, c->colors[color], x, y, t, tlen);
+}
+
+void x_draw(x_connection *_c, x_window *_w)
+{
+  struct x_connection *c = _c;
+  struct x_window *w = _w;
+printf("x_draw XCopyArea w h %d %d display %p window %d pixmap %d\n", w->width, w->height, c->d, (int)w->w, (int)w->p);
+  XCopyArea(c->d, w->p, w->w, c->colors[1], 0, 0, w->width, w->height, 0, 0);
+}
diff --git a/common/utils/T/tracer/gui/x.h b/common/utils/T/tracer/gui/x.h
new file mode 100644
index 0000000000000000000000000000000000000000..41cf65393bcdeaafafa1c4413ca65727c69f04cb
--- /dev/null
+++ b/common/utils/T/tracer/gui/x.h
@@ -0,0 +1,47 @@
+#ifndef _X_H_
+#define _X_H_
+
+/* public X interface */
+
+#define BACKGROUND_COLOR 0
+#define FOREGROUND_COLOR 1
+
+typedef void x_connection;
+typedef void x_window;
+
+x_connection *x_open(void);
+
+x_window *x_create_window(x_connection *x, int width, int height,
+    char *title);
+
+int x_connection_fd(x_connection *x);
+
+void x_flush(x_connection *x);
+
+int x_new_color(x_connection *x, char *color);
+
+/* for x_events, we pass the gui */
+#include "gui.h"
+void x_events(gui *gui);
+
+void x_text_get_dimensions(x_connection *, const char *t,
+                           int *width, int *height, int *baseline);
+
+/* drawing functions */
+
+void x_draw_line(x_connection *c, x_window *w, int color,
+    int x1, int y1, int x2, int y2);
+
+void x_draw_rectangle(x_connection *c, x_window *w, int color,
+    int x, int y, int width, int height);
+
+void x_fill_rectangle(x_connection *c, x_window *w, int color,
+    int x, int y, int width, int height);
+
+void x_draw_string(x_connection *_c, x_window *_w, int color,
+    int x, int y, const char *t);
+
+/* this function copies the pixmap to the window */
+void x_draw(x_connection *c, x_window *w);
+
+#endif /* _X_H_ */
diff --git a/common/utils/T/tracer/gui/x_defs.h b/common/utils/T/tracer/gui/x_defs.h
new file mode 100644
index 0000000000000000000000000000000000000000..43b99f7cf7ae907f2245b2f51ff9ac96b68b7f30
--- /dev/null
+++ b/common/utils/T/tracer/gui/x_defs.h
@@ -0,0 +1,23 @@
+#ifndef _X_DEFS_H_
+#define _X_DEFS_H_
+
+#include <X11/Xlib.h>
+
+struct x_connection {
+  Display *d;
+  GC *colors;
+  int ncolors;
+};
+
+struct x_window {
+  Window w;
+  Pixmap p;
+  int width;
+  int height;
+  /* below: internal data used for X events handling */
+  int redraw;
+  int repaint;
+  int resize, new_width, new_height;
+};
+
+#endif /* _X_DEFS_H_ */
diff --git a/common/utils/T/tracer/gui/xy_plot.c b/common/utils/T/tracer/gui/xy_plot.c
new file mode 100644
index 0000000000000000000000000000000000000000..b2775fa054007c7537a6baba6d406c447761b4d5
--- /dev/null
+++ b/common/utils/T/tracer/gui/xy_plot.c
@@ -0,0 +1,173 @@
+#include "gui.h"
+#include "gui_defs.h"
+#include "x.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+static void paint(gui *_gui, widget *_this)
+{
+  struct gui *g = _gui;
+  struct xy_plot_widget *this = _this;
+  int wanted_plot_width, allocated_plot_width;
+  int wanted_plot_height, allocated_plot_height;
+  float pxsize;
+  float ticdist;
+  float tic;
+  float ticstep;
+  int k, kmin, kmax;
+  float allocated_xmin, allocated_xmax;
+  float allocated_ymin, allocated_ymax;
+  float center;
+
+printf("PAINT xy plot xywh %d %d %d %d\n", this->common.x, this->common.y, this->common.width, this->common.height);
+
+//x_draw_rectangle(g->x, g->xwin, 1, this->common.x, this->common.y, this->common.width, this->common.height);
+
+  /* plot zone */
+  /* TODO: refine height - height of hrule text may be != from label */
+  x_draw_rectangle(g->x, g->xwin, 1,
+      this->common.x + this->vrule_width,
+      this->common.y,
+      this->common.width - this->vrule_width -1, /* -1 to see right border */
+      this->common.height - this->label_height * 2);
+
+  /* horizontal tics */
+  wanted_plot_width = this->wanted_width;
+  allocated_plot_width = this->common.width - this->vrule_width;
+  pxsize = (this->xmax - this->xmin) / wanted_plot_width;
+  ticdist = 100;
+  tic = floor(log10(ticdist * pxsize));
+  ticstep = powf(10, tic);
+  center = (this->xmax + this->xmin) / 2;
+  allocated_xmin = center - ((this->xmax - this->xmin) *
+                             allocated_plot_width / wanted_plot_width) / 2;
+  allocated_xmax = center + ((this->xmax - this->xmin) *
+                             allocated_plot_width / wanted_plot_width) / 2;
+  /* adjust tic if too tight */
+printf("pre x ticstep %g\n", ticstep);
+  while (1) {
+    if (ticstep / (allocated_xmax - allocated_xmin)
+                * (allocated_plot_width - 1) > 40) break;
+    ticstep *= 2;
+  }
+printf("post x ticstep %g\n", ticstep);
+printf("xmin/max %g %g width wanted allocated %d %d alloc xmin/max %g %g ticstep %g\n", this->xmin, this->xmax, wanted_plot_width, allocated_plot_width, allocated_xmin, allocated_xmax, ticstep);
+  kmin = ceil(allocated_xmin / ticstep);
+  kmax = floor(allocated_xmax / ticstep);
+  for (k = kmin; k <= kmax; k++) {
+/*
+    (k * ticstep - allocated_xmin) / (allocated_max - allocated_xmin) =
+    (x - 0) / (allocated_plot_width-1 - 0)
+ */
+    char v[64];
+    int vwidth, dummy;
+    float x = (k * ticstep - allocated_xmin) /
+              (allocated_xmax - allocated_xmin) *
+              (allocated_plot_width - 1);
+    x_draw_line(g->x, g->xwin, FOREGROUND_COLOR,
+        this->common.x + this->vrule_width + x,
+        this->common.y + this->common.height - this->label_height * 2,
+        this->common.x + this->vrule_width + x,
+        this->common.y + this->common.height - this->label_height * 2 - 5);
+    sprintf(v, "%g", k * ticstep);
+    x_text_get_dimensions(g->x, v, &vwidth, &dummy, &dummy);
+    x_draw_string(g->x, g->xwin, FOREGROUND_COLOR,
+        this->common.x + this->vrule_width + x - vwidth/2,
+        this->common.y + this->common.height - this->label_height * 2 + this->label_baseline,
+        v);
+printf("tic k %d val %g x %g\n", k, k * ticstep, x);
+  }
+
+  /* vertical tics */
+  wanted_plot_height = this->wanted_height;
+  allocated_plot_height = this->common.height - this->label_height * 2;
+  pxsize = (this->ymax - this->ymin) / wanted_plot_height;
+  ticdist = 30;
+  tic = floor(log10(ticdist * pxsize));
+  ticstep = powf(10, tic);
+  center = (this->ymax + this->ymin) / 2;
+  allocated_ymin = center - ((this->ymax - this->ymin) *
+                             allocated_plot_height / wanted_plot_height) / 2;
+  allocated_ymax = center + ((this->ymax - this->ymin) *
+                             allocated_plot_height / wanted_plot_height) / 2;
+  /* adjust tic if too tight */
+printf("pre y ticstep %g\n", ticstep);
+  while (1) {
+    if (ticstep / (allocated_ymax - allocated_ymin)
+                * (allocated_plot_height - 1) > 20) break;
+    ticstep *= 2;
+  }
+printf("post y ticstep %g\n", ticstep);
+printf("ymin/max %g %g height wanted allocated %d %d alloc ymin/max %g %g ticstep %g\n", this->ymin, this->ymax, wanted_plot_height, allocated_plot_height, allocated_ymin, allocated_ymax, ticstep);
+  kmin = ceil(allocated_ymin / ticstep);
+  kmax = floor(allocated_ymax / ticstep);
+  for (k = kmin; k <= kmax; k++) {
+    char v[64];
+    int vwidth, dummy;
+    float y = (k * ticstep - allocated_ymin) /
+              (allocated_ymax - allocated_ymin) *
+              (allocated_plot_height - 1);
+    sprintf(v, "%g", k * ticstep);
+    x_text_get_dimensions(g->x, v, &vwidth, &dummy, &dummy);
+    x_draw_line(g->x, g->xwin, FOREGROUND_COLOR,
+        this->common.x + this->vrule_width,
+        this->common.y + y,
+        this->common.x + this->vrule_width + 5,
+        this->common.y + y);
+    x_draw_string(g->x, g->xwin, FOREGROUND_COLOR,
+        this->common.x + this->vrule_width - vwidth - 2,
+        this->common.y + y - this->label_height / 2 + this->label_baseline,
+        v);
+  }
+
+  /* label at bottom, in the middle */
+  x_draw_string(g->x, g->xwin, FOREGROUND_COLOR,
+      this->common.x + (this->common.width - this->label_width) / 2,
+      this->common.y + this->common.height - this->label_height
+          + this->label_baseline,
+      this->label);
+}
+
+static void hints(gui *_gui, widget *_w, int *width, int *height)
+{
+  struct xy_plot_widget *w = _w;
+  *width = w->wanted_width + w->vrule_width;
+  *height = w->wanted_height + w->label_height * 2; /* TODO: refine */
+printf("HINTS xy plot wh %d %d (vrule_width %d) (wanted wh %d %d)\n", *width, *height, w->vrule_width, w->wanted_width, w->wanted_height);
+}
+
+widget *new_xy_plot(gui *_gui, int width, int height, char *label,
+    int vruler_width)
+{
+  struct gui *g = _gui;
+  struct xy_plot_widget *w;
+
+  glock(g);
+
+  w = new_widget(g, XY_PLOT, sizeof(struct xy_plot_widget));
+
+  w->label = strdup(label); if (w->label == NULL) OOM;
+  /* TODO: be sure calling X there is valid wrt "global model" (we are
+   * not in the "gui thread") */
+  x_text_get_dimensions(g->x, label, &w->label_width, &w->label_height,
+      &w->label_baseline);
+printf("XY PLOT label wh %d %d\n", w->label_width, w->label_height);
+
+  w->wanted_width = width;
+  w->wanted_height = height;
+  w->vrule_width = vruler_width;
+
+  w->xmin = -1;
+  w->xmax = 1;
+  w->ymin = -1;
+  w->ymax = 1;
+
+  w->common.paint = paint;
+  w->common.hints = hints;
+
+  gunlock(g);
+
+  return w;
+}