Skip to content

Commit

Permalink
indev: libinput: implement event queue
Browse files Browse the repository at this point in the history
Libinput doesn't buffer events, so hook up a dedicated thread per input
device and a small circular queue to buffer input events. This fixes the
dropped input issue so you can type much faster. If there are too many
events, the oldest events in the queue will be dropped.
  • Loading branch information
calebccff committed Jun 5, 2023
1 parent 9842ca4 commit 0cfcdbb
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 60 deletions.
212 changes: 155 additions & 57 deletions indev/libinput.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <stdbool.h>
#include <dirent.h>
#include <libinput.h>
#include <pthread.h>

#if USE_BSD_LIBINPUT
#include <dev/evdev/input.h>
Expand All @@ -42,6 +43,7 @@ struct input_device {
static bool rescan_devices(void);
static bool add_scanned_device(char *path, libinput_capability capabilities);
static void reset_scanned_devices(void);
static void *libinput_poll_worker(void* data);

static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event);
static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event);
Expand All @@ -55,9 +57,9 @@ static void close_restricted(int fd, void *user_data);
static struct input_device *devices = NULL;
static size_t num_devices = 0;

static libinput_drv_state_t default_state = { .most_recent_touch_point = { .x = 0, .y = 0 } };
static libinput_drv_state_t default_state = { .event_lock = PTHREAD_MUTEX_INITIALIZER, };

static const int timeout = 0; // do not block
static const int timeout = 100; // ms
static const nfds_t nfds = 1;

static const struct libinput_interface interface = {
Expand Down Expand Up @@ -174,9 +176,6 @@ bool libinput_set_file_state(libinput_drv_state_t *state, char* dev_name)
return false;
}

state->button = LV_INDEV_STATE_REL;
state->key_val = 0;

return true;
}

Expand All @@ -186,6 +185,7 @@ bool libinput_set_file_state(libinput_drv_state_t *state, char* dev_name)
*/
void libinput_init(void)
{
memset(&default_state, 0, sizeof(libinput_drv_state_t));
libinput_init_state(&default_state, LIBINPUT_NAME);
}

Expand All @@ -211,6 +211,8 @@ void libinput_init_state(libinput_drv_state_t *state, char* path)
state->fds[0].events = POLLIN;
state->fds[0].revents = 0;

pthread_create(&state->worker_thread, NULL, libinput_poll_worker, state);

