input: use a timer fd to handle keyboard key repeat

Instead of running a repeater thread that writes the key to repeat
over a pipe, use a simple timer fd.

No more locking or condition signalling. No need to track
start/stop/exist states.

We simply set up the initial timeout value to be the 'delay', and the
interval to be the repeat 'rate'.
This commit is contained in:
Daniel Eklöf 2019-08-05 19:33:01 +02:00
parent c62ce72778
commit a82f12dd2b
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
3 changed files with 65 additions and 156 deletions

78
input.c
View file

@ -7,6 +7,7 @@
#include <locale.h> #include <locale.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/timerfd.h>
#include <linux/input-event-codes.h> #include <linux/input-event-codes.h>
@ -65,49 +66,58 @@ keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
term_focus_in(term); term_focus_in(term);
} }
static bool
start_repeater(struct terminal *term, uint32_t key)
{
if (term->kbd.repeat.dont_re_repeat)
return true;
struct itimerspec t = {
.it_value = {.tv_sec = 0, .tv_nsec = term->kbd.repeat.delay * 1000000},
.it_interval = {.tv_sec = 0, .tv_nsec = 1000000000 / term->kbd.repeat.rate},
};
if (t.it_value.tv_nsec >= 1000000000) {
t.it_value.tv_sec += t.it_value.tv_nsec / 1000000000;
t.it_value.tv_nsec %= 1000000000;
}
if (t.it_interval.tv_nsec >= 1000000000) {
t.it_interval.tv_sec += t.it_interval.tv_nsec / 1000000000;
t.it_interval.tv_nsec %= 1000000000;
}
if (timerfd_settime(term->kbd.repeat.fd, 0, &t, NULL) < 0) {
LOG_ERRNO("failed to arm keyboard repeat timer");
return false;
}
term->kbd.repeat.key = key;
return true;
}
static bool
stop_repeater(struct terminal *term, uint32_t key)
{
if (key != -1 && key != term->kbd.repeat.key)
return true;
if (timerfd_settime(term->kbd.repeat.fd, 0, &(struct itimerspec){{0}}, NULL) < 0) {
LOG_ERRNO("failed to disarm keyboard repeat timer");
return false;
}
return true;
}
static void static void
keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
struct wl_surface *surface) struct wl_surface *surface)
{ {
struct terminal *term = data; struct terminal *term = data;
mtx_lock(&term->kbd.repeat.mutex); stop_repeater(term, -1);
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_STOP;
cnd_signal(&term->kbd.repeat.cond);
}
mtx_unlock(&term->kbd.repeat.mutex);
term_focus_out(term); term_focus_out(term);
} }
static void
start_repeater(struct terminal *term, uint32_t key)
{
mtx_lock(&term->kbd.repeat.mutex);
if (!term->kbd.repeat.dont_re_repeat) {
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_START;
term->kbd.repeat.key = key;
cnd_signal(&term->kbd.repeat.cond);
}
}
mtx_unlock(&term->kbd.repeat.mutex);
}
static void
stop_repeater(struct terminal *term, uint32_t key)
{
mtx_lock(&term->kbd.repeat.mutex);
if (term->kbd.repeat.key == key) {
if (term->kbd.repeat.cmd != REPEAT_EXIT) {
term->kbd.repeat.cmd = REPEAT_STOP;
cnd_signal(&term->kbd.repeat.cond);
}
}
mtx_unlock(&term->kbd.repeat.mutex);
}
static void static void
keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state) uint32_t time, uint32_t key, uint32_t state)

136
main.c
View file

