Commit 1f5c0119 authored by Peter Hutterer's avatar Peter Hutterer

touchpad: add timestamp-based jump detection

On Dell i2c touchpads, the controller appears to go to sleep after about 1s of
inactivity on the touchpad. The wakeup takes a while so on the next touch, we
may see a pointer jump, specifially on the third event (i.e. touch down,
event, event+jump). The MSC_TIMESTAMP value carries a hint for what's
happening here, the event sequence for a touchpad with scanout intervals
7300µs is:

	...
	MSC_TIMESTAMP 0
	SYN_REPORT
	...
	MSC_TIMESTAMP 7300
	SYN_REPORT +2ms
	...
	MSC_TIMESTAMP 123456
	SYN_REPORT +7ms
	...
	MSC_TIMESTAMP 123456+7300
	SYN_REPORT +8ms

Note how the SYN_REPORT timestamps don't reflect the MSC_TIMESTAMPS.

This patch adds a quirk activate MSC_TIMESTAMP watching. When we do so, we
monitor for a 0 MSC_TIMESTAMP. Let's assume that the first event after that is
the interval, then check the third event. If that third event's timestamp is too
large rewrite the touches' motion history to reflect the correct timestamps,
i.e. instead of the SYN_REPORT timestamps the motion history now uses
"third-event SYN_REPORT timestamps minus MSC_TIMESTAMP values".

