Merge branch 'wip/advanced-thumb-detection-v2'

This commit is contained in:
Peter Hutterer 2019-07-17 19:37:21 +10:00
commit de2767b152
16 changed files with 786 additions and 517 deletions

View file

@ -18,6 +18,7 @@ to be useful.
gestures.rst
middle-button-emulation.rst
palm-detection.rst
touchpad-thumb-detection.rst
scrolling.rst
t440-support.rst
tapping.rst

View file

@ -159,6 +159,7 @@ src_rst = files(
'touchpad-pressure.rst',
'touchpad-pressure-debugging.rst',
'touchpad-jitter.rst',
'touchpad-thumb-detection.rst',
'touchpads.rst',
'trackpoints.rst',
'trackpoint-configuration.rst',

View file

@ -0,0 +1,89 @@
.. _thumb_detection:
==============================================================================
Thumb detection
==============================================================================
Thumb detection tries to identify touches triggered by a thumb rather than a
pointer-moving finger. This is necessary on :ref:`touchpads_buttons_clickpads`
as a finger pressing a button always creates a new touch, causing
misinterpretation of gestures. Click-and-drag with two fingers (one holding
the button, one moving) would be interpreted as two-finger scrolling without
working thumb detection.
libinput has built-in thumb detection, partially dependent on
hardware-specific capabilities.
- :ref:`thumb_pressure`
- :ref:`thumb_areas`
- :ref:`thumb_speed`
Thumb detection uses multiple approaches and the final decision on whether
to ignore a thumb depends on the interaction at the time.
.. _thumb_pressure:
------------------------------------------------------------------------------
Thumb detection based on pressure or size
------------------------------------------------------------------------------
The simplest form of thumb detection identifies a touch as thumb when the
pressure value goes above a certain threshold. This threshold is usually
high enough that it cannot be triggered by a finger movement.
On touchpads that support the ``ABS_MT_TOUCH_MAJOR`` axes, libinput can perform
thumb detection based on the size of the touch ellipse. This works similar to
the pressure-based palm detection in that a touch is labelled as palm when
it exceeds the (device-specific) touch size threshold.
Pressure- and size-based thumb detection depends on the location of the
thumb and usually only applies within the :ref:`thumb_areas`.
For some information on how to detect pressure on a touch and debug the
pressure ranges, see :ref:`touchpad_pressure`. Pressure- and size-based
thumb detection require thresholds set in the :ref:`device-quirks`.
.. _thumb_areas:
------------------------------------------------------------------------------
Thumb detection areas
------------------------------------------------------------------------------
Pressure and size readings are unreliable at the far bottom of the touchpad.
A thumb hanging mostly off the touchpad will have a small surface area.
libinput has a definitive thumb zone where any touch is considered a
thumb. Immediately above that area is the area where libinput will label a
thumb as such if the pressure or size thresholds are exceeded.
.. figure:: thumb-detection.svg
:align: center
The picture above shows the two detection areas. In the larger (light red)
area, a touch is labelled as thumb when it exceeds a device-specific
pressure threshold. In the lower (dark red) area, a touch is always labelled
as thumb.
Moving outside the areas generally releases the thumb from being a thumb.
.. _thumb_speed:
------------------------------------------------------------------------------
Thumb movement based on speed
------------------------------------------------------------------------------
Regular interactions with thumbs do not usually move the thumb. When fingers
are moving across the touchpad and a thumb is dropped, this can cause
erroneous scroll motion or similar issues. libinput observes the finger
motion speed for all touches - where a finger has been moving a newly
dropped finger is more likely to be labeled as thumb.
------------------------------------------------------------------------------
Thumb detection based on finger positions
------------------------------------------------------------------------------
The shape of the human hand and the interactions that usually involve a
thumb imply that a thumb is situated in a specific position relative to
other fingers (usually to the side and below). This is used by libinput to
detect thumbs during some interactions that do not implicitly require a
thumb (e.g. pinch-and-rotate).

View file

@ -325,6 +325,7 @@ src_libinput = src_libfilter + [
'src/evdev-mt-touchpad.c',
'src/evdev-mt-touchpad.h',
'src/evdev-mt-touchpad-tap.c',
'src/evdev-mt-touchpad-thumb.c',
'src/evdev-mt-touchpad-buttons.c',
'src/evdev-mt-touchpad-edge-scroll.c',
'src/evdev-mt-touchpad-gestures.c',

View file

@ -1042,8 +1042,7 @@ tp_clickfinger_within_distance(struct tp_dispatch *tp,
if (!t1 || !t2)
return 0;
if (t1->thumb.state == THUMB_STATE_YES ||
t2->thumb.state == THUMB_STATE_YES)
if (tp_thumb_ignored(tp, t1) || tp_thumb_ignored(tp, t2))
return 0;
x = abs(t1->point.x - t2->point.x);
@ -1098,7 +1097,7 @@ tp_clickfinger_set_button(struct tp_dispatch *tp)
if (t->state != TOUCH_BEGIN && t->state != TOUCH_UPDATE)
continue;
if (t->thumb.state == THUMB_STATE_YES)
if (tp_thumb_ignored(tp, t))
continue;
if (t->palm.state != PALM_NONE)

View file

@ -260,12 +260,13 @@ tp_edge_scroll_handle_event(struct tp_dispatch *tp,
break;
}
evdev_log_debug(tp->device,
"edge-scroll: touch %d state %s → %s → %s\n",
t->index,
edge_state_to_str(current),
edge_event_to_str(event),
edge_state_to_str(t->scroll.edge_state));
if (current != t->scroll.edge_state)
evdev_log_debug(tp->device,
"edge-scroll: touch %d state %s → %s → %s\n",
t->index,
edge_state_to_str(current),
edge_event_to_str(event),
edge_state_to_str(t->scroll.edge_state));
}
static void
@ -398,8 +399,7 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
if (!t->dirty)
continue;
if (t->palm.state != PALM_NONE ||
t->thumb.state == THUMB_STATE_YES)
if (t->palm.state != PALM_NONE || tp_thumb_ignored(tp, t))
continue;
/* only scroll with the finger in the previous edge */

View file

@ -30,8 +30,8 @@
#include "evdev-mt-touchpad.h"
#define DEFAULT_GESTURE_SWITCH_TIMEOUT ms2us(100)
#define DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT ms2us(150)
#define DEFAULT_GESTURE_2FG_PINCH_TIMEOUT ms2us(75)
#define DEFAULT_GESTURE_SWIPE_TIMEOUT ms2us(150)
#define DEFAULT_GESTURE_PINCH_TIMEOUT ms2us(150)
static inline const char*
gesture_state_to_str(enum tp_gesture_state state)
@ -56,7 +56,7 @@ tp_get_touches_delta(struct tp_dispatch *tp, bool average)
for (i = 0; i < tp->num_slots; i++) {
t = &tp->touches[i];
if (!tp_touch_active(tp, t))
if (!tp_touch_active_for_gesture(tp, t))
continue;
nactive++;
@ -175,7 +175,7 @@ tp_gesture_get_active_touches(const struct tp_dispatch *tp,
memset(touches, 0, count * sizeof(struct tp_touch *));
tp_for_each_touch(tp, t) {
if (tp_touch_active(tp, t)) {
if (tp_touch_active_for_gesture(tp, t)) {
touches[n++] = t;
if (n == count)
return count;
@ -196,21 +196,14 @@ tp_gesture_get_active_touches(const struct tp_dispatch *tp,
}
static uint32_t
tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch,
unsigned int nfingers)
tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch)
{
struct phys_coords mm;
struct device_float_coords delta;
double move_threshold = 1.0; /* mm */
move_threshold *= (nfingers - 1);
delta = device_delta(touch->point, touch->gesture.initial);
mm = tp_phys_delta(tp, delta);
if (length_in_mm(mm) < move_threshold)
return UNDEFINED_DIRECTION;
return phys_get_direction(mm);
}
@ -463,57 +456,127 @@ tp_gesture_init_pinch(struct tp_dispatch *tp)
tp->gesture.prev_scale = 1.0;
}
static struct phys_coords
tp_gesture_mm_moved(struct tp_dispatch *tp, struct tp_touch *t)
{
struct device_coords delta;
delta.x = abs(t->point.x - t->gesture.initial.x);
delta.y = abs(t->point.y - t->gesture.initial.y);
return evdev_device_unit_delta_to_mm(tp->device, &delta);
}
static enum tp_gesture_state
tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *first = tp->gesture.touches[0],
*second = tp->gesture.touches[1];
*second = tp->gesture.touches[1],
*thumb;
uint32_t dir1, dir2;
struct phys_coords mm;
int vert_distance, horiz_distance;
struct device_coords delta;
struct phys_coords first_moved, second_moved, distance_mm;
double first_mm, second_mm; /* movement since gesture start in mm */
double thumb_mm, finger_mm;
double inner = 1.5; /* inner threshold in mm - count this touch */
double outer = 4.0; /* outer threshold in mm - ignore other touch */
vert_distance = abs(first->point.y - second->point.y);
horiz_distance = abs(first->point.x - second->point.x);
/* Need more margin for error when there are more fingers */
outer += 2.0 * (tp->gesture.finger_count - 2);
inner += 0.5 * (tp->gesture.finger_count - 2);
if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) {
/* for two-finger gestures, if the fingers stay unmoving for a
* while, assume (slow) scroll */
first_moved = tp_gesture_mm_moved(tp, first);
first_mm = hypot(first_moved.x, first_moved.y);
second_moved = tp_gesture_mm_moved(tp, second);
second_mm = hypot(second_moved.x, second_moved.y);
delta.x = abs(first->point.x - second->point.x);
delta.y = abs(first->point.y - second->point.y);
distance_mm = evdev_device_unit_delta_to_mm(tp->device, &delta);
/* If both touches moved less than a mm, we cannot decide yet */
if (first_mm < 1 && second_mm < 1)
return GESTURE_STATE_UNKNOWN;
/* Pick the thumb as the lowest point on the touchpad */
if (first->point.y > second->point.y) {
thumb = first;
thumb_mm = first_mm;
finger_mm = second_mm;
} else {
thumb = second;
thumb_mm = second_mm;
finger_mm = first_mm;
}
/* If both touches are within 7mm vertically and 40mm horizontally
* past the timeout, assume scroll/swipe */
if ((!tp->gesture.enabled ||
(distance_mm.x < 40.0 && distance_mm.y < 7.0)) &&
time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
if (tp->gesture.finger_count == 2) {
tp_gesture_set_scroll_buildup(tp);
return GESTURE_STATE_SCROLL;
/* more fingers than slots, don't bother with pinch, always
* assume swipe */
} else if (tp->gesture.finger_count > tp->num_slots) {
return GESTURE_STATE_SWIPE;
}
/* for 3+ finger gestures, check if one finger is > 20mm
below the others */
mm = evdev_convert_xy_to_mm(tp->device,
horiz_distance,
vert_distance);
if (mm.y > 20 && tp->gesture.enabled) {
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
} else {
return GESTURE_STATE_SWIPE;
}
}
if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) {
mm = evdev_convert_xy_to_mm(tp->device, horiz_distance, vert_distance);
if (tp->gesture.finger_count == 2 && mm.x > 40 && mm.y > 40)
/* If one touch exceeds the outer threshold while the other has not
* yet passed the inner threshold, there is either a resting thumb,
* or the user is doing "one-finger-scroll," where one touch stays in
* place while the other moves.
*/
if (first_mm >= outer || second_mm >= outer) {
/* If thumb detection is enabled, and thumb is still while
* finger moves, cancel gestures and mark lower as thumb.
* This applies to all gestures (2, 3, 4+ fingers), but allows
* more thumb motion on >2 finger gestures during detection.
*/
if (tp->thumb.detect_thumbs && thumb_mm < inner) {
tp_thumb_suppress(tp, thumb);
return GESTURE_STATE_NONE;
}
/* If gestures detection is disabled, or if finger is still
* while thumb moves, assume this is "one-finger scrolling."
* This applies only to 2-finger gestures.
*/
if ((!tp->gesture.enabled || finger_mm < inner) &&
tp->gesture.finger_count == 2) {
tp_gesture_set_scroll_buildup(tp);
return GESTURE_STATE_SCROLL;
}
/* If more than 2 fingers are involved, and the thumb moves
* while the fingers stay still, assume a pinch if eligible.
*/
if (finger_mm < inner &&
tp->gesture.finger_count > 2 &&
tp->gesture.enabled &&
tp->thumb.pinch_eligible) {
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
}
}
/* Else wait for both fingers to have moved */
dir1 = tp_gesture_get_direction(tp, first, tp->gesture.finger_count);
dir2 = tp_gesture_get_direction(tp, second, tp->gesture.finger_count);
if (dir1 == UNDEFINED_DIRECTION || dir2 == UNDEFINED_DIRECTION)
/* If either touch is still inside the inner threshold, we can't
* tell what kind of gesture this is.
*/
if ((first_mm < inner) || (second_mm < inner))
return GESTURE_STATE_UNKNOWN;
/* If both touches are moving in the same direction assume
* scroll or swipe */
/* Both touches have exceeded the inner threshold, so we have a valid
* gesture. Update gesture initial time and get directions so we know
* if it's a pinch or swipe/scroll.
*/
dir1 = tp_gesture_get_direction(tp, first);
dir2 = tp_gesture_get_direction(tp, second);
/* If we can't accurately detect pinches, or if the touches are moving
* the same way, this is a scroll or swipe.
*/
if (tp->gesture.finger_count > tp->num_slots ||
tp_gesture_same_directions(dir1, dir2)) {
if (tp->gesture.finger_count == 2) {
@ -522,12 +585,11 @@ tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
} else if (tp->gesture.enabled) {
return GESTURE_STATE_SWIPE;
}
} else {
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
}
return GESTURE_STATE_UNKNOWN;
/* If the touches are moving away from each other, this is a pinch */
tp_gesture_init_pinch(tp);
return GESTURE_STATE_PINCH;
}
static enum tp_gesture_state
@ -642,10 +704,11 @@ tp_gesture_post_gesture(struct tp_dispatch *tp, uint64_t time)
tp->gesture.state =
tp_gesture_handle_state_pinch(tp, time);
evdev_log_debug(tp->device,
"gesture state: %s → %s\n",
gesture_state_to_str(oldstate),
gesture_state_to_str(tp->gesture.state));
if (oldstate != tp->gesture.state)
evdev_log_debug(tp->device,
"gesture state: %s → %s\n",
gesture_state_to_str(oldstate),
gesture_state_to_str(tp->gesture.state));
}
void
@ -654,8 +717,8 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
if (tp->gesture.finger_count == 0)
return;
/* When tap-and-dragging, or a clickpad is clicked force 1fg mode */
if (tp_tap_dragging(tp) || (tp->buttons.is_clickpad && tp->buttons.state)) {
/* When tap-and-dragging, force 1fg mode. */
if (tp_tap_dragging(tp)) {
tp_gesture_cancel(tp, time);
tp->gesture.finger_count = 1;
tp->gesture.finger_count_pending = 0;
@ -758,7 +821,7 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
struct tp_touch *t;
tp_for_each_touch(tp, t) {
if (tp_touch_active(tp, t))
if (tp_touch_active_for_gesture(tp, t))
active_touches++;
}
@ -772,6 +835,10 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
} else if (!tp->gesture.started) {
tp->gesture.finger_count = active_touches;
tp->gesture.finger_count_pending = 0;
/* If in UNKNOWN state, go back to NONE to
* re-evaluate leftmost and rightmost touches
*/
tp->gesture.state = GESTURE_STATE_NONE;
/* Else debounce finger changes */
} else if (active_touches != tp->gesture.finger_count_pending) {
tp->gesture.finger_count_pending = active_touches;

View file

@ -912,12 +912,13 @@ tp_tap_handle_event(struct tp_dispatch *tp,
if (tp->tap.state == TAP_STATE_IDLE || tp->tap.state == TAP_STATE_DEAD)
tp_tap_clear_timer(tp);
evdev_log_debug(tp->device,
"tap: touch %d state %s → %s → %s\n",
t ? (int)t->index : -1,
tap_state_to_str(current),
tap_event_to_str(event),
tap_state_to_str(tp->tap.state));
if (current != tp->tap.state)
evdev_log_debug(tp->device,
"tap: touch %d state %s → %s → %s\n",
t ? (int)t->index : -1,
tap_state_to_str(current),
tap_event_to_str(event),
tap_state_to_str(tp->tap.state));
}
static bool
@ -1014,7 +1015,7 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
/* The simple version: if a touch is a thumb on
* begin we ignore it. All other thumb touches
* follow the normal tap state for now */
if (t->thumb.state == THUMB_STATE_YES) {
if (tp_thumb_ignored_for_tap(tp, t)) {
t->tap.is_thumb = true;
continue;
}
@ -1039,7 +1040,7 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
}
t->tap.state = TAP_TOUCH_STATE_IDLE;
} else if (tp->tap.state != TAP_STATE_IDLE &&
t->thumb.state == THUMB_STATE_YES) {
tp_thumb_ignored(tp, t)) {
tp_tap_handle_event(tp, t, TAP_EVENT_THUMB, time);
} else if (tp->tap.state != TAP_STATE_IDLE &&
tp_tap_exceeds_motion_threshold(tp, t)) {

View file

@ -0,0 +1,417 @@
/*
* Copyright © 2019 Matt Mayfield
* Copyright © 2019 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include "evdev-mt-touchpad.h"
/* distance between fingers to assume it is not a scroll */
#define SCROLL_MM_X 35
#define SCROLL_MM_Y 25
static inline const char*
thumb_state_to_str(enum tp_thumb_state state)
{
switch(state){
CASE_RETURN_STRING(THUMB_STATE_FINGER);
CASE_RETURN_STRING(THUMB_STATE_JAILED);
CASE_RETURN_STRING(THUMB_STATE_PINCH);
CASE_RETURN_STRING(THUMB_STATE_SUPPRESSED);
CASE_RETURN_STRING(THUMB_STATE_REVIVED);
CASE_RETURN_STRING(THUMB_STATE_REVIVED_JAILED);
CASE_RETURN_STRING(THUMB_STATE_DEAD);
}
return NULL;
}
static void
tp_thumb_set_state(struct tp_dispatch *tp,
struct tp_touch *t,
enum tp_thumb_state state)
{
unsigned int index = t ? t->index : UINT_MAX;
if (tp->thumb.state == state && tp->thumb.index == index)
return;
evdev_log_debug(tp->device,
"thumb: touch %d, %s → %s\n",
(int)index,
thumb_state_to_str(tp->thumb.state),
thumb_state_to_str(state));
tp->thumb.state = state;
tp->thumb.index = index;
}
void
tp_thumb_reset(struct tp_dispatch *tp)
{
tp->thumb.state = THUMB_STATE_FINGER;
tp->thumb.index = UINT_MAX;
tp->thumb.pinch_eligible = true;
}
static void
tp_thumb_lift(struct tp_dispatch *tp)
{
tp->thumb.state = THUMB_STATE_FINGER;
tp->thumb.index = UINT_MAX;
}
static bool
tp_thumb_in_exclusion_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return (t->point.y > tp->thumb.lower_thumb_line &&
tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE);
}
static bool
tp_thumb_detect_pressure_size(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
bool is_thumb = false;
if (tp->thumb.use_pressure &&
t->pressure > tp->thumb.pressure_threshold &&
tp_thumb_in_exclusion_area(tp, t)) {
is_thumb = true;
}
if (tp->thumb.use_size &&
(t->major > tp->thumb.size_threshold) &&
(t->minor < (tp->thumb.size_threshold * 0.6))) {
is_thumb = true;
}
return is_thumb;
}
static bool
tp_thumb_needs_jail(const struct tp_dispatch *tp, const struct tp_touch *t)
{
if (t->point.y < tp->thumb.upper_thumb_line ||
tp->scroll.method == LIBINPUT_CONFIG_SCROLL_EDGE)
return false;
if (!tp_thumb_in_exclusion_area(tp, t) &&
(tp->thumb.use_size || tp->thumb.use_pressure) &&
!tp_thumb_detect_pressure_size(tp, t))
return false;
if (t->speed.exceeded_count >= 10)
return false;
return true;
}
bool
tp_thumb_ignored(const struct tp_dispatch *tp, const struct tp_touch *t)
{
return (tp->thumb.detect_thumbs &&
tp->thumb.index == t->index &&
(tp->thumb.state == THUMB_STATE_JAILED ||
tp->thumb.state == THUMB_STATE_PINCH ||
tp->thumb.state == THUMB_STATE_SUPPRESSED ||
tp->thumb.state == THUMB_STATE_REVIVED_JAILED ||
tp->thumb.state == THUMB_STATE_DEAD));
}
bool
tp_thumb_ignored_for_tap(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return (tp->thumb.detect_thumbs &&
tp->thumb.index == t->index &&
(tp->thumb.state == THUMB_STATE_PINCH ||
tp->thumb.state == THUMB_STATE_SUPPRESSED ||
tp->thumb.state == THUMB_STATE_DEAD));
}
bool
tp_thumb_ignored_for_gesture(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return (tp->thumb.detect_thumbs &&
tp->thumb.index == t->index &&
(tp->thumb.state == THUMB_STATE_JAILED ||
tp->thumb.state == THUMB_STATE_SUPPRESSED ||
tp->thumb.state == THUMB_STATE_REVIVED_JAILED ||
tp->thumb.state == THUMB_STATE_DEAD));
}
void
tp_thumb_suppress(struct tp_dispatch *tp, struct tp_touch *t)
{
if(tp->thumb.state == THUMB_STATE_FINGER ||
tp->thumb.state == THUMB_STATE_JAILED ||
tp->thumb.state == THUMB_STATE_PINCH ||
tp->thumb.index != t->index) {
tp_thumb_set_state(tp, t, THUMB_STATE_SUPPRESSED);
return;
}
tp_thumb_set_state(tp, t, THUMB_STATE_DEAD);
}
static void
tp_thumb_pinch(struct tp_dispatch *tp, struct tp_touch *t)
{
if(tp->thumb.state == THUMB_STATE_FINGER ||
tp->thumb.state == THUMB_STATE_JAILED ||
tp->thumb.index != t->index)
tp_thumb_set_state(tp, t, THUMB_STATE_PINCH);
else if (tp->thumb.state != THUMB_STATE_PINCH)
tp_thumb_suppress(tp, t);
}
static void
tp_thumb_revive(struct tp_dispatch *tp, struct tp_touch *t)
{
if((tp->thumb.state != THUMB_STATE_SUPPRESSED &&
tp->thumb.state != THUMB_STATE_PINCH) ||
(tp->thumb.index != t->index))
return;
if(tp_thumb_needs_jail(tp, t))
tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED_JAILED);
else
tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED);
}
void
tp_thumb_update_touch(struct tp_dispatch *tp,
struct tp_touch *t,
uint64_t time)
{
if (!tp->thumb.detect_thumbs)
return;
/* Once any active touch exceeds the speed threshold, don't
* try to detect pinches until all touches lift.
*/
if (t->speed.exceeded_count >= 10 &&
tp->thumb.pinch_eligible &&
tp->gesture.state == GESTURE_STATE_NONE) {
tp->thumb.pinch_eligible = false;
if(tp->thumb.state == THUMB_STATE_PINCH) {
struct tp_touch *thumb;
tp_for_each_touch(tp, thumb) {
if (thumb->index != tp->thumb.index)
continue;
tp_thumb_set_state(tp, thumb, THUMB_STATE_SUPPRESSED);
break;
}
}
}
/* Handle the thumb lifting off the touchpad */
if (t->state == TOUCH_END && t->index == tp->thumb.index) {
tp_thumb_lift(tp);
return;
}
/* If this touch is not the only one, thumb updates happen by context
* instead of here
*/
if (tp->nfingers_down > 1)
return;
/* If we arrived here by other fingers lifting off, revive current touch
* if appropriate
*/
tp_thumb_revive(tp, t);
/* First new touch below the lower_thumb_line, or below the upper_thumb_
* line if hardware can't verify it's a finger, starts as JAILED.
*/
if (t->state == TOUCH_BEGIN && tp_thumb_needs_jail(tp, t)) {
tp_thumb_set_state(tp, t, THUMB_STATE_JAILED);
return;
}
/* If a touch breaks the speed threshold, or leaves the thumb area
* (upper or lower, depending on HW detection), it "escapes" jail.
*/
if (tp->thumb.state == THUMB_STATE_JAILED &&
!(tp_thumb_needs_jail(tp, t)))
tp_thumb_set_state(tp, t, THUMB_STATE_FINGER);
if (tp->thumb.state == THUMB_STATE_REVIVED_JAILED &&
!(tp_thumb_needs_jail(tp, t)))
tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED);
}
void
tp_thumb_update_multifinger(struct tp_dispatch *tp)
{
struct tp_touch *t;
struct tp_touch *first = NULL,
*second = NULL,
*newest = NULL;
struct device_coords distance;
struct phys_coords mm;
unsigned int speed_exceeded_count = 0;
/* Get the first and second bottom-most touches, the max speed exceeded
* count overall, and the newest touch (or one of them, if more).
*/
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE ||
t->state == TOUCH_HOVERING)
continue;
if (t->state == TOUCH_BEGIN)
newest = t;
speed_exceeded_count = max(speed_exceeded_count,
t->speed.exceeded_count);
if (!first) {
first = t;
continue;
}
if (t->point.y > first->point.y) {
second = first;
first = t;
continue;
}
if (!second || t->point.y > second->point.y ) {
second = t;
}
}
if (!first || !second)
return;
assert(first);
assert(second);
distance.x = abs(first->point.x - second->point.x);
distance.y = abs(first->point.y - second->point.y);
mm = evdev_device_unit_delta_to_mm(tp->device, &distance);
/* Speed-based thumb detection: if an existing finger is moving, and
* a new touch arrives, mark it as a thumb if it doesn't qualify as a
* 2-finger scroll.
*/
if (newest &&
tp->thumb.state == THUMB_STATE_FINGER &&
tp->nfingers_down == 2 &&
speed_exceeded_count > 5 &&
(tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG ||
(mm.x > SCROLL_MM_X || mm.y > SCROLL_MM_Y))) {
evdev_log_debug(tp->device,
"touch %d is speed-based thumb\n",
newest->index);
tp_thumb_suppress(tp, newest);
return;
}
/* Position-based thumb detection: When a new touch arrives, check the
* two lowest touches. If they qualify for 2-finger scrolling, clear
* thumb status. If not, mark the lower touch (based on pinch_eligible)
* as either PINCH or SUPPRESSED.
*/
if (mm.y > SCROLL_MM_Y) {
if (tp->thumb.pinch_eligible)
tp_thumb_pinch(tp, first);
else
tp_thumb_suppress(tp, first);
} else {
tp_thumb_lift(tp);
}
}
void
tp_init_thumb(struct tp_dispatch *tp)
{
struct evdev_device *device = tp->device;
double w = 0.0, h = 0.0;
struct device_coords edges;
struct phys_coords mm = { 0.0, 0.0 };
uint32_t threshold;
struct quirks_context *quirks;
struct quirks *q;
tp->thumb.detect_thumbs = false;
if (!tp->buttons.is_clickpad)
return;
/* if the touchpad is less than 50mm high, skip thumb detection.
* it's too small to meaningfully interact with a thumb on the
* touchpad */
evdev_device_get_size(device, &w, &h);
if (h < 50)
return;
tp->thumb.detect_thumbs = true;
tp->thumb.use_pressure = false;
tp->thumb.pressure_threshold = INT_MAX;
/* detect thumbs by pressure in the bottom 15mm, detect thumbs by
* lingering in the bottom 8mm */
mm.y = h * 0.85;
edges = evdev_device_mm_to_units(device, &mm);
tp->thumb.upper_thumb_line = edges.y;
mm.y = h * 0.92;
edges = evdev_device_mm_to_units(device, &mm);
tp->thumb.lower_thumb_line = edges.y;
quirks = evdev_libinput_context(device)->quirks;
q = quirks_fetch_for_device(quirks, device->udev_device);
if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_PRESSURE)) {
if (quirks_get_uint32(q,
QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD,
&threshold)) {
tp->thumb.use_pressure = true;
tp->thumb.pressure_threshold = threshold;
}
}
if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_TOUCH_MAJOR)) {
if (quirks_get_uint32(q,
QUIRK_ATTR_THUMB_SIZE_THRESHOLD,
&threshold)) {
tp->thumb.use_size = true;
tp->thumb.size_threshold = threshold;
}
}
tp_thumb_reset(tp);
quirks_unref(q);
evdev_log_debug(device,
"thumb: enabled thumb detection (area%s%s)\n",
tp->thumb.use_pressure ? ", pressure" : "",
tp->thumb.use_size ? ", size" : "");
}

