diff options
-rw-r--r-- | meson.build | 20 | ||||
-rw-r--r-- | tools/Makefile.am | 20 | ||||
-rw-r--r-- | tools/libinput-measure-touchpad-tap.1 | 73 | ||||
-rw-r--r-- | tools/libinput-measure-touchpad-tap.c | 512 | ||||
-rw-r--r-- | tools/libinput-measure.1 | 30 | ||||
-rw-r--r-- | tools/libinput-measure.c | 92 | ||||
-rw-r--r-- | tools/libinput-tool.c | 52 | ||||
-rw-r--r-- | tools/libinput.1 | 6 | ||||
-rw-r--r-- | tools/shared.c | 131 | ||||
-rw-r--r-- | tools/shared.h | 4 |
10 files changed, 890 insertions, 50 deletions
diff --git a/meson.build b/meson.build index 3cfdb1d..d86f6c0 100644 --- a/meson.build +++ b/meson.build @@ -373,6 +373,26 @@ executable('libinput-list-devices', ) install_man('tools/libinput-list-devices.1') +libinput_measure_sources = [ 'tools/libinput-measure.c' ] +executable('libinput-measure', + libinput_measure_sources, + dependencies : deps_tools, + include_directories : include_directories('src'), + install_dir : libinput_tool_path, + install : true, + ) +install_man('tools/libinput-measure.1') + +libinput_measure_touchpad_tap_sources = [ 'tools/libinput-measure-touchpad-tap.c' ] +executable('libinput-measure-touchpad-tap', + libinput_measure_touchpad_tap_sources, + dependencies : deps_tools, + include_directories : include_directories('src'), + install_dir : libinput_tool_path, + install : true, + ) +install_man('tools/libinput-measure-touchpad-tap.1') + if get_option('debug-gui') dep_gtk = dependency('gtk+-3.0') dep_cairo = dependency('cairo') diff --git a/tools/Makefile.am b/tools/Makefile.am index cc1ce19..8ef2a3b 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -14,7 +14,8 @@ AM_CXXFLAGS = $(GCC_CXXFLAGS) libshared_la_SOURCES = \ shared.c \ shared.h -libshared_la_CFLAGS = $(AM_CFLAGS) $(LIBEVDEV_CFLAGS) +libshared_la_CFLAGS = $(AM_CFLAGS) $(LIBEVDEV_CFLAGS) \ + -DLIBINPUT_TOOL_PATH="\"@libexecdir@/libinput\"" libshared_la_LIBADD = $(LIBEVDEV_LIBS) ptraccel_debug_SOURCES = ptraccel-debug.c @@ -23,8 +24,7 @@ ptraccel_debug_LDFLAGS = -no-install libinput_SOURCES = libinput-tool.c libinput_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) $(LIBEVDEV_LIBS) -libinput_CFLAGS = $(AM_CFLAGS) $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS) \ - -DLIBINPUT_TOOL_PATH="\"@libexecdir@/libinput\"" +libinput_CFLAGS = $(AM_CFLAGS) $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS) dist_man1_MANS = libinput.1 tools_PROGRAMS += libinput-list-devices @@ -39,6 +39,20 @@ libinput_debug_events_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) $( libinput_debug_events_CFLAGS = $(AM_CFLAGS) $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS) dist_man1_MANS += libinput-debug-events.1 +tools_PROGRAMS += libinput-measure +libinput_measure_SOURCES = libinput-measure.c $(shared_sources) +libinput_measure_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) $(LIBEVDEV_LIBS) +libinput_measure_CFLAGS = $(AM_CFLAGS) $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS) \ + -DLIBINPUT_TOOL_PATH="\"@libexecdir@/libinput\"" +dist_man1_MANS += libinput-measure.1 + +tools_PROGRAMS += libinput-measure-touchpad-tap +libinput_measure_touchpad_tap_SOURCES = \ + libinput-measure-touchpad-tap.c +libinput_measure_touchpad_tap_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) $(LIBEVDEV_LIBS) +libinput_measure_touchpad_tap_CFLAGS = $(AM_CFLAGS) $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS) +dist_man1_MANS += libinput-measure-touchpad-tap.1 + if BUILD_DEBUG_GUI tools_PROGRAMS += libinput-debug-gui libinput_debug_gui_SOURCES = libinput-debug-gui.c diff --git a/tools/libinput-measure-touchpad-tap.1 b/tools/libinput-measure-touchpad-tap.1 new file mode 100644 index 0000000..3cea584 --- /dev/null +++ b/tools/libinput-measure-touchpad-tap.1 @@ -0,0 +1,73 @@ +.TH LIBINPUT-MEASURE-TOUCHPAD-TAP "1" +.SH NAME +libinput-measure-touchpad-tap \- measure tap-to-click properities of devices +.SH SYNOPSIS +.B libinput measure touchpad-tap [--help] [--format=<format>] [/dev/input/event0] +.SH DESCRIPTION +.PP +The +.B "libinput measure touchpad-tap" +tool measures properties of the tap-to-click behavior of the user. This is +an interactive tool. When executed, the tool will prompt the user to +interact with the touchpad. On termination, the tool prints a summary of the +tap interactions seen. This data should be attached to any tap-related bug +report. +.PP +For a full description on how libinput's tap-to-click behavior works, see +the online documentation here: +.I https://wayland.freedesktop.org/libinput/doc/latest/tapping.html +.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 +.TP 8 +.B --help +Print help +.TP 8 +.B --format=summary|dat +Specify the data format to be printed. The default (or if +.B --format +is omitted) is "summary". See section +.B DATA FORMATS + +.SH DATA FORMATS +This section describes the data formats printed with the +.B --format +commandline argument. Note that any of the output may change at any time. +.RE +.PP +summary +.RS 4 +The +.I summary +format prints a summary of the data collected. This format is useful to +get a quick overview of a user's tapping behavior and why some taps may or +may not be detected. +.RE +.PP +dat +.RS 4 +The +.I dat +format prints the touch sequence data (raw and processed) in column-style +format, suitable for processing by other tools such as +.B gnuplot(1). +The data is aligned in one row per touch with each column containing a +separate data entry. +.B libinput-measure-touchpad-tap +prints comments at the top of the file to describe each column. +.PP +.B WARNING: +The data contained in the output is grouped by different sort orders. For +example, the first few columns may list tap information in the 'natural' +sort order (i.e. as they occured), the data in the next few columns may list +tap information sorted by the delta time between touch down and touch up. +Comparing columns across these group boundaries will compare data of two +different touch points and result in invalid analysis. +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput-measure-touchpad-tap.c b/tools/libinput-measure-touchpad-tap.c new file mode 100644 index 0000000..123630c --- /dev/null +++ b/tools/libinput-measure-touchpad-tap.c @@ -0,0 +1,512 @@ +/* + * Copyright © 2017 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 <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <poll.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/signalfd.h> + +#include <libudev.h> +#include <libevdev/libevdev.h> + +#include <libinput-util.h> +#include <libinput-version.h> + +#include "shared.h" + +static bool print_dat_file; +/* interactive goes to stdout unless we're redirected, then it goes to + * stderr */ +static FILE *pdest; + +#define error(...) fprintf(stderr, __VA_ARGS__) +#define msg(...) fprintf(pdest, __VA_ARGS__) + +struct touch { + uint32_t tdown, tup; /* in ms */ +}; + +struct tap_data { + struct touch *touches; + size_t touches_sz; + unsigned int count; + + uint32_t toffset; /* in ms */ +}; + +static inline uint32_t +touch_tdelta_ms(const struct touch *t) +{ + return t->tup - t->tdown; +} + +static inline struct tap_data * +tap_data_new(void) +{ + struct tap_data *tap_data = zalloc(sizeof(struct tap_data)); + assert(tap_data); + + return tap_data; +} + +static inline unsigned int +tap_data_ntouches(struct tap_data *tap_data) +{ + return tap_data->count; +} + +static inline void +tap_data_free(struct tap_data **tap_data) +{ + free((*tap_data)->touches); + free((*tap_data)); + *tap_data = NULL; +} + +static inline struct touch * +tap_data_get_current_touch(struct tap_data *tap_data) +{ + assert(tap_data->count > 0); + + return &tap_data->touches[tap_data->count - 1]; +} + +static inline struct touch * +tap_data_get_touch(struct tap_data *tap_data, unsigned int idx) +{ + assert(idx < tap_data->count); + + return &tap_data->touches[idx]; +} + +#define tap_data_for_each(tapdata_, t_) \ + for (unsigned i_ = 0; i_ < (tapdata_)->count && (t_ = &(tapdata_)->touches[i_]); i_++) + +static inline struct tap_data * +tap_data_duplicate_sorted(const struct tap_data *src, + int (*cmp)(const void *a, const void *b)) +{ + struct tap_data *dest= tap_data_new(); + + assert(src->count > 0); + + dest->count = src->count; + dest->touches_sz = dest->count; + dest->touches = zalloc(dest->count * sizeof(*dest->touches)); + memcpy(dest->touches, + src->touches, + dest->count * sizeof(*dest->touches)); + + if (cmp) + qsort(dest->touches, + dest->count, + sizeof(*dest->touches), + cmp); + + return dest; +} + +static inline struct touch* +tap_data_new_touch(struct tap_data *tap_data) +{ + tap_data->count++; + if (tap_data->touches_sz < tap_data->count) { + tap_data->touches_sz += 50; + tap_data->touches = realloc(tap_data->touches, + tap_data->touches_sz * sizeof(*tap_data->touches)); + if (tap_data->touches == NULL) { + error("Allocation error. Oops\n"); + abort(); + } + memset(&tap_data->touches[tap_data->count - 1], + 0, + sizeof(*tap_data->touches)); + } + + return &tap_data->touches[tap_data->count - 1]; +} + +static int +sort_by_time_delta(const void *ap, const void *bp) +{ + const struct touch *a = ap; + const struct touch *b = bp; + uint32_t da, db; + + da = touch_tdelta_ms(a); + db = touch_tdelta_ms(b); + + return da == db ? 0 : da > db ? 1 : -1; +} + +static inline void +print_statistics(struct tap_data *tap_data) +{ + uint64_t delta_sum = 0; + uint32_t average; + uint32_t max = 0, + min = UINT_MAX; + struct tap_data *data_sorted_tdelta; + struct touch *t, + *median, + *pc90, + *pc95; + + if (tap_data->count == 0) { + error("No tap data available.\n"); + return; + } + + tap_data_for_each(tap_data, t) { + uint32_t delta = touch_tdelta_ms(t); + + delta_sum += delta; + max = max(delta, max); + min = min(delta, min); + } + + average = delta_sum/tap_data_ntouches(tap_data); + + printf("Time:\n"); + printf(" Max delta: %dms\n", max); + printf(" Min delta: %dms\n", min); + printf(" Average delta: %dms\n", average); + + /* Median, 90th, 95th percentile, requires sorting by time delta */ + data_sorted_tdelta = tap_data_duplicate_sorted(tap_data, + sort_by_time_delta); + median = tap_data_get_touch(tap_data, + tap_data_ntouches(tap_data)/2); + pc90= tap_data_get_touch(tap_data, + tap_data_ntouches(tap_data) * 0.9); + pc95 = tap_data_get_touch(tap_data, + tap_data_ntouches(tap_data) * 0.95); + printf(" Median delta: %dms\n", touch_tdelta_ms(median)); + printf(" 90th percentile: %dms\n", touch_tdelta_ms(pc90)); + printf(" 95th percentile: %dms\n", touch_tdelta_ms(pc95)); + + tap_data_free(&data_sorted_tdelta); +} + +static inline void +print_dat(struct tap_data *tap_data) +{ + unsigned int i; + struct touch *t; + struct tap_data *sorted; + + printf("# libinput-measure-touchpad-tap (v%s)\n", LIBINPUT_VERSION); + printf("# File contents:\n" + "# This file contains multiple prints of the data in different\n" + "# sort order. Row number is index of touch point within each group.\n" + "# Comparing data across groups will result in invalid analysis.\n" + "# Columns (1-indexed):\n"); + printf("# Group 1, sorted by time of occurence\n" + "# 1: touch down time in ms, offset by first event\n" + "# 2: touch up time in ms, offset by first event\n" + "# 3: time delta in ms\n"); + printf("# Group 2, sorted by touch down-up delta time (ascending)\n" + "# 4: touch down time in ms, offset by first event\n" + "# 5: touch up time in ms, offset by first event\n" + "# 6: time delta in ms\n"); + + sorted = tap_data_duplicate_sorted(tap_data, sort_by_time_delta); + + for (i = 0; i < tap_data_ntouches(tap_data); i++) { + t = tap_data_get_touch(tap_data, i); + printf("%d %d %d ", + t->tdown, + t->tup, + touch_tdelta_ms(t)); + + t = tap_data_get_touch(sorted, i); + printf("%d %d %d ", + t->tdown, + t->tup, + touch_tdelta_ms(t)); + + printf("\n"); + } + + tap_data_free(&sorted); +} + +static inline void +handle_btn_touch(struct tap_data *tap_data, + struct libevdev *evdev, + const struct input_event *ev) +{ + + if (ev->value) { + struct touch *new_touch = tap_data_new_touch(tap_data); + + new_touch->tdown = us2ms(tv2us(&ev->time)) - tap_data->toffset; + } else { + struct touch *current = tap_data_get_current_touch(tap_data); + + msg("\rTouch sequences detected: %d", tap_data->count); + + current->tup = us2ms(tv2us(&ev->time)) - tap_data->toffset; + } +} + +static inline bool +handle_key(struct tap_data *tap_data, + struct libevdev *evdev, + const struct input_event *ev) +{ + switch (ev->code) { + case BTN_TOOL_DOUBLETAP: + case BTN_TOOL_TRIPLETAP: + case BTN_TOOL_QUADTAP: + case BTN_TOOL_QUINTTAP: + error("This tool only supports single-finger taps. Aborting.\n"); + return false; + case BTN_TOUCH: + handle_btn_touch(tap_data, evdev, ev); + break; + default: + break; + } + + return true; +} + +static inline bool +handle_abs(struct tap_data *tap_data, + struct libevdev *evdev, + const struct input_event *ev) +{ + return true; +} + +static inline bool +handle_event(struct tap_data *tap_data, + struct libevdev *evdev, + const struct input_event *ev) +{ + bool rc = true; + + if (tap_data->toffset == 0) + tap_data->toffset = us2ms(tv2us(&ev->time)); + + switch (ev->type) { + default: + error("Unexpected event %s %s (%d, %d). Aborting.\n", + libevdev_event_type_get_name(ev->type), + libevdev_event_code_get_name(ev->type, ev->code), + ev->type, + ev->code); + break; + case EV_KEY: + rc = handle_key(tap_data, evdev, ev); + break; + case EV_ABS: + rc = handle_abs(tap_data, evdev, ev); + break; + case EV_SYN: + rc = true; + break; + } + + return rc; +} + +static int +loop(struct tap_data *data, const char *path) +{ + struct libevdev *evdev; + struct pollfd fds[2]; + sigset_t mask; + int fd; + int rc; + + fd = open(path, O_RDONLY|O_NONBLOCK); + if (fd < 0) { + error("Failed to open device: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + rc = libevdev_new_from_fd(fd, &evdev); + if (rc < 0) { + error("Failed to init device: %s\n", strerror(-rc)); + close(fd); + return EXIT_FAILURE; + } + libevdev_set_clock_id(evdev, CLOCK_MONOTONIC); + + fds[0].fd = fd; + fds[0].events = POLLIN; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + fds[1].fd = signalfd(-1, &mask, SFD_NONBLOCK); + fds[1].events = POLLIN; + + sigprocmask(SIG_BLOCK, &mask, NULL); + + rc = EXIT_FAILURE; + + error("Ready for recording data.\n" + "Tap the touchpad multiple times with a single finger only.\n" + "For useful data we recommend at least 20 taps.\n" + "Ctrl+C to exit\n"); + + while (poll(fds, 2, -1)) { + struct input_event ev; + int rc; + + if (fds[1].revents) + break; + + do { + rc = libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc == LIBEVDEV_READ_STATUS_SYNC) { + error("Error: cannot keep up\n"); + goto out; + } else if (rc == LIBEVDEV_READ_STATUS_SUCCESS) { + if (!handle_event(data, evdev, &ev)) + goto out; + } else if (rc != -EAGAIN && rc < 0) { + error("Error: %s\n", strerror(-rc)); + goto out; + } + } while (rc != -EAGAIN); + } + + rc = EXIT_SUCCESS; +out: + close(fd); + if (evdev) + libevdev_free(evdev); + + printf("\n"); + + return rc; +} + +static inline void +usage(void) +{ + printf("Usage: libinput measure touchpad-tap [--help] [/dev/input/event0]\n"); + printf("\n" + "Measure various properties related to tap-to-click.\n" + "If a path to the device is provided, that device is used. Otherwise, this tool\n" + "will pick the first suitable touchpad device.\n" + "\n" + "Options:\n" + "--help ...... show this help\n" + "\n" + "This tool requires access to the /dev/input/eventX nodes.\n"); +} + +int +main(int argc, char **argv) +{ + struct tap_data *tap_data; + char path[PATH_MAX]; + int option_index = 0; + const char *format = "summary"; + int rc; + + while (1) { + enum opts { + OPT_HELP, + OPT_FORMAT, + }; + static struct option opts[] = { + { "help", no_argument, 0, OPT_HELP }, + { "format", required_argument, 0, OPT_FORMAT}, + { 0, 0, 0, 0 }, + }; + int c; + + c = getopt_long(argc, argv, "", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case OPT_HELP: + usage(); + return EXIT_SUCCESS;; + case OPT_FORMAT: + format = optarg; + break; + default: + usage(); + return EXIT_FAILURE; + } + } + + if (streq(format, "summary")) { + print_dat_file = false; + } else if (streq(format, "dat")) { + print_dat_file = true; + } else { + error("Unknown print format '%s'\n", format); + return EXIT_FAILURE; + } + + if (optind == argc) { + if (!find_touchpad_device(path, sizeof(path))) { + error("Failed to find a touchpad device.\n"); + return EXIT_FAILURE; + } + } else { + snprintf(path, sizeof(path), "%s", argv[optind]); + if (!is_touchpad_device(path)) { + error("Device is not a touchpad.\n"); + return EXIT_FAILURE; + } + } + + if (!isatty(STDOUT_FILENO)) { + pdest = stderr; + } else { + pdest = stdout; + setbuf(stdout, NULL); + } + + tap_data = tap_data_new(); + rc = loop(tap_data, path); + + if (rc != EXIT_SUCCESS) + goto out; + + if (print_dat_file) + print_dat(tap_data); + else + print_statistics(tap_data); + + tap_data_free(&tap_data); +out: + return rc; +} diff --git a/tools/libinput-measure.1 b/tools/libinput-measure.1 new file mode 100644 index 0000000..26d798f --- /dev/null +++ b/tools/libinput-measure.1 @@ -0,0 +1,30 @@ +.TH LIBINPUT-MEASURE "1" +.SH NAME +libinput-measure \- measure properties of devices +.SH SYNOPSIS +.B libinput measure [--help] <feature> [<args>] +.SH DESCRIPTION +.PP +The +.B "libinput measure" +tool measures properties of one (or more) devices. Depending on what is to +be measured, this too may not create a libinput context. +.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 +.TP 8 +.B --help +Print help +.SH FEATURES +Features that can be measured include +.TP 8 +.B libinput-measure-touchpad-tap-time(1) +Measure tap-to-click time. +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput-measure.c b/tools/libinput-measure.c new file mode 100644 index 0000000..af9c1cf --- /dev/null +++ b/tools/libinput-measure.c @@ -0,0 +1,92 @@ +/* + * Copyright © 2017 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 <errno.h> +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <libudev.h> + +#include <libinput-util.h> + +#include "shared.h" + +static inline void +usage(void) +{ + printf("Usage: libinput measure [--help] FEATURE [/dev/input/event0]\n"); + printf("\n" + "Collect measurements related to FEATURE on the given device.\n" + "\n" + "Options:\n" + "--help ...... show this help\n" + "\n" + "Available features are:\n" + " touchpad-tap-time\n" + " Measures the time for tap-to-click interactions\n" + " " + "For information about each feature, see the --help output for that feature.\n" + "\n" + "This tool requires access to the /dev/input/eventX nodes.\n"); +} + +int +main(int argc, char **argv) +{ + int option_index = 0; + + while (1) { + int c; + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "h", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case 'h': + usage(); + return EXIT_SUCCESS; + default: + usage(); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + usage(); + return EXIT_FAILURE; + } + + argc--; + argv++; + + return tools_exec_command("libinput-measure", argc, argv); +} diff --git a/tools/libinput-tool.c b/tools/libinput-tool.c index f37d961..aa07c42 100644 --- a/tools/libinput-tool.c +++ b/tools/libinput-tool.c @@ -35,6 +35,7 @@ #include <libinput-util.h> #include <libinput-version.h> +#include "shared.h" static void usage(void) { @@ -61,6 +62,9 @@ usage(void) "\n" " debug-gui\n" " Display a simple GUI to visualize libinput's events.\n" + "\n" + " measure\n" + " Measure various device properties. See the --help output for more info\n" "\n"); } @@ -69,52 +73,6 @@ enum global_opts { GOPT_VERSION, }; -static inline void -setup_path(void) -{ - const char *path = getenv("PATH"); - char new_path[PATH_MAX]; - - snprintf(new_path, - sizeof(new_path), - "%s:%s", - LIBINPUT_TOOL_PATH, - path ? path : ""); - setenv("PATH", new_path, 1); -} - -static int -exec_command(int real_argc, char **real_argv) -{ - char *argv[64] = {NULL}; - char executable[128]; - const char *command; - int rc; - - assert((size_t)real_argc < ARRAY_LENGTH(argv)); - - command = real_argv[0]; - - rc = snprintf(executable, sizeof(executable), "libinput-%s", command); - if (rc >= (int)sizeof(executable)) { - usage(); - return EXIT_FAILURE; - } - - argv[0] = executable; - for (int i = 1; i < real_argc; i++) - argv[i] = real_argv[i]; - - setup_path(); - - rc = execvp(executable, argv); - fprintf(stderr, - "Failed to execute '%s' (%s)\n", - command, - strerror(errno)); - return EXIT_FAILURE; -} - int main(int argc, char **argv) { @@ -154,5 +112,5 @@ main(int argc, char **argv) argv += optind; argc -= optind; - return exec_command(argc, argv); + return tools_exec_command("libinput-", argc, argv); } diff --git a/tools/libinput.1 b/tools/libinput.1 index bc19655..0734d3a 100644 --- a/tools/libinput.1 +++ b/tools/libinput.1 @@ -44,6 +44,12 @@ Show a GUI to visualize libinput's events. .TP 8 .B libinput-list-devices(1) List all devices recognized by libinput. +.TP 8 +.B libinput-measure(1) +Measure various properties of devices. +.TP 8 +.B libinput-measure-touchpad-tap(1) +Measure tap-to-click time. .SH LIBINPUT Part of the .B libinput(1) diff --git a/tools/shared.c b/tools/shared.c index 51246ff..5eaca3f 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -29,6 +29,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include <libudev.h> #include <libevdev/libevdev.h> @@ -546,3 +547,133 @@ tools_device_apply_config(struct libinput_device *device, options->profile); } } + +static char* +find_device(const char *udev_tag) +{ + struct udev *udev; + struct udev_enumerate *e; + struct udev_list_entry *entry; + struct udev_device *device; + const char *path, *sysname; + char *device_node = NULL; + + udev = udev_new(); + e = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(udev, path); + if (!device) + continue; + + sysname = udev_device_get_sysname(device); + if (strncmp("event", sysname, 5) != 0) { + udev_device_unref(device); + continue; + } + + if (udev_device_get_property_value(device, udev_tag)) + device_node = strdup(udev_device_get_devnode(device)); + + udev_device_unref(device); + + if (device_node) + break; + } + udev_enumerate_unref(e); + udev_unref(udev); + + return device_node; +} + +bool +find_touchpad_device(char *path, size_t path_len) +{ + char *devnode = find_device("ID_INPUT_TOUCHPAD"); + + if (devnode) { + snprintf(path, path_len, "%s", devnode); + free(devnode); + } + + return devnode != NULL; +} + +bool +is_touchpad_device(const char *devnode) +{ + struct udev *udev; + struct udev_device *dev = NULL; + struct stat st; + bool is_touchpad = false; + + if (stat(devnode, &st) < 0) + return false; + + udev = udev_new(); + dev = udev_device_new_from_devnum(udev, 'c', st.st_rdev); + if (!dev) + goto out; + + is_touchpad = udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD"); +out: + if (dev) + udev_device_unref(dev); + udev_unref(udev); + + return is_touchpad; +} + +static inline void +setup_path(void) +{ + const char *path = getenv("PATH"); + char new_path[PATH_MAX]; + + snprintf(new_path, + sizeof(new_path), + "%s:%s", + LIBINPUT_TOOL_PATH, + path ? path : ""); + setenv("PATH", new_path, 1); +} + +int +tools_exec_command(const char *prefix, int real_argc, char **real_argv) +{ + char *argv[64] = {NULL}; + char executable[128]; + const char *command; + int rc; + + assert((size_t)real_argc < ARRAY_LENGTH(argv)); + + command = real_argv[0]; + + rc = snprintf(executable, + sizeof(executable), + "%s-%s", + prefix, + command); + if (rc >= (int)sizeof(executable)) { + fprintf(stderr, "Failed to assemble command.\n"); + return EXIT_FAILURE; + } + + argv[0] = executable; + for (int i = 1; i < real_argc; i++) + argv[i] = real_argv[i]; + + setup_path(); + + rc = execvp(executable, argv); + fprintf(stderr, + "Failed to execute '%s' (%s)\n", + command, + strerror(errno)); + + return EXIT_FAILURE; +} diff --git a/tools/shared.h b/tools/shared.h index 0fb8c28..0a39484 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -71,5 +71,9 @@ struct libinput* tools_open_backend(struct tools_context *context); void tools_device_apply_config(struct libinput_device *device, struct tools_options *options); void tools_usage(const char *command); +int tools_exec_command(const char *prefix, int argc, char **argv); + +bool find_touchpad_device(char *path, size_t path_len); +bool is_touchpad_device(const char *devnode); #endif |