From 878388c5bf20e8fa50683c02f092fdd341035c9f Mon Sep 17 00:00:00 2001 From: Caleb Connolly Date: Tue, 11 Jul 2023 15:24:52 +0100 Subject: [PATCH] libinput: insert dummy events to handle fast OSK typing When typing fast with thumbs, you might hit a key while your other thumb is already pressing a key, however this confuses indev quite a lot. For example, you'll press a key (say 'h') with your right thumb, then before releasing your thumb you'll press the 'e' key with your left thumb, and then finally release the 'h' key followed by the 'e' key. This should result in "he" being typed, but usually results in an "e". Indev assumes that a pointer release will always be for whatever the last position was. This is reasonable for mice but not for multitouch displays. This workaround detects this scenario and inserts two dummy events, first a "pressed" state with the coordinates of the first finger (the 'h' key), then a release event, triggering the keypress, then it sends another pressed events with the position of the second finger (the 'e' key) so that if the next event is that finger releasing, the position will be correct. --- indev/libinput.c | 63 +++++++++++++++++++++++++++++++++++++++----- indev/libinput_drv.h | 3 +++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/indev/libinput.c b/indev/libinput.c index 3e699d5..75ce382 100644 --- a/indev/libinput.c +++ b/indev/libinput.c @@ -284,6 +284,15 @@ libinput_lv_event_t *get_event(libinput_drv_state_t *state) return evt; } +libinput_lv_event_t *peek_event(libinput_drv_state_t *state) +{ + if (state->start == state->end) { + return NULL; + } + + return &state->points[state->start]; +} + bool event_pending(libinput_drv_state_t *state) { return state->start != state->end; @@ -360,8 +369,8 @@ void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_dr pthread_mutex_lock(&state->event_lock); - libinput_lv_event_t *evt = get_event(state); - data->continue_reading = event_pending(state); + /* We may want to send this event twice so only peek it for now */ + libinput_lv_event_t *evt = peek_event(state); if (!evt) evt = &state->last_event; /* indev expects us to report the most recent state */ @@ -376,18 +385,51 @@ void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_dr 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); evt->is_relative = false; + } else if (evt->pressed == LV_INDEV_STATE_REL && evt->slot == 0 + && state->slots[1].pressed == LV_INDEV_STATE_PR && !state->doing_mtouch_dummy_event) { + /* + * We don't support "multitouch", but libinput does. To make fast typing with two thumbs + * on a keyboard feel good, it's necessary to handle two fingers individually. The edge + * case here is if you press a key with one finger and then press a second key with another + * finger. No matter which finger you release, it will count as the second finger releasing + * and ignore the first. + * + * To work around this, detect the case where a finger is releasing while the other finger is + * still pressed and insert a dummy press event for the finger which is still pressed. + */ + + /* evt won't be consumed so it will be re-sent on the next call */ + evt->pressed = LV_INDEV_STATE_PR; + evt->point = state->slots[0].point; + state->doing_mtouch_dummy_event = 1; + + /* slot 1 will become slot 0 when slot 0 is released */ + state->slots[1].pressed = LV_INDEV_STATE_REL; + } else if (state->doing_mtouch_dummy_event == 1) { + /* Now that the position is definitely correct, send the release */ + evt->pressed = LV_INDEV_STATE_REL; + state->doing_mtouch_dummy_event++; + } else if (state->doing_mtouch_dummy_event == 2) { + /* Finally, update the position to the remaining finger and send a press */ + evt->pressed = LV_INDEV_STATE_PR; + evt->point = state->slots[1].point; /* Wherever slot 1 most recently pressed */ + state->doing_mtouch_dummy_event = 0; + } else { + /* Consume the event */ + get_event(state); } data->point = evt->point; data->state = evt->pressed; data->key = evt->key_val; + data->continue_reading = event_pending(state); state->last_event = *evt; /* Remember the last event for the next call */ 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); + LV_LOG_TRACE("libinput_read: %d (%04d, %04d): %d continue_reading? %d", evt->slot, data->point.x, data->point.y, data->state, data->continue_reading); } @@ -510,6 +552,7 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve struct libinput_event_pointer *pointer_event = NULL; libinput_lv_event_t *evt = NULL; enum libinput_event_type type = libinput_event_get_type(event); + int slot; switch (type) { case LIBINPUT_EVENT_TOUCH_MOTION: @@ -531,7 +574,7 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve lv_disp_drv_t *drv = lv_disp_get_default()->driver; /* ignore more than 2 fingers as it will only confuse LVGL */ - if (touch_event && libinput_event_touch_get_slot(touch_event) > 1) + if (touch_event && (slot = libinput_event_touch_get_slot(touch_event)) > 1) return; evt = new_event(state); @@ -547,10 +590,15 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve evt->point.x = x; evt->point.y = y; evt->pressed = LV_INDEV_STATE_PR; + evt->slot = slot; + state->slots[slot].point = evt->point; + state->slots[slot].pressed = evt->pressed; break; } case LIBINPUT_EVENT_TOUCH_UP: evt->pressed = LV_INDEV_STATE_REL; + state->slots[slot].pressed = evt->pressed; + evt->slot = slot; break; case LIBINPUT_EVENT_POINTER_MOTION: evt->point.x += libinput_event_pointer_get_dx(pointer_event); @@ -559,8 +607,7 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve evt->point.y = LV_CLAMP(0, evt->point.y, drv->ver_res - 1); evt->is_relative = true; break; - case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: - pointer_event = libinput_event_get_pointer_event(event); + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { lv_coord_t x_pointer = libinput_event_pointer_get_absolute_x_transformed(pointer_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x; lv_coord_t y_pointer = libinput_event_pointer_get_absolute_y_transformed(pointer_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y; if (x_pointer < 0 || x_pointer > drv->hor_res || y_pointer < 0 || y_pointer > drv->ver_res) { @@ -569,11 +616,13 @@ static void read_pointer(libinput_drv_state_t *state, struct libinput_event *eve evt->point.x = x_pointer; evt->point.y = y_pointer; break; - case LIBINPUT_EVENT_POINTER_BUTTON: + } + case LIBINPUT_EVENT_POINTER_BUTTON: { enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event); evt->pressed = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; evt->is_relative = true; break; + } default: break; } diff --git a/indev/libinput_drv.h b/indev/libinput_drv.h index 4f8776c..e7ca916 100644 --- a/indev/libinput_drv.h +++ b/indev/libinput_drv.h @@ -55,6 +55,7 @@ typedef struct { int key_val; lv_point_t point; bool is_relative; + int slot : 4; } libinput_lv_event_t; #define MAX_EVENTS 32 @@ -64,6 +65,8 @@ typedef struct { /* The points array is implemented as a circular LIFO queue */ libinput_lv_event_t points[MAX_EVENTS]; /* Event buffer */ + libinput_lv_event_t slots[2]; /* Realtime state of up to 2 fingers to handle multitouch */ + int doing_mtouch_dummy_event; 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