From 89ee46cd297e838ec7dac4828044397949ef19ff Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Fri, 17 Dec 2021 00:42:50 +0100 Subject: [PATCH 1/7] Three finger dragging (TFD) state machine Including explicit states for 4+ finger gesture disambiguation, drag-lock timeout, and click/tap to drop. Signed-off-by: Temp Name --- meson.build | 1 + src/evdev-mt-touchpad-gestures.c | 5 +- src/evdev-mt-touchpad-tap.c | 4 + src/evdev-mt-touchpad-tfd.c | 1306 ++++++++++++++++++++++++++++++ src/evdev-mt-touchpad.c | 8 + src/evdev-mt-touchpad.h | 52 ++ 6 files changed, 1374 insertions(+), 2 deletions(-) create mode 100644 src/evdev-mt-touchpad-tfd.c diff --git a/meson.build b/meson.build index dccc32c2..9e29012d 100644 --- a/meson.build +++ b/meson.build @@ -350,6 +350,7 @@ src_libinput = src_libfilter + [ 'src/evdev-middle-button.c', 'src/evdev-mt-touchpad.c', 'src/evdev-mt-touchpad-tap.c', + 'src/evdev-mt-touchpad-tfd.c', 'src/evdev-mt-touchpad-thumb.c', 'src/evdev-mt-touchpad-buttons.c', 'src/evdev-mt-touchpad-edge-scroll.c', diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 9802d106..ae577fc3 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -1303,7 +1303,7 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time, * physical button is down, don't allow gestures unless the button * is held down by a *thumb*, specifically. */ - if (tp_tap_dragging(tp) || + if (tp_tap_dragging(tp) || tp_tfd_dragging(tp) || (tp->buttons.is_clickpad && tp->buttons.state && tp->thumb.state == THUMB_STATE_FINGER)) { if (tp->gesture.state != GESTURE_STATE_POINTER_MOTION) { @@ -1430,7 +1430,8 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time) active_touches++; } - if (active_touches != tp->gesture.finger_count) { + if (active_touches != tp->gesture.finger_count || + (active_touches == 3 && true)) { // tp->tfd.three_finger_dragging_enabled)) { /* If all fingers are lifted immediately end the gesture */ if (active_touches == 0) { tp_gesture_stop(tp, time); diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 3c76c3d6..c689cdc1 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -143,10 +143,14 @@ tp_tap_notify(struct tp_dispatch *tp, else tp->tap.buttons_pressed &= ~bit(nfingers); + evdev_pointer_notify_button(tp->device, time, button, state); + + if (state != LIBINPUT_BUTTON_STATE_PRESSED) + tp_tfd_handle_tap(tp, time); } static void diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c new file mode 100644 index 00000000..54fca8fe --- /dev/null +++ b/src/evdev-mt-touchpad-tfd.c @@ -0,0 +1,1306 @@ +/* + * Copyright © 2013-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 +#include +#include + +#include "evdev-mt-touchpad.h" + +/* 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 */ +#define DEFAULT_DRAG3_WAIT_FOR_FINGERS_DURATION ms2us(50) +/* The interval between three fingers touching and a button press being +performed, if the fingers remain stationary */ +#define DEFAULT_DRAG3_INITIAL_DELAY ms2us(350) +/* The time window during which you can continue a 3 finger drag by reapplying +three fingers. ~700-800 ms seems ideal. */ +#define DEFAULT_DRAG3_WAIT_FOR_RESUME_DURATION ms2us(720) +/* The speed at which the *released* finger needs to travel for the drag to +continue with a single finger */ +#define DEFAULT_DRAG3_1F_CONTINUATION_SPEED 40 /* mm/s */ + +enum tfd_event { + TFD_EVENT_MOTION, + TFD_EVENT_TOUCH_COUNT_INCREASE, + TFD_EVENT_TOUCH_COUNT_DECREASE, + TFD_EVENT_BUTTON, + TFD_EVENT_TAP, + TFD_EVENT_TIMEOUT, + TFD_EVENT_RESUME_TIMEOUT, +}; + +/***************************************** + * + * Look at the state diagram in doc/three-finger-drag-state-machine.svg + * (generated with https://www.diagrams.net) + * + * Any changes in this file must be represented in the diagram. + */ + +static inline const char* +tfd_state_to_str(enum tp_tfd_state state) +{ + switch(state) { + CASE_RETURN_STRING(TFD_STATE_IDLE); + CASE_RETURN_STRING(TFD_STATE_POSSIBLE_DRAG); + CASE_RETURN_STRING(TFD_STATE_DRAG); + CASE_RETURN_STRING(TFD_STATE_AWAIT_RESUME); + CASE_RETURN_STRING(TFD_STATE_POSSIBLE_RESUME); + // CASE_RETURN_STRING(TFD_STATE_3F_DRAG_WAIT1); + // CASE_RETURN_STRING(TFD_STATE_3F_DRAG_WAIT2); + + // CASE_RETURN_STRING(TFD_STATE_DEAD); + } + return NULL; +} + +static inline const char* +tfd_event_to_str(enum tfd_event event) +{ + switch(event) { + CASE_RETURN_STRING(TFD_EVENT_MOTION); + // CASE_RETURN_STRING(TFD_EVENT_MOTION0); + // CASE_RETURN_STRING(TFD_EVENT_MOTION1); + // CASE_RETURN_STRING(TFD_EVENT_MOTION2); + // CASE_RETURN_STRING(TFD_EVENT_MOTION3); + // CASE_RETURN_STRING(TFD_EVENT_MOTION4PLUS); + // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT); + CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT_INCREASE); + CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT_DECREASE); + // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT0); + // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT1); + // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT2); + // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT3); + // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT4PLUS); + CASE_RETURN_STRING(TFD_EVENT_BUTTON); + CASE_RETURN_STRING(TFD_EVENT_TAP); + CASE_RETURN_STRING(TFD_EVENT_TIMEOUT); + CASE_RETURN_STRING(TFD_EVENT_RESUME_TIMEOUT); + } + return NULL; +} + +// static inline void +// log_tfd_bug(struct tp_dispatch *tp, struct tp_touch *t, enum tfd_event event) +// { +// evdev_log_bug_libinput(tp->device, +// "%d: invalid tap event %s in state %s\n", +// t->index, +// tfd_event_to_str(event), +// tfd_state_to_str(tp->tfd.state)); + +// } + +static void +tp_tfd_notify(struct tp_dispatch *tp, + uint64_t time, + int nfingers, + enum libinput_button_state state) +{ + int32_t button; + int32_t button_map[2][3] = { + { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE }, + { BTN_LEFT, BTN_MIDDLE, BTN_RIGHT }, + }; + + assert(tp->tap.map < ARRAY_LENGTH(button_map)); + + if (nfingers < 1 || nfingers > 3) + return; + + button = button_map[tp->tap.map][nfingers - 1]; + + if (state == LIBINPUT_BUTTON_STATE_PRESSED) { + assert(!(tp->tfd.buttons_pressed & (1 << nfingers))); + tp->tfd.buttons_pressed |= (1 << nfingers); + } + else { + assert(tp->tfd.buttons_pressed & (1 << nfingers)); + tp->tfd.buttons_pressed &= ~(1 << nfingers); + } + + evdev_pointer_notify_button(tp->device, + time, + button, + state); +} + +// static void +// tp_tfd_set_timer(struct tp_dispatch *tp, uint64_t time) +// { +// libinput_timer_set(&tp->tfd.timer, time + DEFAULT_TAP_TIMEOUT_PERIOD); +// } + +// static void +// tp_tfd_set_drag_timer(struct tp_dispatch *tp, uint64_t time, +// int nfingers_tapped) +// { +// libinput_timer_set(&tp->tfd.timer, +// time + DEFAULT_DRAG_TIMEOUT_PERIOD_BASE + +// (nfingers_tapped * +// DEFAULT_DRAG_TIMEOUT_PERIOD_PERFINGER)); +// } + +static void +tp_tfd_set_3f_drag_initial_delay_timer(struct tp_dispatch *tp, uint64_t time)//, + // uint64_t duration) +{ + // libinput_timer_set(&tp->tfd.timer, time + duration); + libinput_timer_set(&tp->tfd.timer, time + DEFAULT_DRAG3_INITIAL_DELAY); +} + +static void +tp_tfd_set_more_fingers_timer(struct tp_dispatch *tp, uint64_t time)//, + // uint64_t duration) +{ + // libinput_timer_set(&tp->tfd.timer, time + duration); + libinput_timer_set(&tp->tfd.timer, time + DEFAULT_DRAG3_WAIT_FOR_FINGERS_DURATION); +} + +static void +tp_tfd_set_3f_drag_wait_timer(struct tp_dispatch *tp, uint64_t time) +{ + libinput_timer_set(&tp->tfd.resume_timer, time + DEFAULT_DRAG3_WAIT_FOR_RESUME_DURATION); +} + + +// static void +// tp_tfd_set_draglock_timer(struct tp_dispatch *tp, uint64_t time) +// { +// libinput_timer_set(&tp->tfd.timer, +// time + DEFAULT_DRAGLOCK_TIMEOUT_PERIOD); +// } + +static void +tp_tfd_clear_timer(struct tp_dispatch *tp) +{ + libinput_timer_cancel(&tp->tfd.timer); +} + +static void +tp_tfd_clear_resume_timer(struct tp_dispatch *tp) +{ + libinput_timer_cancel(&tp->tfd.resume_timer); +} + +// static void +// tp_tfd_move_to_dead(struct tp_dispatch *tp, struct tp_touch *t) +// { +// tp->tfd.state = TFD_STATE_DEAD; +// t->tfd.state = TAP_TOUCH_STATE_DEAD; +// tp_tfd_clear_timer(tp); +// } + +bool +tp_touch_active_for_tfd(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && + t->palm.state == PALM_NONE; //&& + // !t->pinned.is_pinned && + // !tp_thumb_ignored_for_gesture(tp, t) && + + // not sure what the reason is for these + // tp_button_touch_active(tp, t) && + // tp_edge_scroll_touch_active(tp, t); +} + + +static struct device_coords +tp_get_aggregate_touches_coords(const struct tp_dispatch *tp, bool average) +{ + struct tp_touch *t; + unsigned int i, nactive = 0; + struct device_coords total = {0, 0}; + + for (i = 0; i < tp->num_slots; i++) { + t = &tp->touches[i]; + + if (!tp_touch_active_for_tfd(tp, t)) + continue; + + nactive++; + + if (t->dirty) { + total.x += t->point.x; + total.y += t->point.y; + } + } + + if (!average || nactive == 0) + return total; + + total.x /= nactive; + total.y /= nactive; + + return total; +} + + + +//-comments = questions +/*-comments = explanations, intents, ToDos... */ + +/* TODO [done -- at the right place?]: disable 3 finger gestures */ + +static void +tp_tfd_pin_fingers(struct tp_dispatch *tp) +{ + tp->tfd.cursor_pinned = true; + tp->tfd.pinned_point = tp_get_aggregate_touches_coords(tp, true); + // struct tp_touch *t; + // tp_for_each_touch(tp, t) { + // tp_tfd_pin_finger(tp, t); + // } +} + +static void +tp_tfd_unpin_fingers(struct tp_dispatch *tp) +{ + tp->tfd.cursor_pinned = false; + // struct tp_touch *t; + // tp_for_each_touch(tp, t) { + // t->pinned.is_pinned = false; + // } +} + +static bool +tp_tfd_should_be_unpinned(const struct tp_dispatch *tp, struct tp_touch *t) +{ + struct phys_coords mm; + struct device_coords delta; + + if (!tp->tfd.cursor_pinned) + return true; + + // unsure why there was a call to abs() here + // delta.x = abs(t->point.x - t->pinned.center.x); + // delta.y = abs(t->point.y - t->pinned.center.y); + + delta = tp_get_aggregate_touches_coords(tp, true); + delta.x = delta.x - tp->tfd.pinned_point.x; + delta.y = delta.y - tp->tfd.pinned_point.y; + + mm = evdev_device_unit_delta_to_mm(tp->device, &delta); + + /* 2.0 mm movement -> unpin */ + return (hypot(mm.x, mm.y) >= 2.0); +} + +static void +tp_tfd_idle_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_INCREASE: + case TFD_EVENT_TOUCH_COUNT_DECREASE: + if (nfingers_down == 3) { + tp->tfd.state = TFD_STATE_POSSIBLE_DRAG; + tp_tfd_set_3f_drag_initial_delay_timer(tp, time); + } + break; + case TFD_EVENT_MOTION: + break; + case TFD_EVENT_RESUME_TIMEOUT: + case TFD_EVENT_TIMEOUT: + break; // bug + case TFD_EVENT_TAP: + case TFD_EVENT_BUTTON: + break; + } +} + +/* We don't have the primary button pressed in this state; the +press is delayed since the fingers have remained stationary */ +static void +tp_tfd_possible_drag_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_INCREASE: + case TFD_EVENT_TOUCH_COUNT_DECREASE: + switch (nfingers_down) { + case 3: + break; // bug + default: + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_timer(tp); + break; + } + break; + case TFD_EVENT_MOTION: + switch (nfingers_down) { + default: + break; // bug + case 3: + /* perform a press since it hasn't already been done by the timer */ + tp->tfd.state = TFD_STATE_DRAG; + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + tp_tfd_clear_timer(tp); + } + break; + case TFD_EVENT_RESUME_TIMEOUT: + break; + case TFD_EVENT_TIMEOUT: + /* we've not moved our three fingers so we perform the press after the + initial delay */ + tp->tfd.state = TFD_STATE_DRAG; + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + break; + case TFD_EVENT_TAP: + case TFD_EVENT_BUTTON: + // TODO: undecided + //tp->tfd.state = TFD_STATE_IDLE; + //tp_tfd_clear_timer(tp); + break; + } +} + + +/* not sure nfingers_down is suitable here since 3f dragging only has a lower +bound for number of touches in order to exit the 3f drag mode -- no upper bound +*/ +static void +tp_tfd_drag_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: + case 1: + tp_tfd_pin_fingers(tp); + /* removing all, or all but one, fingers gives you ~0.7 seconds to + place three fingers back on the touchpad before the drag ends */ + tp_tfd_set_3f_drag_wait_timer(tp, time); + tp->tfd.state = TFD_STATE_AWAIT_RESUME; + + // tp_tfd_pin_fingers(tp); + // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_set_more_fingers_timer(tp, time); + // tp->tfd.state = TFD_STATE_POSSIBLE_RESUME; + + break; + default: + break; + } + break; + case TFD_EVENT_MOTION: + /* TODO: Future improvement: When one finger moves considerably + faster than the others, don't average their deltas for cursor + position updates -- use the fastest finger only */ + break; + case TFD_EVENT_RESUME_TIMEOUT: + case TFD_EVENT_TIMEOUT: + // log bug + break; + case TFD_EVENT_TAP: + break; + case TFD_EVENT_BUTTON: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } +} + + + + + + +/* Drag-lock; After leaving 3 finger dragging there's a small time window where you can +resume the drag with 3 fingers. */ +static void +tp_tfd_await_resume_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tfd_event event, uint64_t time, + int nfingers_down) +{ + switch (event) { + /* we enter this state with 1 or 0 fingers */ + case TFD_EVENT_TOUCH_COUNT_DECREASE: + /* decreasing the amount of fingers does not concern us in this state + as long as an increase to > 3 invariably moves to another state */ + break; + case TFD_EVENT_TOUCH_COUNT_INCREASE: + switch (nfingers_down) { + case 0: + break; // bug + case 1: + case 2: + // tp_tfd_pin_fingers(tp); + // tp_tfd_set_more_fingers_timer(tp, time); + // tp->tfd.state = TFD_STATE_POSSIBLE_RESUME; + // break; + case 3: + /* Exactly three fingers are required to resume dragging, in order to + enable instant scroll and cursor control. */ + // tp->tfd.state = TFD_STATE_POSSIBLE_RESUME; + // tp_tfd_set_timer_wait_for_fingers(tp); + + // tp_tfd_unpin_fingers(tp); + // tp_tfd_clear_resume_timer(tp); + + tp_tfd_pin_fingers(tp); + tp_tfd_set_more_fingers_timer(tp, time); + + // /* when 3 fingers are confirmed, immediately reset drag-lock timeout so + // that 3f drags that are shorter than more_fingers_timer will actually + // prevents drag-lock from timing out */ + // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_clear_resume_timer(tp); + + /* time to disambiguate from a 4 finger gesture */ + tp->tfd.state = TFD_STATE_POSSIBLE_RESUME; + + break; + default: + // TODO: undecided, but this behavior shouldn't be an issue + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + break; + case TFD_EVENT_MOTION: + /* Zero, one, or two fingers can be touching. */ + switch (nfingers_down) { + case 3: + break; // bug + default: // bug, currently + // // TODO: undecided + // tp_tfd_unpin_fingers(tp); + // tp->tfd.state = TFD_STATE_IDLE; + // tp_tfd_clear_resume_timer(tp); + // tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + // break; + case 1: + case 2: + if (tp_tfd_should_be_unpinned(tp, t)) { + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + } + break; + } + break; + case TFD_EVENT_RESUME_TIMEOUT: + /* the drag was not resumed */ + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + case TFD_EVENT_TIMEOUT: + break; // bug + case TFD_EVENT_TAP: + case TFD_EVENT_BUTTON: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } +} + +/* Waiting for more fingers. Fingers have been detected, but it might be +a transitory phase towards 2, 4 or more fingers, which should not resume the +drag. */ +static void +tp_tfd_possible_resume_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_INCREASE: + // bool did_transition_from_three_fingers = tp->tfd.finger_count == 3; + switch (nfingers_down) { + case 0: + case 1: + case 2: + // assert(false); + // tp_tfd_pin_fingers(tp); + // if (did_transition_from_three_fingers) + // tp_tfd_set_3f_drag_wait_timer(tp, time); + break; + case 3: + // assert(false); + // tp_tfd_pin_fingers(tp); + // /* when 3 fingers are confirmed, immediately reset drag-lock timeout so + // that 3f drags that are shorter than more_fingers_timer will actually + // prevent drag-lock from timing out */ + // // TODO: this is wrong. should be set when transitioning from 3 + // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_clear_resume_timer(tp); + break; + default: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_clear_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + break; + case TFD_EVENT_MOTION: + switch (nfingers_down) { + case 0: + case 1: + case 2: + // assert(false); + break; + case 3: + tp_tfd_unpin_fingers(tp); + tp_tfd_clear_resume_timer(tp); + tp_tfd_clear_timer(tp); + tp->tfd.state = TFD_STATE_DRAG; + break; + default: + /* should have left the state already */ + // TODO: undecided + // tp_tfd_unpin_fingers(tp); + // tp->tfd.state = TFD_STATE_IDLE; + // tp_tfd_clear_resume_timer(tp); + // tp_tfd_clear_timer(tp); + // tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + break; + case TFD_EVENT_RESUME_TIMEOUT: + /* immediately evaluate whether to resume. TODO: unwise? */ + switch (nfingers_down) { + case 3: + tp_tfd_unpin_fingers(tp); + tp_tfd_clear_timer(tp); + tp->tfd.state = TFD_STATE_DRAG; + break; + default: + tp_tfd_unpin_fingers(tp); + tp_tfd_clear_timer(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + break; + case TFD_EVENT_TOUCH_COUNT_DECREASE: + /* a decrease forces immediate evaluation as if the timer had fired */ + tp_tfd_clear_timer(tp); + /* fallthrough */ + case TFD_EVENT_TIMEOUT: + /* time to check whether we have 3 fingers touching */ + switch (nfingers_down) { + case 0: + case 1: + case 2: + // tp_tfd_pin_fingers(tp); + // tp_tfd_set_3f_drag_wait_timer(tp, time); + tp->tfd.state = TFD_STATE_AWAIT_RESUME; + break; + case 3: + tp_tfd_unpin_fingers(tp); + tp_tfd_clear_resume_timer(tp); + tp->tfd.state = TFD_STATE_DRAG; + break; + default: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + break; + case TFD_EVENT_TAP: + case TFD_EVENT_BUTTON: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_clear_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } +} + + + + + + +// It's too easy to trigger 3fd while scrolling and a third finger +// touches momentarily. */ + + +static void +tp_tfd_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum tfd_event event, + uint64_t time, + int nfingers_down) +{ + enum tp_tfd_state previous_state; + // int nfingers_down = tp->tfd.finger_count; + previous_state = tp->tfd.state; + + assert(nfingers_down >= 0); + // assert(nfingers_down < 6); // TODO: temp, remove + + switch (event) { + case TFD_EVENT_MOTION: + case TFD_EVENT_TOUCH_COUNT_INCREASE: + assert(nfingers_down > 0); + break; + case TFD_EVENT_TOUCH_COUNT_DECREASE: + // assert(nfingers_down < 5); + break; + default: + break; + } + + switch(tp->tfd.state) { + case TFD_STATE_IDLE: + tp_tfd_idle_handle_event(tp, t, event, time, nfingers_down); + break; + + case TFD_STATE_POSSIBLE_DRAG: + tp_tfd_possible_drag_handle_event(tp, t, event, time, nfingers_down); + break; + + case TFD_STATE_DRAG: + tp_tfd_drag_handle_event(tp, t, event, time, nfingers_down); + break; + + case TFD_STATE_AWAIT_RESUME: + tp_tfd_await_resume_handle_event(tp, t, event, time, nfingers_down); + break; + + case TFD_STATE_POSSIBLE_RESUME: + tp_tfd_possible_resume_handle_event(tp, t, event, time, nfingers_down); + break; + } + + if (previous_state != tp->tfd.state) + evdev_log_debug(tp->device, + "tfd: touch %d (%s), tfd state %s → %s → %s\n", + t ? (int)t->index : -1, + t ? touch_state_to_str(t->state) : "", + tfd_state_to_str(previous_state), + tfd_event_to_str(event), + tfd_state_to_str(tp->tfd.state)); +} + +/* TODO: too arbitrary... need to decide on the semantics of the MOTION event properly */ +#define DEFAULT_TFD_MOVE_THRESHOLD 0.1 //1.0 //1.3 /* mm */ + +// TODO: how often do clients get motion updates? Isn't that the granularity we +// want as well? Otherwise we might miss the button press, which will then occur +// one or more pixels off from the intended position. + +/* reused tap logic */ +static bool +tp_tfd_exceeds_motion_threshold(struct tp_dispatch *tp, + struct tp_touch *t) +{ + struct phys_coords mm = + tp_phys_delta(tp, device_delta(t->point, t->tfd.previous)); + + /* if we have more fingers down than slots, we know that synaptics + * touchpads are likely to give us pointer jumps. + * This triggers the movement threshold, making three-finger taps + * less reliable (#101435) + * + * This uses the real nfingers_down, not the one for taps. + */ + if (tp->device->model_flags & EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD && + (tp->nfingers_down > 2 || tp->old_nfingers_down > 2) && + (tp->nfingers_down > tp->num_slots || + tp->old_nfingers_down > tp->num_slots)) { + return false; + } + + /* Semi-mt devices will give us large movements on finger release, + * depending which touch is released. Make sure we ignore any + * movement in the same frame as a finger change. + */ + if (tp->semi_mt && tp->nfingers_down != tp->old_nfingers_down) + return false; + + double threshold = DEFAULT_TFD_MOVE_THRESHOLD; + if (tp->tfd.state == TFD_STATE_POSSIBLE_DRAG) { + // TODO: have to figure out something better + // MOTION events are too decoupled from what's required to actually move + // the cursor. TODO: look it up + + /* the default threshold is too fine-grained for detection of initial + button press, but is kind of needed for the unpin distance to be + somewhat accurate in the other states a.t.m. */ + threshold = 1.3; // same as tap.c + } + + return length_in_mm(mm) > threshold; +} + +// unused +static bool +tp_tfd_enabled(struct tp_dispatch *tp) +{ + return tp->tfd.enabled && !tp->tfd.suspended; +} + +// unused +bool +tp_touch_active_for_tfd_including_edge_palm(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && + (t->palm.state == PALM_NONE || t->palm.state == PALM_EDGE); //&& + // !t->pinned.is_pinned && + // !tp_thumb_ignored_for_gesture(tp, t) && + + // not sure what the reason is for these + // tp_button_touch_active(tp, t) && + // tp_edge_scroll_touch_active(tp, t); +} + +void +tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) +{ + unsigned int active_touches = 0; + unsigned int active_touches_excluding_edge_palm = 0; + // unsigned int active_touches_including_edge_palm = 0; + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + if (tp_touch_active_for_tfd(tp, t)) + active_touches_excluding_edge_palm++; + // if (tp_touch_active_for_tfd_including_edge_palm(tp, t)) + // active_touches_including_edge_palm++; + } + + // if (active_touches_excluding_edge_palm >= 2) + // active_touches = active_touches_including_edge_palm; + // else + active_touches = active_touches_excluding_edge_palm; + + + if (active_touches < tp->tfd.finger_count) { + tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT_DECREASE, time, active_touches); + } + else if (active_touches > tp->tfd.finger_count) { + tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT_INCREASE, time, active_touches); + } + + // if (active_touches != tp->tfd.finger_count) { + // tp->tfd.finger_count = active_touches; + + // switch (active_touches) { + // case 0: + // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT0, time); + // break; + // case 1: + // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT1, time); + // break; + // case 2: + // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT2, time); + // break; + // case 3: + // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT3, time); + // break; + // default: + // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT4PLUS, time); + // break; + // } + + + // TODO: consider whether transparent debouncing has any value for TFD. + // Probably not; transitions to cursor control and scrolling must never + // affect the position for button release, which seems unavoidable with + // transparent debouncing -- unless the pin/unpin decisions are also broken + // out from event handling... + + // /* If all fingers are lifted immediately end the gesture */ + // if (active_touches == 0) { + // tp_gesture_stop(tp, time); + // tp->tfd.finger_count = 0; + // tp->tfd.finger_count_pending = 0; + // /* Immediately switch to new mode to avoid initial latency */ + // } else if (!tp->tfd.started) { + // tp->tfd.finger_count = active_touches; + // tp->tfd.finger_count_pending = 0; + // /* If in UNKNOWN or POINTER_MOTION state, go back to + // * NONE to re-evaluate leftmost and rightmost touches + // */ + // if (tp->tfd.state == GESTURE_STATE_UNKNOWN || + // tp->tfd.state == GESTURE_STATE_POINTER_MOTION) { + // tp_gesture_handle_event(tp, + // GESTURE_EVENT_RESET, + // time); + // } + // /* Else debounce finger changes */ + // } else if (active_touches != tp->tfd.finger_count_pending) { + // tp->tfd.finger_count_pending = active_touches; + // libinput_timer_set(&tp->tfd.finger_count_switch_timer, + // time + DEFAULT_GESTURE_SWITCH_TIMEOUT); + // } + // } else { + // tp->tfd.finger_count_pending = 0; + // } + + + // if (!tp_tfd_enabled(tp)) + // return 0; + + /* Handle queued button pressed events from clickpads. */ + if (/* tp->buttons.is_clickpad && */ tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) + tp_tfd_handle_event(tp, NULL, TFD_EVENT_BUTTON, time, active_touches); + + + bool motion_occurred = false; + + tp_for_each_touch(tp, t) { + if (!t->dirty || t->state == TOUCH_NONE) + continue; + + if(!tp_touch_active_for_tfd(tp, t)) + continue; + + if (t->state == TOUCH_HOVERING) + continue; + + if (t->state == TOUCH_BEGIN) { + t->tfd.previous = t->point; + } + else if (t->state == TOUCH_UPDATE) { + if (tp_tfd_exceeds_motion_threshold(tp, t)) { // t->tfd.previous.x != t->point.x || t->tfd.previous.y != t->point.y) { + motion_occurred = true; + } + } + } + + if (motion_occurred) { + tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION, time, active_touches); + // switch (active_touches) { + // case 0: + // break; // bug? + // case 1: + // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION1, time); + // break; + // case 2: + // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION2, time); + // break; + // case 3: + // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION3, time); + // break; + // default: + // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION4PLUS, time); + // break; + // } + } + + // /** + // * In any state where motion exceeding the move threshold would + // * move to the next state, filter that motion until we actually + // * exceed it. This prevents small motion events while we're waiting + // * on a decision if a tap is a tap. + // */ + // switch (tp->tfd.state) { + + + // case TFD_STATE_3F_DRAG_WAIT0: + // case TFD_STATE_3F_DRAG_WAIT1: + // case TFD_STATE_3F_DRAG_WAIT2: + + // filter_motion = 1; + // break; + + // case TFD_STATE_3F_DRAG_BEFORE_PRESS: + // case TFD_STATE_3F_DRAG2: + // case TFD_STATE_3F_DRAG3: + // case TFD_STATE_3F_DRAG4PLUS: + // default: + // break; + + // } + + + + + /* finally, update additional state */ + if (motion_occurred) + t->tfd.previous = t->point; + + tp->tfd.finger_count = active_touches; + + // assert(tp->tfd.nfingers_down <= tp->nfingers_down); + // if (tp->nfingers_down == 0) + // assert(tp->tfd.nfingers_down == 0); + + // return filter_motion; +} + +static void +tp_tfd_handle_timeout(uint64_t time, void *data) +{ + struct tp_dispatch *tp = data; + + tp_tfd_handle_event(tp, NULL, TFD_EVENT_TIMEOUT, time, tp->tfd.finger_count); +} + +static void +tp_tfd_handle_resume_timeout(uint64_t time, void *data) +{ + struct tp_dispatch *tp = data; + + tp_tfd_handle_event(tp, NULL, TFD_EVENT_RESUME_TIMEOUT, time, tp->tfd.finger_count); +} + +/* when a tap occurs the drag can be finished ahead of time if in the waiting state */ +void +tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time) +{ + switch (tp->tfd.state) { + case TFD_STATE_AWAIT_RESUME: + case TFD_STATE_POSSIBLE_RESUME: + tp_tfd_handle_event(tp, NULL, TFD_EVENT_TAP, time, tp->tfd.finger_count); + break; + case TFD_STATE_IDLE: + case TFD_STATE_POSSIBLE_DRAG: + case TFD_STATE_DRAG: + break; + } +} + + +/* below are some tap.c functions to be adapted if the need arises */ + + +// static void +// tp_tfd_enabled_update(struct tp_dispatch *tp, bool suspended, bool enabled, uint64_t time) +// { +// bool was_enabled = tp_tfd_enabled(tp); + +// tp->tfd.suspended = suspended; +// tp->tfd.enabled = enabled; + +// if (tp_tfd_enabled(tp) == was_enabled) +// return; + +// if (tp_tfd_enabled(tp)) { +// struct tp_touch *t; + +// /* On resume, all touches are considered palms */ +// tp_for_each_touch(tp, t) { +// if (t->state == TOUCH_NONE) +// continue; + +// t->tfd.is_palm = true; +// t->tfd.state = TAP_TOUCH_STATE_DEAD; +// } + +// tp->tfd.state = TFD_STATE_IDLE; +// tp->tfd.nfingers_down = 0; +// } else { +// tp_release_all_taps(tp, time); +// } +// } + +// static int +// tp_tfd_config_count(struct libinput_device *device) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// return min(tp->ntouches, 3U); /* we only do up to 3 finger tap */ +// } + +// static enum libinput_config_status +// tp_tfd_config_set_enabled(struct libinput_device *device, +// enum libinput_config_tap_state enabled) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// tp_tfd_enabled_update(tp, tp->tfd.suspended, +// (enabled == LIBINPUT_CONFIG_TAP_ENABLED), +// libinput_now(device->seat->libinput)); + +// return LIBINPUT_CONFIG_STATUS_SUCCESS; +// } + +// static enum libinput_config_tap_state +// tp_tfd_config_is_enabled(struct libinput_device *device) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// return tp->tfd.enabled ? LIBINPUT_CONFIG_TAP_ENABLED : +// LIBINPUT_CONFIG_TAP_DISABLED; +// } + +// static enum libinput_config_tap_state +// tp_tfd_default(struct evdev_device *evdev) +// { +// /** +// * If we don't have a left button we must have tapping enabled by +// * default. +// */ +// if (!libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_LEFT)) +// return LIBINPUT_CONFIG_TAP_ENABLED; + +// /** +// * Tapping is disabled by default for two reasons: +// * * if you don't know that tapping is a thing (or enabled by +// * default), you get spurious mouse events that make the desktop +// * feel buggy. +// * * if you do know what tapping is and you want it, you +// * usually know where to enable it, or at least you can search for +// * it. +// */ +// return LIBINPUT_CONFIG_TAP_DISABLED; +// } + +// static enum libinput_config_tap_state +// tp_tfd_config_get_default(struct libinput_device *device) +// { +// struct evdev_device *evdev = evdev_device(device); + +// return tp_tfd_default(evdev); +// } + +// static enum libinput_config_status +// tp_tfd_config_set_map(struct libinput_device *device, +// enum libinput_config_tap_button_map map) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// tp->tfd.want_map = map; + +// tp_tfd_update_map(tp); + +// return LIBINPUT_CONFIG_STATUS_SUCCESS; +// } + +// static enum libinput_config_tap_button_map +// tp_tfd_config_get_map(struct libinput_device *device) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// return tp->tfd.want_map; +// } + +// static enum libinput_config_tap_button_map +// tp_tfd_config_get_default_map(struct libinput_device *device) +// { +// return LIBINPUT_CONFIG_TAP_MAP_LRM; +// } + +// static enum libinput_config_status +// tp_tfd_config_set_drag_enabled(struct libinput_device *device, +// enum libinput_config_drag_state enabled) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// tp->tfd.drag_enabled = enabled; + +// return LIBINPUT_CONFIG_STATUS_SUCCESS; +// } + +// static enum libinput_config_drag_state +// tp_tfd_config_get_drag_enabled(struct libinput_device *device) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// return tp->tfd.drag_enabled; +// } + +// static inline enum libinput_config_drag_state +// tp_drag_default(struct evdev_device *device) +// { +// return LIBINPUT_CONFIG_DRAG_ENABLED; +// } + +// static enum libinput_config_drag_state +// tp_tfd_config_get_default_drag_enabled(struct libinput_device *device) +// { +// struct evdev_device *evdev = evdev_device(device); + +// return tp_drag_default(evdev); +// } + +// static enum libinput_config_status +// tp_tfd_config_set_draglock_enabled(struct libinput_device *device, +// enum libinput_config_drag_lock_state enabled) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// tp->tfd.drag_lock_enabled = enabled; + +// return LIBINPUT_CONFIG_STATUS_SUCCESS; +// } + +// static enum libinput_config_drag_lock_state +// tp_tfd_config_get_draglock_enabled(struct libinput_device *device) +// { +// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; +// struct tp_dispatch *tp = tp_dispatch(dispatch); + +// return tp->tfd.drag_lock_enabled; +// } + +// static inline enum libinput_config_drag_lock_state +// tp_drag_lock_default(struct evdev_device *device) +// { +// return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; +// } + +// static enum libinput_config_drag_lock_state +// tp_tfd_config_get_default_draglock_enabled(struct libinput_device *device) +// { +// struct evdev_device *evdev = evdev_device(device); + +// return tp_drag_lock_default(evdev); +// } + +void +tp_init_tfd(struct tp_dispatch *tp) +{ + char timer_name[64]; + + /* + tp->tfd.config.count = tp_tfd_config_count; + tp->tfd.config.set_enabled = tp_tfd_config_set_enabled; + tp->tfd.config.get_enabled = tp_tfd_config_is_enabled; + tp->tfd.config.get_default = tp_tfd_config_get_default; + tp->tfd.config.set_map = tp_tfd_config_set_map; + tp->tfd.config.get_map = tp_tfd_config_get_map; + tp->tfd.config.get_default_map = tp_tfd_config_get_default_map; + tp->tfd.config.set_drag_enabled = tp_tfd_config_set_drag_enabled; + tp->tfd.config.get_drag_enabled = tp_tfd_config_get_drag_enabled; + tp->tfd.config.get_default_drag_enabled = tp_tfd_config_get_default_drag_enabled; + tp->tfd.config.set_draglock_enabled = tp_tfd_config_set_draglock_enabled; + tp->tfd.config.get_draglock_enabled = tp_tfd_config_get_draglock_enabled; + tp->tfd.config.get_default_draglock_enabled = tp_tfd_config_get_default_draglock_enabled; + tp->device->base.config.tap = &tp->tfd.config; + */ + + tp->tfd.state = TFD_STATE_IDLE; + tp->tfd.enabled = true; //tp_tfd_default(tp->device); + tp->tfd.suspended = false; + //tp->tfd.map = LIBINPUT_CONFIG_TAP_MAP_LRM; + //tp->tfd.want_map = tp->tfd.map; + //tp->tfd.drag_enabled = tp_drag_default(tp->device); + //tp->tfd.drag_lock_enabled = tp_drag_lock_default(tp->device); + tp->tfd.three_finger_dragging_enabled = 1; + tp->tfd.finger_count = 0; + + snprintf(timer_name, + sizeof(timer_name), + "%s tfd", + evdev_device_get_sysname(tp->device)); + libinput_timer_init(&tp->tfd.timer, + tp_libinput_context(tp), + timer_name, + tp_tfd_handle_timeout, tp); + + snprintf(timer_name, + sizeof(timer_name), + "%s tfd resume", + evdev_device_get_sysname(tp->device)); + libinput_timer_init(&tp->tfd.resume_timer, + tp_libinput_context(tp), + timer_name, + tp_tfd_handle_resume_timeout, tp); +} + +// void +// tp_remove_tap(struct tp_dispatch *tp) +// { +// libinput_timer_cancel(&tp->tfd.timer); +// } + +// void +// tp_release_all_taps(struct tp_dispatch *tp, uint64_t now) +// { +// struct tp_touch *t; +// int i; + +// for (i = 1; i <= 3; i++) { +// if (tp->tfd.buttons_pressed & (1 << i)) +// tp_tfd_notify(tp, now, i, LIBINPUT_BUTTON_STATE_RELEASED); +// } + +// /* To neutralize all current touches, we make them all palms */ +// tp_for_each_touch(tp, t) { +// if (t->state == TOUCH_NONE) +// continue; + +// if (t->tfd.is_palm) +// continue; + +// t->tfd.is_palm = true; +// t->tfd.state = TAP_TOUCH_STATE_DEAD; +// } + +// tp->tfd.state = TFD_STATE_IDLE; +// tp->tfd.nfingers_down = 0; +// } + +// void +// tp_tfd_suspend(struct tp_dispatch *tp, uint64_t time) +// { +// tp_tfd_enabled_update(tp, true, tp->tfd.enabled, time); +// } + +// void +// tp_tfd_resume(struct tp_dispatch *tp, uint64_t time) +// { +// tp_tfd_enabled_update(tp, false, tp->tfd.enabled, time); +// } + +bool +tp_tfd_dragging(const struct tp_dispatch *tp) +{ + switch (tp->tfd.state) { + case TFD_STATE_DRAG: + case TFD_STATE_AWAIT_RESUME: + case TFD_STATE_POSSIBLE_RESUME: + return true; + default: + return false; + } +} + diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 9bc6bf6a..c0292aff 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -846,6 +846,10 @@ tp_touch_active_for_gesture(const struct tp_dispatch *tp, const struct tp_touch return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && t->palm.state == PALM_NONE && !t->pinned.is_pinned && + + /* let's see if this works */ + !tp->tfd.cursor_pinned && + !tp_thumb_ignored_for_gesture(tp, t) && tp_button_touch_active(tp, t) && tp_edge_scroll_touch_active(tp, t); @@ -1863,6 +1867,8 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time) ignore_motion |= tp_tap_handle_state(tp, time); ignore_motion |= tp_post_button_events(tp, time); + tp_tfd_handle_state(tp, time); + if (tp->palm.trackpoint_active || tp->dwt.keyboard_active) { tp_edge_scroll_stop_events(tp, time); tp_gesture_cancel(tp, time); @@ -3777,6 +3783,8 @@ tp_init(struct tp_dispatch *tp, if (!tp_init_accel(tp, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE)) return false; + tp_init_tfd(tp); + tp_init_tap(tp); tp_init_buttons(tp, device); tp_init_dwt(tp, device); diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 55547014..5b97b07e 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -134,6 +134,20 @@ enum tp_tap_state { TAP_STATE_DEAD, /**< finger count exceeded */ }; + +enum tp_tfd_state { + /* waiting for 3 fingers */ + TFD_STATE_IDLE, + /* 3 fingers down, possible 4+ f gesture */ + TFD_STATE_POSSIBLE_DRAG, + /* 3 fingers down and button press has been output */ + TFD_STATE_DRAG, + /* drag-lock; waiting for drag continuation */ + TFD_STATE_AWAIT_RESUME, + /* disambiguate between drag continuation and a possible 4+ gesture */ + TFD_STATE_POSSIBLE_RESUME, +}; + enum tp_tap_touch_state { TAP_TOUCH_STATE_IDLE = 16, /**< not in touch */ TAP_TOUCH_STATE_TOUCH, /**< touching, may tap */ @@ -250,6 +264,13 @@ struct tp_touch { bool is_palm; } tap; + struct { + // enum tp_tap_touch_state state; + struct device_coords previous; + // bool is_thumb; + // bool is_palm; + } tfd; + struct { enum tp_edge_scroll_touch_state edge_state; uint32_t edge; @@ -440,6 +461,31 @@ struct tp_dispatch { unsigned int nfingers_down; /* number of fingers down for tapping (excl. thumb/palm) */ } tap; + struct { + //struct libinput_device_config_tap config; + bool enabled; + bool suspended; + struct libinput_timer timer; + struct libinput_timer resume_timer; + enum tp_tfd_state state; + uint32_t buttons_pressed; + uint64_t saved_press_time, + saved_release_time; + + // enum libinput_config_tap_button_map map; + //enum libinput_config_tap_button_map want_map; + + /* true if cursor movement should not be output to clients */ + bool cursor_pinned; + struct device_coords pinned_point; + + //bool drag_enabled; + //bool drag_lock_enabled; + bool three_finger_dragging_enabled; + + unsigned int finger_count; /* number of fingers down for 3 finger dragging */ + } tfd; + struct { struct libinput_device_config_dwtp config; bool dwtp_enabled; @@ -625,6 +671,12 @@ tp_touch_active_for_gesture(const struct tp_dispatch *tp, int tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time); +void +tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time); + +void +tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time); + void tp_tap_post_process_state(struct tp_dispatch *tp); From 932ecd67fb08692a90c2765de07e25bd7bef8572 Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Tue, 4 Jan 2022 19:07:24 +0100 Subject: [PATCH 2/7] Cleanup Signed-off-by: Temp Name --- src/evdev-mt-touchpad-tap.c | 2 +- src/evdev-mt-touchpad-tfd.c | 613 ++++++------------------------------ src/evdev-mt-touchpad.h | 6 + 3 files changed, 107 insertions(+), 514 deletions(-) diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index c689cdc1..9a0635bd 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -148,7 +148,7 @@ tp_tap_notify(struct tp_dispatch *tp, time, button, state); - + if (state != LIBINPUT_BUTTON_STATE_PRESSED) tp_tfd_handle_tap(tp, time); } diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c index 54fca8fe..c12bc27b 100644 --- a/src/evdev-mt-touchpad-tfd.c +++ b/src/evdev-mt-touchpad-tfd.c @@ -1,26 +1,3 @@ -/* - * Copyright © 2013-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 @@ -53,7 +30,7 @@ enum tfd_event { }; /***************************************** - * + * TODO: provide a diagram * Look at the state diagram in doc/three-finger-drag-state-machine.svg * (generated with https://www.diagrams.net) * @@ -69,10 +46,6 @@ tfd_state_to_str(enum tp_tfd_state state) CASE_RETURN_STRING(TFD_STATE_DRAG); CASE_RETURN_STRING(TFD_STATE_AWAIT_RESUME); CASE_RETURN_STRING(TFD_STATE_POSSIBLE_RESUME); - // CASE_RETURN_STRING(TFD_STATE_3F_DRAG_WAIT1); - // CASE_RETURN_STRING(TFD_STATE_3F_DRAG_WAIT2); - - // CASE_RETURN_STRING(TFD_STATE_DEAD); } return NULL; } @@ -82,19 +55,9 @@ tfd_event_to_str(enum tfd_event event) { switch(event) { CASE_RETURN_STRING(TFD_EVENT_MOTION); - // CASE_RETURN_STRING(TFD_EVENT_MOTION0); - // CASE_RETURN_STRING(TFD_EVENT_MOTION1); - // CASE_RETURN_STRING(TFD_EVENT_MOTION2); - // CASE_RETURN_STRING(TFD_EVENT_MOTION3); - // CASE_RETURN_STRING(TFD_EVENT_MOTION4PLUS); // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT); CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT_INCREASE); CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT_DECREASE); - // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT0); - // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT1); - // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT2); - // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT3); - // CASE_RETURN_STRING(TFD_EVENT_TOUCH_COUNT4PLUS); CASE_RETURN_STRING(TFD_EVENT_BUTTON); CASE_RETURN_STRING(TFD_EVENT_TAP); CASE_RETURN_STRING(TFD_EVENT_TIMEOUT); @@ -103,16 +66,15 @@ tfd_event_to_str(enum tfd_event event) return NULL; } -// static inline void -// log_tfd_bug(struct tp_dispatch *tp, struct tp_touch *t, enum tfd_event event) -// { -// evdev_log_bug_libinput(tp->device, -// "%d: invalid tap event %s in state %s\n", -// t->index, -// tfd_event_to_str(event), -// tfd_state_to_str(tp->tfd.state)); - -// } +static inline void +log_tfd_bug(struct tp_dispatch *tp, enum tfd_event event, int nfingers_down) +{ + evdev_log_bug_libinput(tp->device, + "invalid TFD event %s with %d fingers in state %s\n", + tfd_event_to_str(event), + nfingers_down, + tfd_state_to_str(tp->tfd.state)); +} static void tp_tfd_notify(struct tp_dispatch *tp, @@ -148,24 +110,8 @@ tp_tfd_notify(struct tp_dispatch *tp, state); } -// static void -// tp_tfd_set_timer(struct tp_dispatch *tp, uint64_t time) -// { -// libinput_timer_set(&tp->tfd.timer, time + DEFAULT_TAP_TIMEOUT_PERIOD); -// } - -// static void -// tp_tfd_set_drag_timer(struct tp_dispatch *tp, uint64_t time, -// int nfingers_tapped) -// { -// libinput_timer_set(&tp->tfd.timer, -// time + DEFAULT_DRAG_TIMEOUT_PERIOD_BASE + -// (nfingers_tapped * -// DEFAULT_DRAG_TIMEOUT_PERIOD_PERFINGER)); -// } - static void -tp_tfd_set_3f_drag_initial_delay_timer(struct tp_dispatch *tp, uint64_t time)//, +tp_tfd_set_button_press_delay_timer(struct tp_dispatch *tp, uint64_t time)//, // uint64_t duration) { // libinput_timer_set(&tp->tfd.timer, time + duration); @@ -173,7 +119,7 @@ tp_tfd_set_3f_drag_initial_delay_timer(struct tp_dispatch *tp, uint64_t time)//, } static void -tp_tfd_set_more_fingers_timer(struct tp_dispatch *tp, uint64_t time)//, +tp_tfd_set_await_more_fingers_timer(struct tp_dispatch *tp, uint64_t time)//, // uint64_t duration) { // libinput_timer_set(&tp->tfd.timer, time + duration); @@ -181,19 +127,11 @@ tp_tfd_set_more_fingers_timer(struct tp_dispatch *tp, uint64_t time)//, } static void -tp_tfd_set_3f_drag_wait_timer(struct tp_dispatch *tp, uint64_t time) +tp_tfd_set_await_resume_timer(struct tp_dispatch *tp, uint64_t time) { libinput_timer_set(&tp->tfd.resume_timer, time + DEFAULT_DRAG3_WAIT_FOR_RESUME_DURATION); } - -// static void -// tp_tfd_set_draglock_timer(struct tp_dispatch *tp, uint64_t time) -// { -// libinput_timer_set(&tp->tfd.timer, -// time + DEFAULT_DRAGLOCK_TIMEOUT_PERIOD); -// } - static void tp_tfd_clear_timer(struct tp_dispatch *tp) { @@ -206,15 +144,7 @@ tp_tfd_clear_resume_timer(struct tp_dispatch *tp) libinput_timer_cancel(&tp->tfd.resume_timer); } -// static void -// tp_tfd_move_to_dead(struct tp_dispatch *tp, struct tp_touch *t) -// { -// tp->tfd.state = TFD_STATE_DEAD; -// t->tfd.state = TAP_TOUCH_STATE_DEAD; -// tp_tfd_clear_timer(tp); -// } - -bool +static bool tp_touch_active_for_tfd(const struct tp_dispatch *tp, const struct tp_touch *t) { return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && @@ -258,32 +188,19 @@ tp_get_aggregate_touches_coords(const struct tp_dispatch *tp, bool average) return total; } - - -//-comments = questions -/*-comments = explanations, intents, ToDos... */ - -/* TODO [done -- at the right place?]: disable 3 finger gestures */ +/* TODO: disable 3 finger gestures dynamically -- can't have both */ static void tp_tfd_pin_fingers(struct tp_dispatch *tp) { tp->tfd.cursor_pinned = true; tp->tfd.pinned_point = tp_get_aggregate_touches_coords(tp, true); - // struct tp_touch *t; - // tp_for_each_touch(tp, t) { - // tp_tfd_pin_finger(tp, t); - // } } static void tp_tfd_unpin_fingers(struct tp_dispatch *tp) { tp->tfd.cursor_pinned = false; - // struct tp_touch *t; - // tp_for_each_touch(tp, t) { - // t->pinned.is_pinned = false; - // } } static bool @@ -299,6 +216,9 @@ tp_tfd_should_be_unpinned(const struct tp_dispatch *tp, struct tp_touch *t) // delta.x = abs(t->point.x - t->pinned.center.x); // delta.y = abs(t->point.y - t->pinned.center.y); + /* TODO: this should correspond to whatever gesture.c does */ + /* TODO: can't gesture.c just use the touch with the largest delta so as to + allow a single finger to control the cursor at the same speed as all fingers? */ delta = tp_get_aggregate_touches_coords(tp, true); delta.x = delta.x - tp->tfd.pinned_point.x; delta.y = delta.y - tp->tfd.pinned_point.y; @@ -319,13 +239,14 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp, case TFD_EVENT_TOUCH_COUNT_DECREASE: if (nfingers_down == 3) { tp->tfd.state = TFD_STATE_POSSIBLE_DRAG; - tp_tfd_set_3f_drag_initial_delay_timer(tp, time); + tp_tfd_set_button_press_delay_timer(tp, time); } break; case TFD_EVENT_MOTION: break; case TFD_EVENT_RESUME_TIMEOUT: case TFD_EVENT_TIMEOUT: + log_tfd_bug(tp, event, nfingers_down); break; // bug case TFD_EVENT_TAP: case TFD_EVENT_BUTTON: @@ -334,7 +255,7 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp, } /* We don't have the primary button pressed in this state; the -press is delayed since the fingers have remained stationary */ +press is delayed if the fingers have remained stationary */ static void tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -344,23 +265,25 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, case TFD_EVENT_TOUCH_COUNT_INCREASE: case TFD_EVENT_TOUCH_COUNT_DECREASE: switch (nfingers_down) { - case 3: - break; // bug - default: - tp->tfd.state = TFD_STATE_IDLE; - tp_tfd_clear_timer(tp); - break; + case 3: + log_tfd_bug(tp, event, nfingers_down); + break; // bug + default: + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_timer(tp); + break; } break; case TFD_EVENT_MOTION: switch (nfingers_down) { - default: - break; // bug - case 3: - /* perform a press since it hasn't already been done by the timer */ - tp->tfd.state = TFD_STATE_DRAG; - tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); - tp_tfd_clear_timer(tp); + default: + log_tfd_bug(tp, event, nfingers_down); + break; // bug + case 3: + /* perform a press since it hasn't already been done by the timer */ + tp->tfd.state = TFD_STATE_DRAG; + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); + tp_tfd_clear_timer(tp); } break; case TFD_EVENT_RESUME_TIMEOUT: @@ -380,10 +303,6 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, } } - -/* not sure nfingers_down is suitable here since 3f dragging only has a lower -bound for number of touches in order to exit the 3f drag mode -- no upper bound -*/ static void tp_tfd_drag_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -400,12 +319,12 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, tp_tfd_pin_fingers(tp); /* removing all, or all but one, fingers gives you ~0.7 seconds to place three fingers back on the touchpad before the drag ends */ - tp_tfd_set_3f_drag_wait_timer(tp, time); + tp_tfd_set_await_resume_timer(tp, time); tp->tfd.state = TFD_STATE_AWAIT_RESUME; // tp_tfd_pin_fingers(tp); - // tp_tfd_set_3f_drag_wait_timer(tp, time); - // tp_tfd_set_more_fingers_timer(tp, time); + // 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; @@ -420,8 +339,8 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, break; case TFD_EVENT_RESUME_TIMEOUT: case TFD_EVENT_TIMEOUT: - // log bug - break; + log_tfd_bug(tp, event, nfingers_down); + break; // bug case TFD_EVENT_TAP: break; case TFD_EVENT_BUTTON: @@ -433,11 +352,6 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, } } - - - - - /* Drag-lock; After leaving 3 finger dragging there's a small time window where you can resume the drag with 3 fingers. */ static void @@ -451,15 +365,21 @@ tp_tfd_await_resume_handle_event(struct tp_dispatch *tp, case TFD_EVENT_TOUCH_COUNT_DECREASE: /* decreasing the amount of fingers does not concern us in this state as long as an increase to > 3 invariably moves to another state */ + + /* TODO: Bug: very quick drags will immediately terminate because of this + in combination with breaking out of drag during 1 finger or 2 fingers MOTION. + + Solution (?): POSSIBLE_BREAK_OUT state concerned with touch count decrease */ break; case TFD_EVENT_TOUCH_COUNT_INCREASE: switch (nfingers_down) { case 0: + log_tfd_bug(tp, event, nfingers_down); break; // bug case 1: case 2: // tp_tfd_pin_fingers(tp); - // tp_tfd_set_more_fingers_timer(tp, time); + // tp_tfd_set_await_more_fingers_timer(tp, time); // tp->tfd.state = TFD_STATE_POSSIBLE_RESUME; // break; case 3: @@ -472,12 +392,12 @@ tp_tfd_await_resume_handle_event(struct tp_dispatch *tp, // tp_tfd_clear_resume_timer(tp); tp_tfd_pin_fingers(tp); - tp_tfd_set_more_fingers_timer(tp, time); + tp_tfd_set_await_more_fingers_timer(tp, time); // /* when 3 fingers are confirmed, immediately reset drag-lock timeout so // that 3f drags that are shorter than more_fingers_timer will actually // prevents drag-lock from timing out */ - // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_set_await_resume_timer(tp, time); // tp_tfd_clear_resume_timer(tp); /* time to disambiguate from a 4 finger gesture */ @@ -497,8 +417,11 @@ tp_tfd_await_resume_handle_event(struct tp_dispatch *tp, /* Zero, one, or two fingers can be touching. */ switch (nfingers_down) { case 3: + log_tfd_bug(tp, event, nfingers_down); break; // bug default: // bug, currently + log_tfd_bug(tp, event, nfingers_down); + break; // // TODO: undecided // tp_tfd_unpin_fingers(tp); // tp->tfd.state = TFD_STATE_IDLE; @@ -523,6 +446,7 @@ tp_tfd_await_resume_handle_event(struct tp_dispatch *tp, tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; case TFD_EVENT_TIMEOUT: + log_tfd_bug(tp, event, nfingers_down); break; // bug case TFD_EVENT_TAP: case TFD_EVENT_BUTTON: @@ -553,7 +477,7 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, // assert(false); // tp_tfd_pin_fingers(tp); // if (did_transition_from_three_fingers) - // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_set_await_resume_timer(tp, time); break; case 3: // assert(false); @@ -562,7 +486,7 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, // that 3f drags that are shorter than more_fingers_timer will actually // prevent drag-lock from timing out */ // // TODO: this is wrong. should be set when transitioning from 3 - // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_set_await_resume_timer(tp, time); // tp_tfd_clear_resume_timer(tp); break; default: @@ -589,6 +513,7 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, break; default: /* should have left the state already */ + // TODO: undecided // tp_tfd_unpin_fingers(tp); // tp->tfd.state = TFD_STATE_IDLE; @@ -616,6 +541,8 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, break; case TFD_EVENT_TOUCH_COUNT_DECREASE: /* a decrease forces immediate evaluation as if the timer had fired */ + /* TODO: might be beneficial with some debouncing on touch count decrease + as well, in order to not react too quickly... */ tp_tfd_clear_timer(tp); /* fallthrough */ case TFD_EVENT_TIMEOUT: @@ -625,7 +552,7 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, case 1: case 2: // tp_tfd_pin_fingers(tp); - // tp_tfd_set_3f_drag_wait_timer(tp, time); + // tp_tfd_set_await_resume_timer(tp, time); tp->tfd.state = TFD_STATE_AWAIT_RESUME; break; case 3: @@ -652,14 +579,8 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, } } - - - - - -// It's too easy to trigger 3fd while scrolling and a third finger -// touches momentarily. */ - +// TODO: It's too easy to trigger 3fd while scrolling and a third finger +// touches momentarily. */ static void tp_tfd_handle_event(struct tp_dispatch *tp, @@ -723,10 +644,14 @@ tp_tfd_handle_event(struct tp_dispatch *tp, #define DEFAULT_TFD_MOVE_THRESHOLD 0.1 //1.0 //1.3 /* mm */ // TODO: how often do clients get motion updates? Isn't that the granularity we -// want as well? Otherwise we might miss the button press, which will then occur -// one or more pixels off from the intended position. +// want as well? Otherwise we might miss cursor motion, and the button press +// will then occur one or more pixels off from the intended position. + +/* TODO: receive input from gesture.c? */ /* reused tap logic */ +/* TODO: do I really need a threshold? The motion could be handled in event handlers +directly instead */ static bool tp_tfd_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t) @@ -771,25 +696,25 @@ tp_tfd_exceeds_motion_threshold(struct tp_dispatch *tp, } // unused -static bool -tp_tfd_enabled(struct tp_dispatch *tp) -{ - return tp->tfd.enabled && !tp->tfd.suspended; -} +// static bool +// tp_tfd_enabled(struct tp_dispatch *tp) +// { +// return tp->tfd.enabled && !tp->tfd.suspended; +// } // unused -bool -tp_touch_active_for_tfd_including_edge_palm(const struct tp_dispatch *tp, const struct tp_touch *t) -{ - return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && - (t->palm.state == PALM_NONE || t->palm.state == PALM_EDGE); //&& - // !t->pinned.is_pinned && - // !tp_thumb_ignored_for_gesture(tp, t) && +// static bool +// tp_touch_active_for_tfd_including_edge_palm(const struct tp_dispatch *tp, const struct tp_touch *t) +// { +// return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && +// (t->palm.state == PALM_NONE || t->palm.state == PALM_EDGE); //&& +// // !t->pinned.is_pinned && +// // !tp_thumb_ignored_for_gesture(tp, t) && - // not sure what the reason is for these - // tp_button_touch_active(tp, t) && - // tp_edge_scroll_touch_active(tp, t); -} +// // not sure what the reason is for these +// // tp_button_touch_active(tp, t) && +// // tp_edge_scroll_touch_active(tp, t); +// } void tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) @@ -819,63 +744,12 @@ tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT_INCREASE, time, active_touches); } - // if (active_touches != tp->tfd.finger_count) { - // tp->tfd.finger_count = active_touches; - - // switch (active_touches) { - // case 0: - // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT0, time); - // break; - // case 1: - // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT1, time); - // break; - // case 2: - // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT2, time); - // break; - // case 3: - // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT3, time); - // break; - // default: - // tp_tfd_handle_event(tp, t, TFD_EVENT_TOUCH_COUNT4PLUS, time); - // break; - // } - - // TODO: consider whether transparent debouncing has any value for TFD. - // Probably not; transitions to cursor control and scrolling must never + // Maybe not; transitions to cursor control and scrolling must never // affect the position for button release, which seems unavoidable with // transparent debouncing -- unless the pin/unpin decisions are also broken // out from event handling... - // /* If all fingers are lifted immediately end the gesture */ - // if (active_touches == 0) { - // tp_gesture_stop(tp, time); - // tp->tfd.finger_count = 0; - // tp->tfd.finger_count_pending = 0; - // /* Immediately switch to new mode to avoid initial latency */ - // } else if (!tp->tfd.started) { - // tp->tfd.finger_count = active_touches; - // tp->tfd.finger_count_pending = 0; - // /* If in UNKNOWN or POINTER_MOTION state, go back to - // * NONE to re-evaluate leftmost and rightmost touches - // */ - // if (tp->tfd.state == GESTURE_STATE_UNKNOWN || - // tp->tfd.state == GESTURE_STATE_POINTER_MOTION) { - // tp_gesture_handle_event(tp, - // GESTURE_EVENT_RESET, - // time); - // } - // /* Else debounce finger changes */ - // } else if (active_touches != tp->tfd.finger_count_pending) { - // tp->tfd.finger_count_pending = active_touches; - // libinput_timer_set(&tp->tfd.finger_count_switch_timer, - // time + DEFAULT_GESTURE_SWITCH_TIMEOUT); - // } - // } else { - // tp->tfd.finger_count_pending = 0; - // } - - // if (!tp_tfd_enabled(tp)) // return 0; @@ -883,10 +757,10 @@ tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) if (/* tp->buttons.is_clickpad && */ tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) tp_tfd_handle_event(tp, NULL, TFD_EVENT_BUTTON, time, active_touches); - bool motion_occurred = false; tp_for_each_touch(tp, t) { + // TODO: don't know the semantics of `dirty`... if (!t->dirty || t->state == TOUCH_NONE) continue; @@ -908,52 +782,8 @@ tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) if (motion_occurred) { tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION, time, active_touches); - // switch (active_touches) { - // case 0: - // break; // bug? - // case 1: - // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION1, time); - // break; - // case 2: - // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION2, time); - // break; - // case 3: - // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION3, time); - // break; - // default: - // tp_tfd_handle_event(tp, t, TFD_EVENT_MOTION4PLUS, time); - // break; - // } } - // /** - // * In any state where motion exceeding the move threshold would - // * move to the next state, filter that motion until we actually - // * exceed it. This prevents small motion events while we're waiting - // * on a decision if a tap is a tap. - // */ - // switch (tp->tfd.state) { - - - // case TFD_STATE_3F_DRAG_WAIT0: - // case TFD_STATE_3F_DRAG_WAIT1: - // case TFD_STATE_3F_DRAG_WAIT2: - - // filter_motion = 1; - // break; - - // case TFD_STATE_3F_DRAG_BEFORE_PRESS: - // case TFD_STATE_3F_DRAG2: - // case TFD_STATE_3F_DRAG3: - // case TFD_STATE_3F_DRAG4PLUS: - // default: - // break; - - // } - - - - /* finally, update additional state */ if (motion_occurred) t->tfd.previous = t->point; @@ -963,8 +793,6 @@ tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) // assert(tp->tfd.nfingers_down <= tp->nfingers_down); // if (tp->nfingers_down == 0) // assert(tp->tfd.nfingers_down == 0); - - // return filter_motion; } static void @@ -1000,222 +828,26 @@ tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time) } -/* below are some tap.c functions to be adapted if the need arises */ - - -// static void -// tp_tfd_enabled_update(struct tp_dispatch *tp, bool suspended, bool enabled, uint64_t time) -// { -// bool was_enabled = tp_tfd_enabled(tp); - -// tp->tfd.suspended = suspended; -// tp->tfd.enabled = enabled; - -// if (tp_tfd_enabled(tp) == was_enabled) -// return; - -// if (tp_tfd_enabled(tp)) { -// struct tp_touch *t; - -// /* On resume, all touches are considered palms */ -// tp_for_each_touch(tp, t) { -// if (t->state == TOUCH_NONE) -// continue; - -// t->tfd.is_palm = true; -// t->tfd.state = TAP_TOUCH_STATE_DEAD; -// } - -// tp->tfd.state = TFD_STATE_IDLE; -// tp->tfd.nfingers_down = 0; -// } else { -// tp_release_all_taps(tp, time); -// } -// } - -// static int -// tp_tfd_config_count(struct libinput_device *device) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// return min(tp->ntouches, 3U); /* we only do up to 3 finger tap */ -// } - -// static enum libinput_config_status -// tp_tfd_config_set_enabled(struct libinput_device *device, -// enum libinput_config_tap_state enabled) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// tp_tfd_enabled_update(tp, tp->tfd.suspended, -// (enabled == LIBINPUT_CONFIG_TAP_ENABLED), -// libinput_now(device->seat->libinput)); - -// return LIBINPUT_CONFIG_STATUS_SUCCESS; -// } - -// static enum libinput_config_tap_state -// tp_tfd_config_is_enabled(struct libinput_device *device) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// return tp->tfd.enabled ? LIBINPUT_CONFIG_TAP_ENABLED : -// LIBINPUT_CONFIG_TAP_DISABLED; -// } - -// static enum libinput_config_tap_state -// tp_tfd_default(struct evdev_device *evdev) -// { -// /** -// * If we don't have a left button we must have tapping enabled by -// * default. -// */ -// if (!libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_LEFT)) -// return LIBINPUT_CONFIG_TAP_ENABLED; - -// /** -// * Tapping is disabled by default for two reasons: -// * * if you don't know that tapping is a thing (or enabled by -// * default), you get spurious mouse events that make the desktop -// * feel buggy. -// * * if you do know what tapping is and you want it, you -// * usually know where to enable it, or at least you can search for -// * it. -// */ -// return LIBINPUT_CONFIG_TAP_DISABLED; -// } - -// static enum libinput_config_tap_state -// tp_tfd_config_get_default(struct libinput_device *device) -// { -// struct evdev_device *evdev = evdev_device(device); - -// return tp_tfd_default(evdev); -// } - -// static enum libinput_config_status -// tp_tfd_config_set_map(struct libinput_device *device, -// enum libinput_config_tap_button_map map) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// tp->tfd.want_map = map; - -// tp_tfd_update_map(tp); - -// return LIBINPUT_CONFIG_STATUS_SUCCESS; -// } - -// static enum libinput_config_tap_button_map -// tp_tfd_config_get_map(struct libinput_device *device) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// return tp->tfd.want_map; -// } - -// static enum libinput_config_tap_button_map -// tp_tfd_config_get_default_map(struct libinput_device *device) -// { -// return LIBINPUT_CONFIG_TAP_MAP_LRM; -// } - -// static enum libinput_config_status -// tp_tfd_config_set_drag_enabled(struct libinput_device *device, -// enum libinput_config_drag_state enabled) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// tp->tfd.drag_enabled = enabled; - -// return LIBINPUT_CONFIG_STATUS_SUCCESS; -// } - -// static enum libinput_config_drag_state -// tp_tfd_config_get_drag_enabled(struct libinput_device *device) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// return tp->tfd.drag_enabled; -// } - -// static inline enum libinput_config_drag_state -// tp_drag_default(struct evdev_device *device) -// { -// return LIBINPUT_CONFIG_DRAG_ENABLED; -// } - -// static enum libinput_config_drag_state -// tp_tfd_config_get_default_drag_enabled(struct libinput_device *device) -// { -// struct evdev_device *evdev = evdev_device(device); - -// return tp_drag_default(evdev); -// } - -// static enum libinput_config_status -// tp_tfd_config_set_draglock_enabled(struct libinput_device *device, -// enum libinput_config_drag_lock_state enabled) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// tp->tfd.drag_lock_enabled = enabled; - -// return LIBINPUT_CONFIG_STATUS_SUCCESS; -// } - -// static enum libinput_config_drag_lock_state -// tp_tfd_config_get_draglock_enabled(struct libinput_device *device) -// { -// struct evdev_dispatch *dispatch = evdev_device(device)->dispatch; -// struct tp_dispatch *tp = tp_dispatch(dispatch); - -// return tp->tfd.drag_lock_enabled; -// } - -// static inline enum libinput_config_drag_lock_state -// tp_drag_lock_default(struct evdev_device *device) -// { -// return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; -// } - -// static enum libinput_config_drag_lock_state -// tp_tfd_config_get_default_draglock_enabled(struct libinput_device *device) -// { -// struct evdev_device *evdev = evdev_device(device); - -// return tp_drag_lock_default(evdev); -// } void tp_init_tfd(struct tp_dispatch *tp) { char timer_name[64]; - /* - tp->tfd.config.count = tp_tfd_config_count; - tp->tfd.config.set_enabled = tp_tfd_config_set_enabled; - tp->tfd.config.get_enabled = tp_tfd_config_is_enabled; - tp->tfd.config.get_default = tp_tfd_config_get_default; - tp->tfd.config.set_map = tp_tfd_config_set_map; - tp->tfd.config.get_map = tp_tfd_config_get_map; - tp->tfd.config.get_default_map = tp_tfd_config_get_default_map; - tp->tfd.config.set_drag_enabled = tp_tfd_config_set_drag_enabled; - tp->tfd.config.get_drag_enabled = tp_tfd_config_get_drag_enabled; - tp->tfd.config.get_default_drag_enabled = tp_tfd_config_get_default_drag_enabled; - tp->tfd.config.set_draglock_enabled = tp_tfd_config_set_draglock_enabled; - tp->tfd.config.get_draglock_enabled = tp_tfd_config_get_draglock_enabled; - tp->tfd.config.get_default_draglock_enabled = tp_tfd_config_get_default_draglock_enabled; - tp->device->base.config.tap = &tp->tfd.config; - */ + // tp->tfd.config.count = tp_tfd_config_count; + // tp->tfd.config.set_enabled = tp_tfd_config_set_enabled; + // tp->tfd.config.get_enabled = tp_tfd_config_is_enabled; + // tp->tfd.config.get_default = tp_tfd_config_get_default; + // tp->tfd.config.set_map = tp_tfd_config_set_map; + // tp->tfd.config.get_map = tp_tfd_config_get_map; + // tp->tfd.config.get_default_map = tp_tfd_config_get_default_map; + // tp->tfd.config.set_drag_enabled = tp_tfd_config_set_drag_enabled; + // tp->tfd.config.get_drag_enabled = tp_tfd_config_get_drag_enabled; + // tp->tfd.config.get_default_drag_enabled = tp_tfd_config_get_default_drag_enabled; + // tp->tfd.config.set_draglock_enabled = tp_tfd_config_set_draglock_enabled; + // tp->tfd.config.get_draglock_enabled = tp_tfd_config_get_draglock_enabled; + // tp->tfd.config.get_default_draglock_enabled = tp_tfd_config_get_default_draglock_enabled; + // tp->device->base.config.tap = &tp->tfd.config; tp->tfd.state = TFD_STATE_IDLE; tp->tfd.enabled = true; //tp_tfd_default(tp->device); @@ -1246,51 +878,6 @@ tp_init_tfd(struct tp_dispatch *tp) tp_tfd_handle_resume_timeout, tp); } -// void -// tp_remove_tap(struct tp_dispatch *tp) -// { -// libinput_timer_cancel(&tp->tfd.timer); -// } - -// void -// tp_release_all_taps(struct tp_dispatch *tp, uint64_t now) -// { -// struct tp_touch *t; -// int i; - -// for (i = 1; i <= 3; i++) { -// if (tp->tfd.buttons_pressed & (1 << i)) -// tp_tfd_notify(tp, now, i, LIBINPUT_BUTTON_STATE_RELEASED); -// } - -// /* To neutralize all current touches, we make them all palms */ -// tp_for_each_touch(tp, t) { -// if (t->state == TOUCH_NONE) -// continue; - -// if (t->tfd.is_palm) -// continue; - -// t->tfd.is_palm = true; -// t->tfd.state = TAP_TOUCH_STATE_DEAD; -// } - -// tp->tfd.state = TFD_STATE_IDLE; -// tp->tfd.nfingers_down = 0; -// } - -// void -// tp_tfd_suspend(struct tp_dispatch *tp, uint64_t time) -// { -// tp_tfd_enabled_update(tp, true, tp->tfd.enabled, time); -// } - -// void -// tp_tfd_resume(struct tp_dispatch *tp, uint64_t time) -// { -// tp_tfd_enabled_update(tp, false, tp->tfd.enabled, time); -// } - bool tp_tfd_dragging(const struct tp_dispatch *tp) { diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 5b97b07e..b6807ccb 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -686,6 +686,9 @@ tp_button_post_process_state(struct tp_dispatch *tp); void tp_init_tap(struct tp_dispatch *tp); +void +tp_init_tfd(struct tp_dispatch *tp); + void tp_remove_tap(struct tp_dispatch *tp); @@ -736,6 +739,9 @@ tp_tap_resume(struct tp_dispatch *tp, uint64_t time); bool tp_tap_dragging(const struct tp_dispatch *tp); +bool +tp_tfd_dragging(const struct tp_dispatch *tp); + bool tp_tap_dragging_or_double_tapping(const struct tp_dispatch *tp); From 626a0af4ec1c970aaf781fdac0bf4c7a9a2a58e1 Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Thu, 6 Jan 2022 13:22:42 +0100 Subject: [PATCH 3/7] TFD: add debounce state for touch count decrease Don't allow motion events within a certain interval from a touch count decrease (to 1 finger) to finish the drag. This should make even very quick drags remain in drag-lock instead of being unintentionally finished by the last finger. Signed-off-by: Temp Name --- src/evdev-mt-touchpad-tfd.c | 93 ++++++++++++++++++++++++++++++++++--- src/evdev-mt-touchpad.h | 13 ++++-- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c index c12bc27b..49c576bd 100644 --- a/src/evdev-mt-touchpad-tfd.c +++ b/src/evdev-mt-touchpad-tfd.c @@ -44,6 +44,7 @@ tfd_state_to_str(enum tp_tfd_state state) CASE_RETURN_STRING(TFD_STATE_IDLE); CASE_RETURN_STRING(TFD_STATE_POSSIBLE_DRAG); CASE_RETURN_STRING(TFD_STATE_DRAG); + CASE_RETURN_STRING(TFD_STATE_POSSIBLE_ZERO_FINGERS); CASE_RETURN_STRING(TFD_STATE_AWAIT_RESUME); CASE_RETURN_STRING(TFD_STATE_POSSIBLE_RESUME); } @@ -275,6 +276,8 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, } break; case TFD_EVENT_MOTION: + /* this event must ensure it fires upon cursor movement -- alternatively, if + impossible, TODO: cursor should be pinned in this state to ensure this */ switch (nfingers_down) { default: log_tfd_bug(tp, event, nfingers_down); @@ -303,6 +306,12 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, } } + + /* TODO: Future improvement: When one finger moves considerably + faster than the others, don't average their deltas for cursor + position updates -- use the fastest finger only */ + + static void tp_tfd_drag_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -315,10 +324,7 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, case TFD_EVENT_TOUCH_COUNT_DECREASE: switch (nfingers_down) { case 0: - case 1: tp_tfd_pin_fingers(tp); - /* removing all, or all but one, fingers gives you ~0.7 seconds to - place three fingers back on the touchpad before the drag ends */ tp_tfd_set_await_resume_timer(tp, time); tp->tfd.state = TFD_STATE_AWAIT_RESUME; @@ -327,15 +333,18 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, // 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; default: break; } break; case TFD_EVENT_MOTION: - /* TODO: Future improvement: When one finger moves considerably - faster than the others, don't average their deltas for cursor - position updates -- use the fastest finger only */ break; case TFD_EVENT_RESUME_TIMEOUT: case TFD_EVENT_TIMEOUT: @@ -352,6 +361,73 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, } } + +/* Waiting for zero fingers. Drag has decreased to 1 finger, but it might be +a transitory phase towards 0 fingers. Allow a small amount of time for that +before allowing one finger to break out of the drag in the AWAIT state. Makes it +harder to end very fast, brief drags. */ +static void +tp_tfd_possible_zero_fingers_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_DECREASE: + switch (nfingers_down) { + case 0: + tp_tfd_clear_timer(tp); + tp->tfd.state = TFD_STATE_AWAIT_RESUME; + break; + default: + log_tfd_bug(tp, event, nfingers_down); + break; + } + break; + case TFD_EVENT_MOTION: + break; + case TFD_EVENT_RESUME_TIMEOUT: + /* this shouldn't have time to happen */ + log_tfd_bug(tp, event, nfingers_down); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_timer(tp); + tp_tfd_unpin_fingers(tp); + break; + case TFD_EVENT_TOUCH_COUNT_INCREASE: + /* an increase forces immediate evaluation as if the timer had fired */ + tp_tfd_clear_timer(tp); + /* fallthrough */ + case TFD_EVENT_TIMEOUT: + /* time to (most probably) transition to the AWAIT state with 0 or 1 fingers */ + switch (nfingers_down) { + case 0: + case 1: + case 2: + tp->tfd.state = TFD_STATE_AWAIT_RESUME; + break; + case 3: + tp_tfd_set_await_more_fingers_timer(tp, time); + tp->tfd.state = TFD_STATE_POSSIBLE_RESUME; + break; + default: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } + break; + case TFD_EVENT_TAP: + case TFD_EVENT_BUTTON: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_resume_timer(tp); + tp_tfd_clear_timer(tp); + tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); + break; + } +} + /* Drag-lock; After leaving 3 finger dragging there's a small time window where you can resume the drag with 3 fingers. */ static void @@ -621,6 +697,10 @@ tp_tfd_handle_event(struct tp_dispatch *tp, tp_tfd_drag_handle_event(tp, t, event, time, nfingers_down); break; + case TFD_STATE_POSSIBLE_ZERO_FINGERS: + tp_tfd_possible_zero_fingers_handle_event(tp, t, event, time, nfingers_down); + break; + case TFD_STATE_AWAIT_RESUME: tp_tfd_await_resume_handle_event(tp, t, event, time, nfingers_down); break; @@ -816,6 +896,7 @@ void tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time) { switch (tp->tfd.state) { + case TFD_STATE_POSSIBLE_ZERO_FINGERS: case TFD_STATE_AWAIT_RESUME: case TFD_STATE_POSSIBLE_RESUME: tp_tfd_handle_event(tp, NULL, TFD_EVENT_TAP, time, tp->tfd.finger_count); diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index b6807ccb..ddcbe2e3 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -136,15 +136,20 @@ enum tp_tap_state { enum tp_tfd_state { + /* [debounce] = brief states anticipating a further increase/decrease in finger + count, preventing unintended drops on account of motion events */ + /* waiting for 3 fingers */ TFD_STATE_IDLE, - /* 3 fingers down, possible 4+ f gesture */ + /* 3 fingers touching, possible 4+ f gesture */ TFD_STATE_POSSIBLE_DRAG, - /* 3 fingers down and button press has been output */ + /* 3 fingers touching and button press has been output */ TFD_STATE_DRAG, - /* drag-lock; waiting for drag continuation */ + /* [debounce] drag-lock; 1 finger touching, possibly going to 0 fingers */ + TFD_STATE_POSSIBLE_ZERO_FINGERS, + /* drag-lock; waiting for 3 finger drag continuation */ TFD_STATE_AWAIT_RESUME, - /* disambiguate between drag continuation and a possible 4+ gesture */ + /* [debounce] disambiguate between drag continuation and a possible 4+ gesture */ TFD_STATE_POSSIBLE_RESUME, }; From f5b83120ae559766b741661afc4b0046cd338b79 Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Thu, 27 Jan 2022 14:50:30 +0100 Subject: [PATCH 4/7] Take hold gestures and clickpad state into account - Avoid entering the drag state if the clickpad is pressed. - Finish hold gestures and cancel other gestures before entering the drag state. --- src/evdev-mt-touchpad-tfd.c | 77 ++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c index 49c576bd..c1c32862 100644 --- a/src/evdev-mt-touchpad-tfd.c +++ b/src/evdev-mt-touchpad-tfd.c @@ -255,6 +255,28 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp, } } +/* finishes hold gestures and cancels other gestures */ +static void +tp_tfd_interrupt_gestures(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) +{ + switch (tp->gesture.state) { + case GESTURE_STATE_NONE: + case GESTURE_STATE_POINTER_MOTION: + break; /* should be harmless enough? */ + case GESTURE_STATE_HOLD: + case GESTURE_STATE_HOLD_AND_MOTION: + /* starting a drag should finish a hold gesture -- not + cancel it (if there's any difference?) */ + tp_gesture_stop(tp, time); + break; + default: + tp_gesture_cancel(tp, time); + break; + } + /* TODO: sooner or later, calls between state machines will start resulting + in infinite recursion... */ +} + /* We don't have the primary button pressed in this state; the press is delayed if the fingers have remained stationary */ static void @@ -262,6 +284,11 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tfd_event event, uint64_t time, int nfingers_down) { + /* it is possible to use 3 fingers with a clickpad to emulate e.g. middle + mouse button clicks, and no-one wants a primary click while doing a middle + click on a link, for instance */ + bool clickpad_pressed = tp->buttons.is_clickpad && tp->buttons.state; + switch (event) { case TFD_EVENT_TOUCH_COUNT_INCREASE: case TFD_EVENT_TOUCH_COUNT_DECREASE: @@ -276,14 +303,22 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, } break; case TFD_EVENT_MOTION: - /* this event must ensure it fires upon cursor movement -- alternatively, if - impossible, TODO: cursor should be pinned in this state to ensure this */ + /* this event must ensure it fires upon cursor movement + + alternatively: cursor should be pinned in this state to ensure this. + But pinning is not acceptable in this state as long as we check clickpad + state and exit early... */ switch (nfingers_down) { default: log_tfd_bug(tp, event, nfingers_down); break; // bug case 3: - /* perform a press since it hasn't already been done by the timer */ + if (clickpad_pressed) + break; + + /* show special consideration for overlapping hold gestures */ + tp_tfd_interrupt_gestures(tp, t, time); + tp->tfd.state = TFD_STATE_DRAG; tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); tp_tfd_clear_timer(tp); @@ -292,8 +327,12 @@ tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, case TFD_EVENT_RESUME_TIMEOUT: break; case TFD_EVENT_TIMEOUT: - /* we've not moved our three fingers so we perform the press after the - initial delay */ + if (clickpad_pressed) + break; + + /* show special consideration for overlapping hold gestures */ + tp_tfd_interrupt_gestures(tp, t, time); + tp->tfd.state = TFD_STATE_DRAG; tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); break; @@ -658,6 +697,26 @@ tp_tfd_possible_resume_handle_event(struct tp_dispatch *tp, // TODO: It's too easy to trigger 3fd while scrolling and a third finger // touches momentarily. */ +/* Whether to disregard this event for the current state instead of handling it */ +// static bool +// tp_tfd_should_filter_event(struct tp_dispatch *tp, +// struct tp_touch *t, +// enum tfd_event event, +// uint64_t time, +// int nfingers_down) +// { +// switch (tp->tfd.state) { +// case TFD_STATE_POSSIBLE_DRAG: +// /* don't engage TFD while clickpad is pressed */ +// return (event == TFD_EVENT_MOTION || event == TFD_EVENT_TIMEOUT) && +// tp->buttons.is_clickpad && tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS; +// /* TODO: not very clean since motion and timeouts aren't directly +// related to entering the drag state. Consider a more clear solution. */ +// default: +// return false; +// } +// } + static void tp_tfd_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -684,6 +743,12 @@ tp_tfd_handle_event(struct tp_dispatch *tp, break; } + // /* currently used to prevent TFD while clickpad button is pressed */ + // /* TODO: consider just inspecting the clickpad state in the POSSIBLE_DRAG + // handler to at least gather all state transition decisions in one place? */ + // if (tp_tfd_should_filter_event(tp, t, event, time, nfingers_down)) + // return; + switch(tp->tfd.state) { case TFD_STATE_IDLE: tp_tfd_idle_handle_event(tp, t, event, time, nfingers_down); @@ -833,7 +898,7 @@ tp_tfd_handle_state(struct tp_dispatch *tp, uint64_t time) // if (!tp_tfd_enabled(tp)) // return 0; - /* Handle queued button pressed events from clickpads. */ + /* Handle queued button pressed events. */ if (/* tp->buttons.is_clickpad && */ tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) tp_tfd_handle_event(tp, NULL, TFD_EVENT_BUTTON, time, active_touches); From ca45aca406b79474392771f73bb4094a859ac771 Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Mon, 7 Feb 2022 15:44:00 +0100 Subject: [PATCH 5/7] Debounce for 4+ fingers before drag starts When the drag hasn't started there's now a 50 ms debounce time to disambiguate from 4+ finger gestures. --- src/evdev-mt-touchpad-tfd.c | 89 +++++++++++++++++++++++++++++++++---- src/evdev-mt-touchpad.h | 6 ++- 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c index c1c32862..d9a27c8a 100644 --- a/src/evdev-mt-touchpad-tfd.c +++ b/src/evdev-mt-touchpad-tfd.c @@ -42,7 +42,8 @@ tfd_state_to_str(enum tp_tfd_state state) { switch(state) { CASE_RETURN_STRING(TFD_STATE_IDLE); - CASE_RETURN_STRING(TFD_STATE_POSSIBLE_DRAG); + CASE_RETURN_STRING(TFD_STATE_POSSIBLE_BEGIN); + CASE_RETURN_STRING(TFD_STATE_AWAIT_DRAG); CASE_RETURN_STRING(TFD_STATE_DRAG); CASE_RETURN_STRING(TFD_STATE_POSSIBLE_ZERO_FINGERS); CASE_RETURN_STRING(TFD_STATE_AWAIT_RESUME); @@ -239,8 +240,9 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp, case TFD_EVENT_TOUCH_COUNT_INCREASE: case TFD_EVENT_TOUCH_COUNT_DECREASE: if (nfingers_down == 3) { - tp->tfd.state = TFD_STATE_POSSIBLE_DRAG; - tp_tfd_set_button_press_delay_timer(tp, time); + tp->tfd.state = TFD_STATE_POSSIBLE_BEGIN; + // tp_tfd_set_button_press_delay_timer(tp, time); + tp_tfd_set_await_more_fingers_timer(tp, time); } break; case TFD_EVENT_MOTION: @@ -255,6 +257,72 @@ tp_tfd_idle_handle_event(struct tp_dispatch *tp, } } + + + + +/* 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 +drag. */ +static void +tp_tfd_possible_begin_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_INCREASE: + switch (nfingers_down) { + case 0: + case 1: + case 2: + case 3: + break; // bug? + default: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_timer(tp); + break; + } + break; + case TFD_EVENT_MOTION: + break; + case TFD_EVENT_RESUME_TIMEOUT: + break; // bug + case TFD_EVENT_TOUCH_COUNT_DECREASE: + /* a decrease forces immediate evaluation as if the timer had fired */ + tp_tfd_clear_timer(tp); + /* fallthrough */ + case TFD_EVENT_TIMEOUT: + /* time to check whether we have 3 fingers touching */ + switch (nfingers_down) { + case 0: + case 1: + case 2: + default: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + break; + case 3: + tp_tfd_unpin_fingers(tp); + // TODO: compensate for the duration of this state + tp_tfd_set_button_press_delay_timer(tp, time); + tp->tfd.state = TFD_STATE_AWAIT_DRAG; + break; + } + break; + case TFD_EVENT_TAP: + case TFD_EVENT_BUTTON: + tp_tfd_unpin_fingers(tp); + tp->tfd.state = TFD_STATE_IDLE; + tp_tfd_clear_timer(tp); + break; + } +} + + + + /* finishes hold gestures and cancels other gestures */ static void tp_tfd_interrupt_gestures(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) @@ -280,7 +348,7 @@ tp_tfd_interrupt_gestures(struct tp_dispatch *tp, struct tp_touch *t, uint64_t t /* We don't have the primary button pressed in this state; the press is delayed if the fingers have remained stationary */ static void -tp_tfd_possible_drag_handle_event(struct tp_dispatch *tp, +tp_tfd_await_drag_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tfd_event event, uint64_t time, int nfingers_down) { @@ -754,8 +822,12 @@ tp_tfd_handle_event(struct tp_dispatch *tp, tp_tfd_idle_handle_event(tp, t, event, time, nfingers_down); break; - case TFD_STATE_POSSIBLE_DRAG: - tp_tfd_possible_drag_handle_event(tp, t, event, time, nfingers_down); + case TFD_STATE_POSSIBLE_BEGIN: + tp_tfd_possible_begin_handle_event(tp, t, event, time, nfingers_down); + break; + + case TFD_STATE_AWAIT_DRAG: + tp_tfd_await_drag_handle_event(tp, t, event, time, nfingers_down); break; case TFD_STATE_DRAG: @@ -826,7 +898,7 @@ tp_tfd_exceeds_motion_threshold(struct tp_dispatch *tp, return false; double threshold = DEFAULT_TFD_MOVE_THRESHOLD; - if (tp->tfd.state == TFD_STATE_POSSIBLE_DRAG) { + if (tp->tfd.state == TFD_STATE_AWAIT_DRAG) { // TODO: have to figure out something better // MOTION events are too decoupled from what's required to actually move // the cursor. TODO: look it up @@ -967,7 +1039,8 @@ tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time) tp_tfd_handle_event(tp, NULL, TFD_EVENT_TAP, time, tp->tfd.finger_count); break; case TFD_STATE_IDLE: - case TFD_STATE_POSSIBLE_DRAG: + case TFD_STATE_POSSIBLE_BEGIN: + case TFD_STATE_AWAIT_DRAG: case TFD_STATE_DRAG: break; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index ddcbe2e3..09268602 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -141,8 +141,10 @@ enum tp_tfd_state { /* waiting for 3 fingers */ TFD_STATE_IDLE, - /* 3 fingers touching, possible 4+ f gesture */ - TFD_STATE_POSSIBLE_DRAG, + /* [debounce] disambiguate between starting a drag and a possible 4+ gesture */ + TFD_STATE_POSSIBLE_BEGIN, + /* 3 fingers touching, waiting for motion or timeout */ + TFD_STATE_AWAIT_DRAG, /* 3 fingers touching and button press has been output */ TFD_STATE_DRAG, /* [debounce] drag-lock; 1 finger touching, possibly going to 0 fingers */ From a2f3b497b14c80a79912bd83b55801be3b70f138 Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Mon, 7 Feb 2022 16:19:01 +0100 Subject: [PATCH 6/7] Cancel hold gestures instead of finishing them It was requested that we should be consistent here with what happens when swipe gestures interrupt hold gestures. --- src/evdev-mt-touchpad-tfd.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c index d9a27c8a..33fb4a06 100644 --- a/src/evdev-mt-touchpad-tfd.c +++ b/src/evdev-mt-touchpad-tfd.c @@ -331,12 +331,12 @@ tp_tfd_interrupt_gestures(struct tp_dispatch *tp, struct tp_touch *t, uint64_t t case GESTURE_STATE_NONE: case GESTURE_STATE_POINTER_MOTION: break; /* should be harmless enough? */ - case GESTURE_STATE_HOLD: - case GESTURE_STATE_HOLD_AND_MOTION: - /* starting a drag should finish a hold gesture -- not - cancel it (if there's any difference?) */ - tp_gesture_stop(tp, time); - break; + // case GESTURE_STATE_HOLD: + // case GESTURE_STATE_HOLD_AND_MOTION: + // /* starting a drag should finish a hold gesture -- not + // cancel it (if there's any difference?) */ + // tp_gesture_stop(tp, time); + // break; default: tp_gesture_cancel(tp, time); break; From 13c7fa61369662f8e07cd66f913ea0f803c21cdb Mon Sep 17 00:00:00 2001 From: abc def <24701-abcdef@users.noreply.gitlab.freedesktop.org> Date: Mon, 14 Feb 2022 13:59:11 +0100 Subject: [PATCH 7/7] Abort TFD within 50 ms on detection of 4+ fingers Improved method of disambiguating between three finger drags and four finger gestures. --- src/evdev-mt-touchpad-tfd.c | 239 ++++++++++++++++++++++++------------ src/evdev-mt-touchpad.h | 12 +- 2 files changed, 169 insertions(+), 82 deletions(-) diff --git a/src/evdev-mt-touchpad-tfd.c b/src/evdev-mt-touchpad-tfd.c index 33fb4a06..681beedf 100644 --- a/src/evdev-mt-touchpad-tfd.c +++ b/src/evdev-mt-touchpad-tfd.c @@ -7,7 +7,7 @@ #include "evdev-mt-touchpad.h" /* 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) /* The interval between three fingers touching and a button press being performed, if the fingers remain stationary */ @@ -42,8 +42,9 @@ tfd_state_to_str(enum tp_tfd_state state) { switch(state) { 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_DRAG_AND_DEBOUNCE); CASE_RETURN_STRING(TFD_STATE_DRAG); CASE_RETURN_STRING(TFD_STATE_POSSIBLE_ZERO_FINGERS); 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) { switch (event) { - case TFD_EVENT_TOUCH_COUNT_INCREASE: 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) { - tp->tfd.state = TFD_STATE_POSSIBLE_BEGIN; - // tp_tfd_set_button_press_delay_timer(tp, time); - tp_tfd_set_await_more_fingers_timer(tp, time); + tp->tfd.state = TFD_STATE_AWAIT_DRAG; + tp_tfd_set_button_press_delay_timer(tp, time); + // tp_tfd_set_await_more_fingers_timer(tp, time); } break; 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 a transitory phase towards 4 or more fingers, which should not begin the drag. */ -static void -tp_tfd_possible_begin_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_INCREASE: - switch (nfingers_down) { - case 0: - case 1: - case 2: - case 3: - break; // bug? - default: - tp_tfd_unpin_fingers(tp); - tp->tfd.state = TFD_STATE_IDLE; - tp_tfd_clear_timer(tp); - break; - } - break; - case TFD_EVENT_MOTION: - break; - case TFD_EVENT_RESUME_TIMEOUT: - break; // bug - case TFD_EVENT_TOUCH_COUNT_DECREASE: - /* a decrease forces immediate evaluation as if the timer had fired */ - tp_tfd_clear_timer(tp); - /* fallthrough */ - case TFD_EVENT_TIMEOUT: - /* time to check whether we have 3 fingers touching */ - switch (nfingers_down) { - case 0: - case 1: - case 2: - default: - tp_tfd_unpin_fingers(tp); - tp->tfd.state = TFD_STATE_IDLE; - break; - case 3: - tp_tfd_unpin_fingers(tp); - // TODO: compensate for the duration of this state - tp_tfd_set_button_press_delay_timer(tp, time); - tp->tfd.state = TFD_STATE_AWAIT_DRAG; - break; - } - break; - case TFD_EVENT_TAP: - case TFD_EVENT_BUTTON: - tp_tfd_unpin_fingers(tp); - tp->tfd.state = TFD_STATE_IDLE; - tp_tfd_clear_timer(tp); - break; - } -} +// static void +// tp_tfd_possible_begin_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_INCREASE: +// switch (nfingers_down) { +// case 0: +// case 1: +// case 2: +// case 3: +// break; // bug? +// default: +// tp_tfd_unpin_fingers(tp); +// tp->tfd.state = TFD_STATE_IDLE; +// tp_tfd_clear_timer(tp); +// break; +// } +// break; +// case TFD_EVENT_MOTION: +// break; +// case TFD_EVENT_RESUME_TIMEOUT: +// break; // bug +// case TFD_EVENT_TOUCH_COUNT_DECREASE: +// /* a decrease forces immediate evaluation as if the timer had fired */ +// tp_tfd_clear_timer(tp); +// /* fallthrough */ +// case TFD_EVENT_TIMEOUT: +// /* time to check whether we have 3 fingers touching */ +// switch (nfingers_down) { +// case 0: +// case 1: +// case 2: +// default: +// tp_tfd_unpin_fingers(tp); +// tp->tfd.state = TFD_STATE_IDLE; +// break; +// case 3: +// tp_tfd_unpin_fingers(tp); +// // TODO: compensate for the duration of this state +// tp_tfd_set_button_press_delay_timer(tp, time); +// tp->tfd.state = TFD_STATE_AWAIT_DRAG; +// break; +// } +// break; +// case TFD_EVENT_TAP: +// case TFD_EVENT_BUTTON: +// tp_tfd_unpin_fingers(tp); +// tp->tfd.state = TFD_STATE_IDLE; +// tp_tfd_clear_timer(tp); +// break; +// } +// } -/* finishes hold gestures and cancels other gestures */ +/* takes appropriate action to cancel or finish a gesture in progress */ static void 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 */ 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_clear_timer(tp); } break; 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 */ 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); break; case TFD_EVENT_TAP: case TFD_EVENT_BUTTON: // TODO: undecided //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; } } @@ -419,6 +428,71 @@ tp_tfd_await_drag_handle_event(struct tp_dispatch *tp, 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 tp_tfd_drag_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -462,7 +536,6 @@ tp_tfd_drag_handle_event(struct tp_dispatch *tp, case TFD_EVENT_BUTTON: tp_tfd_unpin_fingers(tp); tp->tfd.state = TFD_STATE_IDLE; - tp_tfd_clear_resume_timer(tp); tp_tfd_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; } @@ -797,7 +870,6 @@ tp_tfd_handle_event(struct tp_dispatch *tp, previous_state = tp->tfd.state; assert(nfingers_down >= 0); - // assert(nfingers_down < 6); // TODO: temp, remove switch (event) { 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); break; - case TFD_STATE_POSSIBLE_BEGIN: - tp_tfd_possible_begin_handle_event(tp, t, event, time, nfingers_down); - break; + // case TFD_STATE_POSSIBLE_BEGIN: + // tp_tfd_possible_begin_handle_event(tp, t, event, time, nfingers_down); + // break; case TFD_STATE_AWAIT_DRAG: tp_tfd_await_drag_handle_event(tp, t, event, time, nfingers_down); 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: tp_tfd_drag_handle_event(tp, t, event, time, nfingers_down); break; @@ -1033,16 +1109,17 @@ void tp_tfd_handle_tap(struct tp_dispatch *tp, uint64_t time) { 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_AWAIT_RESUME: case TFD_STATE_POSSIBLE_RESUME: tp_tfd_handle_event(tp, NULL, TFD_EVENT_TAP, time, tp->tfd.finger_count); 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) { 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_POSSIBLE_ZERO_FINGERS: case TFD_STATE_AWAIT_RESUME: case TFD_STATE_POSSIBLE_RESUME: return true; - default: - return false; } + return false; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 09268602..ad0ba5c0 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -141,16 +141,22 @@ enum tp_tfd_state { /* waiting for 3 fingers */ TFD_STATE_IDLE, - /* [debounce] disambiguate between starting a drag and a possible 4+ gesture */ - TFD_STATE_POSSIBLE_BEGIN, + // not used -- TODO: remove after testing drag_and_debounce + // /* [debounce] disambiguate between starting a drag and a possible 4+ gesture */ + // TFD_STATE_POSSIBLE_BEGIN, /* 3 fingers touching, waiting for motion or timeout */ 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 */ 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, /* drag-lock; waiting for 3 finger drag continuation */ 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 */ TFD_STATE_POSSIBLE_RESUME, };