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
