touchpad: work palm detection into the tap state machine

Unlike the already-existing thumb detection, a touch may be labelled palm at
any time, not just during the initial touch down. This requires full
integration into the tap state machine to unwind properly. For most states, a
palm detection simply ignores the finger and reverts to the most recent state.

One exception is the case of two fingers down, one finger up followed by the
remaining finger detected as a palm finger. This triggers a single-finger tap
but with timestamps that may be from the wrong finger. Since we're within a
short tap timeout anyway this should not matter too much.

The special state PALM_UP is only handled in one condition (DEAD). Once a
touch is a palm we basically skip over it from then on. If we end up in the
DEAD state after a button press we still need to handle the palm up events
accordingly to be able to return to IDLE. That transition also requires us to
have an accurate count of the real fingers down (palms don't count) so we need
a separate nfingers_down counter for tapping.

https://bugs.freedesktop.org/show_bug.cgi?id=103210

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2017-11-03 10:59:36 +10:00
parent f35bb9760e
commit 46eab97538
5 changed files with 2056 additions and 969 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View file

@ -44,6 +44,8 @@ enum tap_event {
TAP_EVENT_BUTTON,
TAP_EVENT_TIMEOUT,
TAP_EVENT_THUMB,
TAP_EVENT_PALM,
TAP_EVENT_PALM_UP,
};
/*****************************************
@ -77,6 +79,7 @@ tap_state_to_str(enum tp_tap_state state)
CASE_RETURN_STRING(TAP_STATE_DRAGGING_2);
CASE_RETURN_STRING(TAP_STATE_MULTITAP);
CASE_RETURN_STRING(TAP_STATE_MULTITAP_DOWN);
CASE_RETURN_STRING(TAP_STATE_MULTITAP_PALM);
CASE_RETURN_STRING(TAP_STATE_DEAD);
}
return NULL;
@ -92,6 +95,8 @@ tap_event_to_str(enum tap_event event)
CASE_RETURN_STRING(TAP_EVENT_TIMEOUT);
CASE_RETURN_STRING(TAP_EVENT_BUTTON);
CASE_RETURN_STRING(TAP_EVENT_THUMB);
CASE_RETURN_STRING(TAP_EVENT_PALM);
CASE_RETURN_STRING(TAP_EVENT_PALM_UP);
}
return NULL;
}
@ -178,6 +183,12 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
log_tap_bug(tp, event);
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
t->tap.state = TAP_TOUCH_STATE_DEAD;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -221,9 +232,17 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
tp->tap.state = TAP_STATE_IDLE;
t->tap.is_thumb = true;
tp->tap.nfingers_down--;
t->tap.state = TAP_TOUCH_STATE_DEAD;
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
t->tap.state = TAP_TOUCH_STATE_DEAD;
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -251,8 +270,14 @@ tp_tap_hold_handle_event(struct tp_dispatch *tp,
case TAP_EVENT_THUMB:
tp->tap.state = TAP_STATE_IDLE;
t->tap.is_thumb = true;
tp->tap.nfingers_down--;
t->tap.state = TAP_TOUCH_STATE_DEAD;
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -286,6 +311,10 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp,
LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_THUMB:
case TAP_EVENT_PALM:
log_tap_bug(tp, event);
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -318,6 +347,12 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH;
tp_tap_set_timer(tp, time); /* overwrite timer */
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -345,6 +380,11 @@ tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_HOLD;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -380,6 +420,31 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
/* There's only one saved press time and it's overwritten by
* the last touch down. So in the case of finger down, palm
* down, finger up, palm detected, we use the
* palm touch's press time here instead of the finger's press
* time. Let's wait and see if that's an issue.
*/
tp_tap_notify(tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
if (tp->tap.drag_enabled) {
tp->tap.state = TAP_STATE_TAPPED;
tp->tap.saved_release_time = time;
tp_tap_set_timer(tp, time);
} else {
tp_tap_notify(tp,
time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.state = TAP_STATE_IDLE;
}
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -414,6 +479,11 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH_2;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -439,6 +509,11 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -472,6 +547,11 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TAPPED;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -507,6 +587,15 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -533,6 +622,9 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp,
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_THUMB:
case TAP_EVENT_PALM:
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -562,6 +654,15 @@ tp_tap_dragging_tap_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -589,6 +690,11 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -629,6 +735,9 @@ tp_tap_multitap_handle_event(struct tp_dispatch *tp,
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_THUMB:
case TAP_EVENT_PALM:
break;
case TAP_EVENT_PALM_UP:
break;
}
}
@ -667,6 +776,42 @@ tp_tap_multitap_down_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_MULTITAP_PALM;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
static void
tp_tap_multitap_palm_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event,
uint64_t time)
{
switch (event) {
case TAP_EVENT_RELEASE:
/* This is the palm finger */
break;
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_MULTITAP_DOWN;
break;
case TAP_EVENT_MOTION:
break;
case TAP_EVENT_TIMEOUT:
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_clear_timer(tp);
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_THUMB:
case TAP_EVENT_PALM:
case TAP_EVENT_PALM_UP:
break;
}
}
@ -679,7 +824,7 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
switch (event) {
case TAP_EVENT_RELEASE:
if (tp->nfingers_down == 0)
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_TOUCH:
@ -689,6 +834,11 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
case TAP_EVENT_PALM_UP:
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
break;
}
}
@ -751,6 +901,9 @@ tp_tap_handle_event(struct tp_dispatch *tp,
case TAP_STATE_MULTITAP_DOWN:
tp_tap_multitap_down_handle_event(tp, t, event, time);
break;
case TAP_STATE_MULTITAP_PALM:
tp_tap_multitap_palm_handle_event(tp, t, event, time);
break;
case TAP_STATE_DEAD:
tp_tap_dead_handle_event(tp, t, event, time);
break;
@ -777,6 +930,8 @@ tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp,
* touchpads are likely to give us pointer jumps.
* This triggers the movement threshold, making three-finger taps
* less reliable (#101435)
*
* This uses the real nfingers_down, not the one for taps.
*/
if (tp->device->model_flags & EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD &&
(tp->nfingers_down > 2 || tp->old_nfingers_down > 2) &&
@ -822,10 +977,32 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
if (t->tap.is_thumb)
continue;
/* A palm tap needs to be properly relased because we might
* be who-knows-where in the state machine. Otherwise, we
* ignore any event from it.
*/
if (t->tap.is_palm) {
if (t->state == TOUCH_END)
tp_tap_handle_event(tp,
t,
TAP_EVENT_PALM_UP,
time);
continue;
}
if (t->state == TOUCH_HOVERING)
continue;
if (t->state == TOUCH_BEGIN) {
if (t->palm.state != PALM_NONE) {
assert(!t->tap.is_palm);
tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time);
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
if (t->state != TOUCH_BEGIN) {
assert(tp->tap.nfingers_down > 0);
tp->tap.nfingers_down--;
}
} else if (t->state == TOUCH_BEGIN) {
/* The simple version: if a touch is a thumb on
* begin we ignore it. All other thumb touches
* follow the normal tap state for now */
@ -836,6 +1013,7 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
t->tap.state = TAP_TOUCH_STATE_TOUCH;
t->tap.initial = t->point;
tp->tap.nfingers_down++;
tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time);
/* If we think this is a palm, pretend there's a
@ -846,8 +1024,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time);
} else if (t->state == TOUCH_END) {
if (t->was_down)
if (t->was_down) {
tp->tap.nfingers_down--;
tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time);
}
t->tap.state = TAP_TOUCH_STATE_IDLE;
} else if (tp->tap.state != TAP_STATE_IDLE &&
tp_tap_exceeds_motion_threshold(tp, t)) {
@ -890,6 +1070,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
}
assert(tp->tap.nfingers_down <= tp->nfingers_down);
if (tp->nfingers_down == 0)
assert(tp->tap.nfingers_down == 0);
return filter_motion;
}
@ -938,9 +1122,19 @@ tp_tap_enabled_update(struct tp_dispatch *tp, bool suspended, bool enabled, uint
return;
if (tp_tap_enabled(tp)) {
/* Must restart in DEAD if fingers are down atm */
tp->tap.state =
tp->nfingers_down ? TAP_STATE_DEAD : TAP_STATE_IDLE;
struct tp_touch *t;
/* On resume, all touches are considered palms */
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE)
continue;
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
}
tp->tap.state = TAP_STATE_IDLE;
tp->tap.nfingers_down = 0;
} else {
tp_release_all_taps(tp, time);
}
@ -1154,6 +1348,7 @@ tp_remove_tap(struct tp_dispatch *tp)
void
tp_release_all_taps(struct tp_dispatch *tp, uint64_t now)
{
struct tp_touch *t;
int i;
for (i = 1; i <= 3; i++) {
@ -1161,7 +1356,20 @@ tp_release_all_taps(struct tp_dispatch *tp, uint64_t now)
tp_tap_notify(tp, now, i, LIBINPUT_BUTTON_STATE_RELEASED);
}
tp->tap.state = tp->nfingers_down ? TAP_STATE_DEAD : TAP_STATE_IDLE;
/* To neutralize all current touches, we make them all palms */
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE)
continue;
if (t->tap.is_palm)
continue;
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
}
tp->tap.state = TAP_STATE_IDLE;
tp->tap.nfingers_down = 0;
}
void

View file

@ -290,6 +290,7 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
t->thumb.state = THUMB_STATE_MAYBE;
t->thumb.first_touch_time = time;
t->tap.is_thumb = false;
t->tap.is_palm = false;
assert(tp->nfingers_down >= 1);
tp->hysteresis.last_motion_time = time;
}

View file

@ -103,6 +103,7 @@ enum tp_tap_state {
TAP_STATE_DRAGGING_2,
TAP_STATE_MULTITAP,
TAP_STATE_MULTITAP_DOWN,
TAP_STATE_MULTITAP_PALM,
TAP_STATE_DEAD, /**< finger count exceeded */
};
@ -195,6 +196,7 @@ struct tp_touch {
enum tp_tap_touch_state state;
struct device_coords initial;
bool is_thumb;
bool is_palm;
} tap;
struct {
@ -356,6 +358,8 @@ struct tp_dispatch {
bool drag_enabled;
bool drag_lock_enabled;
unsigned int nfingers_down; /* number of fingers down for tapping (excl. thumb/palm) */
} tap;
struct {

View file

@ -2375,12 +2375,870 @@ START_TEST(touchpad_drag_lock_default_unavailable)
}
END_TEST
static inline bool
touchpad_has_palm_pressure(struct litest_device *dev)
{
struct libevdev *evdev = dev->evdev;
if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_PRESSURE))
return true;
return false;
}
START_TEST(touchpad_tap_palm_on_idle)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* Finger down is immediately palm */
litest_touch_down_extended(dev, 0, 50, 50, axes);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* Finger down is palm after touch begin */
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_hold_timeout)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* Finger down is palm after tap timeout */
litest_touch_down(dev, 0, 50, 50);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_hold_move)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* Finger down is palm after tap move threshold */
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to(dev, 0, 50, 50, 60, 60, 10, 1);
litest_drain_events(li);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_tapped)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* tap + palm down */
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_tapped_2fg)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* tap + palm down + tap with second finger */
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
litest_touch_down(dev, 1, 50, 50);
litest_touch_up(dev, 1);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_drag)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* tap + finger down (->drag), finger turns into palm */
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_touch_down(dev, 0, 50, 50);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_drag_2fg)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int which = _i; /* ranged test */
int this = which % 2,
other = (which + 1) % 2;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* tap + finger down, 2nd finger down, finger turns to palm */
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_touch_down(dev, this, 50, 50);
litest_touch_down(dev, other, 60, 50);
libinput_dispatch(li);
litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
litest_touch_move_to(dev, other, 60, 50, 65, 50, 10, 1);
litest_assert_only_typed_events(li,
LIBINPUT_EVENT_POINTER_MOTION);
litest_touch_up(dev, other);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_touch_up(dev, this);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_2)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int which = _i; /* ranged test */
int this = which % 2,
other = (which + 1) % 2;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* 2fg tap with one finger detected as palm */
litest_touch_down(dev, 0, 50, 50);
litest_touch_down(dev, 1, 60, 60);
litest_drain_events(li);
litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, this);
litest_touch_up(dev, other);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_timeout_tap();
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_2_retouch)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int which = _i; /* ranged test */
int this = which % 2,
other = (which + 1) % 2;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* 2fg tap with one finger detected as palm, that finger is lifted
* and taps again as not-palm */
litest_touch_down(dev, this, 50, 50);
litest_touch_down(dev, other, 60, 60);
litest_drain_events(li);
litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, this);
libinput_dispatch(li);
litest_touch_down(dev, this, 70, 70);
litest_touch_up(dev, this);
litest_touch_up(dev, other);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_timeout_tap();
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_3)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int which = _i; /* ranged test */
int this = which % 3;
if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 3)
return;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* 3fg tap with one finger detected as palm, that finger is lifted,
other two fingers lifted cause 2fg tap */
litest_touch_down(dev, this, 50, 50);
litest_touch_down(dev, (this + 1) % 3, 60, 50);
litest_touch_down(dev, (this + 2) % 3, 70, 50);
litest_drain_events(li);
litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, this);
libinput_dispatch(li);
litest_touch_up(dev, (this + 1) % 3);
litest_touch_up(dev, (this + 2) % 3);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_timeout_tap();
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_3_retouch)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int which = _i; /* ranged test */
int this = which % 3;
if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 3)
return;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* 3fg tap with one finger detected as palm, that finger is lifted,
then put down again as normal finger -> 3fg tap */
litest_touch_down(dev, this, 50, 50);
litest_touch_down(dev, (this + 1) % 3, 60, 50);
litest_touch_down(dev, (this + 2) % 3, 70, 50);
litest_drain_events(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, this);
libinput_dispatch(li);
litest_touch_down(dev, this, 50, 50);
litest_touch_up(dev, this);
litest_touch_up(dev, (this + 1) % 3);
litest_touch_up(dev, (this + 2) % 3);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_timeout_tap();
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_on_touch_4)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int which = _i; /* ranged test */
int this = which % 4;
if (libevdev_get_abs_maximum(dev->evdev, ABS_MT_SLOT) <= 4)
return;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* 3fg tap with one finger detected as palm, that finger is lifted,
other two fingers lifted cause 2fg 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);
litest_touch_down(dev, (this + 3) % 4, 80, 50);
litest_drain_events(li);
litest_touch_move_to_extended(dev, this, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, this);
libinput_dispatch(li);
litest_touch_up(dev, (this + 1) % 4);
litest_touch_up(dev, (this + 2) % 4);
litest_touch_up(dev, (this + 3) % 4);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_after_tap)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_timeout_tap();
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_multitap)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int range = _i, /* ranged test */
ntaps;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
msleep(10);
}
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
}
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_multitap_timeout)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int range = _i, /* ranged test */
ntaps;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
msleep(10);
}
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
}
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_multitap_down_again)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int range = _i, /* ranged test */
ntaps;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
msleep(10);
}
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
/* keep palm finger down */
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_touch_down(dev, 1, 50, 50);
litest_touch_up(dev, 1);
libinput_dispatch(li);
msleep(10);
}
for (ntaps = 0; ntaps <= 2 * range; ntaps++) {
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
}
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_multitap_click)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
int range = _i, /* ranged test */
ntaps;
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
msleep(10);
}
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
/* keep palm finger down */
litest_button_click(dev, BTN_LEFT, true);
litest_button_click(dev, BTN_LEFT, false);
libinput_dispatch(li);
for (ntaps = 0; ntaps <= range; ntaps++) {
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
}
/* the click */
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_click_then_tap)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
litest_touch_down_extended(dev, 0, 50, 50, axes);
libinput_dispatch(li);
litest_button_click(dev, BTN_LEFT, true);
litest_button_click(dev, BTN_LEFT, false);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
litest_touch_down(dev, 0, 50, 50);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_tap_palm_dwt_tap)
{
struct litest_device *dev = litest_current_device();
struct litest_device *keyboard;
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 75 },
{ -1, 0 }
};
if (!touchpad_has_palm_pressure(dev))
return;
keyboard = litest_add_device(li, LITEST_KEYBOARD);
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
litest_keyboard_key(keyboard, KEY_A, true);
litest_keyboard_key(keyboard, KEY_B, true);
litest_keyboard_key(keyboard, KEY_A, false);
litest_drain_events(li);
litest_touch_down(dev, 0, 50, 50);
libinput_dispatch(li);
litest_keyboard_key(keyboard, KEY_B, false);
litest_drain_events(li);
litest_timeout_dwt_long();
libinput_dispatch(li);
/* Changes to palm after dwt timeout */
litest_touch_move_to_extended(dev, 0, 50, 50, 50, 50, axes, 1, 1);
libinput_dispatch(li);
litest_touch_up(dev, 0);
libinput_dispatch(li);
litest_assert_empty_queue(li);
}
END_TEST
void
litest_setup_tests_touchpad_tap(void)
{
struct range multitap_range = {3, 5};
struct range tap_map_range = { LIBINPUT_CONFIG_TAP_MAP_LRM,
LIBINPUT_CONFIG_TAP_MAP_LMR + 1 };
struct range range_2fg = {0, 2};
struct range range_3fg = {0, 3};
struct range range_4fg = {0, 4};
litest_add("tap-1fg:1fg", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap-1fg:1fg", touchpad_1fg_doubletap, LITEST_TOUCHPAD, LITEST_ANY);
@ -2455,4 +3313,25 @@ litest_setup_tests_touchpad_tap(void)
litest_add("tap:drag", touchpad_drag_disabled, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:drag", touchpad_drag_disabled_immediate, LITEST_TOUCHPAD, LITEST_ANY);
litest_add_ranged("tap-multitap:drag", touchpad_drag_disabled_multitap_no_drag, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
litest_add("tap:palm", touchpad_tap_palm_on_idle, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:palm", touchpad_tap_palm_on_touch, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:palm", touchpad_tap_palm_on_touch_hold_timeout, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:palm", touchpad_tap_palm_on_touch_hold_move, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:palm", touchpad_tap_palm_on_tapped, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:palm", touchpad_tap_palm_on_tapped_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("tap:palm", touchpad_tap_palm_on_drag, LITEST_TOUCHPAD, LITEST_ANY);
litest_add_ranged("tap:palm", touchpad_tap_palm_on_drag_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_2fg);
litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_2, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_2fg);
litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_2_retouch, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_2fg);
litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_3, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_3fg);
litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_3_retouch, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_3fg);
litest_add_ranged("tap:palm", touchpad_tap_palm_on_touch_4, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &range_4fg);
litest_add("tap:palm", touchpad_tap_palm_after_tap, LITEST_TOUCHPAD, LITEST_ANY);
litest_add_ranged("tap:palm", touchpad_tap_palm_multitap, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_timeout, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_down_again, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &multitap_range);
litest_add_ranged("tap:palm", touchpad_tap_palm_multitap_click, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
litest_add("tap:palm", touchpad_tap_palm_click_then_tap, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("tap:palm", touchpad_tap_palm_dwt_tap, LITEST_TOUCHPAD, LITEST_ANY);
}