The pointer accel filter code uses absolute timestamps (#123) so we have to
restart the pointer acceleration filter when we detect this jump. This allows
us to reset the 0 time for the filter to the previous event's MSC_TIMESTAMP
time, so that our new large delta has the correct time delta too. This
calculates the acceleration correctly for that window.

The result is that the pointer is still delayed by the wake-up window (not
fixable in libinput) but at least it ends up where it should've.

There are a few side-effects: thumb, gesture, and hysteresis all still use the
unmodified SYN_REPORT time. There is a potential for false detection of either
of these now, but we'll have to fix those as they come up.

Fixes #36Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
parent 0e2f1bab
Pipeline #4041 passed with stages
in 28 minutes and 50 seconds
......@@ -673,6 +673,18 @@ tp_process_key(struct tp_dispatch *tp,
}
}
static void
tp_process_msc(struct tp_dispatch *tp,
const struct input_event *e,
uint64_t time)
{
if (e->code != MSC_TIMESTAMP)
return;
tp->quirks.msc_timestamp.now = e->value;
tp->queued |= TOUCHPAD_EVENT_TIMESTAMP;
}
static void
tp_unpin_finger(const struct tp_dispatch *tp, struct tp_touch *t)
{
......@@ -1549,11 +1561,132 @@ tp_detect_thumb_while_moving(struct tp_dispatch *tp)
second->thumb.state = THUMB_STATE_YES;
}
/**
* Rewrite the motion history so that previous points' timestamps are the
* current point's timestamp minus whatever MSC_TIMESTAMP gives us.
*
* This must be called before tp_motion_history_push()
*
* @param t The touch point
* @param jumping_interval The large time interval in µs
* @param normal_interval Normal hw interval in µs
* @param time Current time in µs
*/
static inline void
tp_motion_history_fix_last(struct tp_dispatch *tp,
struct tp_touch *t,
unsigned int jumping_interval,
unsigned int normal_interval,
uint64_t time)
{
if (t->state != TOUCH_UPDATE)
return;
/* We know the coordinates are correct because the touchpad should
* get that bit right. But the timestamps we got from the kernel are
* messed up, so we go back in the history and fix them.
*
* This way the next delta is huge but it's over a large time, so
* the pointer accel code should do the right thing.
*/
for (int i = 0; i < (int)t->history.count; i++) {
struct tp_history_point *p;
p = tp_motion_history_offset(t, i);
p->time = time - jumping_interval - normal_interval * i;
}
}
static void
tp_process_msc_timestamp(struct tp_dispatch *tp, uint64_t time)
{
struct msc_timestamp *m = &tp->quirks.msc_timestamp;
/* Pointer jump detection based on MSC_TIMESTAMP.
MSC_TIMESTAMP gets reset after a kernel timeout (1s) and on some
devices (Dell XPS) the i2c controller sleeps after a timeout. On
wakeup, some events are swallowed, triggering a cursor jump. The
event sequence after a sleep is always:
initial finger down:
ABS_X/Y x/y
MSC_TIMESTAMP 0
SYN_REPORT +2500ms
second event:
ABS_X/Y x+n/y+n # normal movement
MSC_TIMESTAMP 7300 # the hw interval
SYN_REPORT +2ms
third event:
ABS_X/Y x+lots/y+lots # pointer jump!
MSC_TIMESTAMP 123456 # well above the hw interval
SYN_REPORT +2ms
fourth event:
ABS_X/Y x+lots+n/y+lots+n # all normal again
MSC_TIMESTAMP 123456 + 7300
SYN_REPORT +8ms
Our approach is to detect the 0 timestamp, check the interval on
the next event and then calculate the movement for one fictious
event instead, swallowing all other movements. So if the time
delta is equivalent to 10 events and the movement is x, we
instead pretend there was movement of x/10.
*/
if (m->now == 0) {
m->state = JUMP_STATE_EXPECT_FIRST;
m->interval = 0;
return;
}
switch(m->state) {
case JUMP_STATE_EXPECT_FIRST:
if (m->now > ms2us(20)) {
m->state = JUMP_STATE_IGNORE;
} else {
m->state = JUMP_STATE_EXPECT_DELAY;
m->interval = m->now;
}
break;
case JUMP_STATE_EXPECT_DELAY:
if (m->now > m->interval * 2) {
uint32_t tdelta; /* µs */
struct tp_touch *t;
/* The current time is > 2 times the interval so we
* have a jump. Fix the motion history */
tdelta = m->now - m->interval;
tp_for_each_touch(tp, t) {
tp_motion_history_fix_last(tp,
t,
tdelta,
m->interval,
time);
}
m->state = JUMP_STATE_IGNORE;
/* We need to restart the acceleration filter to forget its history.
* The current point becomes the first point in the history there
* (including timestamp) and that accelerates correctly.
* This has a potential to be incorrect but since we only ever see
* those jumps over the first three events it doesn't matter.
*/
filter_restart(tp->device->pointer.filter, tp, time - tdelta);
}
break;
case JUMP_STATE_IGNORE:
break;
}
}
static void
tp_pre_process_state(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *t;
if (tp->queued & TOUCHPAD_EVENT_TIMESTAMP)
tp_process_msc_timestamp(tp, time);
tp_process_fake_touches(tp, time);
tp_unhover_touches(tp, time);
......@@ -1787,6 +1920,9 @@ tp_interface_process(struct evdev_dispatch *dispatch,
case EV_KEY:
tp_process_key(tp, e, time);
break;
case EV_MSC:
tp_process_msc(tp, e, time);
break;
case EV_SYN:
tp_handle_state(tp, time);
#if 0
......
......@@ -42,6 +42,7 @@ enum touchpad_event {
TOUCHPAD_EVENT_BUTTON_PRESS = (1 << 1),
TOUCHPAD_EVENT_BUTTON_RELEASE = (1 << 2),
TOUCHPAD_EVENT_OTHERAXIS = (1 << 3),
TOUCHPAD_EVENT_TIMESTAMP = (1 << 4),
};
enum touch_state {
......@@ -143,6 +144,12 @@ enum tp_thumb_state {
THUMB_STATE_MAYBE,
};
enum tp_jump_state {
JUMP_STATE_IGNORE = 0,
JUMP_STATE_EXPECT_FIRST,
JUMP_STATE_EXPECT_DELAY,
};
struct tp_touch {
struct tp_dispatch *tp;
unsigned int index;
......@@ -460,6 +467,12 @@ struct tp_dispatch {
* event with the jump.
*/
unsigned int nonmotion_event_count;
struct msc_timestamp {
enum tp_jump_state state;
uint32_t interval;
uint32_t now;
} msc_timestamp;
} quirks;
struct {
......
......@@ -1917,6 +1917,10 @@ evdev_drain_fd(int fd)
static inline void
evdev_pre_configure_model_quirks(struct evdev_device *device)
{
struct quirks_context *quirks;
struct quirks *q;
char *prop;
/* The Cyborg RAT has a mode button that cycles through event codes.
* On press, we get a release for the current mode and a press for the
* next mode:
......@@ -1990,8 +1994,17 @@ evdev_pre_configure_model_quirks(struct evdev_device *device)
EV_ABS,
ABS_MT_TOOL_TYPE);
/* We don't care about them and it can cause unnecessary wakeups */
libevdev_disable_event_code(device->evdev, EV_MSC, MSC_TIMESTAMP);
/* Generally we don't care about MSC_TIMESTAMP and it can cause
* unnecessary wakeups but on some devices we need to watch it for
* pointer jumps */
quirks = evdev_libinput_context(device)->quirks;
q = quirks_fetch_for_device(quirks, device->udev_device);
if (!q ||
!quirks_get_string(q, QUIRK_ATTR_MSC_TIMESTAMP, &prop) ||
!streq(prop, "watch")) {
libevdev_disable_event_code(device->evdev, EV_MSC, MSC_TIMESTAMP);
}
quirks_unref(q);
}
static void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment