Commit 6ccd8e93 authored by Peter Hutterer's avatar Peter Hutterer

touchpad: add a TOUCH_MAYBE_END state

This state is used by the pre-processing of the touch states to indicate that
the touch point has ended and is changed to TOUCH_END as soon as that
pre-processing is finished.

Sometimes we have to resurrect a touch point that has physically or logically
ended but needs to be kept around to keep the BTN_TOOL_* fake finger count
happy. Particularly on Synaptics touchpads, where a BTN_TOOL_TRIPLETAP can
cause a touch point to end (i.e. 1 touch down + TRIPLETAP) but that touch
restarts in the next sequence. We had a quirk for this in place already, but
if we end the touch and then re-instate it with tp_begin_touch(), we may lose
some information about thumb/palm/etc. states that touch already had. As a
result, the state machines can get confused and a touch that was previously
ignored as thumb suddenly isn't one anymore and triggers assertions.

The specific sequence in bug 10528 is:
* touch T1 down
* touch T2 down, detected as speed-based thumb, tap state machine ignores
  it
* frame F: TRIPLETAP down, touch T2 up
* frame F+1: touch T2 down in next frame, but without the thumb bit
* frame F+n: touch T2 ends, tap state machine gets confused because
  that touch should not trigger a release

https://bugs.freedesktop.org/show_bug.cgi?id=105258Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
parent 3979b9e1
......@@ -366,6 +366,14 @@ tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
case TOUCH_UPDATE:
tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_MOTION);
break;
case TOUCH_MAYBE_END:
/* This shouldn't happen we transfer to TOUCH_END
* before processing state */
evdev_log_debug(tp->device,
"touch %d: unexpected state %d\n",
t->index,
t->state);
/* fallthrough */
case TOUCH_END:
tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_RELEASE);
break;
......
......@@ -302,22 +302,66 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
}
/**
* End a touch, even if the touch sequence is still active.
* Schedule a touch to be ended, based on either the events or some
* attributes of the touch (size, pressure). In some cases we need to
* resurrect a touch that has ended, so this doesn't actually end the touch
* yet. All the TOUCH_MAYBE_END touches get properly ended once the device
* state has been processed once and we know how many zombie touches we
* need.
*/
static inline void
tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
tp_maybe_end_touch(struct tp_dispatch *tp,
struct tp_touch *t,
uint64_t time)
{
switch (t->state) {
case TOUCH_HOVERING:
t->state = TOUCH_NONE;
/* fallthough */
case TOUCH_NONE:
case TOUCH_MAYBE_END:
case TOUCH_HOVERING:
return;
case TOUCH_END:
evdev_log_bug_libinput(tp->device,
"touch %d: already in TOUCH_END\n",
t->index);
return;
case TOUCH_BEGIN:
case TOUCH_UPDATE:
break;
}
t->dirty = true;
t->state = TOUCH_MAYBE_END;
assert(tp->nfingers_down >= 1);
tp->nfingers_down--;
}
/**
* Inverse to tp_maybe_end_touch(), restores a touch back to its previous
* state.
*/
static inline void
tp_recover_ended_touch(struct tp_dispatch *tp,
struct tp_touch *t)
{
t->dirty = true;
t->state = TOUCH_UPDATE;
tp->nfingers_down++;
}
/**
* End a touch, even if the touch sequence is still active.
* Use tp_maybe_end_touch() instead.
*/
static inline void
tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
{
if (t->state != TOUCH_MAYBE_END) {
evdev_log_bug_libinput(tp->device,
"touch %d should be MAYBE_END, is %d\n",
t->index,
t->state);
return;
}
t->dirty = true;
......@@ -326,8 +370,6 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
t->pinned.is_pinned = false;
t->time = time;
t->palm.time = 0;
assert(tp->nfingers_down >= 1);
tp->nfingers_down--;
tp->queued |= TOUCHPAD_EVENT_MOTION;
}
......@@ -338,7 +380,7 @@ static inline void
tp_end_sequence(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
{
t->has_ended = true;
tp_end_touch(tp, t, time);
tp_maybe_end_touch(tp, t, time);
}
static void
......@@ -485,13 +527,11 @@ tp_restore_synaptics_touches(struct tp_dispatch *tp,
for (i = 0; i < tp->num_slots; i++) {
struct tp_touch *t = tp_get_touch(tp, i);
if (t->state != TOUCH_END)
if (t->state != TOUCH_MAYBE_END)
continue;
/* new touch, move it through begin to update immediately */
tp_new_touch(tp, t, time);
tp_begin_touch(tp, t, time);
t->state = TOUCH_UPDATE;
tp_recover_ended_touch(tp, t);
}
}
......@@ -1110,7 +1150,7 @@ tp_unhover_pressure(struct tp_dispatch *tp, uint64_t time)
evdev_log_debug(tp->device,
"pressure: end touch %d\n",
t->index);
tp_end_touch(tp, t, time);
tp_maybe_end_touch(tp, t, time);
}
}
}
......@@ -1147,10 +1187,11 @@ tp_unhover_pressure(struct tp_dispatch *tp, uint64_t time)
t = tp_get_touch(tp, i);
if (t->state == TOUCH_HOVERING ||
t->state == TOUCH_NONE)
t->state == TOUCH_NONE ||
t->state == TOUCH_MAYBE_END)
continue;
tp_end_touch(tp, t, time);
tp_maybe_end_touch(tp, t, time);
if (real_fingers_down > 0 &&
tp->nfingers_down == nfake_touches)
......@@ -1194,7 +1235,7 @@ tp_unhover_size(struct tp_dispatch *tp, uint64_t time)
evdev_log_debug(tp->device,
"touch-size: end touch %d\n",
t->index);
tp_end_touch(tp, t, time);
tp_maybe_end_touch(tp, t, time);
}
}
}
......@@ -1248,7 +1289,7 @@ tp_unhover_fake_touches(struct tp_dispatch *tp, uint64_t time)
t->state == TOUCH_NONE)
continue;
tp_end_touch(tp, t, time);
tp_maybe_end_touch(tp, t, time);
if (tp_fake_finger_is_touching(tp) &&
tp->nfingers_down == nfake_touches)
......@@ -1416,6 +1457,21 @@ tp_detect_thumb_while_moving(struct tp_dispatch *tp)
second->thumb.state = THUMB_STATE_YES;
}
static void
tp_pre_process_state(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *t;
tp_process_fake_touches(tp, time);
tp_unhover_touches(tp, time);
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_MAYBE_END)
tp_end_touch(tp, t, time);
}
}
static void
tp_process_state(struct tp_dispatch *tp, uint64_t time)
{
......@@ -1425,8 +1481,6 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
bool have_new_touch = false;
unsigned int speed_exceeded_count = 0;
tp_process_fake_touches(tp, time);
tp_unhover_touches(tp, time);
tp_position_fake_touches(tp);
want_motion_reset = tp_need_motion_history_reset(tp);
......@@ -1588,6 +1642,7 @@ tp_handle_state(struct tp_dispatch *tp,
if (tp->hysteresis.enabled)
tp_maybe_disable_hysteresis(tp, time);
tp_pre_process_state(tp, time);
tp_process_state(tp, time);
tp_post_events(tp, time);
tp_post_process_state(tp, time);
......
......@@ -46,10 +46,11 @@ enum touchpad_event {
enum touch_state {
TOUCH_NONE = 0,
TOUCH_HOVERING,
TOUCH_BEGIN,
TOUCH_UPDATE,
TOUCH_END
TOUCH_HOVERING = 1,
TOUCH_BEGIN = 2,
TOUCH_UPDATE = 3,
TOUCH_MAYBE_END = 4,
TOUCH_END = 5,
};
enum touch_palm_state {
......
......@@ -5423,6 +5423,72 @@ START_TEST(touchpad_pressure_tap_2fg_1fg_light)
}
END_TEST
START_TEST(touchpad_pressure_btntool)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 5 },
{ ABS_PRESSURE, 5 },
{ -1, 0 }
};
/* we only have tripletap, can't test 4 slots because nothing will
* happen */
if (libevdev_get_num_slots(dev->evdev) != 2)
return;
if (!touchpad_has_pressure(dev))
return;
litest_enable_tap(dev->libinput_device);
litest_drain_events(li);
/* Two light touches down, doesn't count */
litest_touch_down_extended(dev, 0, 40, 50, axes);
litest_touch_down_extended(dev, 1, 45, 50, axes);
libinput_dispatch(li);
litest_assert_empty_queue(li);
/* Tripletap but since no finger is logically down, it doesn't count */
litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_empty_queue(li);
/* back to two fingers */
litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
/* make one finger real */
litest_touch_move_to(dev, 0, 40, 50, 41, 52, 10, 10);
litest_drain_events(li);
/* tripletap should now be 3 fingers tap */
litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_tap();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED);
}
END_TEST
static inline bool
touchpad_has_touch_size(struct litest_device *dev)
{
......@@ -5806,6 +5872,7 @@ litest_setup_tests_touchpad(void)
litest_add("touchpad:pressure", touchpad_pressure_tap, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("touchpad:pressure", touchpad_pressure_tap_2fg, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("touchpad:pressure", touchpad_pressure_tap_2fg_1fg_light, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:pressure", touchpad_pressure_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:touch-size", touchpad_touch_size, LITEST_APPLE_CLICKPAD, LITEST_ANY);
litest_add("touchpad:touch-size", touchpad_touch_size_2fg, LITEST_APPLE_CLICKPAD, LITEST_ANY);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment