[PATCH libinput 6/7] touchpad: detect coordinate jumps on touchpads

Peter Hutterer peter.hutterer at who-t.net
Wed Sep 17 21:20:22 PDT 2014


Such jumps are usually consequences of the touchpad firmware failing to
notice that a different finger is in fact touching the touchpad.
This happens frequently when a finger is lifted at the same time as a
second finger is set down. Instead of a ABS_MT_TRACKING_ID  of -1
followed by a new touchpoing we get a large movement from the previous
position to the next position. This leads to a cursor jump on the
screen.

This patch adds detection for these jumps. Since the new finger is a new
touchpoint, all the usual rules for a new touch point should apply
(softbutton detection, tapping, etc.).

Thus, if we find one or more touchpoints to have jumped, we
mark the touchpoint as jumped and end it. Then we process the current
state, releasing all buttons where appropriate.
Then we re-process the state, marking all jumped and ended touchpoints
as TOUCH_BEGIN (and pressing buttons where appropriate).

The jump threshold is 20mm on devices with resolution, or a
quarter of the diagonal for devices without a resolution.

https://bugs.freedesktop.org/show_bug.cgi?id=76722

Based on a patch by Alexander E. Patrakov <patrakov at gmail.com>
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 src/evdev-mt-touchpad-buttons.c |  17 +++++-
 src/evdev-mt-touchpad.c         | 123 ++++++++++++++++++++++++++++++++++++++--
 src/evdev-mt-touchpad.h         |   7 ++-
 3 files changed, 138 insertions(+), 9 deletions(-)

diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index b0a89ae..11b309b 100644
--- a/src/evdev-mt-touchpad-buttons.c
+++ b/src/evdev-mt-touchpad-buttons.c
@@ -416,7 +416,9 @@ tp_button_handle_event(struct tp_dispatch *tp,
 }
 
 int
-tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
+tp_button_handle_state(struct tp_dispatch *tp,
+		       uint64_t time,
+		       bool ignore_button_press)
 {
 	struct tp_touch *t;
 
@@ -440,9 +442,11 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
 			else
 				tp_button_handle_event(tp, t, BUTTON_EVENT_IN_AREA, time);
 		}
+
 		if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE)
 			tp_button_handle_event(tp, t, BUTTON_EVENT_RELEASE, time);
-		if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
+		if (!ignore_button_press &&
+		    tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
 			tp_button_handle_event(tp, t, BUTTON_EVENT_PRESS, time);
 	}
 
@@ -662,6 +666,14 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
 
 		if ((current & 0x1) ^ (old & 0x1)) {
 			bool skip = false;
+
+			/* we may have button state changes but only process
+			   them if the matching tp->queued mask is set.
+
+			   If a touch jump happens, the various post functions
+			   are called separately with the queued mask manually
+			   adjusted and we post release events before
+			   press events */
 			if (!!(current & 0x1) &&
 			    tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
 				state = LIBINPUT_BUTTON_STATE_PRESSED;
@@ -682,7 +694,6 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
 					tp->buttons.old_state &= ~mask;
 			}
 		}
-
 		button++;
 		current >>= 1;
 		old >>= 1;
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index f9efc5f..426c22b 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -160,6 +160,67 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 	tp->queued |= TOUCHPAD_EVENT_MOTION;
 }
 
+static inline void
+tp_disrupt_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
+{
+	if (t->state == TOUCH_NONE)
+		return;
+
+	tp_end_touch(tp, t, time);
+	t->has_jumped = true;
+}
+
+static inline void
+tp_undisrupt_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
+{
+	if (!t->has_jumped)
+		return;
+
+	t->has_jumped = false;
+	tp_begin_touch(tp, t, time);
+}
+
+static inline int
+tp_detect_jumps(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
+{
+	struct tp_motion *oldpos;
+	double dx, dy;
+
+	/* Detect cursor jumps. The sampling rate of touchpads limits what
+	 * we can detect, if one finger leaves touch and another sets down
+	 * quickly enough, the firmware may not notice the touch up/down
+	 * event but assume the finger moved.
+	 * See https://bugs.freedesktop.org/show_bug.cgi?id=76722
+	 *
+	 * We detect the jump here, mark the touch as jumped so we can end
+	 * it and then continue with the current touch as new touch.
+	 *
+	 * This is only done for MT touchpads, on ST touchpads two fingers
+	 * are problematic anyway.
+	 */
+	if (!tp->has_mt ||
+	    t->history.count == 0)
+		return 0;
+
+	/* This is called before tp_motion_history_push(),
+	   so the latest historical datum is at offset 0. */
+	oldpos = tp_motion_history_offset(t, 0);
+	dx = abs(t->x - oldpos->x);
+	dy = abs(t->y - oldpos->y);
+
+	if (tp->device->abs.absinfo_x->resolution > 1 &&
+	    tp->device->abs.absinfo_y->resolution > 1) {
+		dx /= tp->device->abs.absinfo_x->resolution;
+		dy /= tp->device->abs.absinfo_y->resolution;
+	}
+
+	if (dx * dx + dy * dy > tp->jump_threshold * tp->jump_threshold) {
+		tp_disrupt_touch(tp, t, time);
+		return 1;
+	}
+	return 0;
+}
+
 static double
 tp_estimate_delta(int x0, int x1, int x2, int x3)
 {
@@ -397,12 +458,13 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 	t->palm.y = t->y;
 }
 
-static void
+static int
 tp_process_state(struct tp_dispatch *tp, uint64_t time)
 {
 	struct tp_touch *t;
 	struct tp_touch *first = tp_get_touch(tp, 0);
 	unsigned int i;
+	bool touch_jumps = false;
 
 	for (i = 0; i < tp->ntouches; i++) {
 		t = tp_get_touch(tp, i);
@@ -418,18 +480,29 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
 				t->dirty = first->dirty;
 		}
 
+		tp_undisrupt_touch(tp, t, time);
 		if (!t->dirty)
 			continue;
 
 		tp_palm_detect(tp, t, time);
 
-		tp_motion_hysteresis(tp, t);
-		tp_motion_history_push(t);
+		if (tp_detect_jumps(tp, t, time)) {
+			touch_jumps = true;
+		} else {
+			tp_motion_hysteresis(tp, t);
+			tp_motion_history_push(t);
+		}
 
 		tp_unpin_finger(tp, t);
 	}
 
-	tp_button_handle_state(tp, time);
+	/* If we have a touch jump we ignore button presses.
+	 * We get called again, this time without a cursor jump
+	 * and continue normal then. */
+	tp_button_handle_state(tp, time, touch_jumps);
+
+	if (touch_jumps)
+		return 1;
 
 	/*
 	 * We have a physical button down event on a clickpad. To avoid
@@ -440,6 +513,8 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
 	if ((tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) &&
 	    tp->buttons.is_clickpad)
 		tp_pin_fingers(tp);
+
+	return 0;
 }
 
 static void
@@ -562,7 +637,27 @@ static void
 tp_handle_state(struct tp_dispatch *tp,
 		uint64_t time)
 {
-	tp_process_state(tp, time);
+	if (tp_process_state(tp, time)) {
+		/* some touches jumped position and were marked as
+		   TOUCH_END. Post events in the current state to
+		   release anything down, then re-process the
+		   current state which will move the touches to
+		   TOUCH_BEGIN */
+		enum touchpad_event queued = tp->queued;
+
+		/* ditch press events for now */
+		tp->queued &= ~TOUCHPAD_EVENT_BUTTON_PRESS;
+		tp_post_events(tp, time);
+		tp_post_process_state(tp, time);
+
+		/* ditch release events, re-instate press */
+		tp->queued &= ~TOUCHPAD_EVENT_BUTTON_RELEASE;
+		if (queued & TOUCHPAD_EVENT_BUTTON_PRESS)
+			tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS;
+
+		/* process again */
+		tp_process_state(tp, time);
+	}
 	tp_post_events(tp, time);
 	tp_post_process_state(tp, time);
 }
@@ -880,6 +975,22 @@ tp_init_palmdetect(struct tp_dispatch *tp,
 	return 0;
 }
 
+static void
+tp_init_jump_threshold(struct tp_dispatch *tp,
+		       struct evdev_device *device,
+		       double diagonal)
+{
+	int res_x, res_y;
+
+	res_x = device->abs.absinfo_x->resolution;
+	res_y = device->abs.absinfo_y->resolution;
+
+	if (res_x > 1 && res_y > 1)
+		tp->jump_threshold = 20; /* mm */
+	else
+		tp->jump_threshold = diagonal / 4;
+}
+
 static int
 tp_init(struct tp_dispatch *tp,
 	struct evdev_device *device)
@@ -904,6 +1015,8 @@ tp_init(struct tp_dispatch *tp,
 	tp->hysteresis.margin_y =
 		diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
 
+	tp_init_jump_threshold(tp, device, diagonal);
+
 	if (tp_init_accel(tp, diagonal) != 0)
 		return -1;
 
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 3d3932b..92132c9 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -104,6 +104,7 @@ struct tp_touch {
 	enum touch_state state;
 	bool dirty;
 	bool is_pointer;			/* the pointer-controlling touch */
+	bool has_jumped;
 	int32_t x;
 	int32_t y;
 	uint64_t millis;
@@ -167,6 +168,8 @@ struct tp_dispatch {
 		int32_t margin_y;
 	} hysteresis;
 
+	int32_t jump_threshold;
+
 	struct motion_filter *filter;
 
 	struct {
@@ -263,7 +266,9 @@ int
 tp_post_button_events(struct tp_dispatch *tp, uint64_t time);
 
 int
-tp_button_handle_state(struct tp_dispatch *tp, uint64_t time);
+tp_button_handle_state(struct tp_dispatch *tp,
+		       uint64_t time,
+		       bool ignore_button_press);
 
 int
 tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
-- 
1.9.3



More information about the wayland-devel mailing list