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 */ diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index eb3dde3c..ae74cb9b 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -35,8 +35,9 @@ #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 device_coords * +static inline struct tp_history_point* tp_motion_history_offset(struct tp_touch *t, int offset) { int offset_index = @@ -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) { @@ -90,7 +126,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; } @@ -218,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; } @@ -293,10 +332,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 +1291,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,19 +1308,62 @@ 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; } +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); @@ -1297,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) @@ -1315,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 efbdb3bf..9d9f0826 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; @@ -217,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 8643f32d..679406dc 100644 --- a/test/litest.h +++ b/test/litest.h @@ -859,6 +859,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); }