@ -192,83 +192,6 @@ static const struct wl_registry_listener registry_listener = {
.global_remove = &handle_global_remove, .global_remove = &handle_global_remove,
}; };
static int
keyboard_repeater(void *arg)
{
struct terminal *term = arg;
char proc_title[16];
snprintf(proc_title, sizeof(proc_title), "foot:kbd-repeat");
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
LOG_ERRNO("kbd repeat: failed to set process title");
while (true) {
LOG_DBG("repeater: waiting for start");
mtx_lock(&term->kbd.repeat.mutex);
while (term->kbd.repeat.cmd == REPEAT_STOP)
cnd_wait(&term->kbd.repeat.cond, &term->kbd.repeat.mutex);
if (term->kbd.repeat.cmd == REPEAT_EXIT) {
mtx_unlock(&term->kbd.repeat.mutex);
return 0;
}
restart:
LOG_DBG("repeater: started");
assert(term->kbd.repeat.cmd == REPEAT_START);
const long rate_delay = 1000000000 / term->kbd.repeat.rate;
long delay = term->kbd.repeat.delay * 1000000;
while (true) {
assert(term->kbd.repeat.rate > 0);
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_nsec += delay;
if (timeout.tv_nsec >= 1000000000) {
timeout.tv_sec += timeout.tv_nsec / 1000000000;
timeout.tv_nsec %= 1000000000;
}
int ret = cnd_timedwait(&term->kbd.repeat.cond, &term->kbd.repeat.mutex, &timeout);
if (ret == thrd_success) {
if (term->kbd.repeat.cmd == REPEAT_START)
goto restart;
else if (term->kbd.repeat.cmd == REPEAT_STOP) {
mtx_unlock(&term->kbd.repeat.mutex);
break;
} else if (term->kbd.repeat.cmd == REPEAT_EXIT) {
mtx_unlock(&term->kbd.repeat.mutex);
return 0;
}
}
assert(ret == thrd_timedout);
assert(term->kbd.repeat.cmd == REPEAT_START);
LOG_DBG("repeater: repeat: %u", term->kbd.repeat.key);
if (write(term->kbd.repeat.pipe_write_fd, &term->kbd.repeat.key,
sizeof(term->kbd.repeat.key)) != sizeof(term->kbd.repeat.key))
{
LOG_ERRNO("faile to write repeat key to repeat pipe");
mtx_unlock(&term->kbd.repeat.mutex);
return 0;
}
delay = rate_delay;
}
}
assert(false);
return 1;
}
int int
main(int argc, char *const *argv) main(int argc, char *const *argv)
{ {
@ -319,12 +242,6 @@ main(int argc, char *const *argv)
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
setenv("TERM", conf.term, 1); setenv("TERM", conf.term, 1);
int repeat_pipe_fds[2] = {-1, -1};
if (pipe2(repeat_pipe_fds, O_CLOEXEC) == -1) {
LOG_ERRNO("failed to create pipe for repeater thread");
return ret;
}
struct terminal term = { struct terminal term = {
.quit = false, .quit = false,
.ptmx = posix_openpt(O_RDWR | O_NOCTTY), .ptmx = posix_openpt(O_RDWR | O_NOCTTY),
@ -347,9 +264,7 @@ main(int argc, char *const *argv)
}, },
.kbd = { .kbd = {
.repeat = { .repeat = {
.pipe_read_fd = repeat_pipe_fds[0], .fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC),
.pipe_write_fd = repeat_pipe_fds[1],
.cmd = REPEAT_STOP,
}, },
}, },
.colors = { .colors = {
@ -415,15 +330,14 @@ main(int argc, char *const *argv)
} }
if (term.ptmx == -1) { if (term.ptmx == -1) {
LOG_ERRNO("failed to open pseudo terminal"); LOG_ERR("failed to open pseudo terminal");
goto out; goto out;
} }
mtx_init(&term.kbd.repeat.mutex, mtx_plain); if (term.flash.fd == -1 || term.blink.fd == -1 || term.kbd.repeat.fd == -1) {
cnd_init(&term.kbd.repeat.cond); LOG_ERR("failed to create timers");
goto out;
thrd_t keyboard_repeater_id; }
thrd_create(&keyboard_repeater_id, &keyboard_repeater, &term);
sem_init(&term.render.workers.start, 0, 0); sem_init(&term.render.workers.start, 0, 0);
sem_init(&term.render.workers.done, 0, 0); sem_init(&term.render.workers.done, 0, 0);
@ -706,7 +620,7 @@ main(int argc, char *const *argv)
struct pollfd fds[] = { struct pollfd fds[] = {
{.fd = wl_display_get_fd(term.wl.display), .events = POLLIN}, {.fd = wl_display_get_fd(term.wl.display), .events = POLLIN},
{.fd = term.ptmx, .events = POLLIN}, {.fd = term.ptmx, .events = POLLIN},
{.fd = term.kbd.repeat.pipe_read_fd, .events = POLLIN}, {.fd = term.kbd.repeat.fd, .events = POLLIN},
{.fd = term.flash.fd, .events = POLLIN}, {.fd = term.flash.fd, .events = POLLIN},
{.fd = term.blink.fd, .events = POLLIN}, {.fd = term.blink.fd, .events = POLLIN},
}; };
@ -789,20 +703,20 @@ main(int argc, char *const *argv)
} }
if (fds[2].revents & POLLIN) { if (fds[2].revents & POLLIN) {
uint32_t key; uint64_t expiration_count;
if (read(term.kbd.repeat.pipe_read_fd, &key, sizeof(key)) != sizeof(key)) { ssize_t ret = read(
LOG_ERRNO("failed to read repeat key from repeat pipe"); term.kbd.repeat.fd, &expiration_count, sizeof(expiration_count));
break;
if (ret < 0)
LOG_ERRNO("failed to read repeat key from repeat timer fd");
else {
term.kbd.repeat.dont_re_repeat = true;
for (size_t i = 0; i < expiration_count; i++)
input_repeat(&term, term.kbd.repeat.key);
term.kbd.repeat.dont_re_repeat = false;
} }
term.kbd.repeat.dont_re_repeat = true;
input_repeat(&term, key);
term.kbd.repeat.dont_re_repeat = false;
} }
if (fds[2].revents & POLLHUP)
LOG_ERR("keyboard repeat handling thread died");
if (fds[3].revents & POLLIN) { if (fds[3].revents & POLLIN) {
uint64_t expiration_count; uint64_t expiration_count;
ssize_t ret = read( ssize_t ret = read(
@ -852,11 +766,6 @@ main(int argc, char *const *argv)
} }
out: out:
mtx_lock(&term.kbd.repeat.mutex);
term.kbd.repeat.cmd = REPEAT_EXIT;
cnd_signal(&term.kbd.repeat.cond);
mtx_unlock(&term.kbd.repeat.mutex);
mtx_lock(&term.render.workers.lock); mtx_lock(&term.render.workers.lock);
assert(tll_length(term.render.workers.queue) == 0); assert(tll_length(term.render.workers.queue) == 0);
for (size_t i = 0; i < term.render.workers.count; i++) { for (size_t i = 0; i < term.render.workers.count; i++) {
@ -942,14 +851,12 @@ out:
close(term.flash.fd); close(term.flash.fd);
if (term.blink.fd != -1) if (term.blink.fd != -1)
close(term.blink.fd); close(term.blink.fd);
if (term.kbd.repeat.fd != -1)
close(term.kbd.repeat.fd);
if (term.ptmx != -1) if (term.ptmx != -1)
close(term.ptmx); close(term.ptmx);
thrd_join(keyboard_repeater_id, NULL);
cnd_destroy(&term.kbd.repeat.cond);
mtx_destroy(&term.kbd.repeat.mutex);
for (size_t i = 0; i < term.render.workers.count; i++) for (size_t i = 0; i < term.render.workers.count; i++)
thrd_join(term.render.workers.threads[i], NULL); thrd_join(term.render.workers.threads[i], NULL);
free(term.render.workers.threads); free(term.render.workers.threads);
@ -960,9 +867,6 @@ out:
assert(tll_length(term.render.workers.queue) == 0); assert(tll_length(term.render.workers.queue) == 0);
tll_free(term.render.workers.queue); tll_free(term.render.workers.queue);
close(term.kbd.repeat.pipe_read_fd);
close(term.kbd.repeat.pipe_write_fd);
config_free(conf); config_free(conf);
cairo_debug_reset_static_data(); cairo_debug_reset_static_data();

View file

@ -156,12 +156,7 @@ struct kbd {
struct xkb_compose_table *xkb_compose_table; struct xkb_compose_table *xkb_compose_table;
struct xkb_compose_state *xkb_compose_state; struct xkb_compose_state *xkb_compose_state;
struct { struct {
mtx_t mutex; int fd;
cnd_t cond;
int trigger;
int pipe_read_fd;
int pipe_write_fd;
enum {REPEAT_STOP, REPEAT_START, REPEAT_EXIT} cmd;
bool dont_re_repeat; bool dont_re_repeat;
int32_t delay; int32_t delay;