diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index 520a47f1..bd3c0e25 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -733,3 +733,9 @@ tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t) { return t->button.state == BUTTON_STATE_AREA; } + +bool +tp_button_is_inside_softbutton_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return is_inside_top_button_area(tp, t) || is_inside_bottom_button_area(tp, t); +} diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index c7fd3a12..1325355c 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -148,6 +148,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t) t->dirty = true; t->is_pointer = false; + t->is_palm = false; t->state = TOUCH_END; t->pinned.is_pinned = false; assert(tp->nfingers_down >= 1); @@ -339,6 +340,7 @@ static int tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t) { return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && + !t->is_palm && !t->pinned.is_pinned && tp_button_touch_active(tp, t); } @@ -357,6 +359,29 @@ tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t) t->is_pointer = true; } +static void +tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t) +{ + /* once a palm, always a palm */ + if (t->is_palm) + return; + + /* palm must start in exclusion zone, it's ok to move into + the zone without being a palm */ + if (t->state != TOUCH_BEGIN || + (t->x > tp->palm.left_edge && t->x < tp->palm.right_edge)) + return; + + /* don't detect palm in software button areas, it's + likely that legitimate touches start in the area + covered by the exclusion zone */ + if (tp->buttons.is_clickpad && + tp_button_is_inside_softbutton_area(tp, t)) + return; + + t->is_palm = true; +} + static void tp_process_state(struct tp_dispatch *tp, uint64_t time) { @@ -373,6 +398,8 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) continue; } + tp_palm_detect(tp, t); + tp_motion_hysteresis(tp, t); tp_motion_history_push(t); @@ -702,6 +729,23 @@ tp_init_scroll(struct tp_dispatch *tp) return 0; } +static int +tp_init_palmdetect(struct tp_dispatch *tp, + struct evdev_device *device) +{ + int width; + + width = abs(device->abs.absinfo_x->maximum - + device->abs.absinfo_x->minimum); + + /* palm edges are 5% of the width on each side */ + tp->palm.right_edge = device->abs.absinfo_x->maximum - width * 0.05; + tp->palm.left_edge = device->abs.absinfo_x->minimum + width * 0.05; + + return 0; +} + + static int tp_init(struct tp_dispatch *tp, struct evdev_device *device) @@ -738,6 +782,9 @@ tp_init(struct tp_dispatch *tp, if (tp_init_buttons(tp, device) != 0) return -1; + if (tp_init_palmdetect(tp, device) != 0) + return -1; + return 0; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 80f3f606..689687ee 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -103,6 +103,7 @@ struct tp_touch { bool dirty; bool fake; /* a fake touch */ bool is_pointer; /* the pointer-controlling touch */ + bool is_palm; int32_t x; int32_t y; uint64_t millis; @@ -202,6 +203,11 @@ struct tp_dispatch { struct libinput_timer timer; enum tp_tap_state state; } tap; + + struct { + int32_t right_edge; + int32_t left_edge; + } palm; }; #define tp_for_each_touch(_tp, _t) \ @@ -242,4 +248,7 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time); int tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t); +bool +tp_button_is_inside_softbutton_area(struct tp_dispatch *tp, struct tp_touch *t); + #endif diff --git a/test/touchpad.c b/test/touchpad.c index bd5d71eb..672cbf9b 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -1241,6 +1241,117 @@ START_TEST(touchpad_tap_default) } END_TEST +START_TEST(touchpad_palm_detect_at_edge) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 99, 50); + litest_touch_move_to(dev, 0, 99, 50, 99, 70, 5); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 5, 50); + litest_touch_move_to(dev, 0, 5, 50, 5, 70, 5); + litest_touch_up(dev, 0); +} +END_TEST + +START_TEST(touchpad_palm_detect_at_bottom_corners) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + /* Run for non-clickpads only: make sure the bottom corners trigger + palm detection too */ + litest_drain_events(li); + + litest_touch_down(dev, 0, 99, 95); + litest_touch_move_to(dev, 0, 99, 95, 99, 99, 10); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 5, 95); + litest_touch_move_to(dev, 0, 5, 95, 5, 99, 5); + litest_touch_up(dev, 0); +} +END_TEST + +START_TEST(touchpad_palm_detect_at_top_corners) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + /* Run for non-clickpads only: make sure the bottom corners trigger + palm detection too */ + litest_drain_events(li); + + litest_touch_down(dev, 0, 99, 5); + litest_touch_move_to(dev, 0, 99, 5, 99, 9, 10); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); + + litest_touch_down(dev, 0, 5, 5); + litest_touch_move_to(dev, 0, 5, 5, 5, 9, 5); + litest_touch_up(dev, 0); +} +END_TEST + +START_TEST(touchpad_palm_detect_palm_stays_palm) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + + litest_drain_events(li); + + litest_touch_down(dev, 0, 99, 50); + litest_touch_move_to(dev, 0, 99, 50, 0, 70, 5); + litest_touch_up(dev, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(touchpad_palm_detect_no_palm_moving_into_edges) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *ev; + enum libinput_event_type type; + + /* 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, 99, 50, 5); + + litest_drain_events(li); + + litest_touch_move_to(dev, 0, 99, 50, 99, 90, 5); + libinput_dispatch(li); + + type = libinput_next_event_type(li); + do { + + ck_assert_int_eq(type, LIBINPUT_EVENT_POINTER_MOTION); + ev = libinput_get_event(li); + libinput_event_destroy(ev); + + type = libinput_next_event_type(li); + libinput_dispatch(li); + } while (type != LIBINPUT_EVENT_NONE); + + litest_touch_up(dev, 0); + libinput_dispatch(li); + litest_assert_empty_queue(li); +} +END_TEST + int main(int argc, char **argv) { litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY); @@ -1290,5 +1401,11 @@ int main(int argc, char **argv) { litest_add("touchpad:scroll", touchpad_2fg_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add("touchpad:palm", touchpad_palm_detect_at_edge, LITEST_TOUCHPAD, LITEST_ANY); + 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_stays_palm, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("touchpad:palm", touchpad_palm_detect_no_palm_moving_into_edges, LITEST_TOUCHPAD, LITEST_ANY); + return litest_run(argc, argv); }