mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-03-21 19:10:42 +01:00
This adds a movement threshold (5mm) and a timeout (80ms) to the 3fg drag gesture. On 3fg down with 3fg drag enabled we immediately send a GESTURE_SWIPE event. After the timeout expires we check the movement of the fingers - if it is below the threshold cancel the swipe and hold a button down (i.e. a 3fg drag). Otherwise, continue with this being a swipe. This allows for swipe gestures to be used while 3fg drag is enabled. Above applies the same way for 4fg with 4fg drag enabled. Thresholds selected using the "yeah, that seems about alright" method, intentionally quite low because we assume that users that enable 3fg drag prefer 3fg dragging over swipe. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net> Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1410>
2443 lines
71 KiB
C
2443 lines
71 KiB
C
/*
|
|
* Copyright © 2015 Red Hat, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "evdev-mt-touchpad.h"
|
|
|
|
enum gesture_cancelled {
|
|
END_GESTURE = 0,
|
|
CANCEL_GESTURE = 1,
|
|
};
|
|
|
|
#define QUICK_GESTURE_HOLD_TIMEOUT usec_from_millis(40)
|
|
#define DEFAULT_GESTURE_HOLD_TIMEOUT usec_from_millis(180)
|
|
#define DEFAULT_GESTURE_SWITCH_TIMEOUT usec_from_millis(100)
|
|
#define DEFAULT_GESTURE_SWIPE_TIMEOUT usec_from_millis(150)
|
|
#define DEFAULT_GESTURE_PINCH_TIMEOUT usec_from_millis(300)
|
|
#define DRAG_3FG_OR_SWIPE_TIMEOUT usec_from_millis(80)
|
|
|
|
#define HOLD_AND_MOTION_THRESHOLD 0.5 /* mm */
|
|
#define PINCH_DISAMBIGUATION_MOVE_THRESHOLD 1.5 /* mm */
|
|
#define DRAG_3FG_OR_SWIPE_MOVE_THRESHOLD 5 /* mm */
|
|
|
|
enum gesture_event {
|
|
GESTURE_EVENT_RESET,
|
|
GESTURE_EVENT_END,
|
|
GESTURE_EVENT_CANCEL,
|
|
GESTURE_EVENT_FINGER_DETECTED,
|
|
GESTURE_EVENT_FINGER_SWITCH_TIMEOUT,
|
|
GESTURE_EVENT_TAP_TIMEOUT,
|
|
GESTURE_EVENT_HOLD_TIMEOUT,
|
|
GESTURE_EVENT_HOLD_AND_MOTION_START,
|
|
GESTURE_EVENT_POINTER_MOTION_START,
|
|
GESTURE_EVENT_SCROLL_START,
|
|
GESTURE_EVENT_SWIPE_START,
|
|
GESTURE_EVENT_PINCH_START,
|
|
GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
|
|
GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT,
|
|
GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT,
|
|
};
|
|
|
|
/*****************************************
|
|
* DO NOT EDIT THIS FILE!
|
|
*
|
|
* Look at the state diagram in doc/touchpad-gestures-state-machine.svg
|
|
* (generated with https://www.diagrams.net)
|
|
*
|
|
* Any changes in this file must be represented in the diagram.
|
|
*/
|
|
|
|
static inline const char *
|
|
gesture_state_to_str(enum tp_gesture_state state)
|
|
{
|
|
switch (state) {
|
|
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_START);
|
|
CASE_RETURN_STRING(GESTURE_STATE_SCROLL);
|
|
CASE_RETURN_STRING(GESTURE_STATE_PINCH_START);
|
|
CASE_RETURN_STRING(GESTURE_STATE_PINCH);
|
|
CASE_RETURN_STRING(GESTURE_STATE_SWIPE_START);
|
|
CASE_RETURN_STRING(GESTURE_STATE_SWIPE);
|
|
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_OR_SWIPE_START);
|
|
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_OR_SWIPE);
|
|
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_START);
|
|
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG);
|
|
CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_RELEASED);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline const char *
|
|
gesture_event_to_str(enum gesture_event event)
|
|
{
|
|
switch (event) {
|
|
CASE_RETURN_STRING(GESTURE_EVENT_RESET);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_END);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_CANCEL);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_FINGER_DETECTED);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_FINGER_SWITCH_TIMEOUT);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_TAP_TIMEOUT);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_HOLD_TIMEOUT);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_HOLD_AND_MOTION_START);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_POINTER_MOTION_START);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_SCROLL_START);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_SWIPE_START);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_PINCH_START);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT);
|
|
CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct device_float_coords
|
|
tp_get_touches_delta(struct tp_dispatch *tp, bool average)
|
|
{
|
|
struct tp_touch *t;
|
|
unsigned int i, nactive = 0;
|
|
struct device_float_coords delta = { 0.0, 0.0 };
|
|
|
|
for (i = 0; i < tp->num_slots; i++) {
|
|
t = &tp->touches[i];
|
|
|
|
if (!tp_touch_active_for_gesture(tp, t))
|
|
continue;
|
|
|
|
nactive++;
|
|
|
|
if (t->dirty) {
|
|
struct device_coords d;
|
|
|
|
d = tp_get_delta(t);
|
|
|
|
delta.x += d.x;
|
|
delta.y += d.y;
|
|
}
|
|
}
|
|
|
|
if (!average || nactive == 0)
|
|
return delta;
|
|
|
|
delta.x /= nactive;
|
|
delta.y /= nactive;
|
|
|
|
return delta;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_init_scroll(struct tp_dispatch *tp)
|
|
{
|
|
struct phys_coords zero = { 0.0, 0.0 };
|
|
tp->scroll.active.h = false;
|
|
tp->scroll.active.v = false;
|
|
tp->scroll.duration.h = usec_from_uint64_t(0);
|
|
tp->scroll.duration.v = usec_from_uint64_t(0);
|
|
tp->scroll.vector = zero;
|
|
tp->scroll.time_prev = usec_from_uint64_t(0);
|
|
}
|
|
|
|
static inline struct device_float_coords
|
|
tp_get_combined_touches_delta(struct tp_dispatch *tp)
|
|
{
|
|
return tp_get_touches_delta(tp, false);
|
|
}
|
|
|
|
static inline struct device_float_coords
|
|
tp_get_average_touches_delta(struct tp_dispatch *tp)
|
|
{
|
|
return tp_get_touches_delta(tp, true);
|
|
}
|
|
|
|
static struct device_float_coords
|
|
tp_get_raw_pointer_motion(struct tp_dispatch *tp)
|
|
{
|
|
struct device_float_coords raw;
|
|
|
|
/* When a clickpad is clicked, combine motion of all active touches */
|
|
if (tp->buttons.is_clickpad && tp->buttons.state)
|
|
raw = tp_get_combined_touches_delta(tp);
|
|
else
|
|
raw = tp_get_average_touches_delta(tp);
|
|
|
|
return raw;
|
|
}
|
|
|
|
static bool
|
|
tp_has_pending_pointer_motion(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
|
|
if (!(tp->queued & TOUCHPAD_EVENT_MOTION))
|
|
return false;
|
|
|
|
/* Checking for raw pointer motion is enough in this case.
|
|
* Calling tp_filter_motion is intentionally omitted to avoid calling
|
|
* it twice (here and in tp_gesture_post_pointer_motion) with the same
|
|
* event.
|
|
*/
|
|
raw = tp_get_raw_pointer_motion(tp);
|
|
return !device_float_is_zero(raw);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_post_pointer_motion(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
struct normalized_coords delta;
|
|
|
|
raw = tp_get_raw_pointer_motion(tp);
|
|
delta = tp_filter_motion(tp, &raw, time);
|
|
|
|
if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
|
|
struct device_float_coords unaccel;
|
|
|
|
unaccel = tp_scale_to_xaxis(tp, raw);
|
|
pointer_notify_motion(&tp->device->base, time, &delta, &unaccel);
|
|
}
|
|
}
|
|
|
|
static unsigned int
|
|
tp_gesture_get_active_touches(const struct tp_dispatch *tp,
|
|
struct tp_touch **touches,
|
|
unsigned int count)
|
|
{
|
|
unsigned int n = 0;
|
|
struct tp_touch *t;
|
|
|
|
memset(touches, 0, count * sizeof(struct tp_touch *));
|
|
|
|
tp_for_each_touch(tp, t) {
|
|
if (tp_touch_active_for_gesture(tp, t)) {
|
|
touches[n++] = t;
|
|
if (n == count)
|
|
return count;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This can happen when the user does .e.g:
|
|
* 1) Put down 1st finger in center (so active)
|
|
* 2) Put down 2nd finger in a button area (so inactive)
|
|
* 3) Put down 3th finger somewhere, gets reported as a fake finger,
|
|
* so gets same coordinates as 1st -> active
|
|
*
|
|
* We could avoid this by looking at all touches, be we really only
|
|
* want to look at real touches.
|
|
*/
|
|
return n;
|
|
}
|
|
|
|
static inline int
|
|
tp_gesture_same_directions(int dir1, int dir2)
|
|
{
|
|
/*
|
|
* In some cases (semi-mt touchpads) we may seen one finger move
|
|
* e.g. N/NE and the other W/NW so we not only check for overlapping
|
|
* directions, but also for neighboring bits being set.
|
|
* The ((dira & 0x80) && (dirb & 0x01)) checks are to check for bit 0
|
|
* and 7 being set as they also represent neighboring directions.
|
|
*/
|
|
return ((dir1 | (dir1 >> 1)) & dir2) || ((dir2 | (dir2 >> 1)) & dir1) ||
|
|
((dir1 & 0x80) && (dir2 & 0x01)) || ((dir2 & 0x80) && (dir1 & 0x01));
|
|
}
|
|
|
|
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 uint32_t
|
|
tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch)
|
|
{
|
|
struct phys_coords mm;
|
|
struct device_float_coords delta;
|
|
|
|
delta = device_delta(touch->point, touch->gesture.initial);
|
|
mm = tp_phys_delta(tp, delta);
|
|
|
|
return phys_get_direction(mm);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_get_pinch_info(struct tp_dispatch *tp,
|
|
double *distance,
|
|
double *angle,
|
|
struct device_float_coords *center)
|
|
{
|
|
struct normalized_coords normalized;
|
|
struct device_float_coords delta;
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
*second = tp->gesture.touches[1];
|
|
|
|
delta = device_delta(first->point, second->point);
|
|
normalized = tp_normalize_delta(tp, delta);
|
|
*distance = normalized_length(normalized);
|
|
*angle = rad2deg(atan2(normalized.y, normalized.x));
|
|
|
|
*center = device_average(first->point, second->point);
|
|
}
|
|
|
|
static inline void
|
|
tp_gesture_init_pinch(struct tp_dispatch *tp)
|
|
{
|
|
tp_gesture_get_pinch_info(tp,
|
|
&tp->gesture.initial_distance,
|
|
&tp->gesture.angle,
|
|
&tp->gesture.center);
|
|
tp->gesture.prev_scale = 1.0;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_set_scroll_buildup(struct tp_dispatch *tp)
|
|
{
|
|
struct device_float_coords d0, d1;
|
|
struct device_float_coords average;
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
*second = tp->gesture.touches[1];
|
|
|
|
d0 = device_delta(first->point, first->gesture.initial);
|
|
d1 = device_delta(second->point, second->gesture.initial);
|
|
|
|
average = device_float_average(d0, d1);
|
|
tp->device->scroll.buildup = tp_normalize_delta(tp, average);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_apply_scroll_constraints(struct tp_dispatch *tp,
|
|
struct device_float_coords *raw,
|
|
struct normalized_coords *delta,
|
|
usec_t time)
|
|
{
|
|
usec_t tdelta = usec_from_millis(0);
|
|
struct phys_coords delta_mm, vector;
|
|
double vector_decay, vector_length, slope;
|
|
|
|
const usec_t ACTIVE_THRESHOLD = usec_from_millis(100),
|
|
INACTIVE_THRESHOLD = usec_from_millis(50),
|
|
EVENT_TIMEOUT = usec_from_millis(100);
|
|
|
|
/* Both axes active == true means free scrolling is enabled */
|
|
if (tp->scroll.active.h && tp->scroll.active.v)
|
|
return;
|
|
|
|
/* Determine time delta since last movement event */
|
|
if (!usec_is_zero(tp->scroll.time_prev)) {
|
|
usec_t diff = usec_delta(time, tp->scroll.time_prev);
|
|
if (usec_cmp(diff, EVENT_TIMEOUT) <= 0)
|
|
tdelta = diff;
|
|
}
|
|
|
|
tp->scroll.time_prev = time;
|
|
|
|
/* Delta since last movement event in mm */
|
|
delta_mm = tp_phys_delta(tp, *raw);
|
|
|
|
/* Old vector data "fades" over time. This is a two-part linear
|
|
* approximation of an exponential function - for example, for
|
|
* EVENT_TIMEOUT of 100, vector_decay = (0.97)^tdelta. This linear
|
|
* approximation allows easier tweaking of EVENT_TIMEOUT and is faster.
|
|
*/
|
|
if (usec_gt(tdelta, 0)) {
|
|
uint64_t delta = usec_as_uint64_t(tdelta);
|
|
double recent, later;
|
|
uint64_t timeout_us = usec_as_uint64_t(EVENT_TIMEOUT);
|
|
recent = ((timeout_us / 2.0) - delta) / (timeout_us / 2.0);
|
|
later = (timeout_us - delta) / (timeout_us * 2.0);
|
|
vector_decay = delta <= (0.33 * timeout_us) ? recent : later;
|
|
} else {
|
|
vector_decay = 0.0;
|
|
}
|
|
|
|
/* Calculate windowed vector from delta + weighted historic data */
|
|
vector.x = (tp->scroll.vector.x * vector_decay) + delta_mm.x;
|
|
vector.y = (tp->scroll.vector.y * vector_decay) + delta_mm.y;
|
|
vector_length = hypot(vector.x, vector.y);
|
|
tp->scroll.vector = vector;
|
|
|
|
/* We care somewhat about distance and speed, but more about
|
|
* consistency of direction over time. Keep track of the time spent
|
|
* primarily along each axis. If one axis is active, time spent NOT
|
|
* moving much in the other axis is subtracted, allowing a switch of
|
|
* axes in a single scroll + ability to "break out" and go diagonal.
|
|
*
|
|
* Slope to degree conversions (infinity = 90°, 0 = 0°):
|
|
*/
|
|
const double DEGREE_75 = 3.73;
|
|
const double DEGREE_60 = 1.73;
|
|
const double DEGREE_30 = 0.57;
|
|
const double DEGREE_15 = 0.27;
|
|
slope = (vector.x != 0) ? fabs(vector.y / vector.x) : INFINITY;
|
|
|
|
/* Ensure vector is big enough (in mm per EVENT_TIMEOUT) to be confident
|
|
* of direction. Larger = harder to enable diagonal/free scrolling.
|
|
*/
|
|
const double MIN_VECTOR = 0.15;
|
|
|
|
if (slope >= DEGREE_30 && vector_length > MIN_VECTOR) {
|
|
tp->scroll.duration.v = usec_add(tp->scroll.duration.v, tdelta);
|
|
if (usec_cmp(tp->scroll.duration.v, ACTIVE_THRESHOLD) > 0)
|
|
tp->scroll.duration.v = ACTIVE_THRESHOLD;
|
|
if (slope >= DEGREE_75) {
|
|
if (usec_cmp(tp->scroll.duration.h, tdelta) > 0)
|
|
tp->scroll.duration.h =
|
|
usec_sub(tp->scroll.duration.h, tdelta);
|
|
else
|
|
tp->scroll.duration.h = usec_from_uint64_t(0);
|
|
}
|
|
}
|
|
if (slope < DEGREE_60 && vector_length > MIN_VECTOR) {
|
|
tp->scroll.duration.h = usec_add(tp->scroll.duration.h, tdelta);
|
|
if (usec_cmp(tp->scroll.duration.h, ACTIVE_THRESHOLD) > 0)
|
|
tp->scroll.duration.h = ACTIVE_THRESHOLD;
|
|
if (slope < DEGREE_15) {
|
|
if (usec_cmp(tp->scroll.duration.v, tdelta) > 0)
|
|
tp->scroll.duration.v =
|
|
usec_sub(tp->scroll.duration.v, tdelta);
|
|
else
|
|
tp->scroll.duration.v = usec_from_uint64_t(0);
|
|
}
|
|
}
|
|
|
|
if (usec_cmp(tp->scroll.duration.h, ACTIVE_THRESHOLD) == 0) {
|
|
tp->scroll.active.h = true;
|
|
if (usec_cmp(tp->scroll.duration.v, INACTIVE_THRESHOLD) < 0)
|
|
tp->scroll.active.v = false;
|
|
}
|
|
if (usec_cmp(tp->scroll.duration.v, ACTIVE_THRESHOLD) == 0) {
|
|
tp->scroll.active.v = true;
|
|
if (usec_cmp(tp->scroll.duration.h, INACTIVE_THRESHOLD) < 0)
|
|
tp->scroll.active.h = false;
|
|
}
|
|
|
|
/* If vector is big enough in a diagonal direction, always unlock
|
|
* both axes regardless of thresholds
|
|
*/
|
|
if (vector_length > 5.0 && slope < 1.73 && slope >= 0.57) {
|
|
tp->scroll.active.v = true;
|
|
tp->scroll.active.h = true;
|
|
}
|
|
|
|
/* If only one axis is active, constrain motion accordingly. If both
|
|
* are set, we've detected deliberate diagonal movement; enable free
|
|
* scrolling for the life of the gesture.
|
|
*/
|
|
if (!tp->scroll.active.h && tp->scroll.active.v)
|
|
delta->x = 0.0;
|
|
if (tp->scroll.active.h && !tp->scroll.active.v)
|
|
delta->y = 0.0;
|
|
|
|
/* If we haven't determined an axis, use the slope in the meantime */
|
|
if (!tp->scroll.active.h && !tp->scroll.active.v) {
|
|
delta->x = (slope >= DEGREE_60) ? 0.0 : delta->x;
|
|
delta->y = (slope < DEGREE_30) ? 0.0 : delta->y;
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
log_gesture_bug(struct tp_dispatch *tp, enum gesture_event event)
|
|
{
|
|
evdev_log_bug_libinput(tp->device,
|
|
"invalid gesture event %s in state %s\n",
|
|
gesture_event_to_str(event),
|
|
gesture_state_to_str(tp->gesture.state));
|
|
}
|
|
|
|
static bool
|
|
tp_gesture_is_quick_hold(struct tp_dispatch *tp)
|
|
{
|
|
/* When 1 or 2 fingers are used to hold, always use a "quick" hold to
|
|
* make the hold to stop kinetic scrolling user interaction feel more
|
|
* natural.
|
|
*/
|
|
return (tp->gesture.finger_count == 1) || (tp->gesture.finger_count == 2);
|
|
}
|
|
|
|
static bool
|
|
tp_gesture_use_hold_timer(struct tp_dispatch *tp)
|
|
{
|
|
/* When tap is not enabled, always use the timer */
|
|
if (!tp->tap.enabled)
|
|
return true;
|
|
|
|
/* Always use the timer if it is a quick hold */
|
|
if (tp_gesture_is_quick_hold(tp))
|
|
return true;
|
|
|
|
/* If the number of fingers on the touchpad exceeds the number of
|
|
* allowed fingers to tap, use the timer.
|
|
*/
|
|
if (tp->gesture.finger_count > 3)
|
|
return true;
|
|
|
|
/* If the tap state machine is already in a hold status, for example
|
|
* when holding with 3 fingers and then holding with 2, use the timer.
|
|
*/
|
|
if (tp->tap.state == TAP_STATE_HOLD ||
|
|
tp->tap.state == TAP_STATE_TOUCH_2_HOLD ||
|
|
tp->tap.state == TAP_STATE_TOUCH_3_HOLD)
|
|
return true;
|
|
|
|
/* If the tap state machine is in dead status, use the timer. This
|
|
* happens when the user holds after cancelling a gesture/scroll.
|
|
*/
|
|
if (tp->tap.state == TAP_STATE_DEAD)
|
|
return true;
|
|
|
|
/* Otherwise, sync the hold notification with the tap state machine */
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_set_hold_timer(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
usec_t timeout;
|
|
|
|
if (!tp->gesture.hold_enabled)
|
|
return;
|
|
|
|
if (tp_gesture_use_hold_timer(tp)) {
|
|
timeout = tp_gesture_is_quick_hold(tp) ? QUICK_GESTURE_HOLD_TIMEOUT
|
|
: DEFAULT_GESTURE_HOLD_TIMEOUT;
|
|
|
|
libinput_timer_set(&tp->gesture.hold_timer, usec_add(time, timeout));
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
break;
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
/* Note: this makes 3fg drag more responsive but disables
|
|
* 3fg pinch/hold. Those are niche enough to not worry about
|
|
* for now.
|
|
*/
|
|
if (!tp->tap.enabled &&
|
|
tp->drag_3fg.nfingers == tp->gesture.finger_count) {
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
|
|
} else {
|
|
tp_gesture_set_hold_timer(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_UNKNOWN;
|
|
}
|
|
break;
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
|
|
break;
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_set_3fg_drag_3fg_or_swipe_timer(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
libinput_timer_set(&tp->gesture.drag_3fg_or_swipe_timer,
|
|
usec_add(time, DRAG_3FG_OR_SWIPE_TIMEOUT));
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
tp->gesture.state = GESTURE_STATE_HOLD;
|
|
gesture_notify_hold_begin(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count);
|
|
break;
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
/* 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_START:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp_gesture_set_scroll_buildup(tp);
|
|
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
|
break;
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_SWIPE_START;
|
|
break;
|
|
case GESTURE_EVENT_PINCH_START:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp_gesture_init_pinch(tp);
|
|
tp->gesture.state = GESTURE_STATE_PINCH_START;
|
|
break;
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL: {
|
|
bool cancelled = event == GESTURE_EVENT_CANCEL;
|
|
gesture_notify_hold_end(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count,
|
|
cancelled);
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
}
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
tp->gesture.state = GESTURE_STATE_HOLD_AND_MOTION;
|
|
break;
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
|
|
break;
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
tp_gesture_set_scroll_buildup(tp);
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
|
break;
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_SWIPE_START;
|
|
break;
|
|
case GESTURE_EVENT_PINCH_START:
|
|
tp_gesture_cancel(tp, time);
|
|
tp_gesture_init_pinch(tp);
|
|
tp->gesture.state = GESTURE_STATE_PINCH_START;
|
|
break;
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_hold_and_motion(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL: {
|
|
bool cancelled = event == GESTURE_EVENT_CANCEL;
|
|
gesture_notify_hold_end(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count,
|
|
cancelled);
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
}
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_pointer_motion(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
struct tp_touch *first;
|
|
struct phys_coords first_moved;
|
|
double first_mm;
|
|
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_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;
|
|
gesture_notify_hold_begin(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count);
|
|
}
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_scroll_start(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_PINCH_START:
|
|
tp_gesture_init_pinch(tp);
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_PINCH_START;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_scroll(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
tp_gesture_stop_twofinger_scroll(tp, time);
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
case GESTURE_EVENT_PINCH_START:
|
|
tp_gesture_init_pinch(tp);
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_PINCH_START;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_pinch_start(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_pinch(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL: {
|
|
bool cancelled = event == GESTURE_EVENT_CANCEL;
|
|
gesture_notify_pinch_end(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count,
|
|
tp->gesture.prev_scale,
|
|
cancelled);
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
}
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_swipe_start(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_swipe(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL: {
|
|
bool cancelled = event == GESTURE_EVENT_CANCEL;
|
|
gesture_notify_swipe_end(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count,
|
|
cancelled);
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
}
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_3fg_drag_or_swipe_start(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_3fg_drag_or_swipe(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
*second = tp->gesture.touches[1];
|
|
struct phys_coords first_moved, second_moved;
|
|
double first_mm, second_mm;
|
|
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL: {
|
|
bool cancelled = event == GESTURE_EVENT_CANCEL;
|
|
gesture_notify_swipe_end(&tp->device->base,
|
|
time,
|
|
tp->gesture.finger_count,
|
|
cancelled);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
}
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_or_swipe_timer);
|
|
|
|
first_moved = tp_gesture_mm_moved(tp, first);
|
|
second_moved = tp_gesture_mm_moved(tp, second);
|
|
first_mm = hypot(first_moved.x, first_moved.y);
|
|
second_mm = hypot(second_moved.x, second_moved.y);
|
|
if ((first_mm + second_mm) / 2.0 >= DRAG_3FG_OR_SWIPE_MOVE_THRESHOLD) {
|
|
tp->gesture.state = GESTURE_STATE_SWIPE;
|
|
} else {
|
|
/* Cancel the swipe */
|
|
tp_gesture_cancel(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG_START;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_3fg_drag_start(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_set_3fg_drag_timer(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
tp->gesture.drag_3fg_release_time = time;
|
|
libinput_timer_set(&tp->gesture.drag_3fg_timer, usec_add_millis(time, 700));
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_3fg_drag(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_CANCEL:
|
|
/* If the gesture is cancelled we release the button immediately */
|
|
evdev_pointer_notify_button(tp->device,
|
|
tp->gesture.drag_3fg_release_time,
|
|
evdev_usage_from(EVDEV_BTN_LEFT),
|
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
/* If the gesture ends we start the timer so we
|
|
* can keep dragging */
|
|
tp_gesture_set_3fg_drag_timer(tp, time);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG_RELEASED;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
if (tp->gesture.finger_count_pending < 2) {
|
|
evdev_pointer_notify_button(tp->device,
|
|
tp->gesture.drag_3fg_release_time,
|
|
evdev_usage_from(EVDEV_BTN_LEFT),
|
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
}
|
|
break;
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event_on_state_3fg_drag_released(struct tp_dispatch *tp,
|
|
enum gesture_event event,
|
|
usec_t time)
|
|
{
|
|
switch (event) {
|
|
case GESTURE_EVENT_RESET:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
case GESTURE_EVENT_END:
|
|
case GESTURE_EVENT_CANCEL:
|
|
case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
|
|
libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
|
|
evdev_pointer_notify_button(tp->device,
|
|
tp->gesture.drag_3fg_release_time,
|
|
evdev_usage_from(EVDEV_BTN_LEFT),
|
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
break;
|
|
case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
|
|
case GESTURE_EVENT_TAP_TIMEOUT:
|
|
if (tp->gesture.finger_count_pending == tp->drag_3fg.nfingers) {
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG;
|
|
}
|
|
break;
|
|
case GESTURE_EVENT_FINGER_DETECTED:
|
|
break;
|
|
case GESTURE_EVENT_POINTER_MOTION_START:
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
|
|
evdev_pointer_notify_button(tp->device,
|
|
tp->gesture.drag_3fg_release_time,
|
|
evdev_usage_from(EVDEV_BTN_LEFT),
|
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
|
|
break;
|
|
case GESTURE_EVENT_HOLD_AND_MOTION_START:
|
|
case GESTURE_EVENT_HOLD_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
/* Anything that's detected as gesture in this state
|
|
* will be continue the current 3fg drag gesture */
|
|
case GESTURE_EVENT_SCROLL_START:
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
|
|
evdev_pointer_notify_button(tp->device,
|
|
tp->gesture.drag_3fg_release_time,
|
|
evdev_usage_from(EVDEV_BTN_LEFT),
|
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
tp->gesture.state = GESTURE_STATE_SCROLL_START;
|
|
break;
|
|
case GESTURE_EVENT_SWIPE_START:
|
|
case GESTURE_EVENT_PINCH_START:
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG;
|
|
break;
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
|
|
case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
|
|
log_gesture_bug(tp, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_event(struct tp_dispatch *tp, enum gesture_event event, usec_t time)
|
|
{
|
|
enum tp_gesture_state oldstate;
|
|
|
|
oldstate = tp->gesture.state;
|
|
|
|
switch (tp->gesture.state) {
|
|
case GESTURE_STATE_NONE:
|
|
tp_gesture_handle_event_on_state_none(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_UNKNOWN:
|
|
tp_gesture_handle_event_on_state_unknown(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_HOLD:
|
|
tp_gesture_handle_event_on_state_hold(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_HOLD_AND_MOTION:
|
|
tp_gesture_handle_event_on_state_hold_and_motion(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_POINTER_MOTION:
|
|
tp_gesture_handle_event_on_state_pointer_motion(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_SCROLL_START:
|
|
tp_gesture_handle_event_on_state_scroll_start(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_SCROLL:
|
|
tp_gesture_handle_event_on_state_scroll(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_PINCH_START:
|
|
tp_gesture_handle_event_on_state_pinch_start(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_PINCH:
|
|
tp_gesture_handle_event_on_state_pinch(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_SWIPE_START:
|
|
tp_gesture_handle_event_on_state_swipe_start(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_SWIPE:
|
|
tp_gesture_handle_event_on_state_swipe(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
|
|
tp_gesture_handle_event_on_state_3fg_drag_or_swipe_start(tp,
|
|
event,
|
|
time);
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
|
|
tp_gesture_handle_event_on_state_3fg_drag_or_swipe(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG_START:
|
|
tp_gesture_handle_event_on_state_3fg_drag_start(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG:
|
|
tp_gesture_handle_event_on_state_3fg_drag(tp, event, time);
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG_RELEASED:
|
|
tp_gesture_handle_event_on_state_3fg_drag_released(tp, event, time);
|
|
break;
|
|
}
|
|
|
|
if (oldstate != tp->gesture.state) {
|
|
evdev_log_debug(tp->device,
|
|
"gesture: [%dfg] event %s → %s → %s\n",
|
|
tp->gesture.finger_count,
|
|
gesture_state_to_str(oldstate),
|
|
gesture_event_to_str(event),
|
|
gesture_state_to_str(tp->gesture.state));
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_hold_timeout(usec_t now, void *data)
|
|
{
|
|
struct tp_dispatch *tp = data;
|
|
|
|
if (tp_tap_dragging_or_double_tapping(tp) || tp_tap_dragging(tp))
|
|
return;
|
|
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_HOLD_TIMEOUT, now);
|
|
}
|
|
|
|
void
|
|
tp_gesture_tap_timeout(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
if (!tp->gesture.hold_enabled)
|
|
return;
|
|
|
|
if (!tp_gesture_is_quick_hold(tp))
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_TAP_TIMEOUT, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_3fg_drag_timeout(usec_t now, void *data)
|
|
{
|
|
struct tp_dispatch *tp = data;
|
|
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT, now);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_3fg_drag_or_swipe_timeout(usec_t now, void *data)
|
|
{
|
|
struct tp_dispatch *tp = data;
|
|
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT, now);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
*second = tp->gesture.touches[1], *thumb;
|
|
uint32_t dir1, dir2;
|
|
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 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))
|
|
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_HOLD_AND_MOTION_START,
|
|
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_START, time);
|
|
return;
|
|
}
|
|
|
|
/* If we have more fingers than slots, we don't know where the
|
|
* fingers are. Default to swipe/3fg drag */
|
|
if (tp->gesture.enabled && tp->gesture.finger_count > 2 &&
|
|
tp->gesture.finger_count > tp->num_slots) {
|
|
if (tp->drag_3fg.nfingers == tp->gesture.finger_count)
|
|
tp_gesture_handle_event(tp,
|
|
GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
|
|
time);
|
|
else
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
|
|
return;
|
|
}
|
|
|
|
/* Need more margin for error when there are more fingers */
|
|
max_move += 2.0 * (tp->gesture.finger_count - 2);
|
|
min_move += 0.5 * (tp->gesture.finger_count - 2);
|
|
|
|
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 both touches moved less than a mm, we cannot decide yet */
|
|
if (first_mm < 1 && second_mm < 1)
|
|
return;
|
|
|
|
/* If both touches are within 7mm vertically and 40mm horizontally
|
|
* past the timeout, assume scroll/swipe */
|
|
if ((!tp->gesture.enabled || (distance_mm.x < 40.0 && distance_mm.y < 7.0)) &&
|
|
usec_cmp(time,
|
|
usec_add(tp->gesture.initial_time,
|
|
DEFAULT_GESTURE_SWIPE_TIMEOUT)) > 0) {
|
|
if (tp->gesture.finger_count == 2)
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, time);
|
|
else if (tp->drag_3fg.nfingers == tp->gesture.finger_count)
|
|
tp_gesture_handle_event(tp,
|
|
GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
|
|
time);
|
|
else
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
|
|
|
|
return;
|
|
}
|
|
|
|
/* If 3fg dragging touches are within a 60x10mm box, start
|
|
* dragging (or swiping) immediately */
|
|
if (tp->gesture.finger_count == tp->drag_3fg.nfingers && distance_mm.x < 60.0 &&
|
|
distance_mm.y < 10.0) {
|
|
tp_gesture_handle_event(tp,
|
|
GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
|
|
time);
|
|
return;
|
|
}
|
|
|
|
/* If one touch exceeds the max_move threshold while the other has not
|
|
* yet passed the min_move threshold, there is either a resting thumb,
|
|
* or the user is doing "one-finger-scroll," where one touch stays in
|
|
* place while the other moves.
|
|
*/
|
|
if (first_mm >= max_move || second_mm >= max_move) {
|
|
double thumb_mm, finger_mm;
|
|
|
|
/* Pick the thumb as the lowest point on the touchpad */
|
|
if (first->point.y > second->point.y) {
|
|
thumb = first;
|
|
thumb_mm = first_mm;
|
|
finger_mm = second_mm;
|
|
} else {
|
|
thumb = second;
|
|
thumb_mm = second_mm;
|
|
finger_mm = first_mm;
|
|
}
|
|
|
|
/* If thumb detection is enabled, and thumb is still while
|
|
* finger moves, cancel gestures and mark lower as thumb.
|
|
* This applies to all gestures (2, 3, 4+ fingers), but allows
|
|
* more thumb motion on >2 finger gestures during detection.
|
|
*/
|
|
if (tp->thumb.detect_thumbs && thumb_mm < min_move) {
|
|
tp_thumb_suppress(tp, thumb);
|
|
tp_gesture_cancel(tp, time);
|
|
return;
|
|
}
|
|
|
|
/* If gestures detection is disabled, or if finger is still
|
|
* while thumb moves, assume this is "one-finger scrolling."
|
|
* This applies only to 2-finger gestures.
|
|
*/
|
|
if ((!tp->gesture.enabled || finger_mm < min_move) &&
|
|
tp->gesture.finger_count == 2) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, time);
|
|
return;
|
|
}
|
|
|
|
/* If more than 2 fingers are involved, and the thumb moves
|
|
* while the fingers stay still, assume a pinch if eligible.
|
|
*/
|
|
if (finger_mm < min_move && tp->gesture.finger_count > 2 &&
|
|
tp->gesture.enabled && tp->thumb.pinch_eligible) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, time);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If either touch is still below the min_move threshold, we can't
|
|
* tell what kind of gesture this is.
|
|
*/
|
|
if ((first_mm < min_move) || (second_mm < min_move))
|
|
return;
|
|
|
|
/* Both touches have exceeded the min_move threshold, so we have a
|
|
* valid gesture. Update gesture initial time and get 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) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, time);
|
|
return;
|
|
}
|
|
|
|
if (tp->drag_3fg.nfingers == tp->gesture.finger_count) {
|
|
tp_gesture_handle_event(tp,
|
|
GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
|
|
time);
|
|
return;
|
|
}
|
|
|
|
if (tp->gesture.enabled) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If the touches are moving away from each other, this is a pinch */
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, time);
|
|
}
|
|
|
|
static bool
|
|
tp_gesture_is_pinch(struct tp_dispatch *tp)
|
|
{
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
*second = tp->gesture.touches[1];
|
|
uint32_t dir1, dir2;
|
|
struct phys_coords first_moved, second_moved;
|
|
double first_mm, second_mm;
|
|
|
|
dir1 = tp_gesture_get_direction(tp, first);
|
|
dir2 = tp_gesture_get_direction(tp, second);
|
|
if (tp_gesture_same_directions(dir1, dir2))
|
|
return false;
|
|
|
|
first_moved = tp_gesture_mm_moved(tp, first);
|
|
first_mm = hypot(first_moved.x, first_moved.y);
|
|
if (first_mm < PINCH_DISAMBIGUATION_MOVE_THRESHOLD)
|
|
return false;
|
|
|
|
second_moved = tp_gesture_mm_moved(tp, second);
|
|
second_mm = hypot(second_moved.x, second_moved.y);
|
|
if (second_mm < PINCH_DISAMBIGUATION_MOVE_THRESHOLD)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_none(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct tp_touch *first, *second;
|
|
struct tp_touch *touches[4];
|
|
unsigned int ntouches;
|
|
unsigned int i;
|
|
|
|
ntouches = tp_gesture_get_active_touches(tp, touches, 4);
|
|
|
|
first = touches[0];
|
|
second = touches[1];
|
|
|
|
if (ntouches == 0)
|
|
return;
|
|
|
|
if (ntouches == 1) {
|
|
first->gesture.initial = first->point;
|
|
tp->gesture.touches[0] = first;
|
|
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_FINGER_DETECTED, time);
|
|
return;
|
|
}
|
|
|
|
if (!tp->gesture.enabled && !tp->tap.enabled && ntouches == 2) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, time);
|
|
return;
|
|
}
|
|
|
|
/* For 3+ finger gestures, we only really need to track two touches.
|
|
* The human hand's finger arrangement means that for a pinch, the
|
|
* bottom-most touch will always be the thumb, and the top-most touch
|
|
* will always be one of the fingers.
|
|
*
|
|
* For 3+ finger swipes, the fingers will likely (but not necessarily)
|
|
* be in a horizontal line. They all move together, regardless, so it
|
|
* doesn't really matter which two of those touches we track.
|
|
*
|
|
* Tracking top and bottom is a change from previous versions, where
|
|
* we tracked leftmost and rightmost. This change enables:
|
|
*
|
|
* - More accurate pinch detection if thumb is near the center
|
|
* - Better resting-thumb detection while two-finger scrolling
|
|
* - On capable hardware, allow 3- or 4-finger swipes with resting
|
|
* thumb or held-down clickpad
|
|
*/
|
|
if (ntouches > 2) {
|
|
second = touches[0];
|
|
|
|
for (i = 1; i < ntouches && i < tp->num_slots; i++) {
|
|
if (touches[i]->point.y < first->point.y)
|
|
first = touches[i];
|
|
else if (touches[i]->point.y >= second->point.y)
|
|
second = touches[i];
|
|
}
|
|
|
|
if (first == second)
|
|
return;
|
|
}
|
|
|
|
tp->gesture.initial_time = time;
|
|
first->gesture.initial = first->point;
|
|
second->gesture.initial = second->point;
|
|
tp->gesture.touches[0] = first;
|
|
tp->gesture.touches[1] = second;
|
|
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_FINGER_DETECTED, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_unknown(struct tp_dispatch *tp, usec_t time, bool ignore_motion)
|
|
{
|
|
if (!ignore_motion)
|
|
tp_gesture_detect_motion_gestures(tp, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_hold(struct tp_dispatch *tp, usec_t time, bool ignore_motion)
|
|
{
|
|
if (!ignore_motion)
|
|
tp_gesture_detect_motion_gestures(tp, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_hold_and_pointer_motion(struct tp_dispatch *tp, usec_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, usec_t time)
|
|
{
|
|
if (tp->queued & TOUCHPAD_EVENT_MOTION)
|
|
tp_gesture_post_pointer_motion(tp, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_scroll_start(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
struct normalized_coords delta;
|
|
|
|
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
|
|
return;
|
|
|
|
/* We may confuse a pinch for a scroll initially,
|
|
* allow ourselves to correct our guess.
|
|
*/
|
|
if (usec_cmp(time,
|
|
usec_add(tp->gesture.initial_time,
|
|
DEFAULT_GESTURE_PINCH_TIMEOUT)) < 0 &&
|
|
tp_gesture_is_pinch(tp)) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, time);
|
|
return;
|
|
}
|
|
|
|
raw = tp_get_average_touches_delta(tp);
|
|
|
|
/* scroll is not accelerated by default */
|
|
delta = tp_filter_scroll(tp, &raw, time);
|
|
|
|
if (normalized_is_zero(delta))
|
|
return;
|
|
|
|
tp_gesture_init_scroll(tp);
|
|
tp->gesture.state = GESTURE_STATE_SCROLL;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_scroll(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
struct normalized_coords delta;
|
|
|
|
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
|
|
return;
|
|
|
|
/* We may confuse a pinch for a scroll initially,
|
|
* allow ourselves to correct our guess.
|
|
*/
|
|
if (usec_cmp(time,
|
|
usec_add(tp->gesture.initial_time,
|
|
DEFAULT_GESTURE_PINCH_TIMEOUT)) < 0 &&
|
|
tp_gesture_is_pinch(tp)) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, time);
|
|
return;
|
|
}
|
|
|
|
raw = tp_get_average_touches_delta(tp);
|
|
|
|
/* scroll is not accelerated by default */
|
|
delta = tp_filter_scroll(tp, &raw, time);
|
|
|
|
if (normalized_is_zero(delta))
|
|
return;
|
|
|
|
tp_gesture_apply_scroll_constraints(tp, &raw, &delta, time);
|
|
evdev_post_scroll(tp->device,
|
|
time,
|
|
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
|
|
&delta);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_swipe_start(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
struct normalized_coords delta;
|
|
|
|
raw = tp_get_average_touches_delta(tp);
|
|
delta = tp_filter_motion(tp, &raw, time);
|
|
|
|
if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
|
|
const struct normalized_coords zero = { 0.0, 0.0 };
|
|
gesture_notify_swipe(&tp->device->base,
|
|
time,
|
|
LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
|
|
tp->gesture.finger_count,
|
|
&zero,
|
|
&zero);
|
|
tp->gesture.state = GESTURE_STATE_SWIPE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_swipe(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
struct normalized_coords delta, unaccel;
|
|
|
|
raw = tp_get_average_touches_delta(tp);
|
|
delta = tp_filter_motion(tp, &raw, time);
|
|
|
|
if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
|
|
unaccel = tp_filter_motion_unaccelerated(tp, &raw, time);
|
|
gesture_notify_swipe(&tp->device->base,
|
|
time,
|
|
LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
|
|
tp->gesture.finger_count,
|
|
&delta,
|
|
&unaccel);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_pinch_start(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
const struct normalized_coords zero = { 0.0, 0.0 };
|
|
double angle, angle_delta, distance, scale;
|
|
struct device_float_coords center, fdelta;
|
|
struct normalized_coords delta;
|
|
|
|
tp_gesture_get_pinch_info(tp, &distance, &angle, ¢er);
|
|
|
|
scale = distance / tp->gesture.initial_distance;
|
|
|
|
angle_delta = angle - tp->gesture.angle;
|
|
tp->gesture.angle = angle;
|
|
if (angle_delta > 180.0)
|
|
angle_delta -= 360.0;
|
|
else if (angle_delta < -180.0)
|
|
angle_delta += 360.0;
|
|
|
|
fdelta = device_float_delta(center, tp->gesture.center);
|
|
tp->gesture.center = center;
|
|
|
|
delta = tp_filter_motion(tp, &fdelta, time);
|
|
|
|
if (normalized_is_zero(delta) && device_float_is_zero(fdelta) &&
|
|
scale == tp->gesture.prev_scale && angle_delta == 0.0)
|
|
return;
|
|
|
|
gesture_notify_pinch(&tp->device->base,
|
|
time,
|
|
LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
|
|
tp->gesture.finger_count,
|
|
&zero,
|
|
&zero,
|
|
1.0,
|
|
0.0);
|
|
|
|
tp->gesture.prev_scale = scale;
|
|
tp->gesture.state = GESTURE_STATE_PINCH;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_pinch(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
double angle, angle_delta, distance, scale;
|
|
struct device_float_coords center, fdelta;
|
|
struct normalized_coords delta, unaccel;
|
|
|
|
tp_gesture_get_pinch_info(tp, &distance, &angle, ¢er);
|
|
|
|
scale = distance / tp->gesture.initial_distance;
|
|
|
|
angle_delta = angle - tp->gesture.angle;
|
|
tp->gesture.angle = angle;
|
|
if (angle_delta > 180.0)
|
|
angle_delta -= 360.0;
|
|
else if (angle_delta < -180.0)
|
|
angle_delta += 360.0;
|
|
|
|
fdelta = device_float_delta(center, tp->gesture.center);
|
|
tp->gesture.center = center;
|
|
|
|
delta = tp_filter_motion(tp, &fdelta, time);
|
|
|
|
if (normalized_is_zero(delta) && device_float_is_zero(fdelta) &&
|
|
scale == tp->gesture.prev_scale && angle_delta == 0.0)
|
|
return;
|
|
|
|
unaccel = tp_filter_motion_unaccelerated(tp, &fdelta, time);
|
|
gesture_notify_pinch(&tp->device->base,
|
|
time,
|
|
LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
|
|
tp->gesture.finger_count,
|
|
&delta,
|
|
&unaccel,
|
|
scale,
|
|
angle_delta);
|
|
|
|
tp->gesture.prev_scale = scale;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_3fg_drag_start(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
evdev_pointer_notify_button(tp->device,
|
|
time,
|
|
evdev_usage_from(EVDEV_BTN_LEFT),
|
|
LIBINPUT_BUTTON_STATE_PRESSED);
|
|
/* FIXME: immediately send a motion event? */
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG;
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_3fg_drag(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
if (tp->queued & TOUCHPAD_EVENT_MOTION)
|
|
tp_gesture_post_pointer_motion(tp, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_3fg_drag_released(struct tp_dispatch *tp,
|
|
usec_t time,
|
|
bool ignore_motion)
|
|
{
|
|
tp_gesture_detect_motion_gestures(tp, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_3fg_drag_or_swipe(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
struct device_float_coords raw;
|
|
struct normalized_coords delta, unaccel;
|
|
|
|
raw = tp_get_average_touches_delta(tp);
|
|
delta = tp_filter_motion(tp, &raw, time);
|
|
|
|
if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
|
|
unaccel = tp_filter_motion_unaccelerated(tp, &raw, time);
|
|
gesture_notify_swipe(&tp->device->base,
|
|
time,
|
|
LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
|
|
tp->gesture.finger_count,
|
|
&delta,
|
|
&unaccel);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state_3fg_drag_or_swipe_start(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
const struct normalized_coords zero = { 0.0, 0.0 };
|
|
gesture_notify_swipe(&tp->device->base,
|
|
time,
|
|
LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
|
|
tp->gesture.finger_count,
|
|
&zero,
|
|
&zero);
|
|
tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE;
|
|
tp_gesture_set_3fg_drag_3fg_or_swipe_timer(tp, time);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_handle_state(struct tp_dispatch *tp, usec_t time, bool ignore_motion)
|
|
{
|
|
enum tp_gesture_state oldstate = tp->gesture.state;
|
|
enum tp_gesture_state transitions[18] = { 0 };
|
|
enum tp_gesture_state *transition_state = transitions;
|
|
|
|
#define REMEMBER_TRANSITION(_ts, _state) { \
|
|
if (*(_ts) != (_state)) { \
|
|
++(_ts); \
|
|
assert((_ts) < transitions + ARRAY_LENGTH(transitions)); \
|
|
*(_ts) = _state; \
|
|
} \
|
|
}
|
|
|
|
*transition_state = tp->gesture.state;
|
|
if (tp->gesture.state == GESTURE_STATE_NONE) {
|
|
tp_gesture_handle_state_none(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_UNKNOWN) {
|
|
tp_gesture_handle_state_unknown(tp, time, ignore_motion);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_HOLD) {
|
|
tp_gesture_handle_state_hold(tp, time, ignore_motion);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_POINTER_MOTION) {
|
|
tp_gesture_handle_state_pointer_motion(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_HOLD_AND_MOTION) {
|
|
tp_gesture_handle_state_hold_and_pointer_motion(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_SCROLL) {
|
|
tp_gesture_handle_state_scroll(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_SCROLL_START) {
|
|
tp_gesture_handle_state_scroll_start(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_SWIPE) {
|
|
tp_gesture_handle_state_swipe(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_SWIPE_START) {
|
|
tp_gesture_handle_state_swipe_start(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_PINCH) {
|
|
tp_gesture_handle_state_pinch(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_PINCH_START) {
|
|
tp_gesture_handle_state_pinch_start(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG) {
|
|
tp_gesture_handle_state_3fg_drag(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_START) {
|
|
tp_gesture_handle_state_3fg_drag_start(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_RELEASED) {
|
|
tp_gesture_handle_state_3fg_drag_released(tp, time, ignore_motion);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_OR_SWIPE) {
|
|
tp_gesture_handle_state_3fg_drag_or_swipe(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_OR_SWIPE_START) {
|
|
tp_gesture_handle_state_3fg_drag_or_swipe_start(tp, time);
|
|
REMEMBER_TRANSITION(transition_state, tp->gesture.state);
|
|
}
|
|
|
|
#undef REMEMBER_TRANSITION
|
|
|
|
if (oldstate != tp->gesture.state) {
|
|
_autostrvfree_ char **states = NULL;
|
|
states = strv_append_strdup(states, gesture_state_to_str(oldstate));
|
|
for (enum tp_gesture_state *s = transitions + 1; s < transition_state;
|
|
s++) {
|
|
states = strv_append_strdup(states, gesture_state_to_str(*s));
|
|
}
|
|
states = strv_append_strdup(states,
|
|
gesture_state_to_str(tp->gesture.state));
|
|
_autofree_ char *str = strv_join(states, " → ");
|
|
evdev_log_debug(tp->device,
|
|
"gesture: [%dfg] state %s\n",
|
|
tp->gesture.finger_count,
|
|
str);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
tp_gesture_thumb_moved(struct tp_dispatch *tp)
|
|
{
|
|
struct tp_touch *thumb;
|
|
struct phys_coords thumb_moved;
|
|
double thumb_mm;
|
|
|
|
thumb = tp_thumb_get_touch(tp);
|
|
if (!thumb)
|
|
return false;
|
|
|
|
if (!tp_touch_active_for_gesture(tp, thumb))
|
|
return false;
|
|
|
|
thumb_moved = tp_gesture_mm_moved(tp, thumb);
|
|
thumb_mm = hypot(thumb_moved.x, thumb_moved.y);
|
|
return thumb_mm >= PINCH_DISAMBIGUATION_MOVE_THRESHOLD;
|
|
}
|
|
|
|
void
|
|
tp_gesture_post_events(struct tp_dispatch *tp, usec_t time, bool ignore_motion)
|
|
{
|
|
if (tp->gesture.finger_count == 0)
|
|
return;
|
|
|
|
/* When tap-and-dragging, force 1fg mode. On clickpads, if the
|
|
* physical button is down, don't allow gestures unless the button
|
|
* is held down by a *thumb*, specifically.
|
|
*/
|
|
if (tp_tap_dragging(tp) || (tp->buttons.is_clickpad && tp->buttons.state &&
|
|
tp->thumb.state == THUMB_STATE_FINGER)) {
|
|
if (tp->gesture.state != GESTURE_STATE_POINTER_MOTION) {
|
|
tp_gesture_cancel(tp, time);
|
|
tp_gesture_handle_event(tp,
|
|
GESTURE_EVENT_POINTER_MOTION_START,
|
|
time);
|
|
}
|
|
tp->gesture.finger_count = 1;
|
|
tp->gesture.finger_count_pending = 0;
|
|
}
|
|
|
|
/* Don't send events when we're unsure in which mode we are */
|
|
if (tp->gesture.finger_count_pending)
|
|
return;
|
|
|
|
/* When pinching, the thumb tends to move slower than the finger,
|
|
* so we may suppress it too early. Give it some time to move.
|
|
*/
|
|
if (usec_cmp(time,
|
|
usec_add(tp->gesture.initial_time,
|
|
DEFAULT_GESTURE_PINCH_TIMEOUT)) < 0 &&
|
|
tp_gesture_thumb_moved(tp))
|
|
tp_thumb_reset(tp);
|
|
|
|
if (tp->gesture.finger_count <= 4)
|
|
tp_gesture_handle_state(tp, time, ignore_motion);
|
|
}
|
|
|
|
void
|
|
tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
|
|
return;
|
|
|
|
evdev_stop_scroll(tp->device, time, LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_end(struct tp_dispatch *tp, usec_t time, enum gesture_cancelled cancelled)
|
|
{
|
|
switch (tp->gesture.state) {
|
|
case GESTURE_STATE_NONE:
|
|
case GESTURE_STATE_UNKNOWN:
|
|
case GESTURE_STATE_SCROLL_START:
|
|
case GESTURE_STATE_PINCH_START:
|
|
case GESTURE_STATE_SWIPE_START:
|
|
case GESTURE_STATE_3FG_DRAG_START:
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
|
|
break;
|
|
case GESTURE_STATE_HOLD:
|
|
case GESTURE_STATE_HOLD_AND_MOTION:
|
|
case GESTURE_STATE_POINTER_MOTION:
|
|
case GESTURE_STATE_SCROLL:
|
|
case GESTURE_STATE_PINCH:
|
|
case GESTURE_STATE_SWIPE:
|
|
case GESTURE_STATE_3FG_DRAG:
|
|
case GESTURE_STATE_3FG_DRAG_RELEASED:
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
|
|
switch (cancelled) {
|
|
case CANCEL_GESTURE:
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_CANCEL, time);
|
|
break;
|
|
case END_GESTURE:
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_END, time);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
tp_gesture_cancel(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
tp_gesture_end(tp, time, CANCEL_GESTURE);
|
|
}
|
|
|
|
void
|
|
tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
|
|
switch (tp->gesture.state) {
|
|
case GESTURE_STATE_NONE:
|
|
case GESTURE_STATE_UNKNOWN:
|
|
case GESTURE_STATE_SCROLL_START:
|
|
case GESTURE_STATE_PINCH_START:
|
|
case GESTURE_STATE_SWIPE_START:
|
|
case GESTURE_STATE_3FG_DRAG_START:
|
|
break;
|
|
case GESTURE_STATE_HOLD:
|
|
break;
|
|
case GESTURE_STATE_HOLD_AND_MOTION:
|
|
case GESTURE_STATE_POINTER_MOTION:
|
|
case GESTURE_STATE_SCROLL:
|
|
case GESTURE_STATE_PINCH:
|
|
case GESTURE_STATE_SWIPE:
|
|
evdev_log_debug(tp->device, "Cancelling motion gestures\n");
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG:
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG_RELEASED:
|
|
break;
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
|
|
evdev_log_debug(tp->device, "Cancelling motion gestures\n");
|
|
tp_gesture_cancel(tp, time);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
tp_gesture_stop(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
tp_gesture_end(tp, time, END_GESTURE);
|
|
}
|
|
|
|
static void
|
|
tp_gesture_finger_count_switch_timeout(usec_t now, void *data)
|
|
{
|
|
struct tp_dispatch *tp = data;
|
|
|
|
if (!tp->gesture.finger_count_pending)
|
|
return;
|
|
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_FINGER_SWITCH_TIMEOUT, now);
|
|
tp->gesture.finger_count = tp->gesture.finger_count_pending;
|
|
tp->gesture.finger_count_pending = 0;
|
|
}
|
|
|
|
static bool
|
|
tp_gesture_debounce_finger_changes(struct tp_dispatch *tp)
|
|
{
|
|
switch (tp->gesture.state) {
|
|
case GESTURE_STATE_NONE:
|
|
case GESTURE_STATE_UNKNOWN:
|
|
case GESTURE_STATE_SCROLL_START:
|
|
case GESTURE_STATE_PINCH_START:
|
|
case GESTURE_STATE_SWIPE_START:
|
|
case GESTURE_STATE_POINTER_MOTION:
|
|
return false;
|
|
case GESTURE_STATE_HOLD:
|
|
case GESTURE_STATE_HOLD_AND_MOTION:
|
|
case GESTURE_STATE_SCROLL:
|
|
case GESTURE_STATE_PINCH:
|
|
case GESTURE_STATE_SWIPE:
|
|
case GESTURE_STATE_3FG_DRAG_START:
|
|
case GESTURE_STATE_3FG_DRAG_RELEASED:
|
|
case GESTURE_STATE_3FG_DRAG:
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
|
|
case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
|
|
return true;
|
|
}
|
|
|
|
abort();
|
|
}
|
|
|
|
void
|
|
tp_gesture_update_finger_state(struct tp_dispatch *tp, usec_t time)
|
|
{
|
|
unsigned int active_touches = 0;
|
|
struct tp_touch *t;
|
|
|
|
tp_for_each_touch(tp, t) {
|
|
if (tp_touch_active_for_gesture(tp, t))
|
|
active_touches++;
|
|
}
|
|
|
|
if (active_touches != tp->gesture.finger_count) {
|
|
/* If all fingers are lifted immediately end the gesture */
|
|
if (active_touches == 0) {
|
|
tp_gesture_stop(tp, time);
|
|
tp->gesture.finger_count = 0;
|
|
tp->gesture.finger_count_pending = 0;
|
|
/* Immediately switch to new mode to avoid initial latency */
|
|
} else if (!tp_gesture_debounce_finger_changes(tp)) {
|
|
tp->gesture.finger_count = active_touches;
|
|
tp->gesture.finger_count_pending = 0;
|
|
/* If in UNKNOWN or POINTER_MOTION state, go back to
|
|
* NONE to re-evaluate leftmost and rightmost touches
|
|
*/
|
|
if (tp->gesture.state == GESTURE_STATE_UNKNOWN ||
|
|
tp->gesture.state == GESTURE_STATE_POINTER_MOTION) {
|
|
tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
|
|
}
|
|
/* Else debounce finger changes */
|
|
} else if (active_touches != tp->gesture.finger_count_pending) {
|
|
tp->gesture.finger_count_pending = active_touches;
|
|
libinput_timer_set(
|
|
&tp->gesture.finger_count_switch_timer,
|
|
usec_add(time, DEFAULT_GESTURE_SWITCH_TIMEOUT));
|
|
}
|
|
} else {
|
|
tp->gesture.finger_count_pending = 0;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
tp_gesture_are_gestures_enabled(struct tp_dispatch *tp)
|
|
{
|
|
return (!tp->semi_mt && tp->num_slots > 1);
|
|
}
|
|
|
|
static enum libinput_config_status
|
|
tp_gesture_set_hold_enabled(struct libinput_device *device,
|
|
enum libinput_config_hold_state enabled)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
if (!tp_gesture_are_gestures_enabled(tp))
|
|
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
|
|
|
|
tp->gesture.hold_enabled = (enabled == LIBINPUT_CONFIG_HOLD_ENABLED);
|
|
|
|
return LIBINPUT_CONFIG_STATUS_SUCCESS;
|
|
}
|
|
|
|
static enum libinput_config_hold_state
|
|
tp_gesture_is_hold_enabled(struct libinput_device *device)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
return tp->gesture.hold_enabled ? LIBINPUT_CONFIG_HOLD_ENABLED
|
|
: LIBINPUT_CONFIG_HOLD_DISABLED;
|
|
}
|
|
|
|
static enum libinput_config_hold_state
|
|
tp_gesture_get_hold_default(struct libinput_device *device)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
return tp_gesture_are_gestures_enabled(tp) ? LIBINPUT_CONFIG_HOLD_ENABLED
|
|
: LIBINPUT_CONFIG_HOLD_DISABLED;
|
|
}
|
|
|
|
static int
|
|
tp_3fg_drag_count(struct libinput_device *device)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
/* If we can't to gestures we can't do 3fg drag */
|
|
if (!tp_gesture_are_gestures_enabled(tp))
|
|
return 0;
|
|
|
|
/* For now return the number of MT slots until we need to figure out
|
|
* if we can implement this on a 2-finger BTN_TOOL_TRIPLETAP device */
|
|
return tp->num_slots;
|
|
}
|
|
|
|
static enum libinput_config_status
|
|
tp_3fg_drag_set_enabled(struct libinput_device *device,
|
|
enum libinput_config_3fg_drag_state enabled)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
if (tp_3fg_drag_count(device) < 3)
|
|
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
|
|
|
|
switch (enabled) {
|
|
case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
|
|
tp->drag_3fg.want_nfingers = 0;
|
|
break;
|
|
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
|
|
tp->drag_3fg.want_nfingers = 3;
|
|
break;
|
|
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
|
|
tp->drag_3fg.want_nfingers = 4;
|
|
break;
|
|
}
|
|
|
|
tp_3fg_drag_apply_config(evdev_device(device));
|
|
|
|
return LIBINPUT_CONFIG_STATUS_SUCCESS;
|
|
}
|
|
|
|
static enum libinput_config_3fg_drag_state
|
|
tp_3fg_drag_get_enabled(struct libinput_device *device)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
switch (tp->drag_3fg.want_nfingers) {
|
|
case 3:
|
|
return LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG;
|
|
case 4:
|
|
return LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG;
|
|
}
|
|
return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
|
|
}
|
|
|
|
static enum libinput_config_3fg_drag_state
|
|
tp_3fg_drag_default(struct tp_dispatch *tp)
|
|
{
|
|
return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
|
|
}
|
|
|
|
static enum libinput_config_3fg_drag_state
|
|
tp_3fg_drag_get_default_enabled(struct libinput_device *device)
|
|
{
|
|
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
|
|
struct tp_dispatch *tp = tp_dispatch(dispatch);
|
|
|
|
return tp_3fg_drag_default(tp);
|
|
}
|
|
|
|
void
|
|
tp_3fg_drag_apply_config(struct evdev_device *device)
|
|
{
|
|
struct tp_dispatch *tp = (struct tp_dispatch *)device->dispatch;
|
|
|
|
if (tp->drag_3fg.want_nfingers == tp->drag_3fg.nfingers)
|
|
return;
|
|
|
|
if (tp->nfingers_down)
|
|
return;
|
|
|
|
tp->drag_3fg.nfingers = tp->drag_3fg.want_nfingers;
|
|
|
|
evdev_log_debug(device,
|
|
"touchpad-3fg-drag: drag is now for %zd fingers\n",
|
|
tp->drag_3fg.nfingers);
|
|
}
|
|
|
|
void
|
|
tp_init_gesture(struct tp_dispatch *tp)
|
|
{
|
|
char timer_name[64];
|
|
|
|
tp->gesture.config.set_hold_enabled = tp_gesture_set_hold_enabled;
|
|
tp->gesture.config.get_hold_enabled = tp_gesture_is_hold_enabled;
|
|
tp->gesture.config.get_hold_default = tp_gesture_get_hold_default;
|
|
tp->device->base.config.gesture = &tp->gesture.config;
|
|
|
|
tp->drag_3fg.config.count = tp_3fg_drag_count;
|
|
tp->drag_3fg.config.set_enabled = tp_3fg_drag_set_enabled;
|
|
tp->drag_3fg.config.get_enabled = tp_3fg_drag_get_enabled;
|
|
tp->drag_3fg.config.get_default = tp_3fg_drag_get_default_enabled;
|
|
tp->device->base.config.drag_3fg = &tp->drag_3fg.config;
|
|
|
|
switch (tp_3fg_drag_default(tp)) {
|
|
case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
|
|
tp->drag_3fg.nfingers = 0;
|
|
break;
|
|
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
|
|
tp->drag_3fg.nfingers = 3;
|
|
break;
|
|
case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
|
|
tp->drag_3fg.nfingers = 4;
|
|
break;
|
|
}
|
|
tp->drag_3fg.want_nfingers = tp->drag_3fg.nfingers;
|
|
|
|
/* two-finger scrolling is always enabled, this flag just
|
|
* decides whether we detect pinch. semi-mt devices are too
|
|
* unreliable to do pinch gestures. */
|
|
tp->gesture.enabled = tp_gesture_are_gestures_enabled(tp);
|
|
|
|
tp->gesture.state = GESTURE_STATE_NONE;
|
|
tp->gesture.hold_enabled = tp_gesture_are_gestures_enabled(tp);
|
|
|
|
snprintf(timer_name,
|
|
sizeof(timer_name),
|
|
"%s gestures",
|
|
evdev_device_get_sysname(tp->device));
|
|
libinput_timer_init(&tp->gesture.finger_count_switch_timer,
|
|
tp_libinput_context(tp),
|
|
timer_name,
|
|
tp_gesture_finger_count_switch_timeout,
|
|
tp);
|
|
|
|
snprintf(timer_name,
|
|
sizeof(timer_name),
|
|
"%s hold",
|
|
evdev_device_get_sysname(tp->device));
|
|
libinput_timer_init(&tp->gesture.hold_timer,
|
|
tp_libinput_context(tp),
|
|
timer_name,
|
|
tp_gesture_hold_timeout,
|
|
tp);
|
|
snprintf(timer_name,
|
|
sizeof(timer_name),
|
|
"%s drag_3fg",
|
|
evdev_device_get_sysname(tp->device));
|
|
libinput_timer_init(&tp->gesture.drag_3fg_timer,
|
|
tp_libinput_context(tp),
|
|
timer_name,
|
|
tp_gesture_3fg_drag_timeout,
|
|
tp);
|
|
snprintf(timer_name,
|
|
sizeof(timer_name),
|
|
"%s drag_or_swipe",
|
|
evdev_device_get_sysname(tp->device));
|
|
libinput_timer_init(&tp->gesture.drag_3fg_or_swipe_timer,
|
|
tp_libinput_context(tp),
|
|
timer_name,
|
|
tp_gesture_3fg_drag_or_swipe_timeout,
|
|
tp);
|
|
}
|
|
|
|
void
|
|
tp_remove_gesture(struct tp_dispatch *tp)
|
|
{
|
|
libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
|
|
libinput_timer_cancel(&tp->gesture.hold_timer);
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
|
|
libinput_timer_cancel(&tp->gesture.drag_3fg_or_swipe_timer);
|
|
}
|