From 696d3c71f365af985d24b76344b3818d744ba365 Mon Sep 17 00:00:00 2001 From: satrmb <10471-satrmb_true-email-is-private_contact-via-web@gitlab.freedesktop.org> Date: Fri, 7 Aug 2020 17:52:20 +0200 Subject: [PATCH] touchpad: if hold-and-tap is on, treat half-released taps as lesser taps The expectation is that the user means the remaining fingers of the tap to be holding fingers which just happened to be put down at roughly the same time as the released tapping fingers. This necessitates handling a tap with four or more fingers, because only three of them might get lifted, making it actually a three-finger tap and at least one more holding finger. Though not strictly necessary, these states can also be reached if hold-and-tap is off: it may catch a many-finger-tap to end drag-lock, or a three-finger-tap joined by a palm, as demonstrated in the previously existing test case touchpad_tap_palm_on_touch_4. Signed-off-by: satrmb <10471-satrmb@users.noreply.gitlab.freedesktop.org> --- doc/touchpad-tap-state-machine.svg | 2 +- src/evdev-mt-touchpad-tap.c | 486 +++++++++++++++++++++++++++-- src/evdev-mt-touchpad.h | 5 + test/test-touchpad-tap.c | 8 + 4 files changed, 468 insertions(+), 33 deletions(-) diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg index 45b90d4d..6887559f 100644 --- a/doc/touchpad-tap-state-machine.svg +++ b/doc/touchpad-tap-state-machine.svg @@ -1,3 +1,3 @@ -nonoyes(this section exists for [n] = 1, [n] = 2, and [n] = 3)(this section exists for [m] = 1, [m] = 2, and [m] = 3),for a total of 9 times due to combination with [n]yesyesyesnononoyesnoyesyesyesnononononoyesyesnonoyesnonoyesyesyesyesyesyesyes
IDLE
IDLE
firstfinger downTOUCH_TOUCHTOUCHsecondfinger downTOUCH_TOUCHTOUCH_2thirdfinger downTOUCH_TOUCHTOUCH_3TOUCH_2_RELEASEyeseitherfinger upyesremainingfinger upanyfinger upTOUCH_3_RELEASEeitherfinger upTOUCH_3_RELEASE_2yesremainingfinger upTOUCH_IDLEyesTOUCH_IDLETOUCH_IDLEyesTOUCH_IDLETOUCH_IDLEpalmTOUCH_DEADeitherfinger palmTOUCH_DEADyesanyfinger palmTOUCH_DEADyeseitherfinger palmTOUCH_DEADyesremainingfinger palmTOUCH_DEADyesremainingfinger palmTOUCH_DEAD2-finger tap3-finger tapyesfinger upTOUCH_IDLE1-finger tapfourthfinger down
IDLE
IDLE
TAPPEDyes2-finger tappalmfinger upbutton [n]pressbutton [n]releaseDRAGGING_OR_DOUBLETAPfinger downtap-and-dragtimeouttimeoutmove >thresholdDRAGGINGpalmfinger upnoyesdrag-lockenabled?DRAGLOCK_WAITDRAGLOCK_CONTINUEfinger uppalmtimeoutmove >thresholddrag-locktimeout1-finger tapyesnonon-palmfinger count== 0?finger down1-finger taptimeouttimeouthold-and-tap enabled?HOLD3-finger tapnohold-and-tap enabled?timeouthold-and-tap enabled?3-finger tapTOUCH_2_HOLDTOUCH_3_HOLDtimeouthold-and-tap enabled?timeoutyesnohold-and-tap enabled?timeouthold-and-tap enabled?palmfinger upTOUCH_DEADTOUCH_IDLEeitherfinger palmeitherfinger upTOUCH_DEADTOUCH_IDLEanyfinger palmanyfinger upTOUCH_DEADTOUCH_IDLETOUCH_DEADDEADBUTTONclickpadbuttonreleaseall fingersTOUCH_DEADnoyesnon-palmfinger count== 0?clickpadbuttonpressanyfinger upTOUCH_IDLEanyfinger palmmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdhold-and-tap enabled?move >thresholdhold-and-tap enabled?move >thresholdhold-and-tap enabled?3-finger tapclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpress1-finger tap3-finger tapbutton [n]releaseBUTTONclickpadbuttonreleaseclickpadbuttonpressbutton [n]releaseclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressbutton [n]press
[n] = 1
[n] = 1
[n] = 1
[n] = 1
[n] = 2
[n] = 2
[n] = 3
[n] = 3
notap-and-dragenabled?move >thresholdyesnonon-palmfinger count== 0?nonon-palmfinger count== 0?secondfinger downsecondfinger down1-finger tap2-finger tap
[m] = 1
[m] = 1
yes
[m] = 2
[m] = 2
3-finger tap
[m] = 3
[m] = 3
no[n] == [m]?button [m]pressbutton [m]releaseset tap stateto DEADfinger downTOUCH_TOUCHyeshold-and-tap enabled?TOUCH_DEADnoyesnothat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLETOUCH_IDLETOUCH_IDLEyesthat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLEthat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLEthat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLEthumbyesnodrag state== IDLE?yesnon-palmfinger count== 1?TOUCH_DEADthumbTOUCH_DEADnonodrag state== IDLE?drag state== IDLE?drag state== IDLE?nohold-and-tap enabled?3-finger tapnohold-and-tap enabled?nohold-and-tap enabled?finger downTOUCH_TOUCHnohold-and-tap enabled?3-finger tapfinger downTOUCH_TOUCHhold-and-tap enabled?finger downTOUCH_TOUCHnohold-and-tap enabled?3-finger tapall fingersexcept the new oneTOUCH_DEADall fingersTOUCH_DEADfourthfinger downTOUCH_DEADto draggingstate machine:move >threshold
Viewer does not support full SVG 1.1
\ No newline at end of file +(this section exists for [n] = 1, [n] = 2, and [n] = 3)(this section exists for [m] = 1, [m] = 2, and [m] = 3),for a total of 9 times due to combination with [n]yesnoyesnoyesnonononononononononoyesnononoyesnoyesnoyesyesyesnononononononoyesyesyesnonoyesnonoyesyesyesyesyesyesyesyesyesyesyesyesyesyes
IDLE
IDLE
firstfinger downTOUCH_TOUCHTOUCHsecondfinger downTOUCH_TOUCHTOUCH_2thirdfinger downTOUCH_TOUCHTOUCH_3TOUCH_2_RELEASEyeseitherfinger upyesremainingfinger upanyfinger upTOUCH_3_RELEASEeitherfinger upTOUCH_3_RELEASE_2yesremainingfinger upTOUCH_IDLEyesTOUCH_IDLETOUCH_IDLEyesTOUCH_IDLETOUCH_IDLEpalmTOUCH_DEADeitherfinger palmTOUCH_DEADyesanyfinger palmTOUCH_DEADyeseitherfinger palmTOUCH_DEADyesremainingfinger palmTOUCH_DEADyesremainingfinger palmTOUCH_DEAD2-finger tap3-finger tapyesfinger upTOUCH_IDLE1-finger tapfourthfinger down
IDLE
IDLE
TAPPEDyes2-finger tappalmfinger upbutton [n]pressbutton [n]releaseDRAGGING_OR_DOUBLETAPfinger downtap-and-dragtimeouttimeoutmove >thresholdDRAGGINGpalmfinger upnoyesdrag-lockenabled?DRAGLOCK_WAITDRAGLOCK_CONTINUEfinger uppalmtimeoutmove >thresholddrag-locktimeout1-finger tapyesnonon-palmfinger count== 0?finger down1-finger taptimeouttimeouthold-and-tap enabled?HOLD3-finger tapnohold-and-tap enabled?timeouthold-and-tap enabled?3-finger tapTOUCH_2_HOLDTOUCH_3_HOLDtimeouthold-and-tap enabled?timeoutyesnohold-and-tap enabled?timeouthold-and-tap enabled?palmfinger upTOUCH_DEADTOUCH_IDLEeitherfinger palmeitherfinger upTOUCH_DEADTOUCH_IDLEanyfinger palmanyfinger upTOUCH_DEADTOUCH_IDLETOUCH_TOUCHTOUCH_4_PLUSyesanyfinger palmyesanyfinger upTOUCH_IDLEyesTOUCH_DEADTOUCH_4_PLUS_HOLDtimeouthold-and-tap enabled?anyfinger upTOUCH_IDLEanyfinger palmTOUCH_DEADfourthfinger downTOUCH_TOUCHTOUCH_4_PLUS_RELEASEyesanyfinger palmyesanyfinger upyesTOUCH_DEADTOUCH_IDLEtimeouthold-and-tap enabled?TOUCH_4_PLUS_RELEASE_2timeouthold-and-tap enabled?yesanyfinger palmyesanyfinger upTOUCH_DEADTOUCH_IDLETOUCH_4_PLUS_RELEASE_3timeouthold-and-tap enabled?yesanyfinger palmyesanyfinger upyesTOUCH_DEADTOUCH_IDLE1-finger tap2-finger tap3-finger tapDEADBUTTONclickpadbuttonreleaseall fingersTOUCH_DEADnoyesnon-palmfinger count== 0?clickpadbuttonpressanyfinger upTOUCH_IDLEanyfinger palmmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdmove >thresholdhold-and-tap enabled?move >thresholdmove >thresholdhold-and-tap enabled?yesnohold-and-tap enabled?move >thresholdhold-and-tap enabled?3-finger tapmove >thresholdyesnohold-and-tap enabled?move >thresholdyesnohold-and-tap enabled?clickpadbuttonpressclickpadbuttonpressclickpadbuttonpressyesnohold-and-tap enabled?1-finger tapclickpadbuttonpressyesnohold-and-tap enabled?2-finger tapclickpadbuttonpressclickpadbuttonpress3-finger tapyeshold-and-tap enabled?clickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpress1-finger tap3-finger tapbutton [n]releaseBUTTONclickpadbuttonreleaseclickpadbuttonpressbutton [n]releaseclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressclickpadbuttonpressbutton [n]press
[n] = 1
[n] = 1
[n] = 1
[n] = 1
[n] = 2
[n] = 2
[n] = 3
[n] = 3
notap-and-dragenabled?move >thresholdyesnonon-palmfinger count== 0?nonon-palmfinger count== 0?secondfinger downsecondfinger down1-finger tap2-finger tap
[m] = 1
[m] = 1
yes
[m] = 2
[m] = 2
3-finger tap
[m] = 3
[m] = 3
no[n] == [m]?button [m]pressbutton [m]releaseset tap stateto DEADfinger downTOUCH_TOUCHyeshold-and-tap enabled?TOUCH_DEADnoyesnothat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLETOUCH_IDLETOUCH_IDLETOUCH_IDLETOUCH_TOUCHfinger count == 0?yesthat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLEthat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?TOUCH_IDLETOUCH_IDLEthat fingerTOUCH_TOUCH?that fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLETOUCH_IDLEnothat fingerTOUCH_TOUCH?nothat fingerTOUCH_TOUCH?TOUCH_IDLETOUCH_TOUCHfinger count == 1?TOUCH_TOUCHfinger count == 2?noTOUCH_TOUCHfinger count == 3?thumbyesnodrag state== IDLE?yesnon-palmfinger count== 1?TOUCH_DEADthumbTOUCH_DEADnoyesnodrag state== IDLE?drag state== IDLE?drag state== IDLE?drag state== IDLE?yesnodrag state== DRAGLOCK_CONTINUE?1-finger tapnoTOUCH_TOUCHfinger count == 3?nohold-and-tap enabled?3-finger tapnohold-and-tap enabled?nohold-and-tap enabled?nonon-palmfinger count== 1?nononon-palmfinger count== 2?nonon-palmfinger count== 3?finger downTOUCH_TOUCHnohold-and-tap enabled?3-finger tapfinger downTOUCH_TOUCHhold-and-tap enabled?finger downTOUCH_TOUCHnohold-and-tap enabled?3-finger tapfinger downTOUCH_TOUCHyeshold-and-tap enabled?finger downTOUCH_TOUCHyeshold-and-tap enabled?finger downTOUCH_TOUCHyeshold-and-tap enabled?1-finger tap2-finger tap3-finger tapall fingersexcept the new oneTOUCH_DEADfinger downTOUCH_TOUCHfinger downTOUCH_TOUCHall fingersTOUCH_DEADno
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index c819127d..8fa527b0 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -72,6 +72,11 @@ tap_state_to_str(enum tp_tap_state state) CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD); CASE_RETURN_STRING(TAP_STATE_TOUCH_3_RELEASE); CASE_RETURN_STRING(TAP_STATE_TOUCH_3_RELEASE_2); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_HOLD); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_RELEASE); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_RELEASE_2); + CASE_RETURN_STRING(TAP_STATE_TOUCH_4_PLUS_RELEASE_3); CASE_RETURN_STRING(TAP_STATE_BUTTON); CASE_RETURN_STRING(TAP_STATE_DEAD); } @@ -240,8 +245,10 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp, 1, LIBINPUT_BUTTON_STATE_PRESSED); /* taps may end with fingers remaining down, - * those should not start a drag */ - if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) { + * those should not start a drag; + * also, the touch in the process of being released + * is still counted for nfingers_down */ + if (tp->tap.drag_enabled && tp->tap.nfingers_down == 1) { tp->tap.drag_state = DRAG_STATE_1FGTAP_TAPPED; tp_tap_set_drag_timer(tp, time, 1); } else { @@ -256,7 +263,7 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp, tp->tap.saved_press_time, 2, LIBINPUT_BUTTON_STATE_PRESSED); - if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) { + if (tp->tap.drag_enabled && tp->tap.nfingers_down == 1) { tp->tap.drag_state = DRAG_STATE_2FGTAP_TAPPED; tp_tap_set_drag_timer(tp, time, 2); } else { @@ -271,7 +278,7 @@ tp_drag_idle_handle_event(struct tp_dispatch *tp, tp->tap.saved_press_time, 3, LIBINPUT_BUTTON_STATE_PRESSED); - if (tp->tap.drag_enabled && tp->tap.nfingers_down == 0) { + if (tp->tap.drag_enabled && tp->tap.nfingers_down == 1) { tp->tap.drag_state = DRAG_STATE_3FGTAP_TAPPED; tp_tap_set_drag_timer(tp, time, 3); } else { @@ -434,7 +441,7 @@ tp_drag_dragging_handle_event(struct tp_dispatch *tp, case TAP_EVENT_TOUCH: break; case TAP_EVENT_RELEASE: - if (tp->tap.nfingers_down == 0) { + if (tp->tap.nfingers_down == 1) { if (tp->tap.drag_lock != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED) { enum tp_drag_state dest[3] = { DRAG_STATE_1FGTAP_DRAGLOCK_WAIT, @@ -471,7 +478,7 @@ tp_drag_dragging_handle_event(struct tp_dispatch *tp, case TAP_EVENT_THUMB: break; case TAP_EVENT_PALM: - if (tp->tap.nfingers_down == 0) { + if (tp->tap.nfingers_down == 1) { tp_tap_notify(tp, time, nfingers_tapped, @@ -814,7 +821,7 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp, if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { tp->tap.saved_release_time = time; tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; else tp->tap.state = TAP_STATE_DEAD; @@ -847,7 +854,7 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp, break; case TAP_EVENT_PALM: if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; else tp->tap.state = TAP_STATE_DEAD; @@ -1001,6 +1008,7 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); tp_tap_kill_all_touches(tp, t); tp->tap.state = TAP_STATE_TOUCH; } else @@ -1011,23 +1019,29 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp, case TAP_EVENT_RELEASE: if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; else tp->tap.state = TAP_STATE_DEAD; } break; case TAP_EVENT_MOTION: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_TIMEOUT: - if (!tp->tap.hold_tap_enabled && - tp->tap.drag_state == DRAG_STATE_IDLE) + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + } else if (tp->tap.drag_state == DRAG_STATE_IDLE) tp->tap.state = TAP_STATE_HOLD; else tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_BUTTON: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); tp->tap.state = TAP_STATE_BUTTON; break; case TAP_EVENT_BUTTON_UP: @@ -1046,7 +1060,7 @@ tp_tap_touch2_release_handle_event(struct tp_dispatch *tp, * an issue. */ tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; else tp->tap.state = TAP_STATE_DEAD; @@ -1068,10 +1082,9 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_DEAD; - /* this cannot be a tap anymore, get the dragging state machine - * out of DRAGGING_OR_DOUBLETAP or DRAGLOCK_CONTINUE */ - tp_drag_handle_event(tp, t, TAP_EVENT_MOTION, time); + tp->tap.state = TAP_STATE_TOUCH_4_PLUS; + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); break; case TAP_EVENT_RELEASE: if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { @@ -1119,7 +1132,11 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: - tp->tap.state = TAP_STATE_DEAD; + /* Don't even try to revive the previous three touches + * for a tap like in the other hold states, because any tap + * with more than three fingers can only end drag-lock. + * If we got here the timeout for that has already elapsed. */ + tp->tap.state = TAP_STATE_TOUCH_4_PLUS_HOLD; break; case TAP_EVENT_RELEASE: tp->tap.state = TAP_STATE_TOUCH_2_HOLD; @@ -1157,6 +1174,7 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); tp_tap_kill_all_touches(tp, t); tp->tap.state = TAP_STATE_TOUCH; } else { @@ -1173,14 +1191,17 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp, } break; case TAP_EVENT_MOTION: - if (!tp->tap.hold_tap_enabled) + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + else tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_TIMEOUT: - if (tp->tap.hold_tap_enabled) + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); tp->tap.state = TAP_STATE_DEAD; - else { + } else { tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); if (tp->tap.drag_state == DRAG_STATE_IDLE) tp->tap.state = TAP_STATE_TOUCH_2_HOLD; @@ -1189,7 +1210,9 @@ tp_tap_touch3_release_handle_event(struct tp_dispatch *tp, } break; case TAP_EVENT_BUTTON: - if (!tp->tap.hold_tap_enabled) + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + else tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); tp->tap.state = TAP_STATE_BUTTON; break; @@ -1219,6 +1242,7 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp, switch (event) { case TAP_EVENT_TOUCH: if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); tp_tap_kill_all_touches(tp, t); tp->tap.state = TAP_STATE_TOUCH; } else { @@ -1231,21 +1255,24 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp, case TAP_EVENT_RELEASE: if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; else tp->tap.state = TAP_STATE_DEAD; } break; case TAP_EVENT_MOTION: - if (!tp->tap.hold_tap_enabled) + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); + else tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_TIMEOUT: - if (tp->tap.hold_tap_enabled) + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); tp->tap.state = TAP_STATE_DEAD; - else { + } else { tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); if (tp->tap.drag_state == DRAG_STATE_IDLE) tp->tap.state = TAP_STATE_HOLD; @@ -1254,7 +1281,9 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp, } break; case TAP_EVENT_BUTTON: - if (!tp->tap.hold_tap_enabled) + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); + else tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); tp->tap.state = TAP_STATE_BUTTON; break; @@ -1266,7 +1295,7 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp, case TAP_EVENT_PALM: if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; else tp->tap.state = TAP_STATE_DEAD; @@ -1280,6 +1309,384 @@ tp_tap_touch3_release2_handle_event(struct tp_dispatch *tp, } } +static int +tp_count_alive_touches(struct tp_dispatch *tp) +{ + /* In the TOUCH_4_PLUS group of states we lose track of the exact + * number of fingers down which are still alive for tapping. + * There are several relatively unlikely events that may bring us back + * into the states corresponding to a smaller number of fingers, + * most prominently touches turning into palms. + * In these cases we need to figure out how many remaining + * alive touches there are. */ + struct tp_touch *t; + int nfingers = 0; + + /* When hold-and-tap is disabled, either all or no fingers are tapping, + * and the latter case occurs in the DEAD and BUTTON states only + * which don't have the need to ask for alive touches, + * so we have the count stored as the number of fingers. */ + if (!tp->tap.hold_tap_enabled) + return tp->tap.nfingers_down; + + /* If the setting is enabled however, tapping can happen with only some + * of the fingers down, i.e. there may be to-be-ignored holding touches, + * so we need to actually count the tapping ones. */ + tp_for_each_touch(tp, t) { + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) + nfingers++; + } + return nfingers; +} + +static void +tp_tap_touch4plus_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + tp->tap.state = TAP_STATE_TOUCH_4_PLUS_RELEASE; + tp->tap.saved_release_time = time; + tp_tap_set_timer(tp, time); + } + break; + case TAP_EVENT_MOTION: + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_TIMEOUT: + if (!tp->tap.hold_tap_enabled && + tp->tap.drag_state == DRAG_STATE_IDLE) + tp->tap.state = TAP_STATE_TOUCH_4_PLUS_HOLD; + else + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_BUTTON; + break; + case TAP_EVENT_BUTTON_UP: + log_tap_bug(tp, t, event); + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + int remaining = tp_count_alive_touches(tp) - 1; + assert(remaining >= 3); + if (remaining == 3) + tp->tap.state = TAP_STATE_TOUCH_3; + } + break; + case TAP_EVENT_1FGTAP: + case TAP_EVENT_2FGTAP: + case TAP_EVENT_3FGTAP: + log_tap_bug(tp, t, event); + break; + } +} + +static void +tp_tap_touch4plus_hold_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + break; + case TAP_EVENT_RELEASE: { + int remaining = tp_count_alive_touches(tp) - 1; + assert(remaining >= 3); + if (remaining == 3) + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + break; + } + case TAP_EVENT_MOTION: + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_TIMEOUT: + break; + case TAP_EVENT_BUTTON: + tp->tap.state = TAP_STATE_BUTTON; + break; + case TAP_EVENT_BUTTON_UP: + log_tap_bug(tp, t, event); + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: { + int remaining = tp_count_alive_touches(tp) - 1; + assert(remaining >= 3); + if (remaining == 3) + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + break; + } + case TAP_EVENT_1FGTAP: + case TAP_EVENT_2FGTAP: + case TAP_EVENT_3FGTAP: + log_tap_bug(tp, t, event); + break; + } +} + +static void +tp_tap_touch4plus_release_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + tp_tap_kill_all_touches(tp, t); + tp->tap.state = TAP_STATE_TOUCH; + } else + tp->tap.state = TAP_STATE_TOUCH_4_PLUS; + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + tp->tap.state = TAP_STATE_TOUCH_4_PLUS_RELEASE_2; + tp_tap_set_timer(tp, time); + } + break; + case TAP_EVENT_MOTION: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_TIMEOUT: + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + } else { + if (tp->tap.drag_state == DRAG_STATE_IDLE) { + int remaining = tp_count_alive_touches(tp); + assert(remaining >= 3); + if (remaining == 3) + tp->tap.state = TAP_STATE_TOUCH_3_HOLD; + else + tp->tap.state = TAP_STATE_TOUCH_4_PLUS_HOLD; + } else + tp->tap.state = TAP_STATE_DEAD; + } + break; + case TAP_EVENT_BUTTON: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, time); + tp->tap.state = TAP_STATE_BUTTON; + break; + case TAP_EVENT_BUTTON_UP: + log_tap_bug(tp, t, event); + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + int remaining = tp_count_alive_touches(tp) - 1; + assert(remaining >= 2); + if (remaining == 2) + tp->tap.state = TAP_STATE_TOUCH_3_RELEASE; + } + break; + case TAP_EVENT_1FGTAP: + case TAP_EVENT_2FGTAP: + case TAP_EVENT_3FGTAP: + log_tap_bug(tp, t, event); + break; + } +} + +static void +tp_tap_touch4plus_release2_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); + tp_tap_kill_all_touches(tp, t); + tp->tap.state = TAP_STATE_TOUCH; + } else { + int remaining = tp_count_alive_touches(tp); + assert(remaining >= 3); + if (remaining == 3) + tp->tap.state = TAP_STATE_TOUCH_3; + else + tp->tap.state = TAP_STATE_TOUCH_4_PLUS; + } + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + tp->tap.state = TAP_STATE_TOUCH_4_PLUS_RELEASE_3; + tp_tap_set_timer(tp, time); + } + break; + case TAP_EVENT_MOTION: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_TIMEOUT: + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + } else { + if (tp->tap.drag_state == DRAG_STATE_IDLE) { + int remaining = min(tp_count_alive_touches(tp), + 4); + enum tp_tap_state dest[3] = { + TAP_STATE_TOUCH_2_HOLD, + TAP_STATE_TOUCH_3_HOLD, + TAP_STATE_TOUCH_4_PLUS_HOLD, + }; + assert(remaining >= 2); + tp->tap.state = dest[remaining - 2]; + } else + tp->tap.state = TAP_STATE_DEAD; + } + break; + case TAP_EVENT_BUTTON: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_2FGTAP, time); + tp->tap.state = TAP_STATE_BUTTON; + break; + case TAP_EVENT_BUTTON_UP: + log_tap_bug(tp, t, event); + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + int remaining = tp_count_alive_touches(tp) - 1; + assert(remaining >= 1); + if (remaining == 1) + tp->tap.state = TAP_STATE_TOUCH_3_RELEASE_2; + } + break; + case TAP_EVENT_1FGTAP: + case TAP_EVENT_2FGTAP: + case TAP_EVENT_3FGTAP: + log_tap_bug(tp, t, event); + break; + } +} + +static void +tp_tap_touch4plus_release3_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tap_event event, uint64_t time) +{ + + switch (event) { + case TAP_EVENT_TOUCH: + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); + tp_tap_kill_all_touches(tp, t); + tp->tap.state = TAP_STATE_TOUCH; + } else { + int remaining = min(tp_count_alive_touches(tp), + 4); + enum tp_tap_state dest[3] = { + TAP_STATE_TOUCH_2, + TAP_STATE_TOUCH_3, + TAP_STATE_TOUCH_4_PLUS, + }; + assert(remaining >= 2); + tp->tap.state = dest[remaining - 2]; + } + tp->tap.saved_press_time = time; + tp_tap_set_timer(tp, time); + break; + case TAP_EVENT_RELEASE: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + /* four-plus-finger taps are to be ignored, + * except for the purpose of stopping drag-lock. + * We also don't care about remaining fingers + * of this tap anymore */ + if (tp->tap.nfingers_down == 1) + tp->tap.state = TAP_STATE_IDLE; + else + tp->tap.state = TAP_STATE_DEAD; + if (tp->tap.drag_state == DRAG_STATE_1FGTAP_DRAGLOCK_CONTINUE || + tp->tap.drag_state == DRAG_STATE_2FGTAP_DRAGLOCK_CONTINUE || + tp->tap.drag_state == DRAG_STATE_3FGTAP_DRAGLOCK_CONTINUE) { + /* all taps have the same result here, + * a one-finger tap does the trick without + * introducing another event just for this */ + tp_drag_handle_event(tp, t, TAP_EVENT_1FGTAP, + time); + } + } + break; + case TAP_EVENT_MOTION: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + break; + case TAP_EVENT_TIMEOUT: + if (tp->tap.hold_tap_enabled) { + tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); + tp->tap.state = TAP_STATE_DEAD; + } else { + if (tp->tap.drag_state == DRAG_STATE_IDLE) { + int remaining = min(tp_count_alive_touches(tp), + 4); + enum tp_tap_state dest[4] = { + TAP_STATE_HOLD, + TAP_STATE_TOUCH_2_HOLD, + TAP_STATE_TOUCH_3_HOLD, + TAP_STATE_TOUCH_4_PLUS_HOLD, + }; + assert(remaining >= 1); + tp->tap.state = dest[remaining - 1]; + } else + tp->tap.state = TAP_STATE_DEAD; + } + break; + case TAP_EVENT_BUTTON: + if (tp->tap.hold_tap_enabled) + tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, time); + tp->tap.state = TAP_STATE_BUTTON; + break; + case TAP_EVENT_BUTTON_UP: + log_tap_bug(tp, t, event); + break; + case TAP_EVENT_THUMB: + break; + case TAP_EVENT_PALM: + if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { + int remaining = tp_count_alive_touches(tp) - 1; + if (remaining == 0) { + tp_drag_handle_event(tp, t, TAP_EVENT_3FGTAP, + time); + if (tp->tap.nfingers_down == 1) + tp->tap.state = TAP_STATE_IDLE; + else + tp->tap.state = TAP_STATE_DEAD; + } + } + break; + case TAP_EVENT_1FGTAP: + case TAP_EVENT_2FGTAP: + case TAP_EVENT_3FGTAP: + log_tap_bug(tp, t, event); + break; + } +} + static void tp_tap_button_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -1329,7 +1736,7 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp, } break; case TAP_EVENT_RELEASE: - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; break; case TAP_EVENT_MOTION: @@ -1344,7 +1751,7 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp, case TAP_EVENT_THUMB: break; case TAP_EVENT_PALM: - if (tp->tap.nfingers_down == 0) + if (tp->tap.nfingers_down == 1) tp->tap.state = TAP_STATE_IDLE; break; case TAP_EVENT_1FGTAP: @@ -1396,6 +1803,21 @@ tp_tap_handle_event(struct tp_dispatch *tp, case TAP_STATE_TOUCH_3_RELEASE_2: tp_tap_touch3_release2_handle_event(tp, t, event, time); break; + case TAP_STATE_TOUCH_4_PLUS: + tp_tap_touch4plus_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_PLUS_HOLD: + tp_tap_touch4plus_hold_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_PLUS_RELEASE: + tp_tap_touch4plus_release_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_PLUS_RELEASE_2: + tp_tap_touch4plus_release2_handle_event(tp, t, event, time); + break; + case TAP_STATE_TOUCH_4_PLUS_RELEASE_3: + tp_tap_touch4plus_release3_handle_event(tp, t, event, time); + break; case TAP_STATE_BUTTON: tp_tap_button_handle_event(tp, t, event, time); break; @@ -1511,9 +1933,9 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) assert(!t->tap.is_palm); t->tap.is_palm = true; if (t->state != TOUCH_BEGIN) { - assert(tp->tap.nfingers_down > 0); - tp->tap.nfingers_down--; + assert(tp->tap.nfingers_down >= 1); tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time); + tp->tap.nfingers_down--; } t->tap.state = TAP_TOUCH_STATE_DEAD; } else if (t->state == TOUCH_BEGIN) { @@ -1532,8 +1954,8 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) } else if (t->state == TOUCH_END) { if (t->was_down) { assert(tp->tap.nfingers_down >= 1); - tp->tap.nfingers_down--; tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time); + tp->tap.nfingers_down--; } t->tap.state = TAP_TOUCH_STATE_IDLE; } else if (tp->tap.state != TAP_STATE_IDLE && diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 3e7f7d2c..1c601886 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -113,6 +113,11 @@ enum tp_tap_state { TAP_STATE_TOUCH_3_HOLD, TAP_STATE_TOUCH_3_RELEASE, TAP_STATE_TOUCH_3_RELEASE_2, + TAP_STATE_TOUCH_4_PLUS, + TAP_STATE_TOUCH_4_PLUS_HOLD, + TAP_STATE_TOUCH_4_PLUS_RELEASE, + TAP_STATE_TOUCH_4_PLUS_RELEASE_2, + TAP_STATE_TOUCH_4_PLUS_RELEASE_3, TAP_STATE_BUTTON, /**< clickpad button pressed */ TAP_STATE_DEAD, /**< clickpad button released, or finger moved */ }; diff --git a/test/test-touchpad-tap.c b/test/test-touchpad-tap.c index 90f27c5c..591ea273 100644 --- a/test/test-touchpad-tap.c +++ b/test/test-touchpad-tap.c @@ -5146,6 +5146,14 @@ START_TEST(touchpad_tap_palm_on_touch_4) litest_touch_up(dev, (this + 2) % 4); litest_touch_up(dev, (this + 3) % 4); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_timeout_tap(); + litest_assert_button_event(li, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); } END_TEST