[PATCH v2 libinput] tools: add a tool for GUI-based debugging
Peter Hutterer
peter.hutterer at who-t.net
Thu May 29 22:33:41 PDT 2014
Looking at debugging output is nice but not useful when testing for the feel
of a device. Add a tool that presents a canvas and draws the various events
onto it.
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
Changes to v1:
- switched from xlib to GTK, tested under weston
- add LMR button display
- misc minor cleanups
Review of the GTK bits would be appreciated, I'm not that familiar with that
library.
configure.ac | 15 ++
tools/.gitignore | 1 +
tools/Makefile.am | 9 +
tools/event-gui.c | 479 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 504 insertions(+)
create mode 100644 tools/event-gui.c
diff --git a/configure.ac b/configure.ac
index ec9b0de..ada65d8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,6 +72,21 @@ else
fi
AM_CONDITIONAL([HAVE_DOXYGEN], [test "x$have_doxygen" = "xyes"])
+AC_ARG_ENABLE(event-gui,
+ AS_HELP_STRING([--enable-event-gui], [Build the GUI event viewer (default=auto)]),
+ [build_eventgui="$enableval"],
+ [build_eventgui="auto"])
+PKG_CHECK_EXISTS([cairo glib-2.0 gtk+-3.0], [HAVE_GUILIBS="yes"], [HAVE_GUILIBS="no"])
+
+if test "x$build_eventgui" = "xauto"; then
+ build_eventgui="$HAVE_GUILIBS"
+fi
+if test "x$build_eventgui" = "xyes"; then
+ PKG_CHECK_MODULES(CAIRO, [cairo])
+ PKG_CHECK_MODULES(GTK, [glib-2.0 gtk+-3.0])
+fi
+AM_CONDITIONAL(BUILD_EVENTGUI, [test "x$build_eventgui" = "xyes"])
+
AC_ARG_ENABLE(tests,
AS_HELP_STRING([--enable-tests], [Build the tests (default=auto)]),
[build_tests="$enableval"],
diff --git a/tools/.gitignore b/tools/.gitignore
index 2cdd654..cf348a6 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -1 +1,2 @@
event-debug
+event-gui
diff --git a/tools/Makefile.am b/tools/Makefile.am
index a8e34de..ca3a322 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -6,3 +6,12 @@ event_debug_SOURCES = event-debug.c
event_debug_LDADD = ../src/libinput.la $(LIBUDEV_LIBS)
event_debug_LDFLAGS = -static
event_debug_CFLAGS = $(LIBUDEV_CFLAGS)
+
+if BUILD_EVENTGUI
+noinst_PROGRAMS += event-gui
+
+event_gui_SOURCES = event-gui.c
+event_gui_LDADD = ../src/libinput.la $(CAIRO_LIBS) $(GTK_LIBS) $(LIBUDEV_LIBS)
+event_gui_CFLAGS = $(CAIRO_CFLAGS) $(GTK_CFLAGS) $(LIBUDEV_CFLAGS)
+event_gui_LDFLAGS = -static
+endif
diff --git a/tools/event-gui.c b/tools/event-gui.c
new file mode 100644
index 0000000..e60ef27
--- /dev/null
+++ b/tools/event-gui.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#define _GNU_SOURCE
+#include <config.h>
+
+#include <linux/input.h>
+
+#include <cairo.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+#include <glib.h>
+
+#include <libinput.h>
+#include <libinput-util.h>
+
+#define clip(val_, min_, max_) min((max_), max((min_), (val_)))
+
+struct touch {
+ int active;
+ int x, y;
+};
+
+struct window {
+ GtkWidget *win;
+ GtkWidget *area;
+ int width, height; /* of window */
+
+ /* sprite position */
+ int x, y;
+
+ /* abs position */
+ int absx, absy;
+
+ /* scroll bar positions */
+ int vx, vy;
+ int hx, hy;
+
+ /* touch positions */
+ struct touch touches[32];
+
+ /* l/m/r mouse buttons */
+ int l, m, r;
+};
+
+static int
+error(const char *fmt, ...)
+{
+ va_list args;
+ fprintf(stderr, "error: ");
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ return EXIT_FAILURE;
+}
+
+static void
+msg(const char *fmt, ...)
+{
+ va_list args;
+ printf("info: ");
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+}
+
+static void
+usage(void)
+{
+ printf("%s [path/to/device]\n", program_invocation_short_name);
+}
+
+static gboolean
+draw(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ struct window *w = data;
+ struct touch *t;
+
+ cairo_set_source_rgb(cr, 1, 1, 1);
+ cairo_rectangle(cr, 0, 0, w->width, w->height);
+ cairo_fill(cr);
+
+ /* draw pointer sprite */
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_save(cr);
+ cairo_move_to(cr, w->x, w->y);
+ cairo_rel_line_to(cr, 10, 15);
+ cairo_rel_line_to(cr, -10, 0);
+ cairo_rel_line_to(cr, 0, -15);
+ cairo_fill(cr);
+ cairo_restore(cr);
+
+ /* draw scroll bars */
+ cairo_set_source_rgb(cr, .4, .8, 0);
+
+ cairo_save(cr);
+ cairo_rectangle(cr, w->vx - 10, w->vy - 20, 20, 40);
+ cairo_rectangle(cr, w->hx - 20, w->hy - 10, 40, 20);
+ cairo_fill(cr);
+ cairo_restore(cr);
+
+ /* touch points */
+ cairo_set_source_rgb(cr, .8, .2, .2);
+
+ ARRAY_FOR_EACH(w->touches, t) {
+ cairo_save(cr);
+ cairo_arc(cr, t->x, t->y, 10, 0, 2 * M_PI);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ }
+
+ /* abs position */
+ cairo_set_source_rgb(cr, .2, .4, .8);
+
+ cairo_save(cr);
+ cairo_move_to(cr, w->absx, w->absy);
+ cairo_arc(cr, 0, 0, 10, 0, 2 * M_PI);
+ cairo_fill(cr);
+ cairo_restore(cr);
+
+ /* lmr buttons */
+ cairo_save(cr);
+ if (w->l || w->m || w->r) {
+ cairo_set_source_rgb(cr, .2, .8, .8);
+ if (w->l)
+ cairo_rectangle(cr, w->width/2 - 100, w->height - 200, 70, 30);
+ if (w->m)
+ cairo_rectangle(cr, w->width/2 - 20, w->height - 200, 40, 30);
+ if (w->r)
+ cairo_rectangle(cr, w->width/2 + 30, w->height - 200, 70, 30);
+ cairo_fill(cr);
+ }
+
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_rectangle(cr, w->width/2 - 100, w->height - 200, 70, 30);
+ cairo_rectangle(cr, w->width/2 - 20, w->height - 200, 40, 30);
+ cairo_rectangle(cr, w->width/2 + 30, w->height - 200, 70, 30);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ return TRUE;
+}
+
+static void
+map_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ struct window *w = data;
+
+ gtk_window_get_size(GTK_WINDOW(widget), &w->width, &w->height);
+
+ w->x = w->width/2;
+ w->y = w->height/2;
+
+ w->vx = w->width/2;
+ w->vy = w->height/2;
+ w->hx = w->width/2;
+ w->hy = w->height/2;
+
+ g_signal_connect(G_OBJECT(w->area), "draw", G_CALLBACK(draw), w);
+
+ gdk_window_set_cursor(gtk_widget_get_window(w->win),
+ gdk_cursor_new(GDK_BLANK_CURSOR));
+}
+
+static void
+window_init(struct window *w)
+{
+ memset(w, 0, sizeof(*w));
+
+ w->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_events(w->win, 0);
+ gtk_window_set_title(GTK_WINDOW(w->win), "libinput debugging tool");
+ gtk_window_set_default_size(GTK_WINDOW(w->win), 1024, 768);
+ gtk_window_maximize(GTK_WINDOW(w->win));
+ gtk_window_set_resizable(GTK_WINDOW(w->win), TRUE);
+ gtk_widget_realize(w->win);
+ g_signal_connect(G_OBJECT(w->win), "map-event", G_CALLBACK(map_event_cb), w);
+ g_signal_connect(G_OBJECT(w->win), "delete-event", G_CALLBACK(gtk_main_quit), NULL);
+
+ w->area = gtk_drawing_area_new();
+ gtk_widget_set_events(w->area, 0);
+ gtk_container_add(GTK_CONTAINER(w->win), w->area);
+ gtk_widget_show_all(w->win);
+}
+
+static void
+handle_event_device_notify(struct libinput_event *ev)
+{
+ struct libinput_device *dev = libinput_event_get_device(ev);
+ const char *type;
+
+ if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED)
+ type = "added";
+ else
+ type = "removed";
+
+ msg("%s %s\n", libinput_device_get_sysname(dev), type);
+}
+
+static void
+handle_event_motion(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_pointer *p = libinput_event_get_pointer_event(ev);
+ li_fixed_t x = libinput_event_pointer_get_dx(p),
+ y = libinput_event_pointer_get_dy(p);
+ int dx = li_fixed_to_int(x),
+ dy = li_fixed_to_int(y);
+
+ w->x += dx;
+ w->y += dy;
+ w->x = clip(w->x, 0, w->width);
+ w->y = clip(w->y, 0, w->height);
+}
+
+static void
+handle_event_absmotion(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_pointer *p = libinput_event_get_pointer_event(ev);
+ li_fixed_t xf = libinput_event_pointer_get_absolute_x(p),
+ yf = libinput_event_pointer_get_absolute_y(p);
+ int x = li_fixed_to_int(xf),
+ y = li_fixed_to_int(yf);
+
+ w->absx = clip(x, 0, w->width);
+ w->absy = clip(y, 0, w->height);;
+}
+
+static void
+handle_event_touch(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_touch *t = libinput_event_get_touch_event(ev);
+ int slot = libinput_event_touch_get_seat_slot(t);
+ struct touch *touch;
+ li_fixed_t x, y;
+
+ if (slot == -1 || slot >= ARRAY_LENGTH(w->touches))
+ return;
+
+ touch = &w->touches[slot];
+
+ if (libinput_event_get_type(ev) == LIBINPUT_EVENT_TOUCH_UP) {
+ touch->active = 0;
+ return;
+ }
+
+ x = libinput_event_touch_get_x(t),
+ y = libinput_event_touch_get_y(t);
+
+ touch->active = 1;
+ touch->x = li_fixed_to_int(x);
+ touch->y = li_fixed_to_int(y);
+}
+
+static void
+handle_event_axis(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_pointer *p = libinput_event_get_pointer_event(ev);
+ enum libinput_pointer_axis axis = libinput_event_pointer_get_axis(p);
+ li_fixed_t vf = libinput_event_pointer_get_axis_value(p);
+ int v = li_fixed_to_int(vf);
+
+ switch (axis) {
+ case LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL:
+ w->vy += v;
+ w->vy = clip(w->vy, 0, w->height);
+ break;
+ case LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL:
+ w->hx += v;
+ w->hx = clip(w->hx, 0, w->width);
+ break;
+ default:
+ abort();
+ }
+}
+
+static int
+handle_event_keyboard(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_keyboard *k = libinput_event_get_keyboard_event(ev);
+
+ if (libinput_event_keyboard_get_key(k) == KEY_ESC)
+ return 1;
+
+ return 0;
+}
+
+static void
+handle_event_button(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_pointer *p = libinput_event_get_pointer_event(ev);
+ unsigned int button = libinput_event_pointer_get_button(p);
+ int is_press;
+
+ is_press = libinput_event_pointer_get_button_state(p) == LIBINPUT_POINTER_BUTTON_STATE_PRESSED;
+
+ switch (button) {
+ case BTN_LEFT:
+ w->l = is_press;
+ break;
+ case BTN_RIGHT:
+ w->r = is_press;
+ break;
+ case BTN_MIDDLE:
+ w->m = is_press;
+ break;
+ }
+
+}
+
+static gboolean
+handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ struct libinput *li = data;
+ struct window *w = libinput_get_user_data(li);
+ struct libinput_event *ev;
+
+ libinput_dispatch(li);
+
+ while ((ev = libinput_get_event(li))) {
+ switch (libinput_event_get_type(ev)) {
+ case LIBINPUT_EVENT_NONE:
+ abort();
+ case LIBINPUT_EVENT_DEVICE_ADDED:
+ case LIBINPUT_EVENT_DEVICE_REMOVED:
+ handle_event_device_notify(ev);
+ break;
+ case LIBINPUT_EVENT_POINTER_MOTION:
+ handle_event_motion(ev, w);
+ break;
+ case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
+ handle_event_absmotion(ev, w);
+ break;
+ case LIBINPUT_EVENT_TOUCH_DOWN:
+ case LIBINPUT_EVENT_TOUCH_MOTION:
+ case LIBINPUT_EVENT_TOUCH_UP:
+ handle_event_touch(ev, w);
+ break;
+ case LIBINPUT_EVENT_POINTER_AXIS:
+ handle_event_axis(ev, w);
+ break;
+ case LIBINPUT_EVENT_TOUCH_CANCEL:
+ case LIBINPUT_EVENT_TOUCH_FRAME:
+ break;
+ case LIBINPUT_EVENT_POINTER_BUTTON:
+ handle_event_button(ev, w);
+ break;
+ case LIBINPUT_EVENT_KEYBOARD_KEY:
+ if (handle_event_keyboard(ev, w)) {
+ libinput_event_destroy(ev);
+ gtk_main_quit();
+ return FALSE;
+ }
+ break;
+ }
+
+ libinput_event_destroy(ev);
+ libinput_dispatch(li);
+ }
+ gtk_widget_queue_draw(w->area);
+
+ return TRUE;
+}
+
+static void
+sockets_init(struct libinput *li)
+{
+ GIOChannel *c = g_io_channel_unix_new(libinput_get_fd(li));
+
+ g_io_channel_set_encoding(c, NULL, NULL);
+ g_io_add_watch(c, G_IO_IN, handle_event_libinput, li);
+}
+
+static int
+parse_opts(int argc, char *argv[])
+{
+ while (1) {
+ static struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ };
+
+ int option_index = 0;
+ int c;
+
+ c = getopt_long(argc, argv, "h", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch(c) {
+ case 'h':
+ usage();
+ return 0;
+ default:
+ usage();
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+open_restricted(const char *path, int flags, void *user_data)
+{
+ int fd = open(path, flags);
+ return fd < 0 ? -errno : fd;
+}
+
+static void
+close_restricted(int fd, void *user_data)
+{
+ close(fd);
+}
+
+const static struct libinput_interface interface = {
+ .open_restricted = open_restricted,
+ .close_restricted = close_restricted,
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct window w;
+ struct libinput *li;
+ struct udev *udev;
+
+ gtk_init(&argc, &argv);
+
+ if (parse_opts(argc, argv) != 0)
+ return 1;
+
+ udev = udev_new();
+ if (!udev)
+ error("Failed to initialize udev\n");
+
+ li = libinput_udev_create_for_seat(&interface, &w, udev, "seat0");
+ if (!li)
+ error("Failed to initialize context from udev\n");
+
+ window_init(&w);
+ sockets_init(li);
+
+ gtk_main();
+
+ libinput_destroy(li);
+ udev_unref(udev);
+
+ return 0;
+}
--
1.9.3
More information about the wayland-devel
mailing list