[PATCH libinput 5/9] touchpad: add touch-size based touch handling

Peter Hutterer peter.hutterer at who-t.net
Wed Mar 29 04:59:04 UTC 2017


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 <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