View file

@ -39,7 +39,6 @@
#define DEFAULT_TRACKPOINT_EVENT_TIMEOUT ms2us(40)
#define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1 ms2us(200)
#define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2 ms2us(500)
#define THUMB_MOVE_TIMEOUT ms2us(300)
#define FAKE_FINGER_OVERFLOW (1 << 7)
#define THUMB_IGNORE_SPEED_THRESHOLD 20 /* mm/s */
@ -354,8 +353,6 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
t->was_down = true;
tp->nfingers_down++;
t->palm.time = time;
t->thumb.state = THUMB_STATE_MAYBE;
t->thumb.first_touch_time = time;
t->tap.is_thumb = false;
t->tap.is_palm = false;
t->speed.exceeded_count = 0;
@ -774,7 +771,18 @@ tp_touch_active(const struct tp_dispatch *tp, const struct tp_touch *t)
return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) &&
t->palm.state == PALM_NONE &&
!t->pinned.is_pinned &&
t->thumb.state != THUMB_STATE_YES &&
!tp_thumb_ignored(tp, t) &&
tp_button_touch_active(tp, t) &&
tp_edge_scroll_touch_active(tp, t);
}
bool
tp_touch_active_for_gesture(const struct tp_dispatch *tp, const struct tp_touch *t)
{
return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) &&
t->palm.state == PALM_NONE &&
!t->pinned.is_pinned &&
!tp_thumb_ignored_for_gesture(tp, t) &&
tp_button_touch_active(tp, t) &&
tp_edge_scroll_touch_active(tp, t);
}
@ -1145,110 +1153,6 @@ out:
palm_state);
}
static inline const char*
thumb_state_to_str(enum tp_thumb_state state)
{
switch(state){
CASE_RETURN_STRING(THUMB_STATE_NO);
CASE_RETURN_STRING(THUMB_STATE_YES);
CASE_RETURN_STRING(THUMB_STATE_MAYBE);
}
return NULL;
}
static void
tp_thumb_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
{
enum tp_thumb_state state = t->thumb.state;
/* once a thumb, always a thumb, once ruled out always ruled out */
if (!tp->thumb.detect_thumbs ||
t->thumb.state != THUMB_STATE_MAYBE)
return;
if (t->point.y < tp->thumb.upper_thumb_line) {
/* if a potential thumb is above the line, it won't ever
* label as thumb */
t->thumb.state = THUMB_STATE_NO;
goto out;
}
/* If the thumb moves by more than 7mm, it's not a resting thumb */
if (t->state == TOUCH_BEGIN) {
t->thumb.initial = t->point;
} else if (t->state == TOUCH_UPDATE) {
struct device_float_coords delta;
struct phys_coords mm;
delta = device_delta(t->point, t->thumb.initial);
mm = tp_phys_delta(tp, delta);
if (length_in_mm(mm) > 7) {
t->thumb.state = THUMB_STATE_NO;
goto out;
}
}
/* If the finger is below the upper thumb line and we have another
* finger in the same area, neither finger is a thumb (unless we've
* already labeled it as such).
*/
if (t->point.y > tp->thumb.upper_thumb_line &&
tp->nfingers_down > 1) {
struct tp_touch *other;
tp_for_each_touch(tp, other) {
if (other->state != TOUCH_BEGIN &&
other->state != TOUCH_UPDATE)
continue;
if (other->point.y > tp->thumb.upper_thumb_line) {
t->thumb.state = THUMB_STATE_NO;
if (other->thumb.state == THUMB_STATE_MAYBE)
other->thumb.state = THUMB_STATE_NO;
break;
}
}
}
/* Note: a thumb at the edge of the touchpad won't trigger the
* threshold, the surface area is usually too small. So we have a
* two-stage detection: pressure and time within the area.
* A finger that remains at the very bottom of the touchpad becomes
* a thumb.
*/
if (tp->thumb.use_pressure &&
t->pressure > tp->thumb.pressure_threshold) {
t->thumb.state = THUMB_STATE_YES;
} else if (tp->thumb.use_size &&
(t->major > tp->thumb.size_threshold) &&
(t->minor < (tp->thumb.size_threshold * 0.6))) {
t->thumb.state = THUMB_STATE_YES;
} else if (t->point.y > tp->thumb.lower_thumb_line &&
tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE &&
t->thumb.first_touch_time + THUMB_MOVE_TIMEOUT < time) {
t->thumb.state = THUMB_STATE_YES;
}
/* now what? we marked it as thumb, so:
*
* - pointer motion must ignore this touch
* - clickfinger must ignore this touch for finger count
* - software buttons are unaffected
* - edge scrolling unaffected
* - gestures: unaffected
* - tapping: honour thumb on begin, ignore it otherwise for now,
* this gets a tad complicated otherwise
*/
out:
if (t->thumb.state != state)
evdev_log_debug(tp->device,
"thumb state: touch %d, %s → %s\n",
t->index,
thumb_state_to_str(state),
thumb_state_to_str(t->thumb.state));
}
static void
tp_unhover_pressure(struct tp_dispatch *tp, uint64_t time)
{
@ -1588,52 +1492,6 @@ tp_detect_jumps(const struct tp_dispatch *tp,
return is_jump;
}
static void
tp_detect_thumb_while_moving(struct tp_dispatch *tp)
{
struct tp_touch *t;
struct tp_touch *first = NULL,
*second = NULL;
struct device_coords distance;
struct phys_coords mm;
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE ||
t->state == TOUCH_HOVERING)
continue;
if (t->state != TOUCH_BEGIN)
first = t;
else
second = t;
if (first && second)
break;
}
assert(first);
assert(second);
if (tp->scroll.method == LIBINPUT_CONFIG_SCROLL_2FG) {
/* If the second finger comes down next to the other one, we
* assume this is a scroll motion.
*/
distance.x = abs(first->point.x - second->point.x);
distance.y = abs(first->point.y - second->point.y);
mm = evdev_device_unit_delta_to_mm(tp->device, &distance);
if (mm.x <= 25 && mm.y <= 15)
return;
}
/* Finger are too far apart or 2fg scrolling is disabled, mark
* second finger as thumb */
evdev_log_debug(tp->device,
"touch %d is speed-based thumb\n",
second->index);
second->thumb.state = THUMB_STATE_YES;
}
/**
* Rewrite the motion history so that previous points' timestamps are the
* current point's timestamp minus whatever MSC_TIMESTAMP gives us.
@ -1819,7 +1677,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
tp_motion_history_reset(t);
}
tp_thumb_detect(tp, t, time);
tp_thumb_update_touch(tp, t, time);
tp_palm_detect(tp, t, time);
tp_detect_wobbling(tp, t, time);
tp_motion_hysteresis(tp, t);
@ -1838,7 +1696,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
* never count down. Let's see how far we get with that.
*/
if (t->speed.last_speed > THUMB_IGNORE_SPEED_THRESHOLD) {
if (t->speed.exceeded_count < 10)
if (t->speed.exceeded_count < 15)
t->speed.exceeded_count++;
} else if (t->speed.exceeded_count > 0) {
t->speed.exceeded_count--;
@ -1857,12 +1715,10 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
}
}
/* If we have one touch that exceeds the speed and we get a new
* touch down while doing that, the second touch is a thumb */
if (have_new_touch &&
tp->nfingers_down == 2 &&
speed_exceeded_count > 5)
tp_detect_thumb_while_moving(tp);
if (tp->thumb.detect_thumbs &&
have_new_touch &&
tp->nfingers_down >= 2)
tp_thumb_update_multifinger(tp);
if (restart_filter)
filter_restart(tp->device->pointer.filter, tp, time);
@ -1910,6 +1766,9 @@ tp_post_process_state(struct tp_dispatch *tp, uint64_t time)
tp->queued = TOUCHPAD_EVENT_NONE;
if (tp->nfingers_down == 0)
tp_thumb_reset(tp);
tp_tap_post_process_state(tp);
}
@ -2101,6 +1960,8 @@ tp_clear_state(struct tp_dispatch *tp)
*
* Then lift all touches so the touchpad is in a neutral state.
*
* Then reset thumb state.
*
*/
tp_release_all_buttons(tp, now);
tp_release_all_taps(tp, now);
@ -2110,6 +1971,8 @@ tp_clear_state(struct tp_dispatch *tp)
}
tp_release_fake_touches(tp);
tp_thumb_reset(tp);
tp_handle_state(tp, now);
}
@ -3441,70 +3304,6 @@ tp_init_sendevents(struct tp_dispatch *tp,
tp_keyboard_timeout, tp);
}
static void
tp_init_thumb(struct tp_dispatch *tp)
{
struct evdev_device *device = tp->device;
double w = 0.0, h = 0.0;
struct device_coords edges;
struct phys_coords mm = { 0.0, 0.0 };
uint32_t threshold;
struct quirks_context *quirks;
struct quirks *q;
if (!tp->buttons.is_clickpad)
return;
/* if the touchpad is less than 50mm high, skip thumb detection.
* it's too small to meaningfully interact with a thumb on the
* touchpad */
evdev_device_get_size(device, &w, &h);
if (h < 50)
return;
tp->thumb.detect_thumbs = true;
tp->thumb.use_pressure = false;
tp->thumb.pressure_threshold = INT_MAX;
/* detect thumbs by pressure in the bottom 15mm, detect thumbs by
* lingering in the bottom 8mm */
mm.y = h * 0.85;
edges = evdev_device_mm_to_units(device, &mm);
tp->thumb.upper_thumb_line = edges.y;
mm.y = h * 0.92;
edges = evdev_device_mm_to_units(device, &mm);
tp->thumb.lower_thumb_line = edges.y;
quirks = evdev_libinput_context(device)->quirks;
q = quirks_fetch_for_device(quirks, device->udev_device);
if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_PRESSURE)) {
if (quirks_get_uint32(q,
QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD,
&threshold)) {
tp->thumb.use_pressure = true;
tp->thumb.pressure_threshold = threshold;
}
}
if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_TOUCH_MAJOR)) {
if (quirks_get_uint32(q,
QUIRK_ATTR_THUMB_SIZE_THRESHOLD,
&threshold)) {
tp->thumb.use_size = true;
tp->thumb.size_threshold = threshold;
}
}
quirks_unref(q);
evdev_log_debug(device,
"thumb: enabled thumb detection (area%s%s)\n",
tp->thumb.use_pressure ? ", pressure" : "",
tp->thumb.use_size ? ", size" : "");
}
static bool
tp_pass_sanity_check(struct tp_dispatch *tp,
struct evdev_device *device)

