When drawing on a tablet, the hand usually rests on the device, causing touch events. The kernel arbitrates for us in most cases, so we get a touch up and no events while the stylus is in proximity. When lifting the hand off in a natural position, the hand still touches the device when the pen goes out of proximity. This is 'immediately' followed by the hand lifting off the device.
When kernel pen/touch arbitration is active, the pen proximity out causes a touch begin for the hand still on the pad. This is followed by a touch up when the hand lifts which happens to look exactly like a tap-to-click. Fix this by delaying the 'arbitration is now off' toggle, causing any touch that starts immediately after proximity out to be detected as palm and ignored for its lifetime. https://bugs.freedesktop.org/show_bug.cgi?id=104985 Signed-off-by: Peter Hutterer <peter.hutte...@who-t.net> --- src/evdev-mt-touchpad.c | 51 +++++++++++++++++++++++++--- src/evdev-mt-touchpad.h | 6 +++- src/evdev.c | 2 +- test/litest.c | 6 ++++ test/litest.h | 3 ++ test/test-tablet.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 7 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 3b11343e..62ba678e 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -912,7 +912,7 @@ tp_palm_detect_arbitration_triggered(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) { - if (!tp->in_arbitration) + if (!tp->arbitration.in_arbitration) return false; t->palm.state = PALM_ARBITRATION; @@ -1682,6 +1682,8 @@ tp_interface_destroy(struct evdev_dispatch *dispatch) { struct tp_dispatch *tp = tp_dispatch(dispatch); + libinput_timer_cancel(&tp->arbitration.arbitration_timer); + libinput_timer_destroy(&tp->arbitration.arbitration_timer); libinput_timer_destroy(&tp->palm.trackpoint_timer); libinput_timer_destroy(&tp->dwt.keyboard_timer); libinput_timer_destroy(&tp->tap.timer); @@ -2301,6 +2303,15 @@ evdev_tag_touchpad(struct evdev_device *device, } } +static void +tp_arbitration_timeout(uint64_t now, void *data) +{ + struct tp_dispatch *tp = data; + + if (tp->arbitration.in_arbitration) + tp->arbitration.in_arbitration = false; +} + static void tp_interface_toggle_touch(struct evdev_dispatch *dispatch, struct evdev_device *device, @@ -2310,13 +2321,24 @@ tp_interface_toggle_touch(struct evdev_dispatch *dispatch, struct tp_dispatch *tp = tp_dispatch(dispatch); bool arbitrate = !enable; - if (arbitrate == tp->in_arbitration) + if (arbitrate == tp->arbitration.in_arbitration) return; - if (arbitrate) + if (arbitrate) { + libinput_timer_cancel(&tp->arbitration.arbitration_timer); tp_clear_state(tp); - - tp->in_arbitration = arbitrate; + tp->arbitration.in_arbitration = true; + } else { + /* if in-kernel arbitration is in use and there is a touch + * and a pen in proximity, lifting the pen out of proximity + * causes a touch being for the touch. On a hand-lift the + * proximity out precedes the touch up by a few ms, so we + * get what looks like a tap. Fix this by delaying + * arbitration by just a little bit so that any touch in + * event is caught as palm touch. */ + libinput_timer_set(&tp->arbitration.arbitration_timer, + time + ms2us(90)); + } } static struct evdev_dispatch_interface tp_interface = { @@ -2796,6 +2818,23 @@ tp_init_palmdetect_size(struct tp_dispatch *tp, tp->palm.size_threshold = threshold; } +static inline void +tp_init_palmdetect_arbitration(struct tp_dispatch *tp, + struct evdev_device *device) +{ + char timer_name[64]; + + snprintf(timer_name, + sizeof(timer_name), + "%s arbitration", + evdev_device_get_sysname(device)); + libinput_timer_init(&tp->arbitration.arbitration_timer, + tp_libinput_context(tp), + timer_name, + tp_arbitration_timeout, tp); + tp->arbitration.in_arbitration = false; +} + static void tp_init_palmdetect(struct tp_dispatch *tp, struct evdev_device *device) @@ -2805,6 +2844,8 @@ tp_init_palmdetect(struct tp_dispatch *tp, tp->palm.left_edge = INT_MIN; tp->palm.upper_edge = INT_MIN; + tp_init_palmdetect_arbitration(tp, device); + if (device->tags & EVDEV_TAG_EXTERNAL_TOUCHPAD && !tp_is_tpkb_combo_below(device)) return; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 55244064..afd0983d 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -240,7 +240,11 @@ struct tp_dispatch { bool has_mt; bool semi_mt; - bool in_arbitration; /* pen/touch arbitration */ + /* pen/touch arbitration */ + struct { + bool in_arbitration; + struct libinput_timer arbitration_timer; + } arbitration; unsigned int num_slots; /* number of slots */ unsigned int ntouches; /* no slots inc. fakes */ diff --git a/src/evdev.c b/src/evdev.c index 257824aa..5b23201c 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -848,7 +848,7 @@ evdev_process_event(struct evdev_device *device, struct input_event *e) struct evdev_dispatch *dispatch = device->dispatch; uint64_t time = tv2us(&e->time); -#if 0 +#if 1 evdev_print_event(device, e); #endif diff --git a/test/litest.c b/test/litest.c index c102afa8..f5bc8660 100644 --- a/test/litest.c +++ b/test/litest.c @@ -3354,6 +3354,12 @@ litest_timeout_tablet_proxout(void) msleep(70); } +void +litest_timeout_touch_arbitration(void) +{ + msleep(100); +} + void litest_timeout_hysteresis(void) { diff --git a/test/litest.h b/test/litest.h index 50347fea..df725f87 100644 --- a/test/litest.h +++ b/test/litest.h @@ -778,6 +778,9 @@ litest_timeout_trackpoint(void); void litest_timeout_tablet_proxout(void); +void +litest_timeout_touch_arbitration(void); + void litest_timeout_hysteresis(void); diff --git a/test/test-tablet.c b/test/test-tablet.c index 77230ed0..a5a58bc9 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -4113,6 +4113,9 @@ touch_arbitration(struct litest_device *dev, litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + litest_timeout_touch_arbitration(); + libinput_dispatch(li); + /* finger still down */ litest_touch_move_to(finger, 0, 80, 80, 30, 30, 10, 1); litest_touch_up(finger, 0); @@ -4192,6 +4195,11 @@ touch_arbitration_stop_touch(struct litest_device *dev, litest_touch_move_to(finger, 1, 30, 30, 80, 80, 10, 1); litest_assert_empty_queue(li); litest_touch_up(finger, 1); + libinput_dispatch(li); + + litest_timeout_touch_arbitration(); + libinput_dispatch(li); + litest_touch_down(finger, 0, 30, 30); litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1); litest_touch_up(finger, 0); @@ -4264,6 +4272,9 @@ touch_arbitration_suspend_touch(struct litest_device *dev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED); + litest_timeout_touch_arbitration(); + libinput_dispatch(li); + litest_touch_down(dev, 0, 30, 30); litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1); litest_touch_up(dev, 0); @@ -4384,6 +4395,9 @@ touch_arbitration_remove_tablet(struct litest_device *dev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED); + litest_timeout_touch_arbitration(); + libinput_dispatch(li); + /* Touch is still down, don't enable */ litest_touch_move_to(dev, 0, 80, 80, 30, 30, 10, 1); litest_touch_up(dev, 0); @@ -4416,6 +4430,79 @@ START_TEST(cintiq_touch_arbitration_remove_tablet) } END_TEST +START_TEST(intuos_touch_arbitration_keep_ignoring) +{ + struct litest_device *tablet = litest_current_device(); + struct litest_device *finger; + struct libinput *li = tablet->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + finger = litest_add_device(li, LITEST_WACOM_FINGER); + litest_enable_tap(finger->libinput_device); + litest_tablet_proximity_in(tablet, 10, 10, axes); + litest_tablet_motion(tablet, 10, 10, axes); + litest_tablet_motion(tablet, 20, 40, axes); + + litest_touch_down(finger, 0, 30, 30); + litest_drain_events(li); + + litest_tablet_proximity_out(tablet); + litest_drain_events(li); + + /* a touch during pen interaction stays a palm after the pen lifts. + */ + litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1); + litest_touch_up(finger, 0); + libinput_dispatch(li); + + litest_assert_empty_queue(li); + + litest_delete_device(finger); +} +END_TEST + +START_TEST(intuos_touch_arbitration_late_touch_lift) +{ + struct litest_device *tablet = litest_current_device(); + struct litest_device *finger; + struct libinput *li = tablet->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + finger = litest_add_device(li, LITEST_WACOM_FINGER); + litest_enable_tap(finger->libinput_device); + litest_tablet_proximity_in(tablet, 10, 10, axes); + litest_tablet_motion(tablet, 10, 10, axes); + litest_tablet_motion(tablet, 20, 40, axes); + litest_drain_events(li); + + litest_tablet_proximity_out(tablet); + litest_drain_events(li); + + /* with kernel arbitration, a finger + stylus in prox only generates + * stylus events. When lifting the hand off, the stylus goes out of + * prox when the hand is still touching. A few ms later, the hand + * goes out of prox, this can generate a fake tap event. + */ + litest_touch_down(finger, 0, 30, 30); + litest_touch_up(finger, 0); + libinput_dispatch(li); + litest_timeout_tap(); + libinput_dispatch(li); + + litest_assert_empty_queue(li); + + litest_delete_device(finger); +} +END_TEST + START_TEST(huion_static_btn_tool_pen) { struct litest_device *dev = litest_current_device(); @@ -4689,6 +4776,8 @@ litest_setup_tests_tablet(void) litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_suspend_touch_device, LITEST_WACOM_FINGER); litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_remove_touch, LITEST_WACOM_INTUOS); litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_remove_tablet, LITEST_WACOM_FINGER); + litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_keep_ignoring, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_late_touch_lift, LITEST_WACOM_INTUOS); litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration, LITEST_WACOM_CINTIQ_13HDT_PEN); litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_stop_touch, LITEST_WACOM_CINTIQ_13HDT_PEN); -- 2.14.3 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/wayland-devel