From d4ceb671b95707f79676768a8b85d920f1c90cbb Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 17 Jul 2015 09:30:03 +1000 Subject: [PATCH] touchpad: handle serial synaptics slot confusion on TRIPLETAP Synatics touchpads only have 2 slots, but support TRIPLETAP and above. When the third finger touches, the kernel may end the second slot and re-start it with the coordinates of the third touch in the next frame. The event sequence is something like: ABS_MT_SLOT 0 ABS_MT_POSITION_X 4000 ABS_MT_POSITION_Y 4000 ABS_MT_PRESSURE 78 ABS_MT_SLOT 1 ABS_MT_TRACKING_ID -1 ABS_X 4000 ABS_Y 4000 ABS_PRESSURE 78 BTN_TOOL_DOUBLETAP 0 BTN_TOOL_TRIPLETAP 1 --- SYN_REPORT (0) ---------- ABS_MT_SLOT 0 ABS_MT_POSITION_X 4000 ABS_MT_POSITION_Y 4000 ABS_MT_PRESSURE 78 ABS_MT_SLOT 1 ABS_MT_TRACKING_ID 55 ABS_MT_POSITION_X 2000 ABS_MT_POSITION_Y 2000 ABS_MT_PRESSURE 72 ABS_X 4000 ABS_Y 4000 ABS_PRESSURE 78 --- SYN_REPORT (0) ---------- libinput usually ignores any BTN_TOOL_* <= num_slots since we expect that the slot values are valid. Make an exception for the serial synaptics touchpads. If a touch has ended when the fake touch goes above active-slots (but still within num-slots), move that touch back to UPDATE. This ensures the right number of nfingers_down. When the touch restarts again in the next frame, tp_begin_touch() will skip over re-initializing it because it's already in UPDATE anyway. Note that at this point this only handles the transition _to_ TRIPLETAP, not from TRIPLETAP to DOUBLETAP. Need to wait for this to be seen in the wild first. https://bugs.freedesktop.org/show_bug.cgi?id=91352 Signed-off-by: Peter Hutterer Hallelujah-expressed-by: Benjamin Tissoires Reviewed-by: Hans de Goede --- src/evdev-mt-touchpad.c | 38 +++++++++++++ test/touchpad.c | 116 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index b78618b9..1bf206de 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -341,6 +341,40 @@ tp_process_absolute_st(struct tp_dispatch *tp, } } +static inline void +tp_restore_synaptics_touches(struct tp_dispatch *tp, + uint64_t time) +{ + unsigned int i; + unsigned int nfake_touches; + + nfake_touches = tp_fake_finger_count(tp); + if (nfake_touches < 3) + return; + + if (tp->nfingers_down >= nfake_touches || + tp->nfingers_down == tp->num_slots) + return; + + /* Synaptics devices may end touch 2 on BTN_TOOL_TRIPLETAP + * and start it again on the next frame with different coordinates + * (#91352). We search the touches we have, if there is one that has + * just ended despite us being on tripletap, we move it back to + * update. + */ + for (i = 0; i < tp->num_slots; i++) { + struct tp_touch *t = tp_get_touch(tp, i); + + if (t->state != TOUCH_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; + } +} + static void tp_process_fake_touches(struct tp_dispatch *tp, uint64_t time) @@ -353,6 +387,10 @@ tp_process_fake_touches(struct tp_dispatch *tp, if (nfake_touches == FAKE_FINGER_OVERFLOW) return; + if (tp->device->model_flags & + EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD) + tp_restore_synaptics_touches(tp, time); + start = tp->has_mt ? tp->num_slots : 0; for (i = start; i < tp->ntouches; i++) { t = tp_get_touch(tp, i); diff --git a/test/touchpad.c b/test/touchpad.c index 64bdd440..bc10aba5 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -4279,6 +4279,120 @@ START_TEST(touchpad_thumb_tap_hold_2ndfg_tap) } END_TEST +START_TEST(touchpad_tool_tripletap_touch_count) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_pointer *ptrev; + + /* Synaptics touchpads sometimes end one touch point while + * simultaneously setting BTN_TOOL_TRIPLETAP. + * https://bugs.freedesktop.org/show_bug.cgi?id=91352 + */ + litest_drain_events(li); + enable_clickfinger(dev); + + /* touch 1 down */ + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 1); + litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 1200); + litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 3200); + litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 78); + litest_event(dev, EV_ABS, ABS_X, 1200); + litest_event(dev, EV_ABS, ABS_Y, 3200); + litest_event(dev, EV_ABS, ABS_PRESSURE, 78); + litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + msleep(2); + + /* touch 2 down */ + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 1); + litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 2200); + litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 3200); + litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 73); + litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0); + litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + msleep(2); + + /* touch 3 down, coordinate jump + ends slot 1 */ + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 4000); + litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 4000); + litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 78); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_X, 4000); + litest_event(dev, EV_ABS, ABS_Y, 4000); + litest_event(dev, EV_ABS, ABS_PRESSURE, 78); + 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); + msleep(2); + + /* slot 2 reactivated: + * Note, slot is activated close enough that we don't accidentally + * trigger the clickfinger distance check, remains to be seen if + * that is true for real-world interaction. + */ + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 4000); + litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 4000); + litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 78); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 1); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 3); + litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 3500); + litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 3500); + litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 73); + litest_event(dev, EV_ABS, ABS_X, 4000); + litest_event(dev, EV_ABS, ABS_Y, 4000); + litest_event(dev, EV_ABS, ABS_PRESSURE, 78); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + msleep(2); + + /* now a click should trigger middle click */ + litest_event(dev, EV_KEY, BTN_LEFT, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + litest_event(dev, EV_KEY, BTN_LEFT, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_wait_for_event(li); + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_PRESSED); + libinput_event_destroy(event); + event = libinput_get_event(li); + ptrev = litest_is_button_event(event, + BTN_MIDDLE, + LIBINPUT_BUTTON_STATE_RELEASED); + /* silence gcc set-but-not-used warning, litest_is_button_event + * checks what we care about */ + event = libinput_event_pointer_get_base_event(ptrev); + libinput_event_destroy(event); + + /* release everything */ + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_ABS, ABS_MT_SLOT, 0); + litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0); + litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0); + litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); +} +END_TEST + void litest_setup_tests(void) { @@ -4413,4 +4527,6 @@ litest_setup_tests(void) litest_add("touchpad:thumb", touchpad_thumb_tap_hold, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:thumb", touchpad_thumb_tap_hold_2ndfg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:thumb", touchpad_thumb_tap_hold_2ndfg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + + litest_add_for_device("touchpad:bugs", touchpad_tool_tripletap_touch_count, LITEST_SYNAPTICS_TOPBUTTONPAD); }