[PATCH libinput 5/9] touchpad: add touch-size based touch handling
Peter Hutterer
peter.hutterer at who-t.net
Fri Mar 31 05:43:48 UTC 2017
On Wed, Mar 29, 2017 at 02:59:04PM +1000, Peter Hutterer wrote:
> 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.
After some talking about this with benjamin: major/minor is largely useless
on anything but the Apples and even there we need hwdb-specific entries
(which this patch doesn't have). Right now, this series is only useful on
the magic trackpad, the only device it's been tested on.
Cheers,
Peter
>
> Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
> ---
> 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
>
More information about the wayland-devel
mailing list