#if USE_XKB
xkb_init_state(&(state->xkb_state));
#endif
Expand All @@ -223,6 +225,21 @@ void libinput_init_state(libinput_drv_state_t *state, char* path)
*/
void libinput_deinit_state(libinput_drv_state_t *state)
{
if (state->fd)
state->deinit = true;

/* Give worker thread a whole second to quit */
for (int i = 0; i < 100; i++) {
if (!state->deinit)
break;
usleep(10000);
}

if (state->deinit) {
fprintf(stderr, "libinput worker thread did not quit in time!\n");
pthread_cancel(state->worker_thread);
}

if (state->libinput_device) {
libinput_path_remove_device(state->libinput_device);
libinput_device_unref(state->libinput_device);
Expand Down Expand Up @@ -250,6 +267,83 @@ void libinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
libinput_read_state(&default_state, indev_drv, data);
}

libinput_lv_event_t *get_event(libinput_drv_state_t *state)
{
if (state->start == state->end) {
return NULL;
}

libinput_lv_event_t *evt = &state->points[state->start];

if (++state->start == MAX_EVENTS)
state->start = 0;

return evt;
}

bool event_pending(libinput_drv_state_t *state)
{
return state->start != state->end;
}

libinput_lv_event_t *new_event(libinput_drv_state_t *state)
{
libinput_lv_event_t *evt = &state->points[state->end];

if (++state->end == MAX_EVENTS)
state->end = 0;

/* We have overflowed the buffer, start overwriting
* old events.
*/
if (state->end == state->start) {
LV_LOG_INFO("libinput: overflowed event buffer!");
if (++state->start == MAX_EVENTS)
state->start = 0;
}

memset(evt, 0, sizeof(libinput_lv_event_t));

return evt;
}

static void *libinput_poll_worker(void* data)
{
libinput_drv_state_t * state = (libinput_drv_state_t *)data;
struct libinput_event *event;
int rc = 0;

LV_LOG_INFO("libinput: poll worker started");

while (true) {
rc = poll(state->fds, nfds, timeout);
switch (rc){
case -1:
perror(NULL);
__attribute__((fallthrough));
case 0:
if (state->deinit) {
state->deinit = false; /* Signal that we're done */
return NULL;
}
continue;
default:
break;
}
libinput_dispatch(state->libinput_context);
pthread_mutex_lock(&state->event_lock);
while((event = libinput_get_event(state->libinput_context)) != NULL) {
read_pointer(state, event);
read_keypad(state, event);
libinput_event_destroy(event);
}
pthread_mutex_unlock(&state->event_lock);
LV_LOG_INFO("libinput: event read");
}

return NULL;
}

/**
* Read available input events via libinput using a specific driver state. Use this function if you want to
* connect multiple devices.
Expand All @@ -259,37 +353,25 @@ void libinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
*/
void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
struct libinput_event *event;
int rc = 0;
LV_UNUSED(indev_drv);

rc = poll(state->fds, nfds, timeout);
switch (rc){
case -1:
perror(NULL);
case 0:
goto report_most_recent_state;
default:
break;
}
libinput_dispatch(state->libinput_context);
while((event = libinput_get_event(state->libinput_context)) != NULL) {
switch (indev_drv->type) {
case LV_INDEV_TYPE_POINTER:
read_pointer(state, event);
break;
case LV_INDEV_TYPE_KEYPAD:
read_keypad(state, event);
break;
default:
break;
}
libinput_event_destroy(event);
}
report_most_recent_state:
data->point.x = state->most_recent_touch_point.x;
data->point.y = state->most_recent_touch_point.y;
data->state = state->button;
data->key = state->key_val;
pthread_mutex_lock(&state->event_lock);

libinput_lv_event_t *evt = get_event(state);
data->continue_reading = event_pending(state);
if (!evt)
evt = &state->last_event;
/* indev expects us to report the most recent state */
else if (!data->continue_reading)
state->last_event = *evt;

data->point = evt->point;
data->state = evt->pressed;

pthread_mutex_unlock(&state->event_lock);

if (evt)
LV_LOG_TRACE("libinput_read: %d//%d: (%04d,%04d): %d continue_reading? %d", state->start, state->end, data->point.x, data->point.y, data->state, data->continue_reading);
}


Expand Down Expand Up @@ -410,6 +492,7 @@ static void reset_scanned_devices(void) {
static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event) {
struct libinput_event_touch *touch_event = NULL;
struct libinput_event_pointer *pointer_event = NULL;
libinput_lv_event_t *evt = NULL;
enum libinput_event_type type = libinput_event_get_type(event);

/* We need to read unrotated display dimensions directly from the driver because libinput won't account
Expand All @@ -419,30 +502,44 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve
switch (type) {
case LIBINPUT_EVENT_TOUCH_MOTION:
case LIBINPUT_EVENT_TOUCH_DOWN:
case LIBINPUT_EVENT_TOUCH_UP:
touch_event = libinput_event_get_touch_event(event);
/* ignore more than 2 fingers to avoid overflowing the buffer */
if (libinput_event_touch_get_slot(touch_event) > 1)
return;
evt = new_event(state);
default:
break;
}

