Discussion:
[PATCH libinput] touchpad: implement edge-based basic palm detection
Peter Hutterer
2014-07-11 01:08:53 UTC
Permalink
A large part of palm events are situated on the far edges of the touchpad. In
a test run on a T440s while typing a long email all but 2 touch points were
located in the outer ~5% of the touchpad. Define a 10% exclusion zone on the
left and right edges in which new touchpoint is automatically assigned to be a
palm.

A finger may move into that exclusion zone without being marked as palm, it
just can't start in one.

On clickpads, the exclusion zone does not extend into the software buttons.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
src/evdev-mt-touchpad-buttons.c | 6 +++
src/evdev-mt-touchpad.c | 47 ++++++++++++++++
src/evdev-mt-touchpad.h | 9 ++++
test/touchpad.c | 117 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 179 insertions(+)

diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index 520a47f..bd3c0e2 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 69b63e0..fc59c38 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);
}

@@ -358,6 +360,29 @@ tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t)
}

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)
{
struct tp_touch *t;
@@ -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);

@@ -703,6 +730,23 @@ tp_init_scroll(struct tp_dispatch *tp)
}

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 10% of the width on each side */
+ tp->palm.right_edge = device->abs.absinfo_x->maximum - width * 0.1;
+ tp->palm.left_edge = device->abs.absinfo_x->minimum + width * 0.1;
+
+ 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 80f3f60..689687e 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 6a33a44..3dc52f5 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);
}
--
1.9.3
Daniel Martin
2014-07-11 06:34:32 UTC
Permalink
Post by Peter Hutterer
A large part of palm events are situated on the far edges of the touchpad. In
a test run on a T440s while typing a long email all but 2 touch points were
located in the outer ~5% of the touchpad. Define a 10% exclusion zone on the
left and right edges in which new touchpoint is automatically assigned to be a
palm.
A finger may move into that exclusion zone without being marked as palm, it
just can't start in one.
On clickpads, the exclusion zone does not extend into the software buttons.
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
I don't have the big picture (of libinput) atm. Does this stand in the
way of edge-scolling (in the future)?
Peter Hutterer
2014-07-11 07:00:17 UTC
Permalink
Post by Daniel Martin
Post by Peter Hutterer
A large part of palm events are situated on the far edges of the touchpad. In
a test run on a T440s while typing a long email all but 2 touch points were
located in the outer ~5% of the touchpad. Define a 10% exclusion zone on the
left and right edges in which new touchpoint is automatically assigned to be a
palm.
A finger may move into that exclusion zone without being marked as palm, it
just can't start in one.
On clickpads, the exclusion zone does not extend into the software buttons.
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
I don't have the big picture (of libinput) atm. Does this stand in the
way of edge-scolling (in the future)?
in this simple form - yes, it would. once we add edge scrolling we'll need
to take this into account but for now getting basic palm detection is
pretty high on the todo list. The t440 is almost unusable without it.

Cheers,
Peter
Hans de Goede
2014-07-11 09:04:50 UTC
Permalink
Hi,
Post by Daniel Martin
Post by Peter Hutterer
A large part of palm events are situated on the far edges of the touchpad. In
a test run on a T440s while typing a long email all but 2 touch points were
located in the outer ~5% of the touchpad. Define a 10% exclusion zone on the
left and right edges in which new touchpoint is automatically assigned to be a
palm.
A finger may move into that exclusion zone without being marked as palm, it
just can't start in one.
On clickpads, the exclusion zone does not extend into the software buttons.
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
I don't have the big picture (of libinput) atm. Does this stand in the
way of edge-scolling (in the future)?
I've adding edge-scrolling on my to-do list (after a bunch of other
higher prio items). I agree with Peter that it is best to just move
forward with this, and to figure out how this interacts with edge-scrolling
when we implement edge-scrolling.

The patch looks good to me and is:

Reviewed-by: Hans de Goede <hdegoede at redhat.com>

Regards,

Hans


p.s.

Peter, Should we perhaps do something similar for the good old xf86-input-synapatics ?
Hans de Goede
2014-07-11 09:25:45 UTC
Permalink
Hi,
Post by Hans de Goede
Hi,
Post by Daniel Martin
Post by Peter Hutterer
A large part of palm events are situated on the far edges of the touchpad. In
a test run on a T440s while typing a long email all but 2 touch points were
located in the outer ~5% of the touchpad. Define a 10% exclusion zone on the
left and right edges in which new touchpoint is automatically assigned to be a
palm.
A finger may move into that exclusion zone without being marked as palm, it
just can't start in one.
On clickpads, the exclusion zone does not extend into the software buttons.
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
I don't have the big picture (of libinput) atm. Does this stand in the
way of edge-scolling (in the future)?
I've adding edge-scrolling on my to-do list (after a bunch of other
higher prio items). I agree with Peter that it is best to just move
forward with this, and to figure out how this interacts with edge-scrolling
when we implement edge-scrolling.
Reviewed-by: Hans de Goede <hdegoede at redhat.com>
Thinking more about this, I withdraw my reviewed-by.

This patch as it stands will break any pointer-moves starting at the edge
of the touchpad. Which esp. with smaller touchpads is likely to happen
when the user wants to move from one side of the screen to the other.

So 2 things:

1) The once a palm always a palm bits needs to be replaced with keeping a
start position and if a touch moves past a certain threshold from its
start position, no longer consider it a palm (and call tp_set_pointer, as
the touch may now become the pointer if don't have one already).

2) We should probably only do this whole 5% thing on larger^W wide touchpads,
where the palms resting on the left / right edges is thus likely. For touchpads
where the kernel does not provide resolution info, and thus we don't know the
size we should simply not do this.

This also answers my p.s. should we also do this for xf86-input-synaptics, no
we should not, lets just focus on getting libinput ready as a full replacement.

Regards,

Hans

Loading...