[RFC libinput] tools: add a tool for GUI-based debugging

Peter Hutterer peter.hutterer at who-t.net
Mon May 26 23:45:42 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.
---
Still a bit rough and doesn't handle button/key events yet but it makes
testing changes a lot easier (especially for pointer motion and scrolling).
I take suggestions for a less silly name.

To quit, hit escape.

 configure.ac      |  16 ++
 tools/.gitignore  |   1 +
 tools/Makefile.am |   9 +
 tools/guitool.c   | 552 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 578 insertions(+)
 create mode 100644 tools/guitool.c

diff --git a/configure.ac b/configure.ac
index ec9b0de..4ef8a18 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,6 +72,22 @@ else
 fi
 AM_CONDITIONAL([HAVE_DOXYGEN], [test "x$have_doxygen" = "xyes"])
 
+AC_ARG_ENABLE(guitool,
+	      AS_HELP_STRING([--enable-guitool], [Build the GUI tool (default=auto)]),
+	      [build_guitool="$enableval"],
+	      [build_guitool="auto"])
+PKG_CHECK_MODULES(GUILIBS, [cairo x11 xfixes], [HAVE_GUILIBS="yes"], [HAVE_GUILIBS="no"])
+
+if test "x$build_guitool" = "xauto"; then
+	build_guitool="$HAVE_GUILIBS"
+fi
+if test "x$build_guitool" = "xyes"; then
+	if test "x$HAVE_GUILIBS" = "xno"; then
+		AC_MSG_ERROR([Cannot build GUI tool, required dependencies are is missing])
+	fi
+fi
+AM_CONDITIONAL(BUILD_GUITOOL, [test "x$build_guitool" = "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..ce2321f 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -1 +1,2 @@
 event-debug
+guitool
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 9c29f56..da0bc85 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -5,3 +5,12 @@ AM_CPPFLAGS = -I$(top_srcdir)/src
 event_debug_SOURCES = event-debug.c
 event_debug_LDADD = ../src/libinput.la
 event_debug_LDFLAGS = -static
+
+if BUILD_GUITOOL
+noinst_PROGRAMS += guitool
+
+guitool_SOURCES = guitool.c
+guitool_LDADD = ../src/libinput.la $(GUILIBS_LIBS)
+guitool_CFLAGS = $(GUILIBS_CFLAGS) $(LIBUDEV_CFLAGS)
+guitool_LDFLAGS = -static
+endif
diff --git a/tools/guitool.c b/tools/guitool.c
new file mode 100644
index 0000000..c043255
--- /dev/null
+++ b/tools/guitool.c
@@ -0,0 +1,552 @@
+/*
+ * 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 <sys/signalfd.h>
+#include <X11/extensions/Xfixes.h>
+
+#include <cairo.h>
+#include <cairo-xlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <math.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.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 {
+	Display *dpy;
+	Window win;
+	Visual *visual;
+	int screen;
+	float off_x, off_y;
+	int width, height; /* of window */
+
+	/* buffer */
+	cairo_t *cr;
+	cairo_surface_t *surface;
+
+	/* window */
+	cairo_t *cr_win;
+	cairo_surface_t *surface_win;
+
+	/* 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];
+};
+
+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 void
+compose(struct window *w)
+{
+	struct touch *t;
+
+	cairo_set_source_rgb(w->cr, 1, 1, 1);
+	cairo_rectangle(w->cr, 0, 0, w->width, w->height);
+	cairo_fill(w->cr);
+
+	/* draw pointer sprite */
+	cairo_set_source_rgb(w->cr, 0, 0, 0);
+	cairo_save(w->cr);
+	cairo_move_to(w->cr, w->x, w->y);
+	cairo_rel_line_to(w->cr, 10, 15);
+	cairo_rel_line_to(w->cr, -10, 0);
+	cairo_rel_line_to(w->cr, 0, -15);
+	cairo_fill(w->cr);
+	cairo_restore(w->cr);
+
+	/* draw scroll bars */
+	cairo_set_source_rgb(w->cr, .4, .8, 0);
+
+	cairo_save(w->cr);
+	cairo_rectangle(w->cr, w->vx - 10, w->vy - 20, 20, 40);
+	cairo_rectangle(w->cr, w->hx - 20, w->hy - 10, 40, 20);
+	cairo_fill(w->cr);
+	cairo_restore(w->cr);
+
+	/* touch points */
+	cairo_set_source_rgb(w->cr, .8, .2, .2);
+
+	ARRAY_FOR_EACH(w->touches, t) {
+		cairo_save(w->cr);
+		cairo_arc(w->cr, t->x, t->y, 10, 0, 2 * M_PI);
+		cairo_fill(w->cr);
+		cairo_restore(w->cr);
+	}
+
+	/* abs position */
+	cairo_set_source_rgb(w->cr, .2, .4, .8);
+
+	cairo_save(w->cr);
+	cairo_move_to(w->cr, w->absx, w->absy);
+	cairo_arc(w->cr, 0, 0, 10, 0, 2 * M_PI);
+	cairo_fill(w->cr);
+	cairo_restore(w->cr);
+}
+
+static void
+expose(struct window *win, int x, int y, int w, int h) {
+	cairo_set_source_surface(win->cr_win, win->surface, 0, 0);
+	cairo_rectangle(win->cr_win, x, y, w, h);
+	cairo_fill(win->cr_win);
+
+	XFlush(win->dpy);
+}
+
+static void
+window_init(struct window *w)
+{
+	memset(w, 0, sizeof(*w));
+	w->dpy = XOpenDisplay(NULL);
+
+	if (!w->dpy)
+		error("Failed to open display\n");
+
+	w->screen = DefaultScreen(w->dpy);
+
+	w->width = DisplayWidth(w->dpy, w->screen);
+	w->height = DisplayHeight(w->dpy, w->screen);
+	w->off_x = 0;
+	w->off_y = 0;
+	w->win = XCreateSimpleWindow(w->dpy, XDefaultRootWindow(w->dpy),
+				     0, 0, w->width, w->height,
+				     0,
+				     BlackPixel(w->dpy, w->screen),
+				     WhitePixel(w->dpy, w->screen));
+	w->visual = DefaultVisual(w->dpy, w->screen);
+
+
+	w->surface_win = cairo_xlib_surface_create(w->dpy, w->win, w->visual,
+						   w->width, w->height);
+	w->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+						w->width, w->height);
+
+	w->cr_win = cairo_create(w->surface_win);
+	w->cr = cairo_create(w->surface);
+
+	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;
+
+	cairo_set_line_width(w->cr, 1);
+	cairo_set_source_rgb(w->cr, 1, 1, 1);
+	cairo_rectangle(w->cr, 0, 0, w->width, w->height);
+	cairo_fill(w->cr);
+
+	expose(w, 0, 0, w->width, w->height);
+
+	XSelectInput(w->dpy, w->win, StructureNotifyMask|ExposureMask);
+	XMapWindow(w->dpy, w->win);
+	XStoreName(w->dpy, w->win, "libinput debugging tool");
+	XFlush(w->dpy);
+
+	XGrabPointer(w->dpy, w->win, False, 0, GrabModeAsync, GrabModeAsync, None, None, None);
+	XFixesHideCursor(w->dpy, w->win);
+	XSync(w->dpy, False);
+}
+
+static void
+window_destroy(struct window *w)
+{
+	cairo_destroy(w->cr);
+	cairo_destroy(w->cr_win);
+	cairo_surface_destroy(w->surface);
+	cairo_surface_destroy(w->surface_win);
+
+
+	XUnmapWindow(w->dpy, w->win);
+	XDestroyWindow(w->dpy, w->win);
+	XCloseDisplay(w->dpy);
+}
+
+static void
+window_resize(struct window *w, XEvent *xev)
+{
+	XConfigureEvent *cev = (XConfigureEvent *)xev;
+
+	if (cev && cev->width && cev->height) {
+		if (cev->width != w->width || cev->height != w->height) {
+			cairo_destroy(w->cr_win);
+			cairo_surface_destroy(w->surface_win);
+
+			w->width = cev->width;
+			w->height = cev->height;
+			w->surface_win = cairo_xlib_surface_create(w->dpy, w->win,
+								   w->visual,
+								   w->width, w->height);
+			w->cr_win = cairo_create(w->surface_win);
+			expose(w, 0, 0, w->width, w->height);
+		}
+	}
+}
+
+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)
+{
+
+}
+
+static int
+handle_event_libinput(struct libinput *li, struct window *w)
+{
+	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_KEYBOARD_KEY:
+			if (handle_event_keyboard(ev, w)) {
+				libinput_event_destroy(ev);
+				return 1;
+			}
+			break;
+		case LIBINPUT_EVENT_POINTER_BUTTON:
+			handle_event_button(ev, w);
+			break;
+		}
+
+		libinput_event_destroy(ev);
+		libinput_dispatch(li);
+	}
+	compose(w);
+	expose(w, 0, 0, w->width, w->height);
+
+	return 0;
+}
+
+static void
+handle_event_x11(struct window *w)
+{
+	XEvent ev;
+	while (XPending(w->dpy)) {
+		XNextEvent(w->dpy, &ev);
+		if (ev.type == ConfigureNotify)
+			window_resize(w, &ev);
+	}
+}
+
+static void
+mainloop(struct libinput *li, struct window *w)
+{
+	struct pollfd fds[3];
+	sigset_t mask;
+
+	fds[0].fd = libinput_get_fd(li);
+	fds[0].events = POLLIN;
+	fds[0].revents = 0;
+
+	fds[1].fd = ConnectionNumber(w->dpy);
+	fds[1].events = POLLIN;
+	fds[1].revents = 0;
+
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGINT);
+
+	fds[2].fd = signalfd(-1, &mask, SFD_NONBLOCK);
+	fds[2].events = POLLIN;
+	fds[2].revents = 0;
+
+	if (fds[2].fd == -1 ||
+	    sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+		error("Failed to set up signal handling (%s)\n", strerror(errno));
+
+	while (poll(fds, 3, -1) > -1) {
+		if (fds[2].revents)
+			break;
+
+		if (fds[1].revents)
+			handle_event_x11(w);
+
+		if (fds[0].revents) {
+			if (handle_event_libinput(li, w))
+				break;
+		}
+
+		fds[0].revents = 0;
+		fds[1].revents = 0;
+	}
+
+	close(fds[1].fd);
+	close(fds[2].fd);
+}
+
+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;
+
+	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, NULL, udev, "seat0");
+	if (!li)
+		error("Failed to initialize context from udev\n");
+
+	window_init(&w);
+
+	mainloop(li, &w);
+
+	libinput_destroy(li);
+	udev_unref(udev);
+
+	window_destroy(&w);
+
+	return 0;
+}
-- 
1.9.3



More information about the wayland-devel mailing list