mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-01-23 09:10:23 +01:00
touchpad: basic thumb detection within gestures
When a touchpad has thumb detection enabled, avoid false-positive gestures involving a resting thumb by using two thresholds: inner and outer. While both touches remain inside their inner thresholds, remain in UNKNOWN state to allow for accurate gesture detection even with no timeout. If both touches move outside their inner thresholds, start a pinch or swipe/scroll gesture according to direction, as usual. If one touch moves outside its outer threshold while the other has not yet exceeded its inner threshold, and thumb detection is enabled, then if one touch is >20mm lower, mark it as a thumb and cancel the gesture. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
7fbfb14419
commit
7b9a6a94a3
2 changed files with 78 additions and 105 deletions
|
|
@ -195,21 +195,14 @@ tp_gesture_get_active_touches(const struct tp_dispatch *tp,
|
|||
}
|
||||
|
||||
static uint32_t
|
||||
tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch,
|
||||
unsigned int nfingers)
|
||||
tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch)
|
||||
{
|
||||
struct phys_coords mm;
|
||||
struct device_float_coords delta;
|
||||
double move_threshold = 1.0; /* mm */
|
||||
|
||||
move_threshold *= (nfingers - 1);
|
||||
|
||||
delta = device_delta(touch->point, touch->gesture.initial);
|
||||
mm = tp_phys_delta(tp, delta);
|
||||
|
||||
if (length_in_mm(mm) < move_threshold)
|
||||
return UNDEFINED_DIRECTION;
|
||||
|
||||
return phys_get_direction(mm);
|
||||
}
|
||||
|
||||
|
|
@ -462,57 +455,88 @@ tp_gesture_init_pinch(struct tp_dispatch *tp)
|
|||
tp->gesture.prev_scale = 1.0;
|
||||
}
|
||||
|
||||
static struct phys_coords
|
||||
tp_gesture_mm_moved(struct tp_dispatch *tp, struct tp_touch *t)
|
||||
{
|
||||
struct device_coords delta;
|
||||
|
||||
delta.x = abs(t->point.x - t->gesture.initial.x);
|
||||
delta.y = abs(t->point.y - t->gesture.initial.y);
|
||||
|
||||
return evdev_device_unit_delta_to_mm(tp->device, &delta);
|
||||
}
|
||||
|
||||
static enum tp_gesture_state
|
||||
tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
|
||||
{
|
||||
struct tp_touch *first = tp->gesture.touches[0],
|
||||
*second = tp->gesture.touches[1];
|
||||
uint32_t dir1, dir2;
|
||||
struct phys_coords mm;
|
||||
int vert_distance, horiz_distance;
|
||||
struct device_coords delta;
|
||||
struct phys_coords first_moved, second_moved, distance_mm;
|
||||
double first_mm, second_mm; /* movement since gesture start in mm */
|
||||
double inner = 1.5; /* inner threshold in mm - count this touch */
|
||||
double outer = 4.0; /* outer threshold in mm - ignore other touch */
|
||||
|
||||
vert_distance = abs(first->point.y - second->point.y);
|
||||
horiz_distance = abs(first->point.x - second->point.x);
|
||||
/* Need more margin for error when there are more fingers */
|
||||
outer += 2.0 * (tp->gesture.finger_count - 2);
|
||||
inner += 0.5 * (tp->gesture.finger_count - 2);
|
||||
|
||||
if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
|
||||
/* for two-finger gestures, if the fingers stay unmoving for a
|
||||
* while, assume (slow) scroll */
|
||||
if (tp->gesture.finger_count == 2) {
|
||||
tp_gesture_set_scroll_buildup(tp);
|
||||
return GESTURE_STATE_SCROLL;
|
||||
/* more fingers than slots, don't bother with pinch, always
|
||||
* assume swipe */
|
||||
} else if (tp->gesture.finger_count > tp->num_slots) {
|
||||
return GESTURE_STATE_SWIPE;
|
||||
}
|
||||
first_moved = tp_gesture_mm_moved(tp, first);
|
||||
first_mm = hypot(first_moved.x, first_moved.y);
|
||||
|
||||
/* for 3+ finger gestures, check if one finger is > 20mm
|
||||
below the others */
|
||||
mm = evdev_convert_xy_to_mm(tp->device,
|
||||
horiz_distance,
|
||||
vert_distance);
|
||||
if (mm.y > 20 && tp->gesture.enabled) {
|
||||
tp_gesture_init_pinch(tp);
|
||||
return GESTURE_STATE_PINCH;
|
||||
} else {
|
||||
return GESTURE_STATE_SWIPE;
|
||||
}
|
||||
}
|
||||
second_moved = tp_gesture_mm_moved(tp, second);
|
||||
second_mm = hypot(second_moved.x, second_moved.y);
|
||||
|
||||
if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
|
||||
mm = evdev_convert_xy_to_mm(tp->device, horiz_distance, vert_distance);
|
||||
if (tp->gesture.finger_count == 2 && mm.x > 40 && mm.y > 40)
|
||||
return GESTURE_STATE_PINCH;
|
||||
}
|
||||
delta.x = abs(first->point.x - second->point.x);
|
||||
delta.y = abs(first->point.y - second->point.y);
|
||||
distance_mm = evdev_device_unit_delta_to_mm(tp->device, &delta);
|
||||
|
||||
/* Else wait for both fingers to have moved */
|
||||
dir1 = tp_gesture_get_direction(tp, first, tp->gesture.finger_count);
|
||||
dir2 = tp_gesture_get_direction(tp, second, tp->gesture.finger_count);
|
||||
if (dir1 == UNDEFINED_DIRECTION || dir2 == UNDEFINED_DIRECTION)
|
||||
/* If both touches moved less than a mm, we cannot decide yet */
|
||||
if (first_mm < 1 && second_mm < 1)
|
||||
return GESTURE_STATE_UNKNOWN;
|
||||
|
||||
/* If both touches are moving in the same direction assume
|
||||
* scroll or swipe */
|
||||
/* If one touch exceeds the outer threshold while the other has not
|
||||
* yet passed the inner threshold, this is not a valid gesture.
|
||||
* If thumb detection is enabled, and one of the touches is >20mm
|
||||
* below the other, cancel the gesture and mark the thumb.
|
||||
*
|
||||
* Give the thumb a larger effective outer threshold for more reliable
|
||||
* detection of pinch vs. resting thumb.
|
||||
*/
|
||||
if (tp->thumb.detect_thumbs && distance_mm.y > 20.0 &&
|
||||
time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
|
||||
if ((first->point.y >= second->point.y) &&
|
||||
((first_mm >= outer * 2.0) ||
|
||||
(second_mm >= outer))) {
|
||||
tp_gesture_cancel(tp, time);
|
||||
first->thumb.state = THUMB_STATE_YES;
|
||||
return GESTURE_STATE_NONE;
|
||||
}
|
||||
if ((second->point.y >= first->point.y) &&
|
||||
((second_mm >= outer * 2.0) ||
|
||||
(first_mm >= outer))) {
|
||||
tp_gesture_cancel(tp, time);
|
||||
second->thumb.state = THUMB_STATE_YES;
|
||||
return GESTURE_STATE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* If either touch is still inside the inner threshold, we can't
|
||||
* tell what kind of gesture this is.
|
||||
*/
|
||||
if ((first_mm < inner) || (second_mm < inner))
|
||||
return GESTURE_STATE_UNKNOWN;
|
||||
|
||||
/* Both touches have exceeded the inner threshold; get their directions
|
||||
* gesture. G directions so we know if it's a pinch or swipe/scroll.
|
||||
*/
|
||||
dir1 = tp_gesture_get_direction(tp, first);
|
||||
dir2 = tp_gesture_get_direction(tp, second);
|
||||
|
||||
/* If we can't accurately detect pinches, or if the touches are moving
|
||||
* the same way, this is a scroll or swipe.
|
||||
*/
|
||||
if (tp->gesture.finger_count > tp->num_slots ||
|
||||
tp_gesture_same_directions(dir1, dir2)) {
|
||||
if (tp->gesture.finger_count == 2) {
|
||||
|
|
@ -521,12 +545,11 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
|
|||
} else if (tp->gesture.enabled) {
|
||||
return GESTURE_STATE_SWIPE;
|
||||
}
|
||||
} else {
|
||||
tp_gesture_init_pinch(tp);
|
||||
return GESTURE_STATE_PINCH;
|
||||
}
|
||||
|
||||
return GESTURE_STATE_UNKNOWN;
|
||||
/* If the touches are moving away from each other, this is a pinch */
|
||||
tp_gesture_init_pinch(tp);
|
||||
return GESTURE_STATE_PINCH;
|
||||
}
|
||||
|
||||
static enum tp_gesture_state
|
||||
|
|
@ -772,6 +795,10 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
|
|||
} else if (!tp->gesture.started) {
|
||||
tp->gesture.finger_count = active_touches;
|
||||
tp->gesture.finger_count_pending = 0;
|
||||
/* If in UNKNOWN state, go back to NONE to
|
||||
* re-evaluate leftmost and rightmost touches
|
||||
*/
|
||||
tp->gesture.state = GESTURE_STATE_NONE;
|
||||
/* Else debounce finger changes */
|
||||
} else if (active_touches != tp->gesture.finger_count_pending) {
|
||||
tp->gesture.finger_count_pending = active_touches;
|
||||
|
|
|
|||
|
|
@ -489,58 +489,6 @@ START_TEST(gestures_swipe_4fg_btntool)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(gestures_pinch_vertical_position)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
struct libinput *li = dev->libinput;
|
||||
struct libinput_event *event;
|
||||
int nfingers = _i; /* ranged test */
|
||||
|
||||
if (libevdev_get_num_slots(dev->evdev) < nfingers ||
|
||||
!libinput_device_has_capability(dev->libinput_device,
|
||||
LIBINPUT_DEVICE_CAP_GESTURE))
|
||||
return;
|
||||
|
||||
litest_disable_tap(dev->libinput_device);
|
||||
litest_drain_events(li);
|
||||
|
||||
litest_touch_down(dev, 0, 40, 30);
|
||||
litest_touch_down(dev, 1, 50, 70);
|
||||
litest_touch_down(dev, 2, 60, 70);
|
||||
if (nfingers > 3)
|
||||
litest_touch_down(dev, 3, 70, 70);
|
||||
libinput_dispatch(li);
|
||||
litest_timeout_gesture_scroll();
|
||||
libinput_dispatch(li);
|
||||
|
||||
/* This is actually a small swipe gesture, all three fingers moving
|
||||
* down but we're checking for the code that triggers based on
|
||||
* finger position. */
|
||||
litest_touch_move(dev, 0, 40, 30.5);
|
||||
litest_touch_move(dev, 1, 50, 70.5);
|
||||
litest_touch_move(dev, 2, 60, 70.5);
|
||||
if (nfingers > 3)
|
||||
litest_touch_move(dev, 3, 70, 70.5);
|
||||
libinput_dispatch(li);
|
||||
|
||||
event = libinput_get_event(li);
|
||||
litest_is_gesture_event(event,
|
||||
LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
|
||||
nfingers);
|
||||
libinput_event_destroy(event);
|
||||
|
||||
litest_touch_move_to(dev, 0, 40, 30.5, 40, 36, 5);
|
||||
litest_touch_move_to(dev, 1, 50, 70.5, 50, 76, 5);
|
||||
litest_touch_move_to(dev, 2, 60, 70.5, 60, 76, 5);
|
||||
if (nfingers > 3)
|
||||
litest_touch_move_to(dev, 3, 70, 70.5, 60, 76, 5);
|
||||
libinput_dispatch(li);
|
||||
|
||||
litest_assert_only_typed_events(li,
|
||||
LIBINPUT_EVENT_GESTURE_PINCH_UPDATE);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(gestures_pinch)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
|
|
@ -784,7 +732,7 @@ START_TEST(gestures_pinch_4fg)
|
|||
litest_touch_down(dev, 3, 52 - dir_x, 52 - dir_y);
|
||||
libinput_dispatch(li);
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
for (i = 0; i < 7; i++) {
|
||||
litest_push_event_frame(dev);
|
||||
if (dir_x > 0.0)
|
||||
dir_x -= 2;
|
||||
|
|
@ -1045,7 +993,6 @@ END_TEST
|
|||
TEST_COLLECTION(gestures)
|
||||
{
|
||||
struct range cardinals = { N, N + NCARDINALS };
|
||||
struct range fingers = { 3, 5 };
|
||||
|
||||
litest_add("gestures:cap", gestures_cap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
|
||||
litest_add("gestures:cap", gestures_nocap, LITEST_ANY, LITEST_TOUCHPAD);
|
||||
|
|
@ -1058,7 +1005,6 @@ TEST_COLLECTION(gestures)
|
|||
litest_add_ranged("gestures:pinch", gestures_pinch_3fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
|
||||
litest_add_ranged("gestures:pinch", gestures_pinch_4fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
|
||||
litest_add_ranged("gestures:pinch", gestures_spread, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
|
||||
litest_add_ranged("gestures:pinch", gestures_pinch_vertical_position, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &fingers);
|
||||
|
||||
litest_add("gestures:swipe", gestures_3fg_buttonarea_scroll, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
|
||||
litest_add("gestures:swipe", gestures_3fg_buttonarea_scroll_btntool, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue