touchpad: disable tapping for fingers exceeding the timeout/motion threshold

The current code triggers multi-finger tapping even if the finger released was
previously held on the touchpad for a while. For an event sequence of:
1. first finger down
2. first finger move past threshold/wait past timeout
3. second finger down
4. first finger up

The second finger initiates the two-finger tap state, but the button event is
sent when the first finger releases - despite that finger not meeting the
usual tap constraints. This sequence can happen whenever a user swaps fingers.

Add the finger state to the actual touchpoints and update them whenever the
constrains are broken. Then, discard button events if the respective touch
did not meet the conditions.

http://bugs.freedesktop.org/76760

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
Peter Hutterer 2014-06-20 14:16:13 +10:00
parent 14ad471ff5
commit 8fa5d0bf51
4 changed files with 957 additions and 823 deletions

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View file

@ -95,12 +95,16 @@ tap_event_to_str(enum tap_event event) {
static void
tp_tap_notify(struct tp_dispatch *tp,
struct tp_touch *t,
uint64_t time,
int nfingers,
enum libinput_button_state state)
{
int32_t button;
if (t && t->tap.state == TAP_TOUCH_STATE_DEAD)
return;
switch (nfingers) {
case 1: button = BTN_LEFT; break;
case 2: button = BTN_RIGHT; break;
@ -128,7 +132,9 @@ tp_tap_clear_timer(struct tp_dispatch *tp)
}
static void
tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_idle_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
struct libinput *libinput = tp->device->base.seat->libinput;
@ -151,7 +157,9 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t
}
static void
tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_touch_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -161,7 +169,7 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TAPPED;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_TIMEOUT:
@ -176,7 +184,9 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t
}
static void
tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_hold_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -197,7 +207,9 @@ tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t
}
static void
tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_tapped_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
struct libinput *libinput = tp->device->base.seat->libinput;
@ -213,17 +225,19 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_
break;
case TAP_EVENT_TIMEOUT:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
}
static void
tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_touch2_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -233,8 +247,8 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_HOLD;
tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 2, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, t, time, 2, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_MOTION:
@ -249,7 +263,9 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_
}
static void
tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -271,7 +287,9 @@ tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, ui
}
static void
tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_touch3_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -286,8 +304,8 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
tp_tap_notify(tp, time, 3, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, time, 3, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 3, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, t, time, 3, LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
@ -296,7 +314,9 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_
}
static void
tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -317,7 +337,9 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, ui
}
static void
tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
case TAP_EVENT_TOUCH:
@ -325,9 +347,9 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_MOTION:
@ -336,13 +358,15 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
}
static void
tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_dragging_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -359,13 +383,15 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint6
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
}
static void
tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -378,17 +404,19 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event,
break;
case TAP_EVENT_TIMEOUT:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
}
static void
tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_dragging2_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
@ -397,7 +425,7 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint
break;
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_MOTION:
case TAP_EVENT_TIMEOUT:
@ -405,13 +433,16 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_notify(tp, t, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
}
static void
tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_dead_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event,
uint64_t time)
{
switch (event) {
@ -428,7 +459,10 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t
}
static void
tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
tp_tap_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event,
uint64_t time)
{
struct libinput *libinput = tp->device->base.seat->libinput;
enum tp_tap_state current;
@ -440,43 +474,43 @@ tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time)
switch(tp->tap.state) {
case TAP_STATE_IDLE:
tp_tap_idle_handle_event(tp, event, time);
tp_tap_idle_handle_event(tp, t, event, time);
break;
case TAP_STATE_TOUCH:
tp_tap_touch_handle_event(tp, event, time);
tp_tap_touch_handle_event(tp, t, event, time);
break;
case TAP_STATE_HOLD:
tp_tap_hold_handle_event(tp, event, time);
tp_tap_hold_handle_event(tp, t, event, time);
break;
case TAP_STATE_TAPPED:
tp_tap_tapped_handle_event(tp, event, time);
tp_tap_tapped_handle_event(tp, t, event, time);
break;
case TAP_STATE_TOUCH_2:
tp_tap_touch2_handle_event(tp, event, time);
tp_tap_touch2_handle_event(tp, t, event, time);
break;
case TAP_STATE_TOUCH_2_HOLD:
tp_tap_touch2_hold_handle_event(tp, event, time);
tp_tap_touch2_hold_handle_event(tp, t, event, time);
break;
case TAP_STATE_TOUCH_3:
tp_tap_touch3_handle_event(tp, event, time);
tp_tap_touch3_handle_event(tp, t, event, time);
break;
case TAP_STATE_TOUCH_3_HOLD:
tp_tap_touch3_hold_handle_event(tp, event, time);
tp_tap_touch3_hold_handle_event(tp, t, event, time);
break;
case TAP_STATE_DRAGGING_OR_DOUBLETAP:
tp_tap_dragging_or_doubletap_handle_event(tp, event, time);
tp_tap_dragging_or_doubletap_handle_event(tp, t, event, time);
break;
case TAP_STATE_DRAGGING:
tp_tap_dragging_handle_event(tp, event, time);
tp_tap_dragging_handle_event(tp, t, event, time);
break;
case TAP_STATE_DRAGGING_WAIT:
tp_tap_dragging_wait_handle_event(tp, event, time);
tp_tap_dragging_wait_handle_event(tp, t, event, time);
break;
case TAP_STATE_DRAGGING_2:
tp_tap_dragging2_handle_event(tp, event, time);
tp_tap_dragging2_handle_event(tp, t, event, time);
break;
case TAP_STATE_DEAD:
tp_tap_dead_handle_event(tp, event, time);
tp_tap_dead_handle_event(tp, t, event, time);
break;
}
@ -508,19 +542,34 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
int filter_motion = 0;
if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
tp_tap_handle_event(tp, TAP_EVENT_BUTTON, time);
tp_tap_handle_event(tp, NULL, TAP_EVENT_BUTTON, time);
tp_for_each_touch(tp, t) {
if (!t->dirty || t->state == TOUCH_NONE)
continue;
if (t->state == TOUCH_BEGIN)
tp_tap_handle_event(tp, TAP_EVENT_TOUCH, time);
else if (t->state == TOUCH_END)
tp_tap_handle_event(tp, TAP_EVENT_RELEASE, time);
else if (tp->tap.state != TAP_STATE_IDLE &&
tp_tap_exceeds_motion_threshold(tp, t))
tp_tap_handle_event(tp, TAP_EVENT_MOTION, time);
if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
t->tap.state = TAP_TOUCH_STATE_DEAD;
if (t->state == TOUCH_BEGIN) {
t->tap.state = TAP_TOUCH_STATE_TOUCH;
tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time);
} else if (t->state == TOUCH_END) {
tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time);
t->tap.state = TAP_TOUCH_STATE_DEAD;
} else if (tp->tap.state != TAP_STATE_IDLE &&
tp_tap_exceeds_motion_threshold(tp, t)) {
struct tp_touch *tmp;
/* Any touch exceeding the threshold turns all
* touches into DEAD */
tp_for_each_touch(tp, tmp) {
if (tmp->tap.state == TAP_TOUCH_STATE_TOUCH)
tmp->tap.state = TAP_TOUCH_STATE_DEAD;
}
tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time);
}
}
/**
@ -550,8 +599,17 @@ static void
tp_tap_handle_timeout(uint64_t time, void *data)
{
struct tp_dispatch *tp = data;
struct tp_touch *t;
tp_tap_handle_event(tp, TAP_EVENT_TIMEOUT, time);
tp_tap_handle_event(tp, NULL, TAP_EVENT_TIMEOUT, time);
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE ||
t->tap.state == TAP_TOUCH_STATE_IDLE)
continue;
t->tap.state = TAP_TOUCH_STATE_DEAD;
}
}
int

View file

@ -88,6 +88,12 @@ enum tp_tap_state {
TAP_STATE_DEAD, /**< finger count exceeded */
};
enum tp_tap_touch_state {
TAP_TOUCH_STATE_IDLE = 16, /**< not in touch */
TAP_TOUCH_STATE_TOUCH, /**< touching, may tap */
TAP_TOUCH_STATE_DEAD, /**< exceeded motion/timeout */
};
struct tp_motion {
int32_t x;
int32_t y;
@ -131,6 +137,10 @@ struct tp_touch {
enum button_event curr;
struct libinput_timer timer;
} button;
struct {
enum tp_tap_touch_state state;
} tap;
};
struct tp_dispatch {

View file

@ -320,6 +320,82 @@ START_TEST(touchpad_2fg_tap_click_apple)
}
END_TEST
START_TEST(touchpad_no_2fg_tap_after_move)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_drain_events(dev->libinput);
/* one finger down, move past threshold,
second finger down, first finger up
-> no event
*/
litest_touch_down(dev, 0, 50, 50);
litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10);
litest_drain_events(dev->libinput);
litest_touch_down(dev, 1, 70, 50);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_no_2fg_tap_after_timeout)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_drain_events(dev->libinput);
/* one finger down, wait past tap timeout,
second finger down, first finger up
-> no event
*/
litest_touch_down(dev, 0, 50, 50);
libinput_dispatch(dev->libinput);
msleep(300);
libinput_dispatch(dev->libinput);
litest_drain_events(dev->libinput);
litest_touch_down(dev, 1, 70, 50);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_no_first_fg_tap_after_move)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct libinput_event *event;
litest_drain_events(dev->libinput);
/* one finger down, second finger down,
second finger moves beyond threshold,
first finger up
-> no event
*/
litest_touch_down(dev, 0, 50, 50);
litest_touch_down(dev, 1, 70, 50);
libinput_dispatch(dev->libinput);
litest_touch_move_to(dev, 1, 70, 50, 90, 90, 10);
libinput_dispatch(dev->libinput);
litest_touch_up(dev, 0);
litest_touch_up(dev, 1);
libinput_dispatch(dev->libinput);
while ((event = libinput_get_event(li))) {
ck_assert_int_ne(libinput_event_get_type(event),
LIBINPUT_EVENT_POINTER_BUTTON);
libinput_event_destroy(event);
}
}
END_TEST
START_TEST(touchpad_1fg_double_tap_click)
{
struct litest_device *dev = litest_current_device();
@ -1127,6 +1203,11 @@ int main(int argc, char **argv) {
litest_add("touchpad:tap", touchpad_1fg_tap_click, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("touchpad:tap", touchpad_2fg_tap_click, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
litest_add("touchpad:tap", touchpad_2fg_tap_click_apple, LITEST_APPLE_CLICKPAD, LITEST_ANY);
litest_add("touchpad:tap", touchpad_no_2fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:tap", touchpad_no_2fg_tap_after_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:tap", touchpad_no_first_fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:tap", touchpad_no_first_fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
/* Real buttons don't interfere with tapping, so don't run those for
pads with buttons */
litest_add("touchpad:tap", touchpad_1fg_double_tap_click, LITEST_CLICKPAD, LITEST_ANY);