View file

@ -139,9 +139,13 @@ enum tp_gesture_state {
};
enum tp_thumb_state {
THUMB_STATE_NO,
THUMB_STATE_YES,
THUMB_STATE_MAYBE,
THUMB_STATE_FINGER,
THUMB_STATE_JAILED,
THUMB_STATE_PINCH,
THUMB_STATE_SUPPRESSED,
THUMB_STATE_REVIVED,
THUMB_STATE_REVIVED_JAILED,
THUMB_STATE_DEAD,
};
enum tp_jump_state {
@ -237,12 +241,6 @@ struct tp_touch {
struct device_coords initial;
} gesture;
struct {
enum tp_thumb_state state;
uint64_t first_touch_time;
struct device_coords initial;
} thumb;
struct {
double last_speed; /* speed in mm/s at last sample */
unsigned int exceeded_count;
@ -457,6 +455,10 @@ struct tp_dispatch {
bool use_size;
int size_threshold;
enum tp_thumb_state state;
unsigned int index;
bool pinch_eligible;
} thumb;
struct {
@ -569,6 +571,10 @@ tp_filter_motion_unaccelerated(struct tp_dispatch *tp,
bool
tp_touch_active(const struct tp_dispatch *tp, const struct tp_touch *t);
bool
tp_touch_active_for_gesture(const struct tp_dispatch *tp,
const struct tp_touch *t);
int
tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time);
@ -677,4 +683,34 @@ tp_palm_tap_is_palm(const struct tp_dispatch *tp, const struct tp_touch *t);
void
tp_clickpad_middlebutton_apply_config(struct evdev_device *device);
bool
tp_thumb_ignored(const struct tp_dispatch *tp, const struct tp_touch *t);
void
tp_thumb_reset(struct tp_dispatch *tp);
bool
tp_thumb_ignored_for_gesture(const struct tp_dispatch *tp, const struct tp_touch *t);
bool
tp_thumb_ignored_for_tap(const struct tp_dispatch *tp,
const struct tp_touch *t);
void
tp_thumb_suppress(struct tp_dispatch *tp, struct tp_touch *t);
void
tp_thumb_update_touch(struct tp_dispatch *tp,
struct tp_touch *t,
uint64_t time);
void
tp_detect_thumb_while_moving(struct tp_dispatch *tp);
void
tp_thumb_update_multifinger(struct tp_dispatch *tp);
void
tp_init_thumb(struct tp_dispatch *tp);
#endif

View file

@ -3808,12 +3808,6 @@ litest_timeout_hysteresis(void)
msleep(90);
}
void
litest_timeout_thumb(void)
{
msleep(320);
}
void
litest_push_event_frame(struct litest_device *dev)
{

View file

@ -847,9 +847,6 @@ litest_timeout_touch_arbitration(void);
void
litest_timeout_hysteresis(void);
void
litest_timeout_thumb(void);
void
litest_push_event_frame(struct litest_device *dev);

View file

@ -489,58 +489,6 @@ START_TEST(gestures_swipe_4fg_btntool)
}
END_TEST
START_TEST(gestures_pinch_vertical_position)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct libinput_event *event;
int nfingers = _i; /* ranged test */
if (libevdev_get_num_slots(dev->evdev) < nfingers ||
!libinput_device_has_capability(dev->libinput_device,
LIBINPUT_DEVICE_CAP_GESTURE))
return;
litest_disable_tap(dev->libinput_device);
litest_drain_events(li);
litest_touch_down(dev, 0, 40, 30);
litest_touch_down(dev, 1, 50, 70);
litest_touch_down(dev, 2, 60, 70);
if (nfingers > 3)
litest_touch_down(dev, 3, 70, 70);
libinput_dispatch(li);
litest_timeout_gesture_scroll();
libinput_dispatch(li);
/* This is actually a small swipe gesture, all three fingers moving
* down but we're checking for the code that triggers based on
* finger position. */
litest_touch_move(dev, 0, 40, 30.5);
litest_touch_move(dev, 1, 50, 70.5);
litest_touch_move(dev, 2, 60, 70.5);
if (nfingers > 3)
litest_touch_move(dev, 3, 70, 70.5);
libinput_dispatch(li);
event = libinput_get_event(li);
litest_is_gesture_event(event,
LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
nfingers);
libinput_event_destroy(event);
litest_touch_move_to(dev, 0, 40, 30.5, 40, 36, 5);
litest_touch_move_to(dev, 1, 50, 70.5, 50, 76, 5);
litest_touch_move_to(dev, 2, 60, 70.5, 60, 76, 5);
if (nfingers > 3)
litest_touch_move_to(dev, 3, 70, 70.5, 60, 76, 5);
libinput_dispatch(li);
litest_assert_only_typed_events(li,
LIBINPUT_EVENT_GESTURE_PINCH_UPDATE);
}
END_TEST
START_TEST(gestures_pinch)
{
struct litest_device *dev = litest_current_device();
@ -569,6 +517,17 @@ START_TEST(gestures_pinch)
LIBINPUT_DEVICE_CAP_GESTURE))
return;
/* If the device is too small to provide a finger spread wide enough
* to avoid the scroll bias, skip the test */
if (cardinal == E || cardinal == W) {
double w = 0, h = 0;
libinput_device_get_size(dev->libinput_device, &w, &h);
/* 0.6 because the code below gives us points like 20/y and
* 80/y. 45 because the threshold in the code is 40mm */
if (w * 0.6 < 45)
return;
}
dir_x = cardinals[cardinal][0];
dir_y = cardinals[cardinal][1];
@ -784,7 +743,7 @@ START_TEST(gestures_pinch_4fg)
litest_touch_down(dev, 3, 52 - dir_x, 52 - dir_y);
libinput_dispatch(li);
for (i = 0; i < 8; i++) {
for (i = 0; i < 7; i++) {
litest_push_event_frame(dev);
if (dir_x > 0.0)
dir_x -= 2;
@ -886,6 +845,17 @@ START_TEST(gestures_spread)
LIBINPUT_DEVICE_CAP_GESTURE))
return;
/* If the device is too small to provide a finger spread wide enough
* to avoid the scroll bias, skip the test */
if (cardinal == E || cardinal == W) {
double w = 0, h = 0;
libinput_device_get_size(dev->libinput_device, &w, &h);
/* 0.6 because the code below gives us points like 20/y and
* 80/y. 45 because the threshold in the code is 40mm */
if (w * 0.6 < 45)
return;
}
dir_x = cardinals[cardinal][0];
dir_y = cardinals[cardinal][1];
@ -1045,7 +1015,6 @@ END_TEST
TEST_COLLECTION(gestures)
{
struct range cardinals = { N, N + NCARDINALS };
struct range fingers = { 3, 5 };
litest_add("gestures:cap", gestures_cap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("gestures:cap", gestures_nocap, LITEST_ANY, LITEST_TOUCHPAD);
@ -1058,7 +1027,6 @@ TEST_COLLECTION(gestures)
litest_add_ranged("gestures:pinch", gestures_pinch_3fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
litest_add_ranged("gestures:pinch", gestures_pinch_4fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
litest_add_ranged("gestures:pinch", gestures_spread, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
litest_add_ranged("gestures:pinch", gestures_pinch_vertical_position, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &fingers);
litest_add("gestures:swipe", gestures_3fg_buttonarea_scroll, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
litest_add("gestures:swipe", gestures_3fg_buttonarea_scroll_btntool, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);

View file

@ -817,9 +817,9 @@ START_TEST(touchpad_clickfinger_3fg_tool_position)
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_assert_button_event(li, BTN_MIDDLE,
litest_assert_button_event(li, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li, BTN_MIDDLE,
litest_assert_button_event(li, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED);
}
END_TEST

View file

@ -5000,16 +5000,13 @@ has_thumb_detect(struct litest_device *dev)
{
double w, h;
if (!libevdev_has_event_code(dev->evdev, EV_ABS, ABS_MT_PRESSURE))
return 0;
if (libinput_device_get_size(dev->libinput_device, &w, &h) != 0)
return 0;
return h >= 50.0;
}
START_TEST(touchpad_thumb_area_begin_no_motion)
START_TEST(touchpad_thumb_lower_area_movement)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
@ -5021,86 +5018,38 @@ START_TEST(touchpad_thumb_area_begin_no_motion)
litest_drain_events(li);
/* Thumb below lower line - slow movement - no events */
litest_touch_down(dev, 0, 50, 99);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
litest_touch_move_to(dev, 0, 50, 99, 80, 99, 10);
litest_touch_move_to(dev, 0, 55, 99, 60, 99, 50);
litest_assert_empty_queue(li);
/* Thumb below lower line - fast movement - events */
litest_touch_move_to(dev, 0, 60, 99, 90, 99, 30);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
}
END_TEST
START_TEST(touchpad_thumb_area_update_no_motion)
START_TEST(touchpad_thumb_lower_area_movement_rethumb)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_disable_tap(dev->libinput_device);
litest_enable_clickfinger(dev);
if (!has_thumb_detect(dev))
return;
litest_drain_events(li);
litest_touch_down(dev, 0, 59, 99);
litest_touch_move_to(dev, 0, 59, 99, 61, 99, 10);
libinput_dispatch(li);
/* the first move may trigger events, but not after the timeout */
litest_drain_events(li);
litest_timeout_thumb();
libinput_dispatch(li);
litest_touch_move_to(dev, 0, 61, 99, 80, 99, 10);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_thumb_area_small_move)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_disable_tap(dev->libinput_device);
litest_enable_clickfinger(dev);
if (!has_thumb_detect(dev))
return;
litest_drain_events(li);
/* movement less than the threshold */
/* Thumb below lower line - fast movement - events */
litest_touch_down(dev, 0, 50, 99);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
litest_touch_move_to(dev, 0, 50, 99, 52, 99, 10);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(touchpad_thumb_area_large_move)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_disable_tap(dev->libinput_device);
litest_enable_clickfinger(dev);
if (!has_thumb_detect(dev))
return;
litest_touch_move_to(dev, 0, 50, 99, 90, 99, 30);
litest_drain_events(li);
/* moving within the area triggers events */
litest_touch_down(dev, 0, 50, 99);
litest_touch_move_to(dev, 0, 50, 99, 80, 99, 10);
/* slow movement after being a non-touch - still events */
litest_touch_move_to(dev, 0, 90, 99, 60, 99, 50);
litest_touch_up(dev, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
@ -5150,10 +5099,6 @@ START_TEST(touchpad_thumb_area_clickfinger)
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct libinput_event *event;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 31 },
{ -1, 0 }
};
if (!has_thumb_detect(dev))
return;
@ -5165,13 +5110,7 @@ START_TEST(touchpad_thumb_area_clickfinger)
litest_drain_events(li);
litest_touch_down(dev, 0, 50, 99);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
/* Need an extra event because the thumb doesn't have proper timers.
Shouldn't matter in real life */
litest_touch_move_extended(dev, 0, 55, 99, axes);
litest_touch_down(dev, 0, 50, 99); /* thumb */
libinput_dispatch(li);
litest_touch_down(dev, 1, 60, 50);
libinput_dispatch(li);
@ -5192,13 +5131,7 @@ START_TEST(touchpad_thumb_area_clickfinger)
litest_drain_events(li);
litest_touch_down(dev, 1, 60, 99);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
/* Need an extra event because the thumb doesn't have proper timers.
Shouldn't matter in real life */
litest_touch_move_extended(dev, 1, 60, 99, axes);
litest_touch_down(dev, 1, 60, 99); /* thumb */
libinput_dispatch(li);
litest_touch_down(dev, 0, 50, 50);
libinput_dispatch(li);
@ -5220,10 +5153,6 @@ START_TEST(touchpad_thumb_area_btnarea)
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
struct libinput_event *event;
struct axis_replacement axes[] = {
{ ABS_MT_PRESSURE, 31 },
{ -1, 0 }
};
if (!has_thumb_detect(dev))
return;
@ -5235,11 +5164,8 @@ START_TEST(touchpad_thumb_area_btnarea)
litest_drain_events(li);
litest_touch_down(dev, 0, 90, 99);
litest_touch_down(dev, 0, 90, 99); /* thumb */
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
litest_touch_move_extended(dev, 0, 95, 99, axes);
litest_button_click(dev, BTN_LEFT, true);
/* button areas work as usual with a thumb */
@ -5268,17 +5194,17 @@ START_TEST(touchpad_thumb_no_doublethumb)
litest_drain_events(li);
/* two touches in thumb area but we can't have two thumbs */
litest_touch_down(dev, 0, 50, 99);
/* random sleep interval. we don't have a thumb timer, but let's not
* put both touches down and move them immediately because that
* should always be a scroll event anyway. Go with a delay in
* between to make it more likely that this is really testing thumb
* detection.
*/
msleep(200);
libinput_dispatch(li);
litest_touch_down(dev, 1, 70, 99);
/* move touch to trigger the thumb detection */
litest_touch_move(dev, 0, 50, 99.2);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
/* move touch to trigger the thumb detection */
litest_touch_move(dev, 1, 70, 99.2);
libinput_dispatch(li);
litest_touch_move_two_touches(dev, 50, 99, 70, 99, 0, -20, 10);
@ -5289,42 +5215,6 @@ START_TEST(touchpad_thumb_no_doublethumb)
}
END_TEST
START_TEST(touchpad_thumb_no_doublethumb_with_timeout)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
litest_disable_tap(dev->libinput_device);
litest_enable_clickfinger(dev);
if (!has_thumb_detect(dev))
return;
litest_drain_events(li);
litest_touch_down(dev, 0, 50, 99.9);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
/* Thumbs don't have a timeout handler, so we have to move the thumb
* a bit to trigger. */
litest_touch_move(dev, 0, 50, 99.8);
/* first touch should now be a thumb */
litest_touch_down(dev, 1, 70, 99.9);
libinput_dispatch(li);
litest_timeout_thumb();
libinput_dispatch(li);
litest_touch_move(dev, 1, 70, 99.8);
litest_touch_move_two_touches(dev, 50, 99, 70, 99, 0, -20, 10);
litest_touch_up(dev, 0);
litest_touch_up(dev, 1);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
}
END_TEST
START_TEST(touchpad_tool_tripletap_touch_count)
{
struct litest_device *dev = litest_current_device();
@ -6498,6 +6388,9 @@ START_TEST(touchpad_speed_ignore_finger)
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
if (!has_thumb_detect(dev))
return;
if (litest_has_clickfinger(dev))
litest_enable_clickfinger(dev);
@ -6521,6 +6414,9 @@ START_TEST(touchpad_speed_allow_nearby_finger)
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
if (!has_thumb_detect(dev))
return;
if (!litest_has_2fg_scroll(dev))
return;
@ -6550,6 +6446,9 @@ START_TEST(touchpad_speed_ignore_finger_edgescroll)
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
if (!has_thumb_detect(dev))
return;
litest_enable_edge_scroll(dev);
if (litest_has_clickfinger(dev))
litest_enable_clickfinger(dev);
@ -6581,6 +6480,9 @@ START_TEST(touchpad_speed_ignore_hovering_finger)
{ -1, 0 }
};
if (!has_thumb_detect(dev))
return;
litest_drain_events(li);
/* first finger down but below touch size. we use slot 2 because
@ -7128,15 +7030,12 @@ TEST_COLLECTION(touchpad)
litest_add_for_device("touchpad:dwt", touchpad_dwt_multiple_keyboards_bothkeys_modifier, LITEST_SYNAPTICS_I2C);
litest_add_ranged_for_device("touchpad:dwt", touchpad_dwt_multiple_keyboards_remove, LITEST_SYNAPTICS_I2C, &twice);
litest_add("touchpad:thumb", touchpad_thumb_area_begin_no_motion, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_area_update_no_motion, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_area_small_move, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_area_large_move, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_lower_area_movement, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_lower_area_movement_rethumb, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_speed_empty_slots, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
litest_add("touchpad:thumb", touchpad_thumb_area_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_area_btnarea, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_no_doublethumb, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_no_doublethumb_with_timeout, LITEST_CLICKPAD, LITEST_ANY);
litest_add_for_device("touchpad:bugs", touchpad_tool_tripletap_touch_count, LITEST_SYNAPTICS_TOPBUTTONPAD);
litest_add_for_device("touchpad:bugs", touchpad_tool_tripletap_touch_count_late, LITEST_SYNAPTICS_TOPBUTTONPAD);
@ -7165,9 +7064,9 @@ TEST_COLLECTION(touchpad)
litest_add("touchpad:touch-size", touchpad_touch_size, LITEST_APPLE_CLICKPAD, LITEST_ANY);
litest_add("touchpad:touch-size", touchpad_touch_size_2fg, LITEST_APPLE_CLICKPAD, LITEST_ANY);
litest_add("touchpad:speed", touchpad_speed_ignore_finger, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
litest_add("touchpad:speed", touchpad_speed_allow_nearby_finger, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
litest_add("touchpad:speed", touchpad_speed_ignore_finger_edgescroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
litest_add("touchpad:speed", touchpad_speed_ignore_finger, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
litest_add("touchpad:speed", touchpad_speed_allow_nearby_finger, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
litest_add("touchpad:speed", touchpad_speed_ignore_finger_edgescroll, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
litest_add_for_device("touchpad:speed", touchpad_speed_ignore_hovering_finger, LITEST_BCM5974);
litest_add_ranged("touchpad:suspend", touchpad_suspend_abba, LITEST_TOUCHPAD, LITEST_ANY, &suspends);