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.hutte...@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 > _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/wayland-devel