touchpad: add pressure-based thumb-detection
authorPeter Hutterer <peter.hutterer@who-t.net>
Wed, 27 May 2015 08:25:49 +0000 (18:25 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Thu, 9 Jul 2015 01:27:53 +0000 (11:27 +1000)
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>
src/evdev-mt-touchpad-buttons.c
src/evdev-mt-touchpad-gestures.c
src/evdev-mt-touchpad-tap.c
src/evdev-mt-touchpad.c
src/evdev-mt-touchpad.h

index 771d71d..9542d87 100644 (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);
 
index cd853ec..81bbd4d 100644 (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) {
index 7f241de..a43e235 100644 (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);
index d3bb643..3f5daba 100644 (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);
 }
@@ -603,6 +609,33 @@ out:
 }
 
 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)
 {
        struct tp_touch *t;
@@ -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);
@@ -1477,6 +1511,30 @@ tp_init_sendevents(struct tp_dispatch *tp,
 }
 
 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;
index df8be94..eda17a9 100644 (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) \