diff --git a/meson.build b/meson.build index 51dbd7dad..e1e02dd17 100644 --- a/meson.build +++ b/meson.build @@ -495,6 +495,20 @@ configure_file(input : 'tools/libinput-debug-events.man', install_dir : dir_man1, ) +libinput_debug_tablet_sources = [ 'tools/libinput-debug-tablet.c' ] +executable('libinput-debug-tablet', + libinput_debug_tablet_sources, + dependencies : deps_tools, + include_directories : [includes_src, includes_include], + install_dir : libinput_tool_path, + install : true) + +configure_file(input : 'tools/libinput-debug-tablet.man', + output : 'libinput-debug-tablet.1', + configuration : man_config, + install_dir : dir_man1, + ) + libinput_quirks_sources = [ 'tools/libinput-quirks.c' ] libinput_quirks = executable('libinput-quirks', libinput_quirks_sources, diff --git a/src/util-macros.h b/src/util-macros.h index 03728ebc1..785c25bcc 100644 --- a/src/util-macros.h +++ b/src/util-macros.h @@ -49,4 +49,11 @@ #define ANSI_BRIGHT_CYAN "\x1B[0;36;1m" #define ANSI_NORMAL "\x1B[0m" + +#define ANSI_UP "\x1B[%dA" +#define ANSI_DOWN "\x1B[%dB" +#define ANSI_RIGHT "\x1B[%dC" +#define ANSI_LEFT "\x1B[%dD" + + #define CASE_RETURN_STRING(a) case a: return #a diff --git a/tools/libinput-debug-tablet.c b/tools/libinput-debug-tablet.c new file mode 100644 index 000000000..57cb1d4ae --- /dev/null +++ b/tools/libinput-debug-tablet.c @@ -0,0 +1,523 @@ +/* + * Copyright © 2019 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared.h" +#include "util-macros.h" + +static volatile sig_atomic_t stop = 0; +static struct tools_options options; +static int termwidth = 78; + +struct context { + struct libinput *libinput; + struct libinput_device *device; + struct libinput_tablet_tool *tool; + struct libevdev *evdev; + + /* fd[0] ... libinput fd + fd[1] ... libevdev fd */ + struct pollfd fds[2]; + + /* libinput device state */ + bool tip_is_down; + double x, y; + double x_norm, y_norm; + double tx, ty; + double dist, pressure; + double rotation, slider; + + /* libevdev device state */ + struct { + int x, y, z; + int tilt_x, tilt_y; + int distance, pressure; + } abs; +}; + +static void +print_line(const char *format, ...) +{ + char empty[] = " "; + const int width = 80; + int n; + va_list args; + + printf("\r"); + + va_start(args, format); + n = vprintf(format, args); + va_end(args); + printf("%.*s\n", width - n, empty); +} + +static void +print_bar(const char *header, double value, double normalized) +{ + char empty[termwidth]; + bool oob = false; + /* the bar is minimum 10 chars, otherwise 78 or whatever fits. + 32 is the manually-added up length of the prefix + [|] */ + const int width = max(10, min(78, termwidth - 32)); + int left_pad, right_pad; + + memset(empty, '-', sizeof empty); + + if (normalized < 0.0 || normalized > 1.0) { + normalized = min(max(normalized, 0.0), 1.0); + oob = true; + } + + left_pad = width * normalized + 0.5; + right_pad = width - left_pad; + + printf("\r %s%-16s %8.2f [%.*s|%.*s]%s\n", + oob ? ANSI_RED : "", + header, + value, + left_pad, empty, + right_pad, empty, + oob ? ANSI_NORMAL : ""); +} + +static double +normalize(struct libevdev *evdev, int code, int value) +{ + const struct input_absinfo *abs; + + if (!evdev) + return 0.0; + + abs = libevdev_get_abs_info(evdev, code); + + if (!abs) + return 0.0; + + return 1.0 * (value - abs->minimum)/(abs->maximum - abs->minimum + 1); +} + +static int +print_state(struct context *ctx) +{ + const char *tool_str; + double w, h; + int lines_printed = 0; + + if (!ctx->device) { + print_line(ANSI_RED "No device connected" ANSI_NORMAL); + lines_printed++; + } else { + libinput_device_get_size(ctx->device, &w, &h); + print_line("Device: %s (%s)", + libinput_device_get_name(ctx->device), + libinput_device_get_sysname(ctx->device)); + lines_printed++; + } + + if (!ctx->tool) { + print_line(ANSI_RED "No tool in proximity " ANSI_NORMAL); + lines_printed++; + } else { + switch (libinput_tablet_tool_get_type(ctx->tool)) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + tool_str = "pen"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + tool_str = "eraser"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + tool_str = "brush"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + tool_str = "pencil"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + tool_str = "airbrush"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + tool_str = "mouse"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + tool_str = "lens"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: + tool_str = "totem"; + break; + default: + abort(); + } + + printf("\rTool: %s serial %#" PRIx64 ", id %#" PRIx64 "\n", + tool_str, + libinput_tablet_tool_get_serial(ctx->tool), + libinput_tablet_tool_get_tool_id(ctx->tool)); + lines_printed++; + } + printf("libinput:\n"); + print_bar("x:", ctx->x, ctx->x_norm); + print_bar("y:", ctx->y, ctx->y_norm); + print_bar("tilt x:", ctx->tx, (ctx->tx + 90)/180); + print_bar("tilt y:", ctx->ty, (ctx->ty + 90)/180); + print_bar("dist:", ctx->dist, ctx->dist); + print_bar("pressure:", ctx->pressure, ctx->pressure); + print_bar("rotation:", ctx->rotation, ctx->rotation/360.0); + print_bar("slider:", ctx->slider, (ctx->slider + 1.0)/2.0); + lines_printed += 9; + + printf("evdev:\n"); + print_bar("ABS_X:", ctx->abs.x, normalize(ctx->evdev, ABS_X, ctx->abs.x)); + print_bar("ABS_Y:", ctx->abs.y, normalize(ctx->evdev, ABS_Y, ctx->abs.y)); + print_bar("ABS_Z:", ctx->abs.z, normalize(ctx->evdev, ABS_Z, ctx->abs.z)); + print_bar("ABS_TILT_X:", ctx->abs.tilt_x, normalize(ctx->evdev, ABS_TILT_X, ctx->abs.tilt_x)); + print_bar("ABS_TILT_Y:", ctx->abs.tilt_y, normalize(ctx->evdev, ABS_TILT_Y, ctx->abs.tilt_y)); + print_bar("ABS_DISTANCE:", ctx->abs.distance, normalize(ctx->evdev, ABS_DISTANCE, ctx->abs.distance)); + print_bar("ABS_PRESSURE:", ctx->abs.pressure, normalize(ctx->evdev, ABS_PRESSURE, ctx->abs.pressure)); + lines_printed += 8; + + return lines_printed; +} + +static void +handle_device_added(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_device *device = libinput_event_get_device(ev); + struct udev_device *udev_device; + const char *devnode; + + if (ctx->device) + return; + + if (!libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) + return; + + ctx->device = libinput_device_ref(device); + + udev_device = libinput_device_get_udev_device(device); + if (!udev_device) + return; + + devnode = udev_device_get_devnode(udev_device); + if (devnode) { + int fd = open(devnode, O_RDONLY|O_NONBLOCK); + assert(fd != -1); + assert(libevdev_new_from_fd(fd, &ctx->evdev) == 0); + } + + udev_device_unref(udev_device); +} + +static void +handle_device_removed(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_device *device = libinput_event_get_device(ev); + + if (ctx->device != device) + return; + + libinput_device_unref(ctx->device); + ctx->device = NULL; + + libevdev_free(ctx->evdev); + ctx->evdev = NULL; + + close(ctx->fds[1].fd); + ctx->fds[1].fd = -1; +} + +static void +update_tablet_axes(struct context *ctx, struct libinput_event_tablet_tool *t) +{ + ctx->x = libinput_event_tablet_tool_get_x(t); + ctx->y = libinput_event_tablet_tool_get_y(t); + ctx->x_norm = libinput_event_tablet_tool_get_x_transformed(t, 1.0); + ctx->y_norm = libinput_event_tablet_tool_get_y_transformed(t, 1.0); + ctx->tx = libinput_event_tablet_tool_get_tilt_x(t); + ctx->ty = libinput_event_tablet_tool_get_tilt_y(t); + ctx->dist = libinput_event_tablet_tool_get_distance(t); + ctx->pressure = libinput_event_tablet_tool_get_pressure(t); + ctx->rotation = libinput_event_tablet_tool_get_rotation(t); + ctx->slider = libinput_event_tablet_tool_get_slider_position(t); +} + +static void +handle_tablet_axis_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + + update_tablet_axes(ctx, t); +} + +static void +handle_tablet_proximity_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + struct libinput_tablet_tool *tool = libinput_event_tablet_tool_get_tool(t); + + if (ctx->tool) { + libinput_tablet_tool_unref(ctx->tool); + ctx->tool = NULL; + } + + if (libinput_event_tablet_tool_get_proximity_state(t) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) + ctx->tool = libinput_tablet_tool_ref(tool); +} + +static void +handle_tablet_tip_event(struct context *ctx, struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + + ctx->tip_is_down = libinput_event_tablet_tool_get_tip_state(t) == LIBINPUT_TABLET_TOOL_TIP_DOWN; + +} + +static void +handle_libinput_events(struct context *ctx) +{ + struct libinput *li = ctx->libinput; + 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: + handle_device_added(ctx, ev); + tools_device_apply_config(libinput_event_get_device(ev), + &options); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + handle_device_removed(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + handle_tablet_axis_event(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + handle_tablet_proximity_event(ctx, ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + handle_tablet_tip_event(ctx, ev); + break; + default: + break; + } + + libinput_event_destroy(ev); + libinput_dispatch(li); + } +} + +static void +handle_libevdev_events(struct context *ctx) +{ + struct libevdev *evdev = ctx->evdev; + struct input_event event; + + if (!evdev) + return; + + while (libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &event) + == LIBEVDEV_READ_STATUS_SUCCESS) + { + if (event.type != EV_ABS) + continue; + + switch (event.code) { + case ABS_X: + ctx->abs.x = event.value; + break; + case ABS_Y: + ctx->abs.y = event.value; + break; + case ABS_Z: + ctx->abs.z = event.value; + break; + case ABS_PRESSURE: + ctx->abs.pressure = event.value; + break; + case ABS_TILT_X: + ctx->abs.tilt_x = event.value; + break; + case ABS_TILT_Y: + ctx->abs.tilt_y = event.value; + break; + case ABS_DISTANCE: + ctx->abs.distance = event.value; + break; + } + } +} + +static void +sighandler(int signal, siginfo_t *siginfo, void *userdata) +{ + stop = 1; +} + +static void +mainloop(struct context *ctx) +{ + unsigned int lines_printed = 20; + + ctx->fds[0].fd = libinput_get_fd(ctx->libinput); + + /* pre-load the lines */ + for (unsigned int i = 0; i < lines_printed; i++) + printf("\n"); + + do { + handle_libinput_events(ctx); + handle_libevdev_events(ctx); + + printf(ANSI_LEFT, 1000); + printf(ANSI_UP, lines_printed); + lines_printed = print_state(ctx); + } while (!stop && poll(ctx->fds, 2, -1) > -1); + + printf("\n"); +} + +static void +usage(void) { + printf("Usage: libinput debug-tablet [options] [--udev |--device /dev/input/event0]\n"); +} + +static void +init_context(struct context *ctx) +{ + + memset(ctx, 0, sizeof *ctx); + + ctx->fds[0].fd = -1; /* libinput fd */ + ctx->fds[0].events = POLLIN; + ctx->fds[0].revents = 0; + ctx->fds[1].fd = -1; /* libevdev fd */ + ctx->fds[1].events = POLLIN; + ctx->fds[1].revents = 0; +} + +int +main(int argc, char **argv) +{ + struct context ctx; + struct libinput *li; + enum tools_backend backend = BACKEND_NONE; + const char *seat_or_device = "seat0"; + struct sigaction act; + bool grab = false; + + init_context(&ctx); + + tools_init_options(&options); + + while (1) { + int c; + int option_index = 0; + enum { + OPT_DEVICE = 1, + OPT_UDEV, + }; + static struct option opts[] = { + CONFIGURATION_OPTIONS, + { "help", no_argument, 0, 'h' }, + { "device", required_argument, 0, OPT_DEVICE }, + { "udev", required_argument, 0, OPT_UDEV }, + { 0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "h", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case '?': + exit(EXIT_INVALID_USAGE); + break; + case 'h': + usage(); + exit(EXIT_SUCCESS); + break; + case OPT_DEVICE: + backend = BACKEND_DEVICE; + seat_or_device = optarg; + break; + case OPT_UDEV: + backend = BACKEND_UDEV; + seat_or_device = optarg; + break; + } + + } + + if (optind < argc) { + if (optind < argc - 1 || backend != BACKEND_NONE) { + usage(); + return EXIT_INVALID_USAGE; + } + backend = BACKEND_DEVICE; + seat_or_device = argv[optind]; + } else if (backend == BACKEND_NONE) { + backend = BACKEND_UDEV; + } + + memset(&act, 0, sizeof(act)); + act.sa_sigaction = sighandler; + act.sa_flags = SA_SIGINFO; + + if (sigaction(SIGINT, &act, NULL) == -1) { + fprintf(stderr, "Failed to set up signal handling (%s)\n", + strerror(errno)); + return EXIT_FAILURE; + } + + li = tools_open_backend(backend, seat_or_device, false, &grab); + if (!li) + return EXIT_FAILURE; + + struct winsize w; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) + termwidth = w.ws_col; + + ctx.libinput = li; + mainloop(&ctx); + + libinput_unref(li); + + return EXIT_SUCCESS; +} diff --git a/tools/libinput-debug-tablet.man b/tools/libinput-debug-tablet.man new file mode 100644 index 000000000..bfd7573af --- /dev/null +++ b/tools/libinput-debug-tablet.man @@ -0,0 +1,33 @@ +.TH libinput-debug-tablet "1" +.SH NAME +libinput\-debug\-tablet\ \- debug and visualize tablet axes +.SH SYNOPSIS +.B libinput debug-tablet [\-\-help] [options] [\fI/dev/input/event0\fI] +.SH DESCRIPTION +.PP +The +.B "libinput debug-tablet" +tool debugs the values of the various axes on a tablet. This is +an interactive tool. When executed, the tool will prompt the user to +interact with the tablet and display the current value on each available +axis. +.PP +This is a debugging tool only, its output may change at any time. Do not +rely on the output. +.PP +This tool usually needs to be run as root to have access to the +/dev/input/eventX nodes. +.SH OPTIONS +If a device node is given, this tool opens that device node. Otherwise, this +tool searches for the first node that looks like a tablet and uses that +node. +.TP 8 +.B \-\-help +Print help +.PP +Events shown by this tool may not correspond to the events seen by a +different user of libinput. This tool initializes a separate context. +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput.man b/tools/libinput.man index c8f4f40b1..57402c301 100644 --- a/tools/libinput.man +++ b/tools/libinput.man @@ -39,6 +39,9 @@ Print all events as seen by libinput .B libinput\-debug\-gui(1) Show a GUI to visualize libinput's events .TP 8 +.B libinput\-debug\-tablet(1) +A commandline tool to debug tablet axis values +.TP 8 .B libinput\-list\-devices(1) List all devices recognized by libinput .TP 8