diff --git a/doc/palm-detection.dox b/doc/palm-detection.dox index a03f9c14..d787455e 100644 --- a/doc/palm-detection.dox +++ b/doc/palm-detection.dox @@ -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. diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c index f7eae9e9..8a4d892f 100644 --- a/src/evdev-mt-touchpad-edge-scroll.c +++ b/src/evdev-mt-touchpad-edge-scroll.c @@ -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) { diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index db330ec0..e1303637 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -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); } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 9eeb5fbe..bced2b16 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -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; }; diff --git a/test/touchpad.c b/test/touchpad.c index fc774396..5579c043 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -4646,6 +4646,45 @@ START_TEST(touchpad_dwt) } END_TEST +START_TEST(touchpad_dwt_enable_touch) +{ + 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); + 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(); @@ -4661,7 +4700,7 @@ START_TEST(touchpad_dwt_touch_hold) litest_drain_events(li); 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_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); }