mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-25 21:00:06 +01:00
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 #36 Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
0e2f1babc5
commit
1f5c0119e4
3 changed files with 164 additions and 2 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
17
src/evdev.c
17
src/evdev.c
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue