Abort TFD within 50 ms on detection of 4+ fingers

Improved method of disambiguating between three finger drags and four finger gestures.
This commit is contained in:
abc def 2022-02-14 13:59:11 +01:00
parent a2f3b497b1
commit 13c7fa6136
2 changed files with 169 additions and 82 deletions

View file

@ -7,7 +7,7 @@
#include "evdev-mt-touchpad.h" #include "evdev-mt-touchpad.h"
/* when three fingers are detected, this is how long we wait to see if the user /* when three fingers are detected, this is how long we wait to see if the user
actually intends a 3 finger gesture, or is transitioning to e.g. 4 fingers */ actually intends a 3 finger gesture or is transitioning to e.g. 4 fingers */
#define DEFAULT_DRAG3_WAIT_FOR_FINGERS_DURATION ms2us(50) #define DEFAULT_DRAG3_WAIT_FOR_FINGERS_DURATION ms2us(50)
/* The interval between three fingers touching and a button press being /* The interval between three fingers touching and a button press being
performed, if the fingers remain stationary */ performed, if the fingers remain stationary */
@ -42,8 +42,9 @@ tfd_state_to_str(enum tp_tfd_state state)
{ {
switch(state) { switch(state) {
CASE_RETURN_STRING(TFD_STATE_IDLE); CASE_RETURN_STRING(TFD_STATE_IDLE);
CASE_RETURN_STRING(TFD_STATE_POSSIBLE_BEGIN); // CASE_RETURN_STRING(TFD_STATE_POSSIBLE_BEGIN);
CASE_RETURN_STRING(TFD_STATE_AWAIT_DRAG); CASE_RETURN_STRING(TFD_STATE_AWAIT_DRAG);
CASE_RETURN_STRING(TFD_STATE_DRAG_AND_DEBOUNCE);
CASE_RETURN_STRING(TFD_STATE_DRAG); CASE_RETURN_STRING(TFD_STATE_DRAG);
CASE_RETURN_STRING(TFD_STATE_POSSIBLE_ZERO_FINGERS); CASE_RETURN_STRING(TFD_STATE_POSSIBLE_ZERO_FINGERS);
CASE_RETURN_STRING(TFD_STATE_AWAIT_RESUME); CASE_RETURN_STRING(TFD_STATE_AWAIT_RESUME);
@ -237,12 +238,15 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp,
enum tfd_event event, uint64_t time, int nfingers_down) enum tfd_event event, uint64_t time, int nfingers_down)
{ {
switch (event) { switch (event) {
case TFD_EVENT_TOUCH_COUNT_INCREASE:
case TFD_EVENT_TOUCH_COUNT_DECREASE: case TFD_EVENT_TOUCH_COUNT_DECREASE:
/* turns out it's distracting to accidentally initiate dragging while
removing fingers after a 4+ swipe gesture. */
break;
case TFD_EVENT_TOUCH_COUNT_INCREASE:
if (nfingers_down == 3) { if (nfingers_down == 3) {
tp->tfd.state = TFD_STATE_POSSIBLE_BEGIN; tp->tfd.state = TFD_STATE_AWAIT_DRAG;
// tp_tfd_set_button_press_delay_timer(tp, time); tp_tfd_set_button_press_delay_timer(tp, time);
tp_tfd_set_await_more_fingers_timer(tp, time); // tp_tfd_set_await_more_fingers_timer(tp, time);
} }
break; break;
case TFD_EVENT_MOTION: case TFD_EVENT_MOTION:
@ -260,70 +264,70 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp,
// unused -- TODO: remove if drag_and_debounce is successful
/* Waiting for more fingers. Three fingers have been detected, but it might be /* Waiting for more fingers. Three fingers have been detected, but it might be
a transitory phase towards 4 or more fingers, which should not begin the a transitory phase towards 4 or more fingers, which should not begin the
drag. */ drag. */
static void // static void
tp_tfd_possible_begin_handle_event(struct tp_dispatch *tp, // tp_tfd_possible_begin_handle_event(struct tp_dispatch *tp,
struct tp_touch *t, // struct tp_touch *t,
enum tfd_event event, uint64_t time, // enum tfd_event event, uint64_t time,
int nfingers_down) // int nfingers_down)
{ // {
switch (event) { // switch (event) {
case TFD_EVENT_TOUCH_COUNT_INCREASE: // case TFD_EVENT_TOUCH_COUNT_INCREASE:
switch (nfingers_down) { // switch (nfingers_down) {
case 0: // case 0:
case 1: // case 1:
case 2: // case 2:
case 3: // case 3:
break; // bug? // break; // bug?
default: // default:
tp_tfd_unpin_fingers(tp); // tp_tfd_unpin_fingers(tp);
tp->tfd.state = TFD_STATE_IDLE; // tp->tfd.state = TFD_STATE_IDLE;
tp_tfd_clear_timer(tp); // tp_tfd_clear_timer(tp);
break; // break;
} // }
break; // break;
case TFD_EVENT_MOTION: // case TFD_EVENT_MOTION:
break; // break;
case TFD_EVENT_RESUME_TIMEOUT: // case TFD_EVENT_RESUME_TIMEOUT:
break; // bug // break; // bug
case TFD_EVENT_TOUCH_COUNT_DECREASE: // case TFD_EVENT_TOUCH_COUNT_DECREASE:
/* a decrease forces immediate evaluation as if the timer had fired */ // /* a decrease forces immediate evaluation as if the timer had fired */
tp_tfd_clear_timer(tp); // tp_tfd_clear_timer(tp);
/* fallthrough */ // /* fallthrough */
case TFD_EVENT_TIMEOUT: // case TFD_EVENT_TIMEOUT:
/* time to check whether we have 3 fingers touching */ // /* time to check whether we have 3 fingers touching */
switch (nfingers_down) { // switch (nfingers_down) {
case 0: // case 0:
case 1: // case 1:
case 2: // case 2:
default: // default:
tp_tfd_unpin_fingers(tp); // tp_tfd_unpin_fingers(tp);
tp->tfd.state = TFD_STATE_IDLE; // tp->tfd.state = TFD_STATE_IDLE;
break; // break;
case 3: // case 3:
tp_tfd_unpin_fingers(tp); // tp_tfd_unpin_fingers(tp);
// TODO: compensate for the duration of this state // // TODO: compensate for the duration of this state
tp_tfd_set_button_press_delay_timer(tp, time); // tp_tfd_set_button_press_delay_timer(tp, time);
tp->tfd.state = TFD_STATE_AWAIT_DRAG; // tp->tfd.state = TFD_STATE_AWAIT_DRAG;
break; // break;
} // }
break; // break;
case TFD_EVENT_TAP: // case TFD_EVENT_TAP:
case TFD_EVENT_BUTTON: // case TFD_EVENT_BUTTON:
tp_tfd_unpin_fingers(tp); // tp_tfd_unpin_fingers(tp);
tp->tfd.state = TFD_STATE_IDLE; // tp->tfd.state = TFD_STATE_IDLE;
tp_tfd_clear_timer(tp); // tp_tfd_clear_timer(tp);
break; // break;
} // }
} // }
/* finishes hold gestures and cancels other gestures */ /* takes appropriate action to cancel or finish a gesture in progress */
static void static void
tp_tfd_interrupt_gestures(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) tp_tfd_interrupt_gestures(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
{ {
@ -387,9 +391,10 @@ tp_tfd_await_drag_handle_event(struct tp_dispatch *tp,
/* show special consideration for overlapping hold gestures */ /* show special consideration for overlapping hold gestures */
tp_tfd_interrupt_gestures(tp, t, time); tp_tfd_interrupt_gestures(tp, t, time);
tp->tfd.state = TFD_STATE_DRAG; // tp_tfd_clear_timer(tp);
tp_tfd_set_await_more_fingers_timer(tp, time);
tp->tfd.state = TFD_STATE_DRAG_AND_DEBOUNCE;
tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
tp_tfd_clear_timer(tp);
} }
break; break;
case TFD_EVENT_RESUME_TIMEOUT: case TFD_EVENT_RESUME_TIMEOUT:
@ -401,14 +406,18 @@ tp_tfd_await_drag_handle_event(struct tp_dispatch *tp,
/* show special consideration for overlapping hold gestures */ /* show special consideration for overlapping hold gestures */
tp_tfd_interrupt_gestures(tp, t, time); tp_tfd_interrupt_gestures(tp, t, time);
tp->tfd.state = TFD_STATE_DRAG; tp_tfd_set_await_more_fingers_timer(tp, time);
tp->tfd.state = TFD_STATE_DRAG_AND_DEBOUNCE;
tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
break; break;
case TFD_EVENT_TAP: case TFD_EVENT_TAP:
case TFD_EVENT_BUTTON: case TFD_EVENT_BUTTON:
// TODO: undecided // TODO: undecided
//tp->tfd.state = TFD_STATE_IDLE; //tp->tfd.state = TFD_STATE_IDLE;
//tp_tfd_clear_timer(tp); /* if a button is pressed while waiting for timeout, cancel the timeout
since it will most likely just result in a somewhat unanticipated second
button press */
tp_tfd_clear_timer(tp);
break; break;
} }
} }
@ -419,6 +428,71 @@ tp_tfd_await_drag_handle_event(struct tp_dispatch *tp,
position updates -- use the fastest finger only */ position updates -- use the fastest finger only */
/* Brief cancellable drag state to disambiguate between drag and a 4+ finger swipe.
Otherwise same as the drag state. Replaces POSSIBLE_BEGIN state. */
static void
tp_tfd_drag_and_debounce_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tfd_event event, uint64_t time,
int nfingers_down)
{
switch (event) {
// case TFD_EVENT_TOUCH_COUNT:
case TFD_EVENT_TOUCH_COUNT_INCREASE:
case TFD_EVENT_TOUCH_COUNT_DECREASE:
switch (nfingers_down) {
case 0:
tp_tfd_pin_fingers(tp);
tp_tfd_set_await_resume_timer(tp, time);
tp->tfd.state = TFD_STATE_AWAIT_RESUME;
// tp_tfd_pin_fingers(tp);
// tp_tfd_set_await_resume_timer(tp, time);
// tp_tfd_set_await_more_fingers_timer(tp, time);
// tp->tfd.state = TFD_STATE_POSSIBLE_RESUME;
break;
case 1:
tp_tfd_pin_fingers(tp);
tp_tfd_set_await_resume_timer(tp, time);
tp_tfd_set_await_more_fingers_timer(tp, time);
tp->tfd.state = TFD_STATE_POSSIBLE_ZERO_FINGERS;
break;
case 2:
/* Seems far-fetched to interpret 0 -> 3 -> 2 fingers as an intent
to scroll. Fallthrough. */
case 3:
break;
default:
/* 4+ fingers; the drag should abort to give way to 4+ finger
gestures */
// tp_tfd_unpin_fingers(tp);
tp->tfd.state = TFD_STATE_IDLE;
tp_tfd_clear_timer(tp);
tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
break;
case TFD_EVENT_MOTION:
break;
case TFD_EVENT_RESUME_TIMEOUT:
log_tfd_bug(tp, event, nfingers_down);
break; // bug
case TFD_EVENT_TIMEOUT:
// 4+ finger gesture didn't happen -- time to exit the debounce state
tp->tfd.state = TFD_STATE_DRAG;
break;
case TFD_EVENT_TAP:
break;
case TFD_EVENT_BUTTON:
// tp_tfd_unpin_fingers(tp);
tp->tfd.state = TFD_STATE_IDLE;
tp_tfd_clear_timer(tp);
tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
}
static void static void
tp_tfd_drag_handle_event(struct tp_dispatch *tp, tp_tfd_drag_handle_event(struct tp_dispatch *tp,
struct tp_touch *t, struct tp_touch *t,
@ -462,7 +536,6 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp,
case TFD_EVENT_BUTTON: case TFD_EVENT_BUTTON:
tp_tfd_unpin_fingers(tp); tp_tfd_unpin_fingers(tp);
tp->tfd.state = TFD_STATE_IDLE; tp->tfd.state = TFD_STATE_IDLE;
tp_tfd_clear_resume_timer(tp);
tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
break; break;
} }
@ -797,7 +870,6 @@ tp_tfd_handle_event(struct tp_dispatch *tp,
previous_state = tp->tfd.state; previous_state = tp->tfd.state;
assert(nfingers_down >= 0); assert(nfingers_down >= 0);
// assert(nfingers_down < 6); // TODO: temp, remove
switch (event) { switch (event) {
case TFD_EVENT_MOTION: case TFD_EVENT_MOTION:
@ -822,14 +894,18 @@ tp_tfd_handle_event(struct tp_dispatch *tp,
tp_tfd_idle_handle_event(tp, t, event, time, nfingers_down); tp_tfd_idle_handle_event(tp, t, event, time, nfingers_down);
break; break;
case TFD_STATE_POSSIBLE_BEGIN: // case TFD_STATE_POSSIBLE_BEGIN:
tp_tfd_possible_begin_handle_event(tp, t, event, time, nfingers_down); // tp_tfd_possible_begin_handle_event(tp, t, event, time, nfingers_down);
break; // break;
case TFD_STATE_AWAIT_DRAG: case TFD_STATE_AWAIT_DRAG:
tp_tfd_await_drag_handle_event(tp, t, event, time, nfingers_down); tp_tfd_await_drag_handle_event(tp, t, event, time, nfingers_down);
break; break;
case TFD_STATE_DRAG_AND_DEBOUNCE:
tp_tfd_drag_and_debounce_handle_event(tp, t, event, time, nfingers_down);
break;
case TFD_STATE_DRAG: case TFD_STATE_DRAG:
tp_tfd_drag_handle_event(tp, t, event, time, nfingers_down); tp_tfd_drag_handle_event(tp, t, event, time, nfingers_down);
break; break;
@ -1033,16 +1109,17 @@ void
tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time) tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time)
{ {
switch (tp->tfd.state) { switch (tp->tfd.state) {
case TFD_STATE_IDLE:
// case TFD_STATE_POSSIBLE_BEGIN:
case TFD_STATE_AWAIT_DRAG:
case TFD_STATE_DRAG_AND_DEBOUNCE:
case TFD_STATE_DRAG:
break;
case TFD_STATE_POSSIBLE_ZERO_FINGERS: case TFD_STATE_POSSIBLE_ZERO_FINGERS:
case TFD_STATE_AWAIT_RESUME: case TFD_STATE_AWAIT_RESUME:
case TFD_STATE_POSSIBLE_RESUME: case TFD_STATE_POSSIBLE_RESUME:
tp_tfd_handle_event(tp, NULL, TFD_EVENT_TAP, time, tp->tfd.finger_count); tp_tfd_handle_event(tp, NULL, TFD_EVENT_TAP, time, tp->tfd.finger_count);
break; break;
case TFD_STATE_IDLE:
case TFD_STATE_POSSIBLE_BEGIN:
case TFD_STATE_AWAIT_DRAG:
case TFD_STATE_DRAG:
break;
} }
} }
@ -1101,12 +1178,16 @@ bool
tp_tfd_dragging(const struct tp_dispatch *tp) tp_tfd_dragging(const struct tp_dispatch *tp)
{ {
switch (tp->tfd.state) { switch (tp->tfd.state) {
case TFD_STATE_IDLE:
case TFD_STATE_AWAIT_DRAG:
return false;
case TFD_STATE_DRAG_AND_DEBOUNCE:
case TFD_STATE_DRAG: case TFD_STATE_DRAG:
case TFD_STATE_POSSIBLE_ZERO_FINGERS:
case TFD_STATE_AWAIT_RESUME: case TFD_STATE_AWAIT_RESUME:
case TFD_STATE_POSSIBLE_RESUME: case TFD_STATE_POSSIBLE_RESUME:
return true; return true;
default:
return false;
} }
return false;
} }

View file

@ -141,16 +141,22 @@ enum tp_tfd_state {
/* waiting for 3 fingers */ /* waiting for 3 fingers */
TFD_STATE_IDLE, TFD_STATE_IDLE,
/* [debounce] disambiguate between starting a drag and a possible 4+ gesture */ // not used -- TODO: remove after testing drag_and_debounce
TFD_STATE_POSSIBLE_BEGIN, // /* [debounce] disambiguate between starting a drag and a possible 4+ gesture */
// TFD_STATE_POSSIBLE_BEGIN,
/* 3 fingers touching, waiting for motion or timeout */ /* 3 fingers touching, waiting for motion or timeout */
TFD_STATE_AWAIT_DRAG, TFD_STATE_AWAIT_DRAG,
/* [debounce] same as drag state, but cancellable in case of possible 4+
gesture. Replacement state for POSSIBLE_BEGIN */
TFD_STATE_DRAG_AND_DEBOUNCE,
/* 3 fingers touching and button press has been output */ /* 3 fingers touching and button press has been output */
TFD_STATE_DRAG, TFD_STATE_DRAG,
/* [debounce] drag-lock; 1 finger touching, possibly going to 0 fingers */ /* [debounce] drag-lock; 1 finger touching, possibly going to 0 fingers.
Prevents premature cancellation of AWAIT_RESUME by 1 finger motion events. */
TFD_STATE_POSSIBLE_ZERO_FINGERS, TFD_STATE_POSSIBLE_ZERO_FINGERS,
/* drag-lock; waiting for 3 finger drag continuation */ /* drag-lock; waiting for 3 finger drag continuation */
TFD_STATE_AWAIT_RESUME, TFD_STATE_AWAIT_RESUME,
/* TODO: possible to replace this state with DRAG_AND_DEBOUNCE as well? */
/* [debounce] disambiguate between drag continuation and a possible 4+ gesture */ /* [debounce] disambiguate between drag continuation and a possible 4+ gesture */
TFD_STATE_POSSIBLE_RESUME, TFD_STATE_POSSIBLE_RESUME,
}; };