From ff30013c7f5857d4d6a257db994bbe8017cfa843 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 27 May 2015 18:25:49 +1000 Subject: [PATCH libinput] touchpad: add pressure-based thumb-detection All touchpad recordings seen so far show that a value above 100 is definitely a thumb or a palm. Values below are harder to discern, and the same isn't true for touchpads supporting ABS_PRESSURE instead of ABS_MT_PRESSURE. Note that "thumb" is the synonym for "this touch is big enough to not be a normal finger" which obviously means that a thumb-tip will still count as normal finger. The handling of a touch is as outlined in tp_thumb_detect: * thumbs are ignored for pointer motion * thumbs cancel gestures * thumbs are ignored for clickfinger count * edge scrolling doesn't care either way * software buttons don't care either way * tap: only if thumb on begin The tap state machine could be update with a bunch of escape routes when a thumb is detected in the middle of the state machine but this gets complex quick. Let's see if there's a need for it first. Signed-off-by: Peter Hutterer --- src/Makefile.am | 1 + src/evdev-mt-touchpad-buttons.c | 3 ++ src/evdev-mt-touchpad-gestures.c | 28 ++++++++++++++- src/evdev-mt-touchpad-tap.c | 13 +++++++ src/evdev-mt-touchpad-thumb.c | 78 ++++++++++++++++++++++++++++++++++++++++ src/evdev-mt-touchpad.c | 10 ++++++ src/evdev-mt-touchpad.h | 15 ++++++++ 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/evdev-mt-touchpad-thumb.c diff --git a/src/Makefile.am b/src/Makefile.am index 90bdc9a..e01755c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,7 @@ libinput_la_SOURCES = \ evdev-mt-touchpad-buttons.c \ evdev-mt-touchpad-edge-scroll.c \ evdev-mt-touchpad-gestures.c \ + evdev-mt-touchpad-thumb.c \ filter.c \ filter.h \ filter-private.h \ diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index 9c1c096..59cc13d 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -829,6 +829,9 @@ tp_check_clickfinger_distance(struct tp_dispatch *tp, if (!t1 || !t2) return 0; + if (t1->is_thumb || t2->is_thumb) + return 0; + x = abs(t1->point.x - t2->point.x); y = abs(t1->point.y - t2->point.y); diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index e85a9d7..a5f9b4d 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -211,11 +211,37 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time) { unsigned int active_touches = 0; struct tp_touch *t; + uint32_t old_thumb_mask, thumb_mask = 0; + int i = 0; - tp_for_each_touch(tp, t) + tp_for_each_touch(tp, t) { if (tp_touch_active(tp, t)) active_touches++; + if (t->is_thumb) + thumb_mask |= 1 << i; + i++; + } + + old_thumb_mask = tp->gesture.thumb_mask; + tp->gesture.thumb_mask = thumb_mask; + + /* active touches does not include thumb touches, need to count those + * separately, in a bitmask. + * then, if the finger count changes and/or the thumb count changes + * -> cancel gesture. + */ + if (thumb_mask != old_thumb_mask) { + /* FIXME: tp_gesture_cancel() once the gestures branch is + * merged. Not needed right now */ + + /* bailing out here means if a thumb is detected during a + gesture, that gesture is cancelled and the user + effectively needs to restart. we could be smarter here, + but the complexity isn't worth it */ + return; + } + if (active_touches != tp->gesture.finger_count) { /* If all fingers are lifted immediately end the gesture */ if (active_touches == 0) { diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 7f241de..a43e235 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -691,7 +691,20 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) t->tap.state = TAP_TOUCH_STATE_DEAD; + /* If a touch was considered thumb for tapping once, we + * ignore it for the rest of lifetime */ + if (t->tap.is_thumb) + continue; + if (t->state == TOUCH_BEGIN) { + /* 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->is_thumb) { + t->tap.is_thumb = true; + continue; + } + t->tap.state = TAP_TOUCH_STATE_TOUCH; t->tap.initial = t->point; tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time); diff --git a/src/evdev-mt-touchpad-thumb.c b/src/evdev-mt-touchpad-thumb.c new file mode 100644 index 0000000..77c9aa5 --- /dev/null +++ b/src/evdev-mt-touchpad-thumb.c @@ -0,0 +1,78 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include "evdev-mt-touchpad.h" + +int +tp_init_thumb(struct tp_dispatch *tp) +{ + struct evdev_device *device = tp->device; + const struct input_absinfo *abs; + + abs = libevdev_get_abs_info(device->evdev, ABS_MT_PRESSURE); + if (!abs) + return 0; + + if (abs->maximum - abs->minimum < 255) + return 0; + + /* The touchpads we looked at so far have a clear thumb threshold of + * ~100, you don't reach that with a normal finger interaction. + * Note: "thumb" means massive touch that should not interact, not + * "using the tip of my thumb for a pinch gestures". + */ + tp->thumb.threshold = 100; + tp->thumb.detect_thumbs = true; + + return 0; +} + +void +tp_thumb_detect(struct tp_dispatch *tp, struct tp_touch *t) +{ + /* once a thumb, always a thumb */ + if (!tp->thumb.detect_thumbs || t->is_thumb) + return; + + /* Want to hear something sad? if the thumb is at the edge of the + * touchpad (partially offit), this won't trigger because we don't + * get the contact surface area to meet the presure threshold. + * go cry in a corner + */ + if (t->pressure < tp->thumb.threshold) + return; + + t->is_thumb = true; + + /* 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: cancel + * - tapping: honour thumb on begin, ignore it otherwise for now, + * this gets a tad complicated otherwise + */ +} diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 2474340..3e18777 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -212,6 +212,7 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) t->millis = time; tp->nfingers_down++; t->palm.time = time; + t->tap.is_thumb = false; assert(tp->nfingers_down >= 1); } @@ -240,6 +241,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time) t->pinned.is_pinned = false; t->millis = time; t->palm.time = 0; + t->is_thumb = false; assert(tp->nfingers_down >= 1); tp->nfingers_down--; tp->queued |= TOUCHPAD_EVENT_MOTION; @@ -314,6 +316,9 @@ tp_process_absolute(struct tp_dispatch *tp, else tp_end_sequence(tp, t, time); break; + case ABS_MT_PRESSURE: + t->pressure = e->value; + break; } } @@ -461,6 +466,7 @@ tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t) return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && t->palm.state == PALM_NONE && !t->pinned.is_pinned && + !t->is_thumb && tp_button_touch_active(tp, t) && tp_edge_scroll_touch_active(tp, t); } @@ -721,6 +727,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time) if (!t->dirty) continue; + tp_thumb_detect(tp, t); tp_palm_detect(tp, t, time); tp_motion_hysteresis(tp, t); @@ -1562,6 +1569,9 @@ tp_init(struct tp_dispatch *tp, if (tp_init_gesture(tp) != 0) return -1; + if (tp_init_thumb(tp) != 0) + return -1; + device->seat_caps |= EVDEV_DEVICE_POINTER; return 0; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index edad611..44f31de 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -134,9 +134,11 @@ struct tp_touch { enum touch_state state; bool has_ended; /* TRACKING_ID == -1 */ bool dirty; + bool is_thumb; struct device_coords point; uint64_t millis; int distance; /* distance == 0 means touch */ + int pressure; struct { struct device_coords samples[TOUCHPAD_HISTORY_LENGTH]; @@ -166,6 +168,7 @@ struct tp_touch { struct { enum tp_tap_touch_state state; struct device_coords initial; + bool is_thumb; } tap; struct { @@ -216,6 +219,7 @@ struct tp_dispatch { unsigned int finger_count; unsigned int finger_count_pending; struct libinput_timer finger_count_switch_timer; + uint32_t thumb_mask; } gesture; struct { @@ -299,6 +303,11 @@ struct tp_dispatch { uint64_t keyboard_last_press_time; } dwt; + + struct { + bool detect_thumbs; + int threshold; + } thumb; }; #define tp_for_each_touch(_tp, _t) \ @@ -428,6 +437,12 @@ tp_init_gesture(struct tp_dispatch *tp); void tp_remove_gesture(struct tp_dispatch *tp); +int +tp_init_thumb(struct tp_dispatch *tp); + +void +tp_thumb_detect(struct tp_dispatch *tp, struct tp_touch *t); + void tp_gesture_stop(struct tp_dispatch *tp, uint64_t time); -- 2.4.3