From 0cfe8b08084416b722e536bb83bfb65322967924 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 30 Aug 2017 14:00:09 +1000 Subject: [PATCH 1/3] touchpad: ignore thumbs during edge scrolling Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad-edge-scroll.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c index 4704c68e..cab9a87d 100644 --- a/src/evdev-mt-touchpad-edge-scroll.c +++ b/src/evdev-mt-touchpad-edge-scroll.c @@ -384,7 +384,8 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time) if (!t->dirty) continue; - if (t->palm.state != PALM_NONE) + if (t->palm.state != PALM_NONE || + t->thumb.state == THUMB_STATE_YES) continue; /* only scroll with the finger in the previous edge */ From 10569680d98a5109c2c4fb1b20d6470cdff36393 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 21 Jun 2017 11:16:27 +1000 Subject: [PATCH 2/3] touchpad: store the time in the motion history Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 20 +++++++++++--------- src/evdev-mt-touchpad.h | 5 ++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 80e74efd..fed25824 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -36,7 +36,7 @@ #define THUMB_MOVE_TIMEOUT ms2us(300) #define FAKE_FINGER_OVERFLOW (1 << 7) -static inline struct device_coords * +static inline struct tp_history_point* tp_motion_history_offset(struct tp_touch *t, int offset) { int offset_index = @@ -90,7 +90,8 @@ tp_motion_history_push(struct tp_touch *t) if (t->history.count < TOUCHPAD_HISTORY_LENGTH) t->history.count++; - t->history.samples[motion_index] = t->point; + t->history.samples[motion_index].point = t->point; + t->history.samples[motion_index].time = t->time; t->history.index = motion_index; } @@ -293,10 +294,10 @@ tp_get_delta(struct tp_touch *t) if (t->history.count <= 1) return zero; - delta.x = tp_motion_history_offset(t, 0)->x - - tp_motion_history_offset(t, 1)->x; - delta.y = tp_motion_history_offset(t, 0)->y - - tp_motion_history_offset(t, 1)->y; + delta.x = tp_motion_history_offset(t, 0)->point.x - + tp_motion_history_offset(t, 1)->point.x; + delta.y = tp_motion_history_offset(t, 0)->point.y - + tp_motion_history_offset(t, 1)->point.y; return tp_normalize_delta(t->tp, delta); } @@ -1252,9 +1253,10 @@ tp_need_motion_history_reset(struct tp_dispatch *tp) static bool tp_detect_jumps(const struct tp_dispatch *tp, struct tp_touch *t) { - struct device_coords *last, delta; + struct device_coords delta; struct phys_coords mm; const int JUMP_THRESHOLD_MM = 20; + struct tp_history_point *last; /* We haven't seen pointer jumps on Wacom tablets yet, so exclude * those. @@ -1268,8 +1270,8 @@ tp_detect_jumps(const struct tp_dispatch *tp, struct tp_touch *t) /* called before tp_motion_history_push, so offset 0 is the most * recent coordinate */ last = tp_motion_history_offset(t, 0); - delta.x = abs(t->point.x - last->x); - delta.y = abs(t->point.y - last->y); + delta.x = abs(t->point.x - last->point.x); + delta.y = abs(t->point.y - last->point.y); mm = evdev_device_unit_delta_to_mm(tp->device, &delta); return hypot(mm.x, mm.y) > JUMP_THRESHOLD_MM; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 664514de..3f0d821c 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -164,7 +164,10 @@ struct tp_touch { } quirks; struct { - struct device_coords samples[TOUCHPAD_HISTORY_LENGTH]; + struct tp_history_point { + uint64_t time; + struct device_coords point; + } samples[TOUCHPAD_HISTORY_LENGTH]; unsigned int index; unsigned int count; } history; From de5246dae0201ea951715f62da806f90321978c5 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 21 Jun 2017 12:29:52 +1000 Subject: [PATCH 3/3] touchpad: use motion speed to ignore accidental 2fg touches Calculate the speed of the touch and compare it against a fixed speed limit. If a touch exceeds the speed when a second touch is set down, that second touch is marked as a thumb and ignored (unless it's right next to the other finger, then it's likely a 2fg scroll). The speed calculation is simple but has to lag behind by one sample - we reset the motion history whenever a new finger is set down (to avoid pointer jumps) so we need to know if the finger was moving fast *before* this happens. Plus, with the pointer jumps we're more likely to get false positives if we calculate the speed on actual finger down. This is the simplest version for now, the speed varies greatly between movements and should probably be averaged across the last 3-or-so samples. https://bugs.freedesktop.org/show_bug.cgi?id=99703 Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 125 +++++++++++++++++++++++++++++++++++++++- src/evdev-mt-touchpad.h | 5 ++ test/litest.h | 9 +++ test/test-touchpad.c | 86 ++++++++++++++++++++++++++- 4 files changed, 221 insertions(+), 4 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index fed25824..3fcc1ac5 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -35,6 +35,7 @@ #define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2 ms2us(500) #define THUMB_MOVE_TIMEOUT ms2us(300) #define FAKE_FINGER_OVERFLOW (1 << 7) +#define THUMB_IGNORE_SPEED_THRESHOLD 20 /* mm/s */ static inline struct tp_history_point* tp_motion_history_offset(struct tp_touch *t, int offset) @@ -82,6 +83,41 @@ tp_filter_motion_unaccelerated(struct tp_dispatch *tp, &raw, tp, time); } +static inline void +tp_calculate_motion_speed(struct tp_dispatch *tp, struct tp_touch *t) +{ + const struct tp_history_point *last; + struct device_coords delta; + struct phys_coords mm; + double distance; + double speed; + + /* This doesn't kick in until we have at least 4 events in the + * motion history. As a side-effect, this automatically handles the + * 2fg scroll where a finger is down and moving fast before the + * other finger comes down for the scroll. + * + * We do *not* reset the speed to 0 here though. The motion history + * is reset whenever a new finger is down, so we'd be resetting the + * speed and failing. + */ + if (t->history.count < 4) + return; + + /* TODO: we probably need a speed history here so we can average + * across a few events */ + last = tp_motion_history_offset(t, 1); + delta.x = abs(t->point.x - last->point.x); + delta.y = abs(t->point.y - last->point.y); + mm = evdev_device_unit_delta_to_mm(tp->device, &delta); + + distance = length_in_mm(mm); + speed = distance/(t->time - last->time); /* mm/us */ + speed *= 1000000; /* mm/s */ + + t->speed.last_speed = speed; +} + static inline void tp_motion_history_push(struct tp_touch *t) { @@ -219,6 +255,8 @@ tp_new_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) t->state = TOUCH_HOVERING; t->pinned.is_pinned = false; t->time = time; + t->speed.last_speed = 0; + t->speed.exceeded_count = 0; tp->queued |= TOUCHPAD_EVENT_MOTION; } @@ -1277,12 +1315,55 @@ tp_detect_jumps(const struct tp_dispatch *tp, struct tp_touch *t) return hypot(mm.x, mm.y) > JUMP_THRESHOLD_MM; } +static void +tp_detect_thumb_while_moving(struct tp_dispatch *tp) +{ + struct tp_touch *t; + struct tp_touch *first = NULL, + *second = NULL; + struct device_coords distance; + struct phys_coords mm; + + tp_for_each_touch(tp, t) { + if (t->state != TOUCH_BEGIN) + first = t; + else + second = t; + + if (first && second) + break; + } + + assert(first); + assert(second); + + if (tp->scroll.method == LIBINPUT_CONFIG_SCROLL_2FG) { + /* If the second finger comes down next to the other one, we + * assume this is a scroll motion. + */ + distance.x = abs(first->point.x - second->point.x); + distance.y = abs(first->point.y - second->point.y); + mm = evdev_device_unit_delta_to_mm(tp->device, &distance); + + if (mm.x <= 25 && mm.y <= 15) + return; + } + + /* Finger are too far apart or 2fg scrolling is disabled, mark + * second finger as thumb */ + evdev_log_debug(tp->device, + "touch is speed-based thumb\n"); + second->thumb.state = THUMB_STATE_YES; +} + static void tp_process_state(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; bool restart_filter = false; bool want_motion_reset; + bool have_new_touch = false; + unsigned int speed_exceeded_count = 0; tp_process_fake_touches(tp, time); tp_unhover_touches(tp, time); @@ -1299,8 +1380,15 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) t->quirks.reset_motion_history = false; } - if (!t->dirty) + if (!t->dirty) { + /* A non-dirty touch must be below the speed limit */ + if (t->speed.exceeded_count > 0) + t->speed.exceeded_count--; + + speed_exceeded_count = max(speed_exceeded_count, + t->speed.exceeded_count); continue; + } if (tp_detect_jumps(tp, t)) { if (!tp->semi_mt) @@ -1317,12 +1405,45 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) tp_motion_hysteresis(tp, t); tp_motion_history_push(t); + /* Touch speed handling: if we'are above the threshold, + * count each event that we're over the threshold up to 10 + * events. Count down when we are below the speed. + * + * Take the touch with the highest speed excess, if it is + * above a certain threshold (5, see below), assume a + * dropped finger is a thumb. + * + * Yes, this relies on the touchpad to keep sending us + * events even if the finger doesn't move, otherwise we + * never count down. Let's see how far we get with that. + */ + if (t->speed.last_speed > THUMB_IGNORE_SPEED_THRESHOLD) { + if (t->speed.exceeded_count < 10) + t->speed.exceeded_count++; + } else if (t->speed.exceeded_count > 0) { + t->speed.exceeded_count--; + } + + speed_exceeded_count = max(speed_exceeded_count, + t->speed.exceeded_count); + + tp_calculate_motion_speed(tp, t); + tp_unpin_finger(tp, t); - if (t->state == TOUCH_BEGIN) + if (t->state == TOUCH_BEGIN) { + have_new_touch = true; restart_filter = true; + } } + /* If we have one touch that exceeds the speed and we get a new + * touch down while doing that, the second touch is a thumb */ + if (have_new_touch && + tp->nfingers_down == 2 && + speed_exceeded_count > 5) + tp_detect_thumb_while_moving(tp); + if (restart_filter) filter_restart(tp->device->pointer.filter, tp, time); diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 3f0d821c..03e74a07 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -220,6 +220,11 @@ struct tp_touch { uint64_t first_touch_time; struct device_coords initial; } thumb; + + struct { + double last_speed; /* speed in mm/s at last sample */ + unsigned int exceeded_count; + } speed; }; struct tp_dispatch { diff --git a/test/litest.h b/test/litest.h index f33c05fa..0511136f 100644 --- a/test/litest.h +++ b/test/litest.h @@ -857,6 +857,15 @@ litest_enable_edge_scroll(struct litest_device *dev) litest_assert_int_eq(status, expected); } +static inline bool +litest_has_clickfinger(struct litest_device *dev) +{ + struct libinput_device *device = dev->libinput_device; + uint32_t methods = libinput_device_config_click_get_methods(device); + + return methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; +} + static inline void litest_enable_clickfinger(struct litest_device *dev) { diff --git a/test/test-touchpad.c b/test/test-touchpad.c index a6bb4f53..6d2aee57 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -1428,8 +1428,8 @@ START_TEST(touchpad_no_palm_detect_2fg_scroll) /* first finger is palm, second finger isn't so we trigger 2fg * scrolling */ litest_touch_down(dev, 0, 99, 50); - litest_touch_move_to(dev, 0, 99, 50, 99, 40, 10, 0); - litest_touch_move_to(dev, 0, 99, 40, 99, 50, 10, 0); + litest_touch_move_to(dev, 0, 99, 50, 99, 40, 35, 12); + litest_touch_move_to(dev, 0, 99, 40, 99, 50, 35, 12); litest_assert_empty_queue(li); litest_touch_down(dev, 1, 50, 50); litest_assert_empty_queue(li); @@ -5411,6 +5411,84 @@ START_TEST(touchpad_palm_detect_touch_size) } END_TEST +START_TEST(touchpad_speed_ignore_finger) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (litest_has_clickfinger(dev)) + litest_enable_clickfinger(dev); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 20); + litest_touch_move_to(dev, 0, 20, 20, 85, 80, 20, 0); + litest_touch_down(dev, 1, 20, 80); + litest_touch_move_two_touches(dev, 85, 80, 20, 80, -20, -20, 10, 0); + libinput_dispatch(li); + + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + +START_TEST(touchpad_speed_allow_nearby_finger) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!litest_has_2fg_scroll(dev)) + return; + + if (litest_has_clickfinger(dev)) + litest_enable_clickfinger(dev); + + litest_enable_2fg_scroll(dev); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 20); + litest_touch_move_to(dev, 0, 20, 20, 80, 80, 20, 0); + litest_drain_events(li); + litest_touch_down(dev, 1, 79, 80); + litest_touch_move_two_touches(dev, 80, 80, 79, 80, -20, -20, 10, 0); + libinput_dispatch(li); + + litest_touch_up(dev, 0); + litest_touch_up(dev, 1); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); +} +END_TEST + +START_TEST(touchpad_speed_ignore_finger_edgescroll) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_enable_edge_scroll(dev); + if (litest_has_clickfinger(dev)) + litest_enable_clickfinger(dev); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 20); + litest_touch_move_to(dev, 0, 20, 20, 60, 80, 20, 0); + litest_drain_events(li); + litest_touch_down(dev, 1, 59, 80); + litest_touch_move_two_touches(dev, 60, 80, 59, 80, -20, -20, 10, 0); + libinput_dispatch(li); + + litest_touch_up(dev, 0); + libinput_dispatch(li); + litest_touch_up(dev, 1); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); +} +END_TEST + void litest_setup_tests_touchpad(void) { @@ -5576,4 +5654,8 @@ litest_setup_tests_touchpad(void) litest_add("touchpad:touch-size", touchpad_touch_size, LITEST_APPLE_CLICKPAD, LITEST_ANY); litest_add("touchpad:touch-size", touchpad_touch_size_2fg, LITEST_APPLE_CLICKPAD, LITEST_ANY); + + litest_add("touchpad:speed", touchpad_speed_ignore_finger, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add("touchpad:speed", touchpad_speed_allow_nearby_finger, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); + litest_add("touchpad:speed", touchpad_speed_ignore_finger_edgescroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT); }