This patch removes a loose end in the filter mechanisms of wstpad; it's
related to the one that was fixed a few weeks ago.
In order to determine whether a touch is moving stably, the driver counts
how often its position updates match, roughly, the same direction. The
method works as intended, but it's an improvisation that looks a bit
hackish. Moreover, while it identifies stable movements, it does not
identify touches that are resting stably (yes, that can be a problem).
The "thumb detection" method can interfere with two-finger scrolling for
this reason.
The new version identifies stable states by means of timestamps; instead
of counting matches, it simply checks for how long the current state has
been valid. For recognizing resting touches properly, the direction
updates now require hysteresis-filtered input, and the callers are adapted
accordingly.
If you are masochistic and want to test this, take a laptop with an MT
touchpad and start upward scrolling with one finger in the bottom area,
and the other one in the main area of the touchpad; if you do that
repeatedly and somewhat sloppily, it may happen that you move the pointer.
With the patch, the detection should be much more reliable.
OK?
Index: dev/wscons/wstpad.c
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wstpad.c,v
retrieving revision 1.19
diff -u -p -r1.19 wstpad.c
--- dev/wscons/wstpad.c 10 Nov 2018 14:27:51 -0000 1.19
+++ dev/wscons/wstpad.c 5 Dec 2018 00:09:26 -0000
@@ -60,7 +60,8 @@
#define CLICKDELAY_MS 20
#define FREEZE_MS 100
-#define MATCHINTERVAL_MS 55
+#define MATCHINTERVAL_MS 45
+#define STOPINTERVAL_MS 55
enum tpad_handlers {
SOFTBUTTON_HDLR,
@@ -121,8 +122,8 @@ struct tpad_touch {
int x;
int y;
int dir;
- int matches;
- struct timespec stop;
+ struct timespec start;
+ struct timespec match;
struct {
int x;
int y;
@@ -226,6 +227,12 @@ struct wstpad {
} scroll;
};
+static const struct timespec match_interval =
+ { .tv_sec = 0, .tv_nsec = MATCHINTERVAL_MS * 1000000 };
+
+static const struct timespec stop_interval =
+ { .tv_sec = 0, .tv_nsec = STOPINTERVAL_MS * 1000000 };
+
/*
* Coordinates in the wstpad struct are "normalized" device coordinates,
* the orientation is left-to-right and upward.
@@ -254,16 +261,11 @@ normalize_rel(struct axis_filter *filter
* 7 | 4
* 6 | 5
*
- * Two direction values "match" each other if they are equal or adjacent in
- * this ring. Some handlers require that a movement is "stable" and check
- * the number of matches.
*/
/* Tangent constants in [*.12] fixed-point format: */
#define TAN_DEG_60 7094
#define TAN_DEG_30 2365
-#define STABLE 3
-
#define NORTH(d) ((d) == 0 || (d) == 11)
#define SOUTH(d) ((d) == 5 || (d) == 6)
#define EAST(d) ((d) == 2 || (d) == 3)
@@ -297,41 +299,59 @@ dircmp(int dir1, int dir2)
return (diff <= 6 ? diff : 12 - diff);
}
+/*
+ * Update direction and timespec attributes for a touch. They are used to
+ * determine whether it is moving - or resting - stably.
+ *
+ * The callers pass touches from the current frame and the touches that are
+ * no longer present in the update cycle to this function. Even though this
+ * ensures that pairs of zero deltas do not result from stale coordinates,
+ * zero deltas do not reset the state immediately. A short time span - the
+ * "stop interval" - must pass before the state is cleared, which is
+ * necessary because some touchpads report intermediate stops when a touch
+ * is moving very slowly.
+ */
void
wstpad_set_direction(struct wstpad *tp, struct tpad_touch *t, int dx, int dy)
{
int dir;
- static const struct timespec interval =
- { .tv_sec = 0, .tv_nsec = MATCHINTERVAL_MS * 1000000 };
+ struct timespec ts;
if (t->state != TOUCH_UPDATE) {
t->dir = -1;
- t->matches = 0;
- } else {
- dir = direction(dx, dy, tp->ratio);
- if (dir >= 0) {
- if (t->dir >= 0 && dircmp(t->dir, dir) <= 1)
- t->matches++;
- else
- t->matches = 1;
- t->dir = dir;
-
- timespecadd(&tp->time, &interval, &t->stop);
+ memcpy(&t->start, &tp->time, sizeof(struct timespec));
+ return;
+ }
- } else if (t->dir >= 0) {
- /*
- * Some touchpads report intermediate zero deltas
- * when a touch is moving very slowly. Keep the
- * the state long enough to avoid errors.
- */
- if (timespeccmp(&tp->time, &t->stop, >=)) {
- t->dir = -1;
- t->matches = 0;
- }
+ dir = direction(dx, dy, tp->ratio);
+ if (dir >= 0) {
+ if (t->dir < 0 || dircmp(dir, t->dir) > 1) {
+ memcpy(&t->start, &tp->time, sizeof(struct timespec));
+ }
+ t->dir = dir;
+ memcpy(&t->match, &tp->time, sizeof(struct timespec));
+ } else if (t->dir >= 0) {
+ timespecsub(&tp->time, &t->match, &ts);
+ if (timespeccmp(&ts, &stop_interval, >=)) {
+ t->dir = -1;
+ memcpy(&t->start, &t->match, sizeof(struct timespec));
}
}
}
+int
+wstpad_is_stable(struct wstpad *tp, struct tpad_touch *t)
+{
+ struct timespec ts;
+
+ if (t->dir >= 0)
+ timespecsub(&t->match, &t->start, &ts);
+ else
+ timespecsub(&tp->time, &t->start, &ts);
+
+ return (timespeccmp(&ts, &match_interval, >=));
+}
+
/*
* If a touch starts in an edge area, pointer movement will be
* suppressed as long as it stays in that area.
@@ -387,11 +407,12 @@ chk_scroll_state(struct wstpad *tp)
return (0);
}
/*
- * Try to exclude accidental scroll events by checking the matches.
- * The check, which causes a short delay, is only applied initially,
- * a touch that stops and resumes scrolling is not affected.
+ * Try to exclude accidental scroll events by checking whether the
+ * pointer-controlling touch is stable. The check, which may cause
+ * a short delay, is only applied initially, a touch that stops and
+ * resumes scrolling is not affected.
*/
- if (tp->t->matches < STABLE && !(tp->scroll.dz || tp->scroll.dw))
+ if (!wstpad_is_stable(tp, tp->t) && !(tp->scroll.dz || tp->scroll.dw))
return (0);
return (tp->dx || tp->dy);
@@ -460,15 +481,14 @@ wstpad_f2scroll(struct wsmouseinput *inp
return;
if ((dx > 0 && !EAST(dir)) || (dx < 0 && !WEST(dir)))
return;
- if (t2->matches < STABLE &&
+ if (!wstpad_is_stable(tp, t2) &&
!(tp->scroll.dz || tp->scroll.dw))
return;
centered |= CENTERED(t2);
}
if (centered) {
wstpad_scroll(tp, dx, dy, cmds);
- if (tp->t->matches > STABLE)
- set_freeze_ts(tp, 0, FREEZE_MS);
+ set_freeze_ts(tp, 0, FREEZE_MS);
}
}
}
@@ -956,6 +976,8 @@ wstpad_mt_inputs(struct wsmouseinput *in
dy = normalize_abs(&input->filter.v, mts->pos.y) - t->y;
t->y += dy;
t->flags &= (~EDGES | edge_flags(tp, t->x, t->y));
+ if (wsmouse_hysteresis(input, &mts->pos))
+ dx = dy = 0;
wstpad_set_direction(tp, t, dx, dy);
} else if ((1 << slot) & inactive) {
wstpad_set_direction(tp, t, 0, 0);
@@ -971,7 +993,7 @@ wstpad_mt_masks(struct wsmouseinput *inp
struct wstpad *tp = input->tp;
struct tpad_touch *t;
u_int mask;
- int d, slot;
+ int slot;
tp->ignore &= input->mt.touches;
@@ -999,15 +1021,16 @@ wstpad_mt_masks(struct wsmouseinput *inp
* touch is not, treat the latter as "thumb". It will not block
* pointer movement, and wstpad_f2scroll will ignore it.
*/
- if ((tp->dx || tp->dy) && (input->mt.ptr_mask & ~input->mt.ptr)) {
+ if (tp->t->dir >= 0
+ && wstpad_is_stable(tp, tp->t)
+ && (input->mt.ptr_mask & ~input->mt.ptr)) {
+
slot = ffs(input->mt.ptr_mask) - 1;
t = &tp->tpad_touches[slot];
- if (t->flags & B_EDGE) {
- d = tp->t->matches - t->matches;
- /* Do not hamper upward scrolling. */
- if (d > STABLE && (!NORTH(t->dir) || d > 2 * STABLE))
- tp->ignore = input->mt.ptr_mask;
- }
+
+ if ((t->flags & B_EDGE)
+ && t->dir < 0 && wstpad_is_stable(tp, t))
+ tp->ignore = input->mt.ptr_mask;
}
}
@@ -1018,9 +1041,17 @@ wstpad_touch_inputs(struct wsmouseinput
struct tpad_touch *t;
int slot;
- /* Use the unfiltered deltas. */
- tp->dx = normalize_rel(&input->filter.h, input->motion.pos.dx);
- tp->dy = normalize_rel(&input->filter.v, input->motion.pos.dy);
+ /*
+ * Use the normalized, hysteresis-filtered, but otherwise untransformed
+ * relative coordinates of the pointer-controlling touch for filtering
+ * and scrolling.
+ */
+ if (wsmouse_hysteresis(input, &input->motion.pos)) {
+ tp->dx = tp->dy = 0;
+ } else {
+ tp->dx = normalize_rel(&input->filter.h, input->motion.pos.dx);
+ tp->dy = normalize_rel(&input->filter.v, input->motion.pos.dy);
+ }
tp->btns = input->btn.buttons;
tp->btns_sync = input->btn.sync;