diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 47e4ed85..d4133b52 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -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; diff --git a/test/test-gestures.c b/test/test-gestures.c index 4a9596d3..76f80f37 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -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);