diff --git a/doc/user/features.rst b/doc/user/features.rst index 6e644a8a..55c9cbb5 100644 --- a/doc/user/features.rst +++ b/doc/user/features.rst @@ -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 diff --git a/doc/user/meson.build b/doc/user/meson.build index fce6ec15..7d81bcff 100644 --- a/doc/user/meson.build +++ b/doc/user/meson.build @@ -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', diff --git a/doc/user/touchpad-thumb-detection.rst b/doc/user/touchpad-thumb-detection.rst new file mode 100644 index 00000000..caf26dc5 --- /dev/null +++ b/doc/user/touchpad-thumb-detection.rst @@ -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). diff --git a/meson.build b/meson.build index ce0c4fc2..d43828d1 100644 --- a/meson.build +++ b/meson.build @@ -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', diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index 11d1d6a5..6e721935 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.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) diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c index 497e4bd6..25e92a62 100644 --- a/src/evdev-mt-touchpad-edge-scroll.c +++ b/src/evdev-mt-touchpad-edge-scroll.c @@ -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 */ diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 1a3518e5..4a0287c7 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -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; diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 5255469e..a4828499 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -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)) { diff --git a/src/evdev-mt-touchpad-thumb.c b/src/evdev-mt-touchpad-thumb.c new file mode 100644 index 00000000..eb20616a --- /dev/null +++ b/src/evdev-mt-touchpad-thumb.c @@ -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" : ""); +} diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 6619501d..4ffc4a39 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -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) diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 204fc25a..5df284f2 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -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 diff --git a/test/litest.c b/test/litest.c index b31a3f48..5b09ec4e 100644 --- a/test/litest.c +++ b/test/litest.c @@ -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) { diff --git a/test/litest.h b/test/litest.h index 98b536ba..85a0a1f2 100644 --- a/test/litest.h +++ b/test/litest.h @@ -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); diff --git a/test/test-gestures.c b/test/test-gestures.c index 4a9596d3..ae2da795 100644 --- a/test/test-gestures.c +++ b/test/test-gestures.c @@ -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); diff --git a/test/test-touchpad-buttons.c b/test/test-touchpad-buttons.c index 73c0a60f..4759b896 100644 --- a/test/test-touchpad-buttons.c +++ b/test/test-touchpad-buttons.c @@ -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 diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 3b934145..38506768 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -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);