[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