switch (type) {
/* Ignore motion events to avoid generating two events for a single "action". */
case LIBINPUT_EVENT_TOUCH_MOTION:
case LIBINPUT_EVENT_TOUCH_DOWN: {
lv_coord_t x = libinput_event_touch_get_x_transformed(touch_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x;
lv_coord_t y = libinput_event_touch_get_y_transformed(touch_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y;
if (x < 0 || x > drv->hor_res || y < 0 || y > drv->ver_res) {
break; /* ignore touches that are out of bounds */
}
state->most_recent_touch_point.x = x;
state->most_recent_touch_point.y = y;
state->button = LV_INDEV_STATE_PR;
evt->point.x = x;
evt->point.y = y;
evt->pressed = LV_INDEV_STATE_PR;
break;
}
case LIBINPUT_EVENT_TOUCH_UP:
state->button = LV_INDEV_STATE_REL;
evt->pressed = LV_INDEV_STATE_REL;
break;
case LIBINPUT_EVENT_POINTER_MOTION:
pointer_event = libinput_event_get_pointer_event(event);
state->most_recent_touch_point.x += libinput_event_pointer_get_dx(pointer_event);
state->most_recent_touch_point.y += libinput_event_pointer_get_dy(pointer_event);
state->most_recent_touch_point.x = LV_CLAMP(0, state->most_recent_touch_point.x, drv->hor_res - 1);
state->most_recent_touch_point.y = LV_CLAMP(0, state->most_recent_touch_point.y, drv->ver_res - 1);
evt->point.x += libinput_event_pointer_get_dx(pointer_event);
evt->point.y += libinput_event_pointer_get_dy(pointer_event);
evt->point.x = LV_CLAMP(0, evt->point.x, drv->hor_res - 1);
evt->point.y = LV_CLAMP(0, evt->point.y, drv->ver_res - 1);
break;
case LIBINPUT_EVENT_POINTER_BUTTON:
pointer_event = libinput_event_get_pointer_event(event);
enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event);
state->button = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
evt->pressed = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
break;
default:
break;
Expand All @@ -457,50 +554,51 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve
static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event) {
struct libinput_event_keyboard *keyboard_event = NULL;
enum libinput_event_type type = libinput_event_get_type(event);
libinput_lv_event_t *evt = NULL;
switch (type) {
case LIBINPUT_EVENT_KEYBOARD_KEY:
keyboard_event = libinput_event_get_keyboard_event(event);
enum libinput_key_state key_state = libinput_event_keyboard_get_key_state(keyboard_event);
uint32_t code = libinput_event_keyboard_get_key(keyboard_event);
#if USE_XKB
state->key_val = xkb_process_key_state(&(state->xkb_state), code, key_state == LIBINPUT_KEY_STATE_PRESSED);
evt->key_val = xkb_process_key_state(&(state->xkb_state), code, key_state == LIBINPUT_KEY_STATE_PRESSED);
#else
switch(code) {
case KEY_BACKSPACE:
state->key_val = LV_KEY_BACKSPACE;
evt->key_val = LV_KEY_BACKSPACE;
break;
case KEY_ENTER:
state->key_val = LV_KEY_ENTER;
evt->key_val = LV_KEY_ENTER;
break;
case KEY_PREVIOUS:
state->key_val = LV_KEY_PREV;
evt->key_val = LV_KEY_PREV;
break;
case KEY_NEXT:
state->key_val = LV_KEY_NEXT;
evt->key_val = LV_KEY_NEXT;
break;
case KEY_UP:
state->key_val = LV_KEY_UP;
evt->key_val = LV_KEY_UP;
break;
case KEY_LEFT:
state->key_val = LV_KEY_LEFT;
evt->key_val = LV_KEY_LEFT;
break;
case KEY_RIGHT:
state->key_val = LV_KEY_RIGHT;
evt->key_val = LV_KEY_RIGHT;
break;
case KEY_DOWN:
state->key_val = LV_KEY_DOWN;
evt->key_val = LV_KEY_DOWN;
break;
case KEY_TAB:
state->key_val = LV_KEY_NEXT;
evt->key_val = LV_KEY_NEXT;
break;
default:
state->key_val = 0;
evt->key_val = 0;
break;
}
#endif /* USE_XKB */
if (state->key_val != 0) {
if (evt->key_val != 0) {
/* Only record button state when actual output is produced to prevent widgets from refreshing */
state->button = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
evt->pressed = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
}
break;
default:
Expand Down
21 changes: 18 additions & 3 deletions indev/libinput_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern "C" {
#endif

#include <poll.h>
#include <pthread.h>

#if USE_XKB
#include "xkb.h"
Expand All @@ -49,13 +50,27 @@ typedef enum {
LIBINPUT_CAPABILITY_TOUCH = 1U << 2
} libinput_capability;

typedef struct {
lv_indev_state_t pressed;
int key_val;
lv_point_t point;
} libinput_lv_event_t;

#define MAX_EVENTS 32
typedef struct {
int fd;
struct pollfd fds[1];

int button;
int key_val;
lv_point_t most_recent_touch_point;
/* The points array is implemented as a circular LIFO queue */
libinput_lv_event_t points[MAX_EVENTS]; /* Event buffer */
int start; /* Index of start of event queue */
int end; /* Index of end of queue*/
libinput_lv_event_t last_event; /* Report when no new events
* to keep indev state consistent
*/
bool deinit; /* Tell worker thread to quit */
pthread_mutex_t event_lock;
pthread_t worker_thread;

struct libinput *libinput_context;
struct libinput_device *libinput_device;
Expand Down

0 comments on commit 0cfcdbb

Please sign in to comment.