From ff90e967c18aff4a2d131dbdaf5a96453133da25 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 20 Jan 2026 11:52:48 +1000 Subject: [PATCH 1/2] touchpad: track QUINTTAP as proper fake finger Previously we stopped tracking at 4 fingers and 5 fingers were simply "too many". Alas, in order to track 4 fingers properly for tapping we need to also track when we have 5 fingers. Remove the FAKE_FINGER_OVERFLOW tracking and instead just track the fingers as they are. --- src/evdev-mt-touchpad.c | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 41b9c098..3f2b2850 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -42,7 +42,6 @@ #define DEFAULT_TRACKPOINT_EVENT_TIMEOUT usec_from_millis(40) #define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1 usec_from_millis(200) #define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2 usec_from_millis(500) -#define FAKE_FINGER_OVERFLOW bit(7) #define THUMB_IGNORE_SPEED_THRESHOLD 20 /* mm/s */ enum notify { @@ -259,7 +258,7 @@ tp_get_touch(struct tp_dispatch *tp, unsigned int slot) static inline unsigned int tp_fake_finger_count(struct tp_dispatch *tp) { - unsigned int fake_touches = tp->fake_touches & ~(FAKE_FINGER_OVERFLOW | 0x1); + unsigned int fake_touches = tp->fake_touches & ~0x1; /* Only one of BTN_TOOL_DOUBLETAP/TRIPLETAP/... may be set at any * time */ @@ -268,9 +267,6 @@ tp_fake_finger_count(struct tp_dispatch *tp) "Invalid fake finger state %#x\n", tp->fake_touches); - if (tp->fake_touches & FAKE_FINGER_OVERFLOW) - return FAKE_FINGER_OVERFLOW; - /* don't count BTN_TOUCH */ return ffs(tp->fake_touches >> 1); } @@ -288,33 +284,29 @@ tp_fake_finger_set(struct tp_dispatch *tp, evdev_usage_t usage, bool is_press) switch (evdev_usage_enum(usage)) { case EVDEV_BTN_TOUCH: - if (!is_press) - tp->fake_touches &= ~FAKE_FINGER_OVERFLOW; shift = 0; break; case EVDEV_BTN_TOOL_FINGER: shift = 1; break; case EVDEV_BTN_TOOL_DOUBLETAP: - case EVDEV_BTN_TOOL_TRIPLETAP: - case EVDEV_BTN_TOOL_QUADTAP: - shift = evdev_usage_enum(usage) - EVDEV_BTN_TOOL_DOUBLETAP + 2; + shift = 2; + break; + case EVDEV_BTN_TOOL_TRIPLETAP: + shift = 3; + break; + case EVDEV_BTN_TOOL_QUADTAP: + shift = 4; break; - /* when QUINTTAP is released we're either switching to 6 fingers - (flag stays in place until BTN_TOUCH is released) or - one of DOUBLE/TRIPLE/QUADTAP (will clear the flag on press) */ case EVDEV_BTN_TOOL_QUINTTAP: - if (is_press) - tp->fake_touches |= FAKE_FINGER_OVERFLOW; - return; + shift = 5; + break; default: return; } if (is_press) { - tp->fake_touches &= ~FAKE_FINGER_OVERFLOW; tp->fake_touches |= bit(shift); - } else { tp->fake_touches &= ~bit(shift); } @@ -627,9 +619,6 @@ tp_process_fake_touches(struct tp_dispatch *tp, usec_t time) unsigned int i, start; nfake_touches = tp_fake_finger_count(tp); - if (nfake_touches == FAKE_FINGER_OVERFLOW) - return; - if (tp->device->model_flags & EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD) tp_restore_synaptics_touches(tp, time); @@ -1142,9 +1131,6 @@ tp_unhover_pressure(struct tp_dispatch *tp, usec_t time) unsigned int real_fingers_down = 0; nfake_touches = tp_fake_finger_count(tp); - if (nfake_touches == FAKE_FINGER_OVERFLOW) - nfake_touches = 0; - for (i = 0; i < (int)tp->num_slots; i++) { t = tp_get_touch(tp, i); @@ -1268,9 +1254,6 @@ tp_unhover_fake_touches(struct tp_dispatch *tp, usec_t time) return; nfake_touches = tp_fake_finger_count(tp); - if (nfake_touches == FAKE_FINGER_OVERFLOW) - return; - if (tp->nfingers_down == nfake_touches && ((tp->nfingers_down == 0 && !tp_fake_finger_is_touching(tp)) || (tp->nfingers_down > 0 && tp_fake_finger_is_touching(tp)))) From 63f9e0f2ae915f40841fd2a9054c5f8ef2081739 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 20 Jan 2026 10:42:47 +1000 Subject: [PATCH 2/2] tap: implement support for 4-finger tap as a new gesture Unlike 1, 2, 3 finger taps which neatly map into buttons a 4-finger tap does not. Let's export that one as a separate gesture so clients can make use of it. --- src/evdev-mt-touchpad-gestures.c | 3 +- src/evdev-mt-touchpad-tap.c | 287 +++++++++++++++++++++++++++++-- src/evdev-mt-touchpad.h | 4 + src/libinput-private.h | 9 + src/libinput.c | 56 +++++- src/libinput.h | 16 ++ src/util-libinput.c | 13 ++ test/litest.c | 6 + test/test-gestures.c | 23 ++- test/test-touchpad-tap.c | 60 ++----- tools/libinput-debug-gui.c | 58 +++++++ 11 files changed, 465 insertions(+), 70 deletions(-) diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 7478f8d9..693a7230 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -688,11 +688,12 @@ tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp, tp->gesture.state = GESTURE_STATE_3FG_DRAG_START; break; case GESTURE_EVENT_HOLD_TIMEOUT: - case GESTURE_EVENT_TAP_TIMEOUT: case GESTURE_EVENT_FINGER_DETECTED: case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT: log_gesture_bug(tp, event); break; + case GESTURE_EVENT_TAP_TIMEOUT: + break; } } diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 596ba944..a3ff9534 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -72,6 +72,10 @@ tap_state_to_str(enum tp_tap_state state) CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD); CASE_RETURN_STRING(TAP_STATE_TOUCH_3_RELEASE); CASE_RETURN_STRING(TAP_STATE_TOUCH_3_RELEASE_2); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_HOLD); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_RELEASE); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_RELEASE_4); CASE_RETURN_STRING(TAP_STATE_1FGTAP_DRAGGING); CASE_RETURN_STRING(TAP_STATE_2FGTAP_DRAGGING); CASE_RETURN_STRING(TAP_STATE_3FGTAP_DRAGGING); @@ -132,22 +136,33 @@ tp_tap_notify(struct tp_dispatch *tp, assert(tp->tap.map < ARRAY_LENGTH(button_map)); - if (nfingers < 1 || nfingers > 3) + if (nfingers < 1 || nfingers > 4) return; tp_gesture_cancel(tp, time); - button = button_map[tp->tap.map][nfingers - 1]; + if (nfingers < 4) { + button = button_map[tp->tap.map][nfingers - 1]; - if (state == LIBINPUT_BUTTON_STATE_PRESSED) - tp->tap.buttons_pressed |= bit(nfingers); - else - tp->tap.buttons_pressed &= ~bit(nfingers); + if (state == LIBINPUT_BUTTON_STATE_PRESSED) + tp->tap.buttons_pressed |= bit(nfingers); + else + tp->tap.buttons_pressed &= ~bit(nfingers); - evdev_pointer_notify_button(tp->device, - time, - evdev_usage_from_uint32_t(button), - state); + evdev_pointer_notify_button(tp->device, + time, + evdev_usage_from_uint32_t(button), + state); + } else { + if (state == LIBINPUT_BUTTON_STATE_PRESSED) { + gesture_notify_tap_begin(&tp->device->base, time, nfingers); + } else { + gesture_notify_tap_end(&tp->device->base, + time, + nfingers, + false); + } + } } static void @@ -511,8 +526,9 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_DEAD; - tp_tap_clear_timer(tp); + tp->tap.state = TAP_STATE_TOUCH_4; + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); break; case TAP_EVENT_MOTION: tp_tap_move_to_dead(tp, t); @@ -549,7 +565,8 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_DEAD; + tp->tap.state = TAP_STATE_TOUCH_4; + tp->tap.saved_press_time = time; tp_tap_set_timer(tp, time); break; case TAP_EVENT_RELEASE: @@ -739,6 +756,238 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp, } } +static void +tp_tap_touch4_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, + usec_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_clear_timer(tp); + break; + case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; + case TAP_EVENT_TIMEOUT: + tp->tap.state = TAP_STATE_TOUCH_4_HOLD; + tp_tap_clear_timer(tp); + tp_gesture_tap_timeout(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_4_RELEASE; + tp->tap.saved_release_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + tp->tap.state = TAP_STATE_TOUCH_3; + break; + case TAP_EVENT_PALM_UP: + break; + } +} + +static void +tp_tap_touch4_hold_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, + usec_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.state = TAP_STATE_DEAD; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + break; + case TAP_EVENT_MOTION: + tp_tap_move_to_dead(tp, t); + break; + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + tp->tap.state = TAP_STATE_TOUCH_3; + break; + case TAP_EVENT_PALM_UP: + break; + } +} + +static void +tp_tap_touch4_release_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, + usec_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_TOUCH_4_HOLD; + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp->tap.state = TAP_STATE_TOUCH_4_RELEASE_4; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_MOTION: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp_tap_move_to_dead(tp, t); + break; + case TAP_EVENT_TIMEOUT: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + break; + case TAP_EVENT_BUTTON: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + tp->tap.state = TAP_STATE_TOUCH_3_RELEASE; + break; + case TAP_EVENT_PALM_UP: + break; + } +} + +static void +tp_tap_touch4_release4_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, + usec_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_TOUCH_3; + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_IDLE; + break; + case TAP_EVENT_MOTION: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp_tap_move_to_dead(tp, t); + break; + case TAP_EVENT_TIMEOUT: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_HOLD; + break; + case TAP_EVENT_BUTTON: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 4, + LIBINPUT_BUTTON_STATE_PRESSED); + tp_tap_notify(tp, + tp->tap.saved_release_time, + 4, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + tp_tap_notify(tp, + tp->tap.saved_press_time, + 3, + LIBINPUT_BUTTON_STATE_PRESSED); + if (tp->tap.drag_enabled) { + /* Resetting the timer to the appropriate delay + * for a three-finger tap would be ideal, but the + * timestamp of the last real finger release is lost, + * so the in-progress similar delay for release + * of the finger which became a palm instead + * will have to do */ + tp->tap.state = TAP_STATE_3FGTAP_TAPPED; + } else { + tp_tap_notify(tp, + tp->tap.saved_release_time, + 3, + LIBINPUT_BUTTON_STATE_RELEASED); + tp->tap.state = TAP_STATE_IDLE; + } + break; + case TAP_EVENT_PALM_UP: + break; + } +} + static void tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -1107,6 +1356,18 @@ tp_tap_handle_event(struct tp_dispatch *tp, case TAP_STATE_TOUCH_3_RELEASE_2: tp_tap_touch3_release2_handle_event(tp, t, event, time); break; + case TAP_STATE_TOUCH_4: + tp_tap_touch4_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_HOLD: + tp_tap_touch4_hold_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_RELEASE: + tp_tap_touch4_release_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_RELEASE_4: + tp_tap_touch4_release4_handle_event(tp, t, event, time); + break; case TAP_STATE_1FGTAP_DRAGGING_OR_DOUBLETAP: tp_tap_dragging_or_doubletap_handle_event(tp, t, event, time, 1); break; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 37c56bef..675f4464 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -116,6 +116,10 @@ enum tp_tap_state { TAP_STATE_TOUCH_3_HOLD, TAP_STATE_TOUCH_3_RELEASE, TAP_STATE_TOUCH_3_RELEASE_2, + TAP_STATE_TOUCH_4, + TAP_STATE_TOUCH_4_HOLD, + TAP_STATE_TOUCH_4_RELEASE, + TAP_STATE_TOUCH_4_RELEASE_4, TAP_STATE_1FGTAP_DRAGGING_OR_DOUBLETAP, TAP_STATE_2FGTAP_DRAGGING_OR_DOUBLETAP, TAP_STATE_3FGTAP_DRAGGING_OR_DOUBLETAP, diff --git a/src/libinput-private.h b/src/libinput-private.h index 79ab96e0..bedd5f92 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -892,6 +892,15 @@ gesture_notify_hold_end(struct libinput_device *device, int finger_count, bool cancelled); +void +gesture_notify_tap_begin(struct libinput_device *device, usec_t time, int finger_count); + +void +gesture_notify_tap_end(struct libinput_device *device, + usec_t time, + int finger_count, + bool cancelled); + void tablet_notify_axis(struct libinput_device *device, usec_t time, diff --git a/src/libinput.c b/src/libinput.c index 6abd33d0..dbcfbcca 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -118,6 +118,8 @@ event_type_to_str(enum libinput_event_type type) CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_PINCH_END); CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_HOLD_BEGIN); CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_HOLD_END); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_TAP_BEGIN); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_TAP_END); CASE_RETURN_STRING(LIBINPUT_EVENT_SWITCH_TOGGLE); case LIBINPUT_EVENT_NONE: abort(); @@ -448,7 +450,9 @@ libinput_event_get_gesture_event(struct libinput_event *event) LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, LIBINPUT_EVENT_GESTURE_PINCH_END, LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, - LIBINPUT_EVENT_GESTURE_HOLD_END); + LIBINPUT_EVENT_GESTURE_HOLD_END, + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + LIBINPUT_EVENT_GESTURE_TAP_END); return (struct libinput_event_gesture *)event; } @@ -997,7 +1001,9 @@ libinput_event_gesture_get_time(struct libinput_event_gesture *event) LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, LIBINPUT_EVENT_GESTURE_SWIPE_END, LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, - LIBINPUT_EVENT_GESTURE_HOLD_END); + LIBINPUT_EVENT_GESTURE_HOLD_END, + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + LIBINPUT_EVENT_GESTURE_TAP_END); return usec_to_millis(event->time); } @@ -1015,7 +1021,9 @@ libinput_event_gesture_get_time_usec(struct libinput_event_gesture *event) LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, LIBINPUT_EVENT_GESTURE_SWIPE_END, LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, - LIBINPUT_EVENT_GESTURE_HOLD_END); + LIBINPUT_EVENT_GESTURE_HOLD_END, + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + LIBINPUT_EVENT_GESTURE_TAP_END); return usec_as_uint64_t(event->time); } @@ -1033,7 +1041,9 @@ libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event) LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, LIBINPUT_EVENT_GESTURE_SWIPE_END, LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, - LIBINPUT_EVENT_GESTURE_HOLD_END); + LIBINPUT_EVENT_GESTURE_HOLD_END, + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + LIBINPUT_EVENT_GESTURE_TAP_END); return event->finger_count; } @@ -1046,7 +1056,8 @@ libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event) 0, LIBINPUT_EVENT_GESTURE_PINCH_END, LIBINPUT_EVENT_GESTURE_SWIPE_END, - LIBINPUT_EVENT_GESTURE_HOLD_END); + LIBINPUT_EVENT_GESTURE_HOLD_END, + LIBINPUT_EVENT_GESTURE_TAP_END); return event->cancelled; } @@ -3213,6 +3224,41 @@ gesture_notify_hold_end(struct libinput_device *device, 0.0); } +void +gesture_notify_tap_begin(struct libinput_device *device, usec_t time, int finger_count) +{ + const struct normalized_coords zero = { 0.0, 0.0 }; + + gesture_notify(device, + time, + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + finger_count, + 0, + &zero, + &zero, + 0.0, + 0.0); +} + +void +gesture_notify_tap_end(struct libinput_device *device, + usec_t time, + int finger_count, + bool cancelled) +{ + const struct normalized_coords zero = { 0.0, 0.0 }; + + gesture_notify(device, + time, + LIBINPUT_EVENT_GESTURE_TAP_END, + finger_count, + cancelled, + &zero, + &zero, + 0.0, + 0.0); +} + void switch_notify_toggle(struct libinput_device *device, usec_t time, diff --git a/src/libinput.h b/src/libinput.h index 0085d6fa..7e488fd0 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -1036,6 +1036,22 @@ enum libinput_event_type { LIBINPUT_EVENT_GESTURE_HOLD_BEGIN, LIBINPUT_EVENT_GESTURE_HOLD_END, + /** + * A tap gesture signals a number of fingers put down and released in + * the same position within a given timeout. + * + * For historical reasons, taps for one, two and three fingers are + * sent as button events, see libinput_device_config_tap_set_button_map() + * This gesture is currently only sent for a four-finger tap but may + * be sent for other taps in the future. + * + * See libinput_device_config_tap_set_enabled() for details. + * + * @since 1.31 + */ + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + LIBINPUT_EVENT_GESTURE_TAP_END, + /** * @since 1.7 */ diff --git a/src/util-libinput.c b/src/util-libinput.c index e2b5bb97..6269c9fb 100644 --- a/src/util-libinput.c +++ b/src/util-libinput.c @@ -108,6 +108,12 @@ event_type_to_str(enum libinput_event_type evtype) case LIBINPUT_EVENT_GESTURE_HOLD_END: type = "GESTURE_HOLD_END"; break; + case LIBINPUT_EVENT_GESTURE_TAP_BEGIN: + type = "GESTURE_TAP_BEGIN"; + break; + case LIBINPUT_EVENT_GESTURE_TAP_END: + type = "GESTURE_TAP_END"; + break; case LIBINPUT_EVENT_TABLET_TOOL_AXIS: type = "TABLET_TOOL_AXIS"; break; @@ -789,6 +795,7 @@ print_gesture_event_without_coords(struct libinput_event *ev, if (type == LIBINPUT_EVENT_GESTURE_SWIPE_END || type == LIBINPUT_EVENT_GESTURE_PINCH_END || + type == LIBINPUT_EVENT_GESTURE_TAP_END || type == LIBINPUT_EVENT_GESTURE_HOLD_END) cancelled = libinput_event_gesture_get_cancelled(t); @@ -1076,6 +1083,12 @@ libinput_event_to_str(struct libinput_event *ev, case LIBINPUT_EVENT_GESTURE_HOLD_END: event_str = print_gesture_event_without_coords(ev, &opts); break; + case LIBINPUT_EVENT_GESTURE_TAP_BEGIN: + event_str = print_gesture_event_without_coords(ev, &opts); + break; + case LIBINPUT_EVENT_GESTURE_TAP_END: + event_str = print_gesture_event_without_coords(ev, &opts); + break; case LIBINPUT_EVENT_TABLET_TOOL_AXIS: event_str = print_tablet_axis_event(ev, &opts); break; diff --git a/test/litest.c b/test/litest.c index d7e88ac0..0e328241 100644 --- a/test/litest.c +++ b/test/litest.c @@ -3702,6 +3702,12 @@ litest_event_type_str(enum libinput_event_type type) case LIBINPUT_EVENT_GESTURE_HOLD_END: str = "GESTURE HOLD END"; break; + case LIBINPUT_EVENT_GESTURE_TAP_BEGIN: + str = "GESTURE TAP BEGIN"; + break; + case LIBINPUT_EVENT_GESTURE_TAP_END: + str = "GESTURE TAP END"; + break; case LIBINPUT_EVENT_TABLET_TOOL_AXIS: str = "TABLET TOOL AXIS"; break; diff --git a/test/test-gestures.c b/test/test-gestures.c index e5f87cc1..8c47a063 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -1751,7 +1751,7 @@ START_TEST(gestures_3fg_drag_lock_resume_3fg_release_no_motion) bool wait_for_timeout = litest_test_param_get_bool(test_env->params, "wait"); /* tap-enabled for 4fg finger count doesn't make a difference */ - bool expect_tap = finger_count <= 3 && tap_enabled && !wait_for_timeout; + bool expect_tap = tap_enabled && !wait_for_timeout; if (litest_slot_count(dev) < 3) return LITEST_NOT_APPLICABLE; @@ -1819,12 +1819,21 @@ START_TEST(gestures_3fg_drag_lock_resume_3fg_release_no_motion) BTN_LEFT, LIBINPUT_BUTTON_STATE_RELEASED); litest_checkpoint("Expecting 3fg tap"); - litest_assert_button_event(li, - BTN_MIDDLE, - LIBINPUT_BUTTON_STATE_PRESSED); - litest_assert_button_event(li, - BTN_MIDDLE, - LIBINPUT_BUTTON_STATE_RELEASED); + if (finger_count < 4) { + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + } else { + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_TAP_BEGIN, + finger_count); + litest_assert_gesture_event(li, + LIBINPUT_EVENT_GESTURE_TAP_END, + finger_count); + } } litest_assert_empty_queue(li); diff --git a/test/test-touchpad-tap.c b/test/test-touchpad-tap.c index 3ef82b54..c4009a01 100644 --- a/test/test-touchpad-tap.c +++ b/test/test-touchpad-tap.c @@ -3225,13 +3225,17 @@ START_TEST(touchpad_4fg_tap) { struct litest_device *dev = litest_current_device(); struct libinput *li = dev->libinput; + bool with_hold = litest_test_param_get_bool(test_env->params, "with-hold"); int i; if (litest_slot_count(dev) <= 4) return LITEST_NOT_APPLICABLE; litest_enable_tap(dev->libinput_device); - litest_disable_hold_gestures(dev->libinput_device); + if (with_hold) + litest_enable_hold_gestures(dev->libinput_device); + else + litest_disable_hold_gestures(dev->libinput_device); for (i = 0; i < 4; i++) { litest_drain_events(li); @@ -3247,49 +3251,12 @@ START_TEST(touchpad_4fg_tap) litest_touch_up(dev, (i + 0) % 4); litest_dispatch(li); - litest_assert_empty_queue(li); - litest_timeout_tap(li); - litest_assert_empty_queue(li); + litest_assert_gesture_event(li, LIBINPUT_EVENT_GESTURE_TAP_BEGIN, 4); + litest_assert_gesture_event(li, LIBINPUT_EVENT_GESTURE_TAP_END, 4); } } END_TEST -START_TEST(touchpad_4fg_tap_quickrelease) -{ - struct litest_device *dev = litest_current_device(); - struct libinput *li = dev->libinput; - - if (litest_slot_count(dev) <= 4) - return LITEST_NOT_APPLICABLE; - - litest_enable_tap(dev->libinput_device); - litest_disable_hold_gestures(dev->libinput_device); - litest_drain_events(li); - - litest_touch_down(dev, 0, 50, 50); - litest_touch_down(dev, 1, 70, 50); - litest_touch_down(dev, 2, 80, 50); - litest_touch_down(dev, 3, 90, 50); - - litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); - litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); - litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); - litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); - litest_event(dev, EV_ABS, ABS_MT_SLOT, 2); - litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); - litest_event(dev, EV_ABS, ABS_MT_SLOT, 3); - litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); - litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 0); - litest_event(dev, EV_KEY, BTN_TOUCH, 0); - litest_event(dev, EV_SYN, SYN_REPORT, 0); - - litest_dispatch(li); - litest_assert_empty_queue(li); - litest_timeout_tap(li); - litest_assert_empty_queue(li); -} -END_TEST - START_TEST(touchpad_move_after_touch) { struct litest_device *dev = litest_current_device(); @@ -4825,8 +4792,7 @@ START_TEST(touchpad_tap_palm_on_touch_4) litest_drain_events(li); /* 4fg tap with one finger detected as palm, that finger is lifted, - other two fingers lifted cause nothing because we terminate - tapping as soon as 4 fingers are down */ + then put down again as normal finger -> 3fg tap */ litest_touch_down(dev, this, 50, 50); litest_touch_down(dev, (this + 1) % 4, 60, 50); litest_touch_down(dev, (this + 2) % 4, 70, 50); @@ -4840,6 +4806,11 @@ START_TEST(touchpad_tap_palm_on_touch_4) litest_touch_up(dev, (this + 2) % 4); litest_touch_up(dev, (this + 3) % 4); + litest_dispatch(li); + litest_assert_button_event(li, BTN_MIDDLE, LIBINPUT_BUTTON_STATE_PRESSED); + litest_timeout_tap(li); + litest_assert_button_event(li, BTN_MIDDLE, LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); } END_TEST @@ -5420,8 +5391,9 @@ TEST_COLLECTION(touchpad_tap) litest_add_for_device(touchpad_3fg_tap_slot_release_btntool, LITEST_SYNAPTICS_TOPBUTTONPAD); litest_add(touchpad_3fg_tap_after_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); - litest_add(touchpad_4fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); - litest_add(touchpad_4fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_with_parameters(params, "with-hold", 'b') { + litest_add_parametrized(touchpad_4fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT, params); + } litest_add(touchpad_5fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); litest_add(touchpad_5fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); diff --git a/tools/libinput-debug-gui.c b/tools/libinput-debug-gui.c index 36aa9dd6..1b4a5e42 100644 --- a/tools/libinput-debug-gui.c +++ b/tools/libinput-debug-gui.c @@ -162,6 +162,11 @@ struct window { bool active; } hold; + struct { + int nfingers; + bool active; + } tap; + struct { double x, y; double x_in, y_in; @@ -607,6 +612,20 @@ draw_gestures(struct window *w, cairo_t *cr) cairo_stroke(cr); } + /* 4fg tap uses the same rings but blue */ + for (int i = 4; w->tap.active && i > 0; i--) { /* 4 fg max */ + double r, g, b, hold_alpha; + + r = .2; + g = .2; + b = .4 + .2 * (i % 2); + hold_alpha = i <= w->hold.nfingers ? 1 : .5; + + cairo_set_source_rgba(cr, r, g, b, hold_alpha); + cairo_arc(cr, 0, 0, 20 * i, 0, 2 * M_PI); + cairo_fill(cr); + } + cairo_restore(cr); } @@ -1671,6 +1690,40 @@ handle_event_hold(struct libinput_event *ev, struct window *w) } } +static gboolean +undo_tap(gpointer data) +{ + struct window *w = data; + + w->tap.active = false; + gtk_widget_queue_draw(w->area); + + return G_SOURCE_REMOVE; +} + +static void +handle_event_tap(struct libinput_event *ev, struct window *w) +{ + struct libinput_event_gesture *g = libinput_event_get_gesture_event(ev); + int nfingers; + + nfingers = libinput_event_gesture_get_finger_count(g); + + switch (libinput_event_get_type(ev)) { + case LIBINPUT_EVENT_GESTURE_TAP_BEGIN: + w->tap.nfingers = nfingers; + w->tap.active = true; + g_timeout_add(75, undo_tap, w); + break; + case LIBINPUT_EVENT_GESTURE_TAP_END: + /* active is handled by a timer because we get begin/end + * at effectively the same time */ + break; + default: + abort(); + } +} + static void handle_event_tablet(struct libinput_event *ev, struct window *w) { @@ -1811,6 +1864,7 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data) tools_dispatch(li); while ((ev = libinput_get_event(li))) { + switch (libinput_event_get_type(ev)) { case LIBINPUT_EVENT_NONE: abort(); @@ -1864,6 +1918,10 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data) case LIBINPUT_EVENT_GESTURE_HOLD_END: handle_event_hold(ev, w); break; + case LIBINPUT_EVENT_GESTURE_TAP_BEGIN: + case LIBINPUT_EVENT_GESTURE_TAP_END: + handle_event_tap(ev, w); + break; case LIBINPUT_EVENT_TABLET_TOOL_AXIS: case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: case LIBINPUT_EVENT_TABLET_TOOL_TIP: