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.

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 handling of thumbs while tapping is the simplest approach only, more to
come in follow-up patches.

Note that "thumb" is the synonym for "this touch is too big to be a
fingertip". Which means that a light thumb touch will still be counted as a
finger. The side-effect here is that thumbs resting a the bottom edge of the
touchpad will almost certainly not trigger the pressure threshold because
most of the thumb is off the touchpad.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
Peter Hutterer 2015-05-27 18:25:49 +10:00
parent 4a3288dc59
commit 3dcf28b919
5 changed files with 110 additions and 1 deletions

View file

@ -832,6 +832,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);

View file

@ -525,11 +525,34 @@ 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) {
/* if a thumb is detected during a gesture, that gesture is
* cancelled and the user effectively needs to restart. we
* could be smarter, but the complexity isn't worth it */
tp_gesture_cancel(tp, time);
return;
}
if (active_touches != tp->gesture.finger_count) {
/* If all fingers are lifted immediately end the gesture */
if (active_touches == 0) {

View file

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

View file

@ -212,6 +212,8 @@ 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->is_thumb = false;
t->tap.is_thumb = false;
assert(tp->nfingers_down >= 1);
}
@ -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);
}
@ -602,6 +608,33 @@ out:
t->palm.state == PALM_TYPING ? "typing" : "trackpoint");
}
static 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;
/* Note: a thumb at the edge of the touchpad won't trigger the
* threshold, the surface areas is usually too small.
*/
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
*/
}
static void
tp_unhover_abs_distance(struct tp_dispatch *tp, uint64_t time)
{
@ -720,6 +753,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);
@ -1476,6 +1510,30 @@ tp_init_sendevents(struct tp_dispatch *tp,
return 0;
}
static 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;
}
static int
tp_sanity_check(struct tp_dispatch *tp,
struct evdev_device *device)
@ -1558,6 +1616,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;

View file

@ -141,9 +141,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];
@ -173,6 +175,7 @@ struct tp_touch {
struct {
enum tp_tap_touch_state state;
struct device_coords initial;
bool is_thumb;
} tap;
struct {
@ -234,6 +237,7 @@ struct tp_dispatch {
double prev_scale;
double angle;
struct device_float_coords center;
uint32_t thumb_mask;
} gesture;
struct {
@ -317,6 +321,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) \