}
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);
}
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);
+
+ 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);
- 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;
+ /* If both touches moved less than a mm, we cannot decide yet */
+ if (first_mm < 1 && second_mm < 1)
+ return GESTURE_STATE_UNKNOWN;
+
+ /* 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;
+ }
}
- /* 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 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;
- /* If both touches are moving in the same direction assume
- * scroll or swipe */
+ /* 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) {
} 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
} 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;
}
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();
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;
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);
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);