gestures: use events to change between states

Refactor the gesture state machine to emit events to change between states.

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
This commit is contained in:
José Expósito 2021-05-27 19:19:12 +02:00 committed by Peter Hutterer
parent d5636eb934
commit b5b6f835af

View file

@ -34,6 +34,15 @@
#define PINCH_DISAMBIGUATION_MOVE_THRESHOLD 1.5 /* mm */
enum gesture_event {
GESTURE_EVENT_RESET,
GESTURE_EVENT_FINGER_DETECTED,
GESTURE_EVENT_POINTER_MOTION,
GESTURE_EVENT_SCROLL,
GESTURE_EVENT_SWIPE,
GESTURE_EVENT_PINCH,
};
static inline const char*
gesture_state_to_str(enum tp_gesture_state state)
{
@ -48,6 +57,20 @@ gesture_state_to_str(enum tp_gesture_state state)
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_FINGER_DETECTED);
CASE_RETURN_STRING(GESTURE_EVENT_POINTER_MOTION);
CASE_RETURN_STRING(GESTURE_EVENT_SCROLL);
CASE_RETURN_STRING(GESTURE_EVENT_SWIPE);
CASE_RETURN_STRING(GESTURE_EVENT_PINCH);
}
return NULL;
}
static struct device_float_coords
tp_get_touches_delta(struct tp_dispatch *tp, bool average)
{
@ -436,7 +459,183 @@ tp_gesture_apply_scroll_constraints(struct tp_dispatch *tp,
}
}
static enum tp_gesture_state
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 void
tp_gesture_none_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_FINGER_DETECTED:
tp->gesture.state = GESTURE_STATE_UNKNOWN;
break;
case GESTURE_EVENT_POINTER_MOTION:
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
break;
case GESTURE_EVENT_SCROLL:
tp->gesture.state = GESTURE_STATE_SCROLL;
break;
case GESTURE_EVENT_RESET:
case GESTURE_EVENT_SWIPE:
case GESTURE_EVENT_PINCH:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_unknown_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_RESET:
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_POINTER_MOTION:
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
break;
case GESTURE_EVENT_SCROLL:
tp_gesture_set_scroll_buildup(tp);
tp->gesture.state = GESTURE_STATE_SCROLL;
break;
case GESTURE_EVENT_SWIPE:
tp->gesture.state = GESTURE_STATE_SWIPE;
break;
case GESTURE_EVENT_PINCH:
tp_gesture_init_pinch(tp);
tp->gesture.state = GESTURE_STATE_PINCH;
break;
case GESTURE_EVENT_FINGER_DETECTED:
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)
{
switch(event) {
case GESTURE_EVENT_RESET:
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_POINTER_MOTION:
case GESTURE_EVENT_SCROLL:
case GESTURE_EVENT_SWIPE:
case GESTURE_EVENT_PINCH:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_scroll_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_RESET:
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_POINTER_MOTION:
case GESTURE_EVENT_SCROLL:
case GESTURE_EVENT_SWIPE:
case GESTURE_EVENT_PINCH:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_pinch_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_RESET:
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_POINTER_MOTION:
case GESTURE_EVENT_SCROLL:
case GESTURE_EVENT_SWIPE:
case GESTURE_EVENT_PINCH:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_swipe_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
switch(event) {
case GESTURE_EVENT_RESET:
tp->gesture.state = GESTURE_STATE_NONE;
break;
case GESTURE_EVENT_FINGER_DETECTED:
case GESTURE_EVENT_POINTER_MOTION:
case GESTURE_EVENT_SCROLL:
case GESTURE_EVENT_SWIPE:
case GESTURE_EVENT_PINCH:
log_gesture_bug(tp, event);
break;
}
}
static void
tp_gesture_handle_event(struct tp_dispatch *tp,
enum gesture_event event,
uint64_t time)
{
enum tp_gesture_state oldstate;
oldstate = tp->gesture.state;
switch(tp->gesture.state) {
case GESTURE_STATE_NONE:
tp_gesture_none_handle_event(tp, event, time);
break;
case GESTURE_STATE_UNKNOWN:
tp_gesture_unknown_handle_event(tp, event, time);
break;
case GESTURE_STATE_POINTER_MOTION:
tp_gesture_pointer_motion_handle_event(tp, event, time);
break;
case GESTURE_STATE_SCROLL:
tp_gesture_scroll_handle_event(tp, event, time);
break;
case GESTURE_STATE_PINCH:
tp_gesture_pinch_handle_event(tp, event, time);
break;
case GESTURE_STATE_SWIPE:
tp_gesture_swipe_handle_event(tp, event, time);
break;
}
if (oldstate != tp->gesture.state) {
evdev_log_debug(tp->device,
"gesture state %s → %s → %s\n",
gesture_state_to_str(oldstate),
gesture_event_to_str(event),
gesture_state_to_str(tp->gesture.state));
}
}
static void
tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *first = tp->gesture.touches[0],
@ -451,17 +650,21 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
double max_move = 4.0; /* max movement threshold in mm - ignore other touch */
if (tp->gesture.finger_count == 1) {
if (tp_has_pending_pointer_motion(tp, time))
return GESTURE_STATE_POINTER_MOTION;
return GESTURE_STATE_UNKNOWN;
if (tp_has_pending_pointer_motion(tp, time)) {
tp_gesture_handle_event(tp,
GESTURE_EVENT_POINTER_MOTION,
time);
}
return;
}
/* If we have more fingers than slots, we don't know where the
* fingers are. Default to swipe */
if (tp->gesture.enabled && tp->gesture.finger_count > 2 &&
tp->gesture.finger_count > tp->num_slots)
return GESTURE_STATE_SWIPE;
tp->gesture.finger_count > tp->num_slots) {
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE, time);
return;
}
/* Need more margin for error when there are more fingers */
max_move += 2.0 * (tp->gesture.finger_count - 2);
@ -479,7 +682,7 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
/* If both touches moved less than a mm, we cannot decide yet */
if (first_mm < 1 && second_mm < 1)
return GESTURE_STATE_UNKNOWN;
return;
/* Pick the thumb as the lowest point on the touchpad */
if (first->point.y > second->point.y) {
@ -497,11 +700,12 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
if ((!tp->gesture.enabled ||
(distance_mm.x < 40.0 && distance_mm.y < 7.0)) &&
time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
if (tp->gesture.finger_count == 2) {
tp_gesture_set_scroll_buildup(tp);
return GESTURE_STATE_SCROLL;
}
return GESTURE_STATE_SWIPE;
if (tp->gesture.finger_count == 2)
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
else
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE, time);
return;
}
/* If one touch exceeds the max_move threshold while the other has not
@ -517,7 +721,8 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
*/
if (tp->thumb.detect_thumbs && thumb_mm < min_move) {
tp_thumb_suppress(tp, thumb);
return GESTURE_STATE_NONE;
tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
return;
}
/* If gestures detection is disabled, or if finger is still
@ -526,8 +731,8 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
*/
if ((!tp->gesture.enabled || finger_mm < min_move) &&
tp->gesture.finger_count == 2) {
tp_gesture_set_scroll_buildup(tp);
return GESTURE_STATE_SCROLL;
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
return;
}
/* If more than 2 fingers are involved, and the thumb moves
@ -537,8 +742,8 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
tp->gesture.finger_count > 2 &&
tp->gesture.enabled &&
tp->thumb.pinch_eligible) {
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH, time);
return;
}
}
@ -546,7 +751,7 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
* tell what kind of gesture this is.
*/
if ((first_mm < min_move) || (second_mm < min_move))
return GESTURE_STATE_UNKNOWN;
return;
/* Both touches have exceeded the min_move threshold, so we have a
* valid gesture. Update gesture initial time and get directions so
@ -561,18 +766,18 @@ tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
if (tp->gesture.finger_count > tp->num_slots ||
tp_gesture_same_directions(dir1, dir2)) {
if (tp->gesture.finger_count == 2) {
tp_gesture_set_scroll_buildup(tp);
return GESTURE_STATE_SCROLL;
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
return;
}
if (tp->gesture.enabled) {
return GESTURE_STATE_SWIPE;
tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE, time);
return;
}
}
/* If the touches are moving away from each other, this is a pinch */
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH, time);
}
static bool
@ -602,7 +807,7 @@ tp_gesture_is_pinch(struct tp_dispatch *tp)
return true;
}
static enum tp_gesture_state
static void
tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *first, *second;
@ -611,13 +816,22 @@ tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time)
unsigned int i;
ntouches = tp_gesture_get_active_touches(tp, touches, 4);
if (ntouches < 2)
return GESTURE_STATE_UNKNOWN;
if (ntouches == 0)
return;
if (ntouches == 1) {
tp_gesture_handle_event(tp,
GESTURE_EVENT_FINGER_DETECTED,
time);
return;
}
if (!tp->gesture.enabled) {
if (ntouches == 2)
return GESTURE_STATE_SCROLL;
return GESTURE_STATE_NONE;
tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
return;
}
first = touches[0];
@ -651,7 +865,7 @@ tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time)
}
if (first == second)
return GESTURE_STATE_NONE;
return;
}
@ -661,32 +875,30 @@ tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time)
tp->gesture.touches[0] = first;
tp->gesture.touches[1] = second;
return GESTURE_STATE_UNKNOWN;
tp_gesture_handle_event(tp, GESTURE_EVENT_FINGER_DETECTED, time);
}
static enum tp_gesture_state
static void
tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
{
return tp_gesture_detect_motion_gestures(tp, time);
tp_gesture_detect_motion_gestures(tp, time);
}
static enum tp_gesture_state
static void
tp_gesture_handle_state_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
if (tp->queued & TOUCHPAD_EVENT_MOTION)
tp_gesture_post_pointer_motion(tp, time);
return GESTURE_STATE_POINTER_MOTION;
}
static enum tp_gesture_state
static void
tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
{
struct device_float_coords raw;
struct normalized_coords delta;
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
return GESTURE_STATE_SCROLL;
return;
/* We may confuse a pinch for a scroll initially,
* allow ourselves to correct our guess.
@ -694,8 +906,8 @@ tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
if (time < (tp->gesture.initial_time + DEFAULT_GESTURE_PINCH_TIMEOUT) &&
tp_gesture_is_pinch(tp)) {
tp_gesture_cancel(tp, time);
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH, time);
return;
}
raw = tp_get_average_touches_delta(tp);
@ -704,7 +916,7 @@ tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
delta = tp_filter_motion_unaccelerated(tp, &raw, time);
if (normalized_is_zero(delta))
return GESTURE_STATE_SCROLL;
return;
tp_gesture_start(tp, time);
tp_gesture_apply_scroll_constraints(tp, &raw, &delta, time);
@ -712,11 +924,9 @@ tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
time,
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
&delta);
return GESTURE_STATE_SCROLL;
}
static enum tp_gesture_state
static void
tp_gesture_handle_state_swipe(struct tp_dispatch *tp, uint64_t time)
{
struct device_float_coords raw;
@ -733,11 +943,9 @@ tp_gesture_handle_state_swipe(struct tp_dispatch *tp, uint64_t time)
tp->gesture.finger_count,
&delta, &unaccel);
}
return GESTURE_STATE_SWIPE;
}
static enum tp_gesture_state
static void
tp_gesture_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
{
double angle, angle_delta, distance, scale;
@ -762,7 +970,7 @@ tp_gesture_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
if (normalized_is_zero(delta) && device_float_is_zero(fdelta) &&
scale == tp->gesture.prev_scale && angle_delta == 0.0)
return GESTURE_STATE_PINCH;
return;
unaccel = tp_filter_motion_unaccelerated(tp, &fdelta, time);
tp_gesture_start(tp, time);
@ -772,44 +980,28 @@ tp_gesture_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
&delta, &unaccel, scale, angle_delta);
tp->gesture.prev_scale = scale;
return GESTURE_STATE_PINCH;
}
static void
tp_gesture_post_gesture(struct tp_dispatch *tp, uint64_t time)
{
enum tp_gesture_state oldstate = tp->gesture.state;
if (tp->gesture.state == GESTURE_STATE_NONE)
tp->gesture.state =
tp_gesture_handle_state_none(tp, time);
tp_gesture_handle_state_none(tp, time);
if (tp->gesture.state == GESTURE_STATE_UNKNOWN)
tp->gesture.state =
tp_gesture_handle_state_unknown(tp, time);
tp_gesture_handle_state_unknown(tp, time);
if (tp->gesture.state == GESTURE_STATE_POINTER_MOTION)
tp->gesture.state =
tp_gesture_handle_state_pointer_motion(tp, time);
tp_gesture_handle_state_pointer_motion(tp, time);
if (tp->gesture.state == GESTURE_STATE_SCROLL)
tp->gesture.state =
tp_gesture_handle_state_scroll(tp, time);
tp_gesture_handle_state_scroll(tp, time);
if (tp->gesture.state == GESTURE_STATE_SWIPE)
tp->gesture.state =
tp_gesture_handle_state_swipe(tp, time);
tp_gesture_handle_state_swipe(tp, time);
if (tp->gesture.state == GESTURE_STATE_PINCH)
tp->gesture.state =
tp_gesture_handle_state_pinch(tp, time);
if (oldstate != tp->gesture.state)
evdev_log_debug(tp->device,
"gesture state: %s → %s\n",
gesture_state_to_str(oldstate),
gesture_state_to_str(tp->gesture.state));
tp_gesture_handle_state_pinch(tp, time);
}
static bool
@ -843,7 +1035,9 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
tp->thumb.state == THUMB_STATE_FINGER)) {
if (tp->gesture.state != GESTURE_STATE_POINTER_MOTION) {
tp_gesture_cancel(tp, time);
tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
tp_gesture_handle_event(tp,
GESTURE_EVENT_POINTER_MOTION,
time);
}
tp->gesture.finger_count = 1;
tp->gesture.finger_count_pending = 0;
@ -880,10 +1074,10 @@ tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled)
{
enum tp_gesture_state state = tp->gesture.state;
tp->gesture.state = GESTURE_STATE_NONE;
if (!tp->gesture.started)
if (!tp->gesture.started) {
tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
return;
}
switch (state) {
case GESTURE_STATE_NONE:
@ -912,6 +1106,7 @@ tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled)
}
tp->gesture.started = false;
tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
}
void
@ -960,10 +1155,15 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
} else if (!tp->gesture.started) {
tp->gesture.finger_count = active_touches;
tp->gesture.finger_count_pending = 0;
/* If in UNKNOWN state, go back to NONE to
* re-evaluate leftmost and rightmost touches
/* If in UNKNOWN or POINTER_MOTION state, go back to
* NONE to re-evaluate leftmost and rightmost touches
*/
tp->gesture.state = GESTURE_STATE_NONE;
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;