diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2021-02-18 17:10:15 +1000 |
---|---|---|
committer | Peter Hutterer <peter.hutterer@who-t.net> | 2021-02-23 10:56:53 +1000 |
commit | 4a7dece88cde97537bddbd9b580c470433738cf0 (patch) | |
tree | 99d1e59b2c390111a8dee3705bfdadec59bd725c | |
parent | d8b64d413dabf00d404b880d4ef559e2c4755133 (diff) | |
download | libinput-4a7dece88cde97537bddbd9b580c470433738cf0.tar.gz |
tools/record: switch record over to using epoll
Using poll means more difficult fd management, epoll (together with am
modified version of the libinput_sources) makes this a lot easier by simply
using dispatch.
This means we are no longer reliant on a specific file descriptor order in the
poll array.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
-rw-r--r-- | tools/libinput-record.c | 281 |
1 files changed, 184 insertions, 97 deletions
diff --git a/tools/libinput-record.c b/tools/libinput-record.c index db787cc4..1d9ef7c5 100644 --- a/tools/libinput-record.c +++ b/tools/libinput-record.c @@ -24,6 +24,7 @@ #include "config.h" #include <errno.h> +#include <sys/epoll.h> #include <inttypes.h> #include <linux/input.h> #include <libevdev/libevdev.h> @@ -113,6 +114,28 @@ struct record_context { unsigned int indent; struct libinput *libinput; + + int epoll_fd; + struct list sources; + + struct { + bool had_events_since_last_time; + bool skipped_timer_print; + } timestamps; + + bool had_events; + bool stop; +}; + +typedef void (*source_dispatch_t)(struct record_context *ctx, + int fd, + void *user_data); + +struct source { + source_dispatch_t dispatch; + void *user_data; + int fd; + struct list link; }; static bool @@ -2134,52 +2157,167 @@ arm_timer(int timerfd) timerfd_settime(timerfd, 0, &interval, NULL); } +static struct source * +add_source(struct record_context *ctx, + int fd, + source_dispatch_t dispatch, + void *user_data) +{ + struct source *source; + struct epoll_event ep; + + assert(fd != -1); + + source = zalloc(sizeof *source); + source->dispatch = dispatch; + source->user_data = user_data; + source->fd = fd; + list_append(&ctx->sources, &source->link); + + memset(&ep, 0, sizeof ep); + ep.events = EPOLLIN; + ep.data.ptr = source; + + if (epoll_ctl(ctx->epoll_fd, EPOLL_CTL_ADD, fd, &ep) < 0) { + free(source); + return NULL; + } + + return source; +} + +static void +destroy_source(struct record_context *ctx, struct source *source) +{ + list_remove(&source->link); + epoll_ctl(ctx->epoll_fd, EPOLL_CTL_DEL, source->fd, NULL); + close(source->fd); + free(source); +} + +static void +signalfd_dispatch(struct record_context *ctx, int fd, void *data) +{ + struct signalfd_siginfo fdsi; + + read(fd, &fdsi, sizeof(fdsi)); + + ctx->stop = true; +} + +static void +timefd_dispatch(struct record_context *ctx, int fd, void *data) +{ + char discard[64]; + + read(fd, discard, sizeof(discard)); + + if (ctx->timestamps.had_events_since_last_time) { + print_wall_time(ctx); + ctx->timestamps.had_events_since_last_time = false; + ctx->timestamps.skipped_timer_print = false; + } else { + ctx->timestamps.skipped_timer_print = true; + } +} + +static void +evdev_dispatch(struct record_context *ctx, int fd, void *data) +{ + struct record_device *first_device = NULL; + struct record_device *this_device = data; + + if (ctx->timestamps.skipped_timer_print) { + print_wall_time(ctx); + ctx->timestamps.skipped_timer_print = false; + } + + ctx->had_events = true; + ctx->timestamps.had_events_since_last_time = true; + + first_device = list_first_entry(&ctx->devices, first_device, link); + handle_events(ctx, this_device, this_device == first_device); +} + +static void +libinput_ctx_dispatch(struct record_context *ctx, int fd, void *data) +{ + struct record_device *first_device = NULL; + size_t count, offset; + + /* This function should only handle events caused by internal + * timeouts etc. The real input events caused by the evdev devices + * are already processed in handle_events */ + first_device = list_first_entry(&ctx->devices, first_device, link); + libinput_dispatch(ctx->libinput); + offset = first_device->nevents; + count = handle_libinput_events(ctx, first_device); + if (count) { + print_cached_events(ctx, + first_device, + offset, + count); + } +} + +static int +dispatch_sources(struct record_context *ctx) +{ + struct source *source; + struct epoll_event ep[64]; + int i, count; + + count = epoll_wait(ctx->epoll_fd, ep, ARRAY_LENGTH(ep), ctx->timeout); + if (count < 0) + return -errno; + + for (i = 0; i < count; ++i) { + source = ep[i].data.ptr; + if (source->fd == -1) + continue; + source->dispatch(ctx, source->fd, source->user_data); + } + + return count; +} + static int mainloop(struct record_context *ctx) { bool autorestart = (ctx->timeout > 0); - struct pollfd fds[ctx->ndevices + 3]; - struct pollfd *signal_fd = &fds[0]; - struct pollfd *time_fd = &fds[1]; - struct pollfd *libinput_fd = NULL; - unsigned int nfds = 0; + struct source *source, *tmp; struct record_device *d = NULL; sigset_t mask; + int sigfd, timerfd; assert(ctx->timeout != 0); assert(!list_empty(&ctx->devices)); + ctx->epoll_fd = epoll_create1(0); + assert(ctx->epoll_fd >= 0); + sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGQUIT); sigprocmask(SIG_BLOCK, &mask, NULL); - for (size_t i = 0; i < ARRAY_LENGTH(fds); i++) { - fds[i] = (struct pollfd) { - .fd = -1, - .events = POLLIN, - .revents = 0, - }; - } - - signal_fd->fd = signalfd(-1, &mask, SFD_NONBLOCK); - assert(signal_fd->fd != -1); - nfds++; + sigfd = signalfd(-1, &mask, SFD_NONBLOCK); + add_source(ctx, sigfd, signalfd_dispatch, NULL); - time_fd->fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); - assert(time_fd->fd != -1); - arm_timer(time_fd->fd); - nfds++; + timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC|TFD_NONBLOCK); + add_source(ctx, timerfd, timefd_dispatch, NULL); + arm_timer(timerfd); - if (ctx->libinput) { - libinput_fd = &fds[nfds++]; - libinput_fd->fd = libinput_get_fd(ctx->libinput); + list_for_each(d, &ctx->devices, link) { + add_source(ctx, libevdev_get_fd(d->evdev), evdev_dispatch, d); } - list_for_each(d, &ctx->devices, link) { - fds[nfds].fd = libevdev_get_fd(d->evdev); - assert(fds[nfds].fd != -1); - nfds++; + if (ctx->libinput) { + /* See the note in the dispatch function */ + add_source(ctx, + libinput_get_fd(ctx->libinput), + libinput_ctx_dispatch, + NULL); } /* If we have more than one device, the time starts at recording @@ -2193,10 +2331,6 @@ mainloop(struct record_context *ctx) } do { - int rc; - bool had_events = false; /* we delete files without events */ - bool had_events_since_last_time = false; - bool skipped_timer_print = false; struct record_device *first_device = NULL; if (!open_output_file(ctx, autorestart)) { @@ -2209,6 +2343,8 @@ mainloop(struct record_context *ctx) isatty(STDERR_FILENO) ? "" : "# ", ctx->output_file); + ctx->had_events = false; + print_header(ctx); if (autorestart) iprintf(ctx, @@ -2237,75 +2373,23 @@ mainloop(struct record_context *ctx) } while (true) { - rc = poll(fds, nfds, ctx->timeout); - if (rc == -1) { /* error */ - fprintf(stderr, "Error: %m\n"); - autorestart = false; + int rc = dispatch_sources(ctx); + if (rc < 0) { /* error */ + fprintf(stderr, "Error: %s\n", strerror(-rc)); + ctx->stop = true; break; } + /* set by the signalfd handler */ + if (ctx->stop) + break; + if (rc == 0) { fprintf(stderr, " ... timeout%s\n", - had_events ? "" : " (file is empty)"); - break; - - } - - if (signal_fd->revents != 0) { /* signal */ - autorestart = false; + ctx->had_events ? "" : " (file is empty)"); break; - } - - if (time_fd->revents) { /* timer expiry */ - char discard[64]; - read(time_fd->fd, discard, sizeof(discard)); - - if (had_events_since_last_time) { - print_wall_time(ctx); - had_events_since_last_time = false; - skipped_timer_print = false; - } else { - skipped_timer_print = true; - } - if (rc == 1) /* no other fds have data */ - continue; - } - - if (skipped_timer_print) { - print_wall_time(ctx); - skipped_timer_print = false; - } - - /* Pull off the evdev events first since they cause - * libinput events. - * handle_events de-queues libinput events so by the - * time we finish that, we hopefully have all evdev - * events and libinput events roughly in sync. - */ - had_events = true; - had_events_since_last_time = true; - list_for_each(d, &ctx->devices, link) { - handle_events(ctx, d, d == first_device); - } - - /* This shouldn't pull any events off unless caused - * by libinput-internal timeouts (e.g. tapping) */ - if (ctx->libinput && libinput_fd->revents) { - size_t count, offset; - - libinput_dispatch(ctx->libinput); - offset = first_device->nevents; - count = handle_libinput_events(ctx, - first_device); - if (count) { - print_cached_events(ctx, - first_device, - offset, - count); - } - rc--; } if (ctx->out_fd != STDOUT_FILENO) @@ -2340,7 +2424,7 @@ mainloop(struct record_context *ctx) /* If we didn't have events, delete the file. */ if (!isatty(ctx->out_fd)) { - if (!had_events && ctx->output_file) { + if (!ctx->had_events && ctx->output_file) { fprintf(stderr, "No events recorded, deleting '%s'\n", ctx->output_file); unlink(ctx->output_file); } @@ -2350,13 +2434,15 @@ mainloop(struct record_context *ctx) } free(ctx->output_file); ctx->output_file = NULL; - } while (autorestart); - - close(signal_fd->fd); - close(time_fd->fd); + } while (autorestart && !ctx->stop); sigprocmask(SIG_UNBLOCK, &mask, NULL); + list_for_each_safe(source, tmp, &ctx->sources, link) { + destroy_source(ctx, source); + } + close(ctx->epoll_fd); + return 0; } @@ -2552,6 +2638,7 @@ main(int argc, char **argv) int rc = EXIT_FAILURE; list_init(&ctx.devices); + list_init(&ctx.sources); while (1) { int c; |