mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-03-21 21:30:36 +01:00
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 <peter.hutterer@who-t.net>
This commit is contained in:
parent
10569680d9
commit
de5246dae0
4 changed files with 221 additions and 4 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue