#define DEFAULT_GESTURE_SWIPE_TIMEOUT ms2us(150)
#define DEFAULT_GESTURE_PINCH_TIMEOUT ms2us(300)
+#define HOLD_AND_MOTION_THRESHOLD 0.5 /* mm */
#define PINCH_DISAMBIGUATION_MOVE_THRESHOLD 1.5 /* mm */
enum gesture_event {
GESTURE_EVENT_RESET,
GESTURE_EVENT_FINGER_DETECTED,
GESTURE_EVENT_HOLD_TIMEOUT,
+ GESTURE_EVENT_HOLD_AND_MOTION,
GESTURE_EVENT_POINTER_MOTION,
GESTURE_EVENT_SCROLL,
GESTURE_EVENT_SWIPE,
CASE_RETURN_STRING(GESTURE_STATE_NONE);
CASE_RETURN_STRING(GESTURE_STATE_UNKNOWN);
CASE_RETURN_STRING(GESTURE_STATE_HOLD);
+ CASE_RETURN_STRING(GESTURE_STATE_HOLD_AND_MOTION);
CASE_RETURN_STRING(GESTURE_STATE_POINTER_MOTION);
CASE_RETURN_STRING(GESTURE_STATE_SCROLL);
CASE_RETURN_STRING(GESTURE_STATE_PINCH);
CASE_RETURN_STRING(GESTURE_EVENT_RESET);
CASE_RETURN_STRING(GESTURE_EVENT_FINGER_DETECTED);
CASE_RETURN_STRING(GESTURE_EVENT_HOLD_TIMEOUT);
+ CASE_RETURN_STRING(GESTURE_EVENT_HOLD_AND_MOTION);
CASE_RETURN_STRING(GESTURE_EVENT_POINTER_MOTION);
CASE_RETURN_STRING(GESTURE_EVENT_SCROLL);
CASE_RETURN_STRING(GESTURE_EVENT_SWIPE);
__func__);
break;
case GESTURE_STATE_HOLD:
+ case GESTURE_STATE_HOLD_AND_MOTION:
gesture_notify_hold(&tp->device->base, time,
tp->gesture.finger_count);
break;
case GESTURE_EVENT_SCROLL:
tp->gesture.state = GESTURE_STATE_SCROLL;
break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
case GESTURE_EVENT_SWIPE:
case GESTURE_EVENT_PINCH:
log_gesture_bug(tp, event);
tp_gesture_start(tp, time);
break;
case GESTURE_EVENT_POINTER_MOTION:
- libinput_timer_cancel(&tp->gesture.hold_timer);
+ /* Don't cancel the hold timer. This pointer motion can end up
+ * being recognised as hold and motion. */
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
break;
case GESTURE_EVENT_SCROLL:
tp_gesture_init_pinch(tp);
tp->gesture.state = GESTURE_STATE_PINCH;
break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
case GESTURE_EVENT_FINGER_DETECTED:
log_gesture_bug(tp, event);
break;
libinput_timer_cancel(&tp->gesture.hold_timer);
tp->gesture.state = GESTURE_STATE_NONE;
break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
+ tp->gesture.state = GESTURE_STATE_HOLD_AND_MOTION;
+ break;
case GESTURE_EVENT_POINTER_MOTION:
tp_gesture_cancel(tp, time);
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
}
}
+static void
+tp_gesture_hold_and_motion_handle_event(struct tp_dispatch *tp,
+ enum gesture_event event,
+ uint64_t time)
+{
+ switch(event) {
+ case GESTURE_EVENT_RESET:
+ libinput_timer_cancel(&tp->gesture.hold_timer);
+ tp->gesture.state = GESTURE_STATE_NONE;
+ break;
+ case GESTURE_EVENT_POINTER_MOTION:
+ tp_gesture_cancel(tp, time);
+ tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
+ break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
+ case GESTURE_EVENT_FINGER_DETECTED:
+ case GESTURE_EVENT_HOLD_TIMEOUT:
+ case GESTURE_EVENT_SCROLL:
+ case GESTURE_EVENT_SWIPE:
+ case GESTURE_EVENT_PINCH:
+ log_gesture_bug(tp, event);
+ break;
+ }
+}
+
static void
tp_gesture_pointer_motion_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
+ struct tp_touch *first;
+ struct phys_coords first_moved;
+ double first_mm;
+
switch(event) {
case GESTURE_EVENT_RESET:
libinput_timer_cancel(&tp->gesture.hold_timer);
tp->gesture.state = GESTURE_STATE_NONE;
break;
- case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_HOLD_TIMEOUT:
+ if (tp->gesture.finger_count != 1)
+ break;
+
+ first = tp->gesture.touches[0];
+ first_moved = tp_gesture_mm_moved(tp, first);
+ first_mm = hypot(first_moved.x, first_moved.y);
+
+ if (first_mm < HOLD_AND_MOTION_THRESHOLD) {
+ tp->gesture.state = GESTURE_STATE_HOLD_AND_MOTION;
+ tp_gesture_start(tp, time);
+ }
+ break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
+ case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_POINTER_MOTION:
case GESTURE_EVENT_SCROLL:
case GESTURE_EVENT_SWIPE:
libinput_timer_cancel(&tp->gesture.hold_timer);
tp->gesture.state = GESTURE_STATE_NONE;
break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_HOLD_TIMEOUT:
case GESTURE_EVENT_POINTER_MOTION:
libinput_timer_cancel(&tp->gesture.hold_timer);
tp->gesture.state = GESTURE_STATE_NONE;
break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_HOLD_TIMEOUT:
case GESTURE_EVENT_POINTER_MOTION:
libinput_timer_cancel(&tp->gesture.hold_timer);
tp->gesture.state = GESTURE_STATE_NONE;
break;
+ case GESTURE_EVENT_HOLD_AND_MOTION:
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_HOLD_TIMEOUT:
case GESTURE_EVENT_POINTER_MOTION:
case GESTURE_STATE_HOLD:
tp_gesture_hold_handle_event(tp, event, time);
break;
+ case GESTURE_STATE_HOLD_AND_MOTION:
+ tp_gesture_hold_and_motion_handle_event(tp, event, time);
+ break;
case GESTURE_STATE_POINTER_MOTION:
tp_gesture_pointer_motion_handle_event(tp, event, time);
break;
double thumb_mm, finger_mm;
double min_move = 1.5; /* min movement threshold in mm - count this touch */
double max_move = 4.0; /* max movement threshold in mm - ignore other touch */
+ bool is_hold_and_motion;
first_moved = tp_gesture_mm_moved(tp, first);
first_mm = hypot(first_moved.x, first_moved.y);
if (tp->gesture.finger_count == 1) {
- if (tp_has_pending_pointer_motion(tp, time)) {
+ if (!tp_has_pending_pointer_motion(tp, time))
+ return;
+
+ is_hold_and_motion = (first_mm < HOLD_AND_MOTION_THRESHOLD);
+
+ if (tp->gesture.state == GESTURE_STATE_HOLD &&
+ is_hold_and_motion) {
tp_gesture_handle_event(tp,
- GESTURE_EVENT_POINTER_MOTION,
+ GESTURE_EVENT_HOLD_AND_MOTION,
time);
+ return;
}
+
+ if (tp->gesture.state == GESTURE_STATE_HOLD_AND_MOTION &&
+ is_hold_and_motion)
+ return;
+
+ tp_gesture_handle_event(tp,
+ GESTURE_EVENT_POINTER_MOTION,
+ time);
return;
}
tp_gesture_detect_motion_gestures(tp, time);
}
+static void
+tp_gesture_handle_state_hold_and_pointer_motion(struct tp_dispatch *tp, uint64_t time)
+{
+ if (tp->queued & TOUCHPAD_EVENT_MOTION)
+ tp_gesture_post_pointer_motion(tp, time);
+
+ tp_gesture_detect_motion_gestures(tp, time);
+}
+
static void
tp_gesture_handle_state_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
if (tp->gesture.state == GESTURE_STATE_HOLD)
tp_gesture_handle_state_hold(tp, time, ignore_motion);
+ if (tp->gesture.state == GESTURE_STATE_HOLD_AND_MOTION)
+ tp_gesture_handle_state_hold_and_pointer_motion(tp, time);
+
if (tp->gesture.state == GESTURE_STATE_POINTER_MOTION)
tp_gesture_handle_state_pointer_motion(tp, time);
__func__);
break;
case GESTURE_STATE_HOLD:
+ case GESTURE_STATE_HOLD_AND_MOTION:
gesture_notify_hold_end(&tp->device->base, time,
tp->gesture.finger_count, cancelled);
break;
}
END_TEST
+START_TEST(gestures_hold_and_motion_before_timeout)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+
+ if (!libinput_device_has_capability(dev->libinput_device,
+ LIBINPUT_DEVICE_CAP_GESTURE))
+ return;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ libinput_dispatch(li);
+
+ litest_touch_move_to(dev, 0, 50, 50, 51, 51, 1);
+ litest_touch_move_to(dev, 0, 51, 51, 50, 50, 1);
+ libinput_dispatch(li);
+
+ litest_timeout_gesture_quick_hold();
+
+ litest_drain_events_of_type(li, LIBINPUT_EVENT_POINTER_MOTION, -1);
+
+ litest_assert_gesture_event(li,
+ LIBINPUT_EVENT_GESTURE_HOLD_BEGIN,
+ 1);
+
+ litest_touch_up(dev, 0);
+ libinput_dispatch(li);
+
+ litest_assert_gesture_event(li,
+ LIBINPUT_EVENT_GESTURE_HOLD_END,
+ 1);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(gestures_hold_and_motion_after_timeout)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+
+ if (!libinput_device_has_capability(dev->libinput_device,
+ LIBINPUT_DEVICE_CAP_GESTURE))
+ return;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ libinput_dispatch(li);
+ litest_timeout_gesture_quick_hold();
+
+ litest_assert_gesture_event(li,
+ LIBINPUT_EVENT_GESTURE_HOLD_BEGIN,
+ 1);
+
+ litest_touch_move_to(dev, 0, 50, 50, 51, 51, 1);
+ litest_touch_move_to(dev, 0, 51, 51, 50, 50, 1);
+ libinput_dispatch(li);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_touch_up(dev, 0);
+ libinput_dispatch(li);
+
+ litest_assert_gesture_event(li,
+ LIBINPUT_EVENT_GESTURE_HOLD_END,
+ 1);
+ litest_assert_empty_queue(li);
+}
+END_TEST
+
TEST_COLLECTION(gestures)
{
struct range cardinals = { N, N + NCARDINALS };
litest_add(gestures_hold_once_on_double_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add_ranged(gestures_hold_once_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY, &range_multifinger_tap);
+ litest_add(gestures_hold_and_motion_before_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+ litest_add(gestures_hold_and_motion_after_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+
/* Timing-sensitive test, valgrind is too slow */
if (!RUNNING_ON_VALGRIND)
litest_add(gestures_swipe_3fg_unaccel, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);