mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-02-10 17:00:27 +01:00
Merge branch 'wip/advanced-thumb-detection-v2'
This commit is contained in:
commit
de2767b152
16 changed files with 786 additions and 517 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
89
doc/user/touchpad-thumb-detection.rst
Normal file
89
doc/user/touchpad-thumb-detection.rst
Normal 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).
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
417
src/evdev-mt-touchpad-thumb.c
Normal file
417
src/evdev-mt-touchpad-thumb.c
Normal 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" : "");
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue