--- /dev/null
+/*
+ * 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;
+}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <libudev.h>
#include <libevdev/libevdev.h>
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;
+}