diff --git a/doc/palm-detection.dox b/doc/palm-detection.dox index c8ceb4eb..4dbd67c0 100644 --- a/doc/palm-detection.dox +++ b/doc/palm-detection.dox @@ -27,16 +27,20 @@ pressure ranges, see @ref touchpad_pressure. @section palm_exclusion_zones Palm exclusion zones -libinput enables palm detection on the edge of the touchpad. Two exclusion -zones are defined on the left and right edge of the touchpad. -If a touch starts in the exclusion zone, it is considered a palm and the -touch point is ignored. However, for fast cursor movements across the -screen, it is common for a finger to start inside an exclusion zone and move -rapidly across the touchpad. libinput detects such movements and avoids palm -detection on such touch sequences. +libinput enables palm detection on the left, right and top edges of the +touchpad. Two exclusion zones are defined on the left and right edge of the +touchpad. If a touch starts in the exclusion zone, it is considered a palm +and the touch point is ignored. However, for fast cursor movements across +the screen, it is common for a finger to start inside an exclusion zone and +move rapidly across the touchpad. libinput detects such movements and avoids +palm detection on such touch sequences. -Each exclusion zone is divided into a top part and a bottom part. A touch -starting in the top part of the exclusion zone does not trigger a +Another exclusion zone is defined on the top edge of the touchpad. As with +the edge zones, libinput detects vertical movements out of the edge zone and +avoids palm detection on such touch sequences. + +Each side edge exclusion zone is divided into a top part and a bottom part. +A touch starting in the top part of the exclusion zone does not trigger a tap (see @ref tapping). In the diagram below, the exclusion zones are painted red. diff --git a/doc/svg/palm-detection.svg b/doc/svg/palm-detection.svg index c3e45f44..2849e265 100644 --- a/doc/svg/palm-detection.svg +++ b/doc/svg/palm-detection.svg @@ -36,16 +36,17 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" - inkscape:window-height="1136" + inkscape:window-height="1016" id="namedview3477" showgrid="false" inkscape:zoom="3.5662625" - inkscape:cx="199.35048" - inkscape:cy="156.74673" + inkscape:cx="180.54059" + inkscape:cy="269.48563" inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1" - inkscape:current-layer="svg2" /> + inkscape:current-layer="svg2" + inkscape:document-rotation="0" /> + y="7.1355872" /> + x="321.22849" + y="6.8830237" /> C + + diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 2b9bc28b..b68d7669 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -550,14 +550,45 @@ tp_touch_active(const struct tp_dispatch *tp, const struct tp_touch *t) tp_edge_scroll_touch_active(tp, t); } +static inline bool +tp_palm_was_in_side_edge(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return t->palm.first.x < tp->palm.left_edge || + t->palm.first.x > tp->palm.right_edge; +} + +static inline bool +tp_palm_was_in_top_edge(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return t->palm.first.y < tp->palm.upper_edge; +} + +static inline bool +tp_palm_in_side_edge(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return t->point.x < tp->palm.left_edge || + t->point.x > tp->palm.right_edge; +} + +static inline bool +tp_palm_in_top_edge(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return t->point.y < tp->palm.upper_edge; +} + +static inline bool +tp_palm_in_edge(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + return tp_palm_in_side_edge(tp, t) || tp_palm_in_top_edge(tp, t); +} + bool tp_palm_tap_is_palm(const struct tp_dispatch *tp, const struct tp_touch *t) { if (t->state != TOUCH_BEGIN) return false; - if (t->point.x > tp->palm.left_edge && - t->point.x < tp->palm.right_edge) + if (!tp_palm_in_edge(tp, t)) return false; evdev_log_debug(tp->device, "palm: palm-tap detected\n"); @@ -654,16 +685,22 @@ tp_palm_detect_move_out_of_edge(struct tp_dispatch *tp, uint64_t time) { const int PALM_TIMEOUT = ms2us(200); - const int DIRECTIONS = NE|E|SE|SW|W|NW; + int directions = 0; struct device_float_coords delta; int dirs; - if (time < t->palm.time + PALM_TIMEOUT && - (t->point.x > tp->palm.left_edge && t->point.x < tp->palm.right_edge)) { - delta = device_delta(t->point, t->palm.first); - dirs = phys_get_direction(tp_phys_delta(tp, delta)); - if ((dirs & DIRECTIONS) && !(dirs & ~DIRECTIONS)) - return true; + if (time < t->palm.time + PALM_TIMEOUT && !tp_palm_in_edge(tp, t)) { + if (tp_palm_was_in_side_edge(tp, t)) + directions = NE|E|SE|SW|W|NW; + else if (tp_palm_was_in_top_edge(tp, t)) + directions = S|SE|SW; + + if (directions) { + delta = device_delta(t->point, t->palm.first); + dirs = phys_get_direction(tp_phys_delta(tp, delta)); + if ((dirs & directions) && !(dirs & ~directions)) + return true; + } } return false; @@ -725,8 +762,7 @@ tp_palm_detect_edge(struct tp_dispatch *tp, /* palm must start in exclusion zone, it's ok to move into the zone without being a palm */ - if (t->state != TOUCH_BEGIN || - (t->point.x > tp->palm.left_edge && t->point.x < tp->palm.right_edge)) + if (t->state != TOUCH_BEGIN || !tp_palm_in_edge(tp, t)) return false; /* don't detect palm in software button areas, it's @@ -2329,6 +2365,13 @@ tp_init_palmdetect_edge(struct tp_dispatch *tp, mm.x = width * 0.92; edges = evdev_device_mm_to_units(device, &mm); tp->palm.right_edge = edges.x; + + if (!tp->buttons.has_topbuttons) { + /* top edge is 5% of the height */ + mm.y = height * 0.05; + edges = evdev_device_mm_to_units(device, &mm); + tp->palm.upper_edge = edges.y; + } } static int @@ -2374,6 +2417,7 @@ tp_init_palmdetect(struct tp_dispatch *tp, tp->palm.right_edge = INT_MAX; tp->palm.left_edge = INT_MIN; + tp->palm.upper_edge = INT_MIN; if (device->tags & EVDEV_TAG_EXTERNAL_TOUCHPAD && !tp_is_tpkb_combo_below(device)) diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 6d014607..d601f7e5 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -335,6 +335,7 @@ struct tp_dispatch { struct { int32_t right_edge; /* in device coordinates */ int32_t left_edge; /* in device coordinates */ + int32_t upper_edge; /* in device coordinates */ bool trackpoint_active; struct libinput_event_listener trackpoint_listener; diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 913fad64..01d659e4 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -1000,6 +1000,26 @@ START_TEST(touchpad_palm_detect_at_edge) } END_TEST +START_TEST(touchpad_palm_detect_at_top) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!touchpad_has_palm_detect_size(dev)) + return; + + litest_disable_tap(dev->libinput_device); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 1); + litest_touch_move_to(dev, 0, 20, 1, 70, 1, 10, 0); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_no_palm_detect_at_edge_for_edge_scrolling) { struct litest_device *dev = litest_current_device(); @@ -1102,6 +1122,26 @@ START_TEST(touchpad_palm_detect_palm_stays_palm) } END_TEST +START_TEST(touchpad_palm_detect_top_palm_stays_palm) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!touchpad_has_palm_detect_size(dev)) + return; + + litest_disable_tap(dev->libinput_device); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 20, 1); + litest_touch_move_to(dev, 0, 20, 1, 90, 30, 10, 0); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_palm_detect_palm_becomes_pointer) { struct litest_device *dev = litest_current_device(); @@ -1129,6 +1169,30 @@ START_TEST(touchpad_palm_detect_palm_becomes_pointer) } END_TEST +START_TEST(touchpad_palm_detect_top_palm_becomes_pointer) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!touchpad_has_palm_detect_size(dev)) + return; + + litest_disable_tap(dev->libinput_device); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 1); + litest_touch_move_to(dev, 0, 50, 1, 50, 60, 20, 0); + litest_touch_up(dev, 0); + + libinput_dispatch(li); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_palm_detect_no_palm_moving_into_edges) { struct litest_device *dev = litest_current_device(); @@ -1158,6 +1222,56 @@ START_TEST(touchpad_palm_detect_no_palm_moving_into_edges) } END_TEST +START_TEST(touchpad_palm_detect_no_palm_moving_into_top) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!touchpad_has_palm_detect_size(dev)) + return; + + litest_disable_tap(dev->libinput_device); + + /* moving non-palm into the edge does not label it as palm */ + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 50); + litest_touch_move_to(dev, 0, 50, 50, 0, 2, 10, 0); + + litest_drain_events(li); + + litest_touch_move_to(dev, 0, 0, 2, 50, 50, 10, 0); + libinput_dispatch(li); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); + + litest_touch_up(dev, 0); + libinput_dispatch(li); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_palm_detect_no_tap_top_edge) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + if (!touchpad_has_palm_detect_size(dev)) + return; + + litest_enable_tap(dev->libinput_device); + + litest_drain_events(li); + + litest_touch_down(dev, 0, 50, 1); + litest_touch_up(dev, 0); + libinput_dispatch(li); + + litest_timeout_tap(); + litest_assert_empty_queue(li); +} +END_TEST + START_TEST(touchpad_palm_detect_tap_hardbuttons) { struct litest_device *dev = litest_current_device(); @@ -1344,6 +1458,7 @@ START_TEST(touchpad_palm_detect_both_edges) litest_touch_move_to(dev, 0, 99, 50, 99, 40, 10, 0); litest_touch_move_to(dev, 0, 99, 40, 99, 50, 10, 0); litest_assert_empty_queue(li); + /* This set generates events */ litest_touch_down(dev, 1, 1, 50); litest_touch_move_to(dev, 1, 1, 50, 1, 40, 10, 0); litest_touch_move_to(dev, 1, 1, 40, 1, 50, 10, 0); @@ -5152,11 +5267,16 @@ litest_setup_tests_touchpad(void) litest_add("touchpad:scroll", touchpad_edge_scroll_into_area, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:palm", touchpad_palm_detect_at_edge, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:palm", touchpad_palm_detect_at_top, LITEST_TOUCHPAD, LITEST_TOPBUTTONPAD); litest_add("touchpad:palm", touchpad_palm_detect_at_bottom_corners, LITEST_TOUCHPAD, LITEST_CLICKPAD); litest_add("touchpad:palm", touchpad_palm_detect_at_top_corners, LITEST_TOUCHPAD, LITEST_TOPBUTTONPAD); litest_add("touchpad:palm", touchpad_palm_detect_palm_becomes_pointer, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:palm", touchpad_palm_detect_top_palm_becomes_pointer, LITEST_TOUCHPAD, LITEST_TOPBUTTONPAD); litest_add("touchpad:palm", touchpad_palm_detect_palm_stays_palm, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:palm", touchpad_palm_detect_top_palm_stays_palm, LITEST_TOUCHPAD, LITEST_TOPBUTTONPAD); litest_add("touchpad:palm", touchpad_palm_detect_no_palm_moving_into_edges, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:palm", touchpad_palm_detect_no_palm_moving_into_top, LITEST_TOUCHPAD, LITEST_TOPBUTTONPAD); + litest_add("touchpad:palm", touchpad_palm_detect_no_tap_top_edge, LITEST_TOUCHPAD, LITEST_TOPBUTTONPAD); litest_add("touchpad:palm", touchpad_palm_detect_tap_hardbuttons, LITEST_TOUCHPAD, LITEST_CLICKPAD); litest_add("touchpad:palm", touchpad_palm_detect_tap_softbuttons, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:palm", touchpad_palm_detect_tap_clickfinger, LITEST_CLICKPAD, LITEST_ANY);