touchpad: add upper edge into exclusion zone

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@who-t.net>
This commit is contained in:
Ming-Yang Lu 2017-07-02 18:07:20 +08:00 committed by Peter Hutterer
parent 6a77cae850
commit 5dc330bdea
5 changed files with 218 additions and 38 deletions

View file

@ -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.

View file

@ -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>

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -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))

View file

@ -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;

View file

@ -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);