Apple touchpads don't use ABS_MT_PRESSURE but they are multitouch touchpads,
so the current pressure-based handling code doesn't apply because it expects
slot-based pressure for mt touchpads.

Apple does however send useful data for ABS_MT_WIDTH_MAJOR/MINOR, so let's use
that instead. Bonus point: we can use sizes in mm instead of magic pressure
thresholds. Negative point: the touch size detected is a lot smaller than one
would think. So we require 5mm touches to start but don't do a touch up until
we hit less than 1mm on either axis, anything higher than that is unreliable.

Signed-off-by: Peter Hutterer <[email protected]>
---
 src/evdev-mt-touchpad.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++-
 src/evdev-mt-touchpad.h |  13 +++++
 src/libinput-util.h     |  85 +++++++++++++++++++++++++++++
 test/test-misc.c        |  33 ++++++++++++
 4 files changed, 269 insertions(+), 1 deletion(-)

diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 3fd1f29..b2c22ff 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -333,6 +333,21 @@ tp_process_absolute(struct tp_dispatch *tp,
                t->dirty = true;
                tp->queued |= TOUCHPAD_EVENT_OTHERAXIS;
                break;
+       case ABS_MT_TOUCH_MAJOR:
+               t->major = e->value;
+               t->dirty = true;
+               tp->queued |= TOUCHPAD_EVENT_OTHERAXIS;
+               break;
+       case ABS_MT_TOUCH_MINOR:
+               t->minor = e->value;
+               t->dirty = true;
+               tp->queued |= TOUCHPAD_EVENT_OTHERAXIS;
+               break;
+       case ABS_MT_ORIENTATION:
+               t->orientation = e->value;
+               t->dirty = true;
+               tp->queued |= TOUCHPAD_EVENT_OTHERAXIS;
+               break;
        }
 }
 
@@ -907,6 +922,52 @@ tp_unhover_pressure(struct tp_dispatch *tp, uint64_t time)
 }
 
 static void
+tp_unhover_size(struct tp_dispatch *tp, uint64_t time)
+{
+       struct tp_touch *t;
+       const struct rthreshold *low = &tp->touch_size.low,
+                               *high = &tp->touch_size.high;
+       int i;
+
+       /* We require 5 slots for size handling, so we don't need to care
+        * about fake touches here */
+
+       for (i = 0; i < (int)tp->num_slots; i++) {
+               int hi, lo;
+               int angle;
+
+               t = tp_get_touch(tp, i);
+
+               if (t->state == TOUCH_NONE)
+                       continue;
+
+               if (!t->dirty)
+                       continue;
+
+               angle = t->orientation * tp->touch_size.orientation_to_angle;
+               hi = rthreshold_at_angle(high, angle);
+               lo = rthreshold_at_angle(low, angle);
+
+               if (t->state == TOUCH_HOVERING) {
+                       if ((t->major > hi && t->minor > lo) ||
+                           (t->major > lo && t->minor > hi)) {
+                               evdev_log_debug(tp->device,
+                                               "touch-size: begin touch\n");
+                               /* avoid jumps when landing a finger */
+                               tp_motion_history_reset(t);
+                               tp_begin_touch(tp, t, time);
+                       }
+               } else {
+                       if (t->major < lo || t->minor < lo) {
+                               evdev_log_debug(tp->device,
+                                               "touch-size: end touch\n");
+                               tp_end_touch(tp, t, time);
+                       }
+               }
+       }
+}
+
+static void
 tp_unhover_fake_touches(struct tp_dispatch *tp, uint64_t time)
 {
        struct tp_touch *t;
@@ -970,6 +1031,8 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time)
 {
        if (tp->pressure.use_pressure)
                tp_unhover_pressure(tp, time);
+       else if (tp->touch_size.use_touch_size)
+               tp_unhover_size(tp, time);
        else
                tp_unhover_fake_touches(tp, time);
 
@@ -1865,6 +1928,18 @@ tp_sync_touch(struct tp_dispatch *tp,
                t->pressure = libevdev_get_event_value(evdev,
                                                       EV_ABS,
                                                       ABS_PRESSURE);
+       libevdev_fetch_slot_value(evdev,
+                                 slot,
+                                 ABS_MT_ORIENTATION,
+                                 &t->orientation);
+       libevdev_fetch_slot_value(evdev,
+                                 slot,
+                                 ABS_MT_TOUCH_MAJOR,
+                                 &t->major);
+       libevdev_fetch_slot_value(evdev,
+                                 slot,
+                                 ABS_MT_TOUCH_MINOR,
+                                 &t->minor);
 }
 
 static inline void
@@ -2437,10 +2512,68 @@ tp_init_pressure(struct tp_dispatch *tp,
                        "using pressure-based touch detection\n");
 }
 
+static bool
+tp_init_touch_size(struct tp_dispatch *tp,
+                  struct evdev_device *device)
+{
+       int xres, yres;
+       int omax;
+
+       /* Thresholds are in mm. We want a decent (5mm) touch to start but
+          once started we don't release until we see something tiny, less
+          than 1mm. low thresholds higher than that proved to be
+          unreliable, especially because the large Apple touchpads result
+          in some finger-tip interaction where the touchpoint is around 2mm
+          or less.
+        */
+       const double low = 1, high = 5; /* mm */
+
+       if (!libevdev_has_event_code(device->evdev, EV_ABS,
+                                    ABS_MT_TOUCH_MAJOR) ||
+           !libevdev_has_event_code(device->evdev, EV_ABS,
+                                    ABS_MT_TOUCH_MINOR) ||
+           !libevdev_has_event_code(device->evdev, EV_ABS,
+                                    ABS_MT_ORIENTATION)) {
+               tp->touch_size.use_touch_size = false;
+               return false;
+       }
+
+       if (libevdev_get_num_slots(device->evdev) < 5) {
+               evdev_log_bug_libinput(device,
+                              "Expected 5 slots for touch size detection\n");
+               tp->touch_size.use_touch_size = false;
+               return false;
+       }
+
+       omax = libevdev_get_abs_maximum(device->evdev, ABS_MT_ORIENTATION);
+       if (omax == 0) {
+               evdev_log_bug_kernel(device,
+                                    "Invalid range for ABS_MT_ORIENTATION\n");
+               return false;
+       }
+
+       xres = device->abs.absinfo_x->resolution;
+       yres = device->abs.absinfo_y->resolution;
+       tp->touch_size.low = rthreshold_init(low, xres, yres);
+       tp->touch_size.high = rthreshold_init(high, xres, yres);
+
+       /* Kernel defines orientation max as 90 degrees */
+       tp->touch_size.orientation_to_angle = 90.0/omax;
+
+       tp->touch_size.use_touch_size = true;
+
+       evdev_log_debug(device,
+                       "using size-based touch detection\n");
+
+       return true;
+}
+
 static int
 tp_init(struct tp_dispatch *tp,
        struct evdev_device *device)
 {
+       bool use_touch_size = false;
+
        tp->base.dispatch_type = DISPATCH_TOUCHPAD;
        tp->base.interface = &tp_interface;
        tp->device = device;
@@ -2455,7 +2588,11 @@ tp_init(struct tp_dispatch *tp,
 
        evdev_device_init_abs_range_warnings(device);
 
-       tp_init_pressure(tp, device);
+       if (device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD)
+               use_touch_size = tp_init_touch_size(tp, device);
+
+       if (!use_touch_size)
+               tp_init_pressure(tp, device);
 
        /* Set the dpi to that of the x axis, because that's what we normalize
           to when needed*/
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 2d531b5..5282b67 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -154,6 +154,8 @@ struct tp_touch {
        struct device_coords point;
        uint64_t millis;
        int pressure;
+       int orientation;
+       int major, minor;
 
        bool was_down; /* if distance == 0, false for pure hovering
                          touches */
@@ -254,6 +256,17 @@ struct tp_dispatch {
                int low;
        } pressure;
 
+       /* If touch size (either axis) goes above high -> touch down,
+          if touch size (either axis) goes below low -> touch up */
+       struct  {
+               bool use_touch_size;
+               struct rthreshold low;
+               struct rthreshold high;
+
+               /* convert device units to angle */
+               double orientation_to_angle;
+       } touch_size;
+
        struct device_coords hysteresis_margin;
 
        struct {
diff --git a/src/libinput-util.h b/src/libinput-util.h
index 3fe0a02..8aeebe9 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -509,4 +509,89 @@ strv_free(char **strv) {
        free (strv);
 }
 
+struct rthreshold {
+       int vals[4]; /* in device units */
+};
+
+/**
+ * Rotation thresholds are used for measuring the length of a
+ * distance at arbitrary rotation. Doing this properly on every
+ * input event is costly and not required. So instead we pre-calculate
+ * the thresholds.
+ *
+ * We divide the circle into 16 sections and calculate the
+ * conversion rates for vector to get to a physical length
+ * approximation for the each angle. The spokes are thus every 22.5
+ * degrees, but the we calculate for the center of each
+ * section, i.e. at 11.25 degrees, 33.75, etc.
+ *
+ * Rotational symmetry means we only need to calculate one quartant,
+ * the rest are mirror images of that.
+ *
+ * We will get from the hardware: some vector v (major or minor) and angle
+ * θ, i.e. polar coordinates in device units.
+ *
+ *         /|
+ *      v / | a
+ *       /  |
+ *     θ/___|
+ *        b
+ *
+ * where a = v * sin(θ)
+ *       b = v * cos(θ)
+ * and physical lengths are
+ *       a' = a/yres
+ *       b' = b/xres
+ * and thus physical size of v is:
+ *       len(v) = hypot(a', b')
+ *
+ * but we don't care about actual size, only about thresholds.
+ * So with the 4 sections per quadrant we just pre-calculate the
+ * hypothenuse for each thresholds and let the code compare
+ * against that.
+ */
+static inline struct rthreshold
+rthreshold_init(double mm, int xres, int yres)
+{
+       struct rthreshold thr;
+
+       static_assert(ARRAY_LENGTH(thr.vals) == 4, "Invalid array size");
+
+       /* Note that because we start with the threshold in mm, the
+        * calculation is effectively the reverse of the comment above */
+       for (int i = 0; i < 4; i++) {
+               double angle = (11.25 + i * 22.5);
+               size_t section = angle/22.5;
+               double rad = angle * M_PI/180.0;
+               double cosa = cos(rad),
+                      sina = sin(rad);
+               double au, bu; /* a, b in device units */
+               double cu;
+
+               assert(section < ARRAY_LENGTH(thr.vals));
+
+               /* convert to a/b in device units*/
+               au = mm * sina * yres;
+               bu = mm * cosa * xres;
+
+               /* convert c to device units */
+               cu = hypot(au, bu);
+
+               thr.vals[section] = cu;
+       }
+
+       return thr;
+}
+
+static inline int
+rthreshold_at_angle(const struct rthreshold *thr, int angle)
+{
+       const double section_angle = 90/ARRAY_LENGTH(thr->vals) + 1;
+       size_t idx = (abs(angle) % 90)/section_angle;
+
+       assert(idx < ARRAY_LENGTH(thr->vals));
+
+       return thr->vals[idx]; /* device units */
+}
+
 #endif /* LIBINPUT_UTIL_H */
diff --git a/test/test-misc.c b/test/test-misc.c
index 3f4b229..f2f4dad 100644
--- a/test/test-misc.c
+++ b/test/test-misc.c
@@ -1011,6 +1011,38 @@ START_TEST(time_conversion)
 }
 END_TEST
 
+START_TEST(rthreshold_helpers)
+{
+       struct rthreshold thr;
+       int xres = 100, yres = 100;
+       int threshold = 50;
+       int val;
+
+       thr = rthreshold_init(threshold, xres, yres);
+
+       /* for even resolutions, the threshold is always the same */
+       for (int angle = 0; angle < 360; angle++) {
+               val = rthreshold_at_angle(&thr, angle);
+
+               /* allow for a bit of rounding error */
+               ck_assert_int_ge(val, threshold * yres - 1);
+               ck_assert_int_le(val, threshold * yres);
+       }
+
+       /* Test some precalculated ones */
+       xres = 100, yres = 200;
+       thr = rthreshold_init(threshold, xres, yres);
+       val = rthreshold_at_angle(&thr, 0);
+       ck_assert_int_eq(val, 5277);
+       val = rthreshold_at_angle(&thr, 30);
+       ck_assert_int_eq(val, 6938);
+       val = rthreshold_at_angle(&thr, 60);
+       ck_assert_int_eq(val, 8766);
+       val = rthreshold_at_angle(&thr, 85);
+       ck_assert_int_eq(val, 9856);
+}
+END_TEST
+
 struct atoi_test {
        char *str;
        bool success;
@@ -1279,6 +1311,7 @@ litest_setup_tests_misc(void)
        litest_add_no_device("misc:parser", safe_atod_test);
        litest_add_no_device("misc:parser", strsplit_test);
        litest_add_no_device("misc:time", time_conversion);
+       litest_add_no_device("misc:thresholds", rthreshold_helpers);
 
        litest_add_no_device("misc:fd", fd_no_event_leak);
 
-- 
2.9.3

_______________________________________________
wayland-devel mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/wayland-devel

Reply via email to