Merge branch 'wip/4fg-tap' into 'main'

Draft: tap: implement support for 4-finger tap as a new gesture

See merge request libinput/libinput!1417
This commit is contained in:
Peter Hutterer 2026-02-03 10:30:49 +10:00
commit 5d3741ce11
12 changed files with 475 additions and 97 deletions

View file

@ -692,12 +692,13 @@ tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp,
tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
break;
case GESTURE_EVENT_HOLD_TIMEOUT:
case GESTURE_EVENT_TAP_TIMEOUT:
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
log_gesture_bug(tp, event);
break;
case GESTURE_EVENT_TAP_TIMEOUT:
break;
}
}

View file

@ -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;

View file

@ -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 */
#define MOUSE_HAS_SENT_EVENTS bit(1)
@ -261,7 +260,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 */
@ -270,9 +269,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);
}
@ -290,33 +286,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);
}
@ -629,9 +621,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);
@ -1144,9 +1133,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);
@ -1270,9 +1256,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))))

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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
*/

View file

@ -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;

View file

@ -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;

View file

@ -1772,7 +1772,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;
@ -1845,12 +1845,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);

View file

@ -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);

View file

@ -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: