[PATCH libinput 2/2] touchpad: add upper edge into exclusion zone

Peter Hutterer peter.hutterer at who-t.net
Thu Jul 6 03:49:58 UTC 2017


From: Ming-Yang Lu <op8867555 at gmail.com>

This reduces unexpected cursor moves when placing the thumb near the border
of trackpoint buttons and upper edge of touchpad.

https://bugs.freedesktop.org/show_bug.cgi?id=101574

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 doc/palm-detection.dox     |  22 +++++----
 doc/svg/palm-detection.svg |  47 +++++++++++-------
 src/evdev-mt-touchpad.c    |  66 ++++++++++++++++++++-----
 src/evdev-mt-touchpad.h    |   1 +
 test/test-touchpad.c       | 120 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 218 insertions(+), 38 deletions(-)

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" />
   <defs
      id="defs4">
     <marker
@@ -138,15 +139,14 @@
      id="path13492"
      d="m 38.928571,67.914286 c 0,0 3.508205,24.810617 9.642857,57.857144 6.134651,33.04652 23.277202,79.68584 89.642852,90.35714" />
   <rect
-     style="fill:#000000;fill-opacity:0.3559322;fill-rule:evenodd;stroke:none;stroke-width:3.30527353px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     style="fill:#000000;fill-opacity:0.3559322;fill-rule:evenodd;stroke:none;stroke-width:3.30510259px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
      id="rect3490"
-     width="65.272476"
-     height="136.21509"
+     width="65.310997"
+     height="136.12065"
      x="7.0411549"
-     y="7.0411549" />
+     y="7.1355872" />
   <text
-     sodipodi:linespacing="100%"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:100%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:0%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
      xml:space="preserve"
      id="text13874"
      y="63.628628"
@@ -160,11 +160,10 @@
      id="rect3490-2"
      width="65.272476"
      height="136.21509"
-     x="321.23563"
-     y="6.7607527" />
+     x="321.22849"
+     y="6.8830237" />
   <text
-     sodipodi:linespacing="100%"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:100%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:0%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none"
      xml:space="preserve"
      id="text13874-8"
      y="98.748993"
@@ -183,8 +182,7 @@
      id="layer1"
      style="display:inline" />
   <text
-     sodipodi:linespacing="100%"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:100%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:0%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none"
      xml:space="preserve"
      id="text13874-8-1"
      y="46.009491"
@@ -194,8 +192,7 @@
        y="46.009491"
        x="342.27759">C</tspan></text>
   <text
-     sodipodi:linespacing="100%"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:100%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;line-height:0%;font-family:Utopia;-inkscape-font-specification:Utopia;text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;stroke:none"
      xml:space="preserve"
      id="text13874-8-1-4"
      y="215.65927"
@@ -218,4 +215,18 @@
      cy="194.8819"
      r="4.0658817"
      transform="scale(-1,1)" />
+  <rect
+     width="248.87633"
+     height="6.8111157"
+     x="72.35215"
+     y="7.1355872"
+     id="rect4355"
+     style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ff0000;fill-opacity:0.32017547;fill-rule:nonzero;stroke:none;stroke-width:1.11822701;marker:none;enable-background:accumulate" />
+  <rect
+     y="7.1355872"
+     x="72.35215"
+     height="6.8111153"
+     width="248.87634"
+     id="rect4353"
+     style="fill:#000000;fill-opacity:0.3559322;fill-rule:evenodd;stroke:none;stroke-width:1.44321382px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
 </svg>
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);
-- 
2.13.0



More information about the wayland-devel mailing list