touchpad: touches after the last key press can be released
authorPeter Hutterer <peter.hutterer@who-t.net>
Thu, 21 May 2015 06:32:42 +0000 (16:32 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Wed, 27 May 2015 07:38:25 +0000 (17:38 +1000)
The current code labels a touch as palm if it started within the typing
timeouts. To move the pointer even after the timeout expires, a user has to
lift the finger which is quite annoying and different to the old synaptics
driver behaviour (which had a simple on/off toggle on whether to let events
through or not).

Be smarter about this: if a touch starts _after_ the last key press event,
release it for pointer motion once the timeout expires. Touches started before
the last key press remain labelled as palms. This makes it possible to rest
the palm on the touchpad while typing without getting interference but also
provides a more responsive UI when moving from typing to using the touchpad
normally.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Tested-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
doc/palm-detection.dox
src/evdev-mt-touchpad-edge-scroll.c
src/evdev-mt-touchpad.c
src/evdev-mt-touchpad.h
test/touchpad.c

index a03f9c14161876fdc1a35d59eb36081d14f17226..d787455ec615a5e64c03dceef5630b3fb9e033f8 100644 (file)
@@ -74,8 +74,9 @@ Notable behaviors of libinput's disable-while-typing feature:
 - Some keys do not trigger the timeout, specifically some modifier keys 
   (Ctrl, Alt, Shift, and Fn). Actions such as Ctrl + click thus stay
   responsive.
-- Touches started while the touchpad is disabled do not control the cursor,
-  it is thus possible to rest the palm on the touchpad while typing.
+- Touches started while typing do not control the cursor even after typing
+  has stopped, it is thus possible to rest the palm on the touchpad while
+  typing.
 - Physical buttons work even while the touchpad is disabled. This includes
   software-emulated buttons.
 
index f7eae9e9298cb1671ad17ea594dcaed262506c96..8a4d892f33d5e026268509456cbc18fa64cdfbe7 100644 (file)
@@ -361,6 +361,9 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
                if (!t->dirty)
                        continue;
 
+               if (t->palm.state != PALM_NONE)
+                       continue;
+
                switch (t->scroll.edge) {
                        case EDGE_NONE:
                                if (t->scroll.direction != -1) {
index db330ec0ab7248f47dd4989ccade199fed2b4908..e1303637fbad32f7e0ca6d4403d0052c1e72d30c 100644 (file)
@@ -237,6 +237,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
        t->state = TOUCH_END;
        t->pinned.is_pinned = false;
        t->millis = time;
+       t->palm.time = 0;
        assert(tp->nfingers_down >= 1);
        tp->nfingers_down--;
        tp->queued |= TOUCHPAD_EVENT_MOTION;
@@ -487,14 +488,28 @@ tp_palm_tap_is_palm(struct tp_dispatch *tp, struct tp_touch *t)
 static int
 tp_palm_detect_dwt(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 {
-       if (!tp->dwt.keyboard_active)
-               return 0;
-
-       if (t->state == TOUCH_BEGIN) {
+       if (tp->dwt.keyboard_active &&
+           t->state == TOUCH_BEGIN) {
                t->palm.state = PALM_TYPING;
                t->palm.time = time;
                t->palm.first = t->point;
                return 1;
+       } else if (!tp->dwt.keyboard_active &&
+                  t->state == TOUCH_UPDATE &&
+                  t->palm.state == PALM_TYPING)
+       {
+               /* If a touch has started before the first or after the last
+                  key press, release it on timeout. Benefit: a palm rested
+                  while typing on the touchpad will be ignored, but a touch
+                  started once we stop typing will be able to control the
+                  pointer (alas not tap, etc.).
+                  */
+               if (t->palm.time == 0 ||
+                   t->palm.time > tp->dwt.keyboard_last_press_time) {
+                       t->palm.state = PALM_NONE;
+                       log_debug(tp_libinput_context(tp),
+                                 "palm: touch released, timeout after typing\n");
+               }
        }
 
        return 0;
@@ -1002,6 +1017,7 @@ tp_keyboard_event(uint64_t time, struct libinput_event *event, void *data)
                timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2;
        }
 
+       tp->dwt.keyboard_last_press_time = time;
        libinput_timer_set(&tp->dwt.keyboard_timer,
                           time + timeout);
 }
index 9eeb5fbe931ad4edf1288474e4f4637e51557497..bced2b16a1eb9e7d111f92353f53bc7ebf87d2c9 100644 (file)
@@ -287,6 +287,8 @@ struct tp_dispatch {
                struct libinput_event_listener keyboard_listener;
                struct libinput_timer keyboard_timer;
                struct evdev_device *keyboard;
+
+               uint64_t keyboard_last_press_time;
        } dwt;
 };
 
index fc7743964b9bfb31d841a20d27665cedfc2771b8..5579c043f1e26b6c39f79474557e8fde2e74083e 100644 (file)
@@ -4646,7 +4646,7 @@ START_TEST(touchpad_dwt)
 }
 END_TEST
 
-START_TEST(touchpad_dwt_touch_hold)
+START_TEST(touchpad_dwt_enable_touch)
 {
        struct litest_device *touchpad = litest_current_device();
        struct litest_device *keyboard;
@@ -4661,7 +4661,46 @@ START_TEST(touchpad_dwt_touch_hold)
        litest_drain_events(li);
 
        litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* finger down after last key event, but
+          we're still within timeout - no events */
+       msleep(10);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_dwt_short();
        libinput_dispatch(li);
+
+       /* same touch after timeout  - motion events*/
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_touch_hold)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = litest_add_device(li, LITEST_KEYBOARD);
+       libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+                                              LIBINPUT_CONFIG_TAP_DISABLED);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       msleep(1); /* make sure touch starts after key press */
        litest_touch_down(touchpad, 0, 50, 50);
        litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
 
@@ -4678,7 +4717,7 @@ START_TEST(touchpad_dwt_touch_hold)
        libinput_dispatch(li);
        litest_touch_move_to(touchpad, 0, 30, 50, 50, 50, 5, 1);
        litest_touch_up(touchpad, 0);
-       litest_assert_empty_queue(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
        litest_delete_device(keyboard);
 }
@@ -4837,6 +4876,7 @@ START_TEST(touchpad_dwt_tap_drag)
 
        litest_keyboard_key(keyboard, KEY_A, true);
        libinput_dispatch(li);
+       msleep(1); /* make sure touch starts after key press */
        litest_touch_down(touchpad, 0, 50, 50);
        litest_touch_up(touchpad, 0);
        litest_touch_down(touchpad, 0, 50, 50);
@@ -4849,7 +4889,7 @@ START_TEST(touchpad_dwt_tap_drag)
        libinput_dispatch(li);
        litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 5, 1);
        litest_touch_up(touchpad, 0);
-       litest_assert_empty_queue(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
        litest_delete_device(keyboard);
 }
@@ -4887,6 +4927,101 @@ START_TEST(touchpad_dwt_click)
 }
 END_TEST
 
+START_TEST(touchpad_dwt_edge_scroll)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       enable_edge_scroll(touchpad);
+
+       keyboard = litest_add_device(li, LITEST_KEYBOARD);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_down(touchpad, 0, 99, 20);
+       libinput_dispatch(li);
+       litest_timeout_edgescroll();
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       /* edge scroll timeout is 300ms atm, make sure we don't accidentally
+          exit the DWT timeout */
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_move_to(touchpad, 0, 99, 20, 99, 80, 60, 10);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       litest_touch_move_to(touchpad, 0, 99, 80, 99, 20, 60, 10);
+       litest_touch_up(touchpad, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_edge_scroll_interrupt)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       struct libinput_event_pointer *stop_event;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       enable_edge_scroll(touchpad);
+
+       keyboard = litest_add_device(li, LITEST_KEYBOARD);
+       litest_drain_events(li);
+
+       litest_touch_down(touchpad, 0, 99, 20);
+       libinput_dispatch(li);
+       litest_timeout_edgescroll();
+       litest_touch_move_to(touchpad, 0, 99, 20, 99, 30, 10, 10);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+
+       /* scroll stop event */
+       litest_wait_for_event(li);
+       stop_event = litest_is_axis_event(libinput_get_event(li),
+                                         LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                         LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(stop_event));
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_timeout_dwt_long();
+
+       /* Known bad behavior: a touch starting to edge-scroll before dwt
+        * kicks in will stop to scroll but be recognized as normal
+        * pointer-moving touch once the timeout expires. We'll fix that
+        * when we need to.
+        */
+       litest_touch_move_to(touchpad, 0, 99, 30, 99, 80, 10, 5);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
 void
 litest_setup_tests(void)
 {
@@ -5037,6 +5172,7 @@ litest_setup_tests(void)
        litest_add_ranged("touchpad:state", touchpad_initial_state, LITEST_TOUCHPAD, LITEST_ANY, &axis_range);
 
        litest_add("touchpad:dwt", touchpad_dwt, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_enable_touch, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:dwt", touchpad_dwt_touch_hold, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:dwt", touchpad_dwt_key_hold, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:dwt", touchpad_dwt_type, LITEST_TOUCHPAD, LITEST_ANY);
@@ -5044,4 +5180,6 @@ litest_setup_tests(void)
        litest_add("touchpad:dwt", touchpad_dwt_tap, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:dwt", touchpad_dwt_tap_drag, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:dwt", touchpad_dwt_click, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_edge_scroll, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:dwt", touchpad_dwt_edge_scroll_interrupt, LITEST_TOUCHPAD, LITEST_CLICKPAD);
 }