[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