diff --git a/configure.ac b/configure.ac index 13cf3a81..361eafe7 100644 --- a/configure.ac +++ b/configure.ac @@ -74,6 +74,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 2cdd654f..cf348a6f 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 8b6e22be..f59068da 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -7,3 +7,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 00000000..95540936 --- /dev/null +++ b/tools/event-gui.c @@ -0,0 +1,474 @@ +/* + * 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 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#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); + double dx = libinput_event_pointer_get_dx(p), + dy = libinput_event_pointer_get_dy(p); + + w->x += (int)dx; + w->y += (int)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); + double x = libinput_event_pointer_get_absolute_x(p), + y = libinput_event_pointer_get_absolute_y(p); + + w->absx = clip((int)x, 0, w->width); + w->absy = clip((int)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; + double 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 = (int)x; + touch->y = (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); + double v = libinput_event_pointer_get_axis_value(p); + + switch (axis) { + case LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL: + w->vy += (int)v; + w->vy = clip(w->vy, 0, w->height); + break; + case LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL: + w->hx += (int)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_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; +}