fallback: add timer-based touch arbitration
authorPeter Hutterer <peter.hutterer@who-t.net>
Sun, 6 Jan 2019 22:27:22 +0000 (08:27 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Thu, 31 Jan 2019 05:17:28 +0000 (05:17 +0000)
When a hand is resting on a pen+touch device, lifting the hand may remove the
stylus from proximity before the hand leaves the surface. If the kernel
performs touch arbitration, this triggers a touch down on proximity out,
followed by a touch up immediately after when the hand stops touching.

This can cause ghost touch events. Prevent this by using a timer-based
arbitration toggle.

Same as 2a378beab032d742770e24a6822378faa90cf1f6 but for the fallback
interface.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
src/evdev-fallback.c
src/evdev-fallback.h
test/test-tablet.c

index c064557..c584e09 100644 (file)
@@ -1017,7 +1017,7 @@ fallback_interface_process(struct evdev_dispatch *evdev_dispatch,
 {
        struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
 
-       if (dispatch->ignore_events)
+       if (dispatch->arbitration.in_arbitration)
                return;
 
        switch (event->type) {
@@ -1202,13 +1202,26 @@ fallback_interface_toggle_touch(struct evdev_dispatch *evdev_dispatch,
        struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
        bool ignore_events = !enable;
 
-       if (ignore_events == dispatch->ignore_events)
+       if (ignore_events == dispatch->arbitration.ignore_events)
                return;
 
-       if (ignore_events)
+       if (ignore_events) {
+               libinput_timer_cancel(&dispatch->arbitration.arbitration_timer);
                fallback_return_to_neutral_state(dispatch, device);
+               dispatch->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 begin 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(&dispatch->arbitration.arbitration_timer,
+                                  time + ms2us(90));
+       }
 
-       dispatch->ignore_events = ignore_events;
+       dispatch->arbitration.ignore_events = ignore_events;
 }
 
 static void
@@ -1216,6 +1229,7 @@ fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch)
 {
        struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
 
+       libinput_timer_destroy(&dispatch->arbitration.arbitration_timer);
        libinput_timer_destroy(&dispatch->debounce.timer);
        libinput_timer_destroy(&dispatch->debounce.timer_short);
 
@@ -1596,6 +1610,33 @@ fallback_dispatch_init_switch(struct fallback_dispatch *dispatch,
        libinput_device_init_event_listener(&dispatch->tablet_mode.other.listener);
 }
 
+static void
+fallback_arbitration_timeout(uint64_t now, void *data)
+{
+       struct fallback_dispatch *dispatch = data;
+
+       if (dispatch->arbitration.in_arbitration)
+               dispatch->arbitration.in_arbitration = false;
+}
+
+static void
+fallback_init_arbitration(struct fallback_dispatch *dispatch,
+                         struct evdev_device *device)
+{
+       char timer_name[64];
+
+       snprintf(timer_name,
+                sizeof(timer_name),
+                 "%s arbitration",
+                 evdev_device_get_sysname(device));
+       libinput_timer_init(&dispatch->arbitration.arbitration_timer,
+                           evdev_libinput_context(device),
+                           timer_name,
+                           fallback_arbitration_timeout,
+                           dispatch);
+       dispatch->arbitration.in_arbitration = false;
+}
+
 struct evdev_dispatch *
 fallback_dispatch_create(struct libinput_device *libinput_device)
 {
@@ -1652,6 +1693,7 @@ fallback_dispatch_create(struct libinput_device *libinput_device)
        }
 
        fallback_init_debounce(dispatch);
+       fallback_init_arbitration(dispatch, device);
 
        return &dispatch->base;
 }
index 6f00816..47f9f5e 100644 (file)
@@ -118,10 +118,6 @@ struct fallback_dispatch {
 
        enum evdev_event_type pending_event;
 
-       /* true if we're reading events (i.e. not suspended) but we're
-          ignoring them */
-       bool ignore_events;
-
        struct {
                unsigned int button_code;
                uint64_t button_time;
@@ -144,6 +140,15 @@ struct fallback_dispatch {
                 */
                struct list paired_keyboard_list;
        } lid;
+
+       /* pen/touch arbitration has a delayed state, if ignore_events is
+        * true we want to ignore events, in_arbitration actually filters.
+        */
+       struct {
+               bool ignore_events;
+               bool in_arbitration;
+               struct libinput_timer arbitration_timer;
+       } arbitration;
 };
 
 static inline struct fallback_dispatch*
index 1130aea..2d5b2d8 100644 (file)
@@ -4287,6 +4287,9 @@ START_TEST(touch_arbitration)
        litest_touch_up(finger, 0);
        litest_assert_empty_queue(li);
 
+       litest_timeout_touch_arbitration();
+       libinput_dispatch(li);
+
        /* lift finger, expect expect events */
        litest_touch_down(finger, 0, 30, 30);
        litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10);
@@ -4585,9 +4588,10 @@ START_TEST(touch_arbitration_keep_ignoring)
 }
 END_TEST
 
-START_TEST(intuos_touch_arbitration_late_touch_lift)
+START_TEST(touch_arbitration_late_touch_lift)
 {
        struct litest_device *tablet = litest_current_device();
+       enum litest_device_type other;
        struct litest_device *finger;
        struct libinput *li = tablet->libinput;
        struct axis_replacement axes[] = {
@@ -4595,9 +4599,16 @@ START_TEST(intuos_touch_arbitration_late_touch_lift)
                { ABS_PRESSURE, 0 },
                { -1, -1 }
        };
+       bool is_touchpad;
 
-       finger = litest_add_device(li, LITEST_WACOM_FINGER);
-       litest_enable_tap(finger->libinput_device);
+       other = paired_device(tablet);
+       if (other == LITEST_NO_DEVICE)
+               return;
+
+       finger = litest_add_device(li, other);
+       is_touchpad = !libevdev_has_property(finger->evdev, INPUT_PROP_DIRECT);
+       if (is_touchpad)
+               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);
@@ -4901,8 +4912,7 @@ TEST_COLLECTION(tablet)
        litest_add("tablet:touch-arbitration", touch_arbitration_remove_touch, LITEST_TABLET, LITEST_ANY);
        litest_add("tablet:touch-arbitration", touch_arbitration_remove_tablet, LITEST_TOUCH, LITEST_ANY);
        litest_add("tablet:touch-arbitration", touch_arbitration_keep_ignoring, LITEST_TABLET, LITEST_ANY);
-
-       litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_late_touch_lift, LITEST_WACOM_INTUOS);
+       litest_add("tablet:touch-arbitration", touch_arbitration_late_touch_lift, LITEST_TABLET, LITEST_ANY);
 
        litest_add_for_device("tablet:quirks", huion_static_btn_tool_pen, LITEST_HUION_TABLET);
        litest_add_for_device("tablet:quirks", huion_static_btn_tool_pen_no_timeout_during_usage, LITEST_HUION_TABLET);