libinput/src/evdev-mt-touchpad-gestures.c
Hans de Goede 07b20dcce6 touchpad: Only use slot 0 deltas for 2fg scrolling on semi-mt touchpads
Some semi-mt model touchpads have a better accuracy for slot 0 then for
slot 1 (they only have 2), so on semi-mt models only use the movement of
the touch in slot 0 for 2fg scrolling, rather then the average movement of
the 2 touches.

This fixes 2fg scrolling being choppy / jumpy in some cases.

BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=89683
Signed-off-by: Hans de Goede <hdegoede@redhat.com>

Also needed: change the touchpad scroll source test to use more separate
events. The current litest semi-mt touch implementation only moves the first
touch on every second move. With 5 movements this isn't enough to fill the
motion history and generate events, so double it to 10 so we're guaranteed to
get scroll events.

Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2015-04-27 11:08:52 +10:00

248 lines
6.2 KiB
C

/*
* Copyright © 2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the copyright holders not be used in
* advertising or publicity pertaining to distribution of the software
* without specific, written prior permission. The copyright holders make
* no representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <limits.h>
#include "evdev-mt-touchpad.h"
#define DEFAULT_GESTURE_SWITCH_TIMEOUT 100 /* ms */
static struct normalized_coords
tp_get_touches_delta(struct tp_dispatch *tp, bool average)
{
struct tp_touch *t;
unsigned int i, nchanged = 0;
struct normalized_coords normalized;
struct normalized_coords delta = {0.0, 0.0};
for (i = 0; i < tp->num_slots; i++) {
t = &tp->touches[i];
if (tp_touch_active(tp, t) && t->dirty) {
nchanged++;
normalized = tp_get_delta(t);
delta.x += normalized.x;
delta.y += normalized.y;
}
}
if (!average || nchanged == 0)
return delta;
delta.x /= nchanged;
delta.y /= nchanged;
return delta;
}
static inline struct normalized_coords
tp_get_combined_touches_delta(struct tp_dispatch *tp)
{
return tp_get_touches_delta(tp, false);
}
static inline struct normalized_coords
tp_get_average_touches_delta(struct tp_dispatch *tp)
{
return tp_get_touches_delta(tp, true);
}
static void
tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
{
if (tp->gesture.started)
return;
switch (tp->gesture.finger_count) {
case 2:
/* NOP */
break;
}
tp->gesture.started = true;
}
static void
tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
struct normalized_coords delta, unaccel;
/* When a clickpad is clicked, combine motion of all active touches */
if (tp->buttons.is_clickpad && tp->buttons.state)
unaccel = tp_get_combined_touches_delta(tp);
else
unaccel = tp_get_average_touches_delta(tp);
delta = tp_filter_motion(tp, &unaccel, time);
if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) {
pointer_notify_motion(&tp->device->base, time,
&delta, &unaccel);
}
}
static void
tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
{
struct normalized_coords delta;
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
return;
/* On some semi-mt models slot 0 is more accurate, so for semi-mt
* we only use slot 0. */
if (tp->semi_mt) {
if (!tp->touches[0].dirty)
return;
delta = tp_get_delta(&tp->touches[0]);
} else {
delta = tp_get_average_touches_delta(tp);
}
delta = tp_filter_motion(tp, &delta, time);
if (normalized_is_zero(delta))
return;
tp_gesture_start(tp, time);
evdev_post_scroll(tp->device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
&delta);
}
void
tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
{
if (tp->gesture.finger_count == 0)
return;
/* When tap-and-dragging, or a clickpad is clicked force 1fg mode */
if (tp_tap_dragging(tp) || (tp->buttons.is_clickpad && tp->buttons.state)) {
tp_gesture_stop(tp, time);
tp->gesture.finger_count = 1;
tp->gesture.finger_count_pending = 0;
}
/* Don't send events when we're unsure in which mode we are */
if (tp->gesture.finger_count_pending)
return;
switch (tp->gesture.finger_count) {
case 1:
tp_gesture_post_pointer_motion(tp, time);
break;
case 2:
tp_gesture_post_twofinger_scroll(tp, time);
break;
}
}
void
tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
{
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
return;
evdev_stop_scroll(tp->device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
}
void
tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
{
if (!tp->gesture.started)
return;
switch (tp->gesture.finger_count) {
case 2:
tp_gesture_stop_twofinger_scroll(tp, time);
break;
}
tp->gesture.started = false;
}
static void
tp_gesture_finger_count_switch_timeout(uint64_t now, void *data)
{
struct tp_dispatch *tp = data;
if (!tp->gesture.finger_count_pending)
return;
tp_gesture_stop(tp, now); /* End current gesture */
tp->gesture.finger_count = tp->gesture.finger_count_pending;
tp->gesture.finger_count_pending = 0;
}
void
tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
{
unsigned int active_touches = 0;
struct tp_touch *t;
tp_for_each_touch(tp, t)
if (tp_touch_active(tp, t))
active_touches++;
if (active_touches != tp->gesture.finger_count) {
/* If all fingers are lifted immediately end the gesture */
if (active_touches == 0) {
tp_gesture_stop(tp, time);
tp->gesture.finger_count = 0;
tp->gesture.finger_count_pending = 0;
/* Immediately switch to new mode to avoid initial latency */
} else if (!tp->gesture.started) {
tp->gesture.finger_count = active_touches;
tp->gesture.finger_count_pending = 0;
/* Else debounce finger changes */
} else if (active_touches != tp->gesture.finger_count_pending) {
tp->gesture.finger_count_pending = active_touches;
libinput_timer_set(&tp->gesture.finger_count_switch_timer,
time + DEFAULT_GESTURE_SWITCH_TIMEOUT);
}
} else {
tp->gesture.finger_count_pending = 0;
}
}
int
tp_init_gesture(struct tp_dispatch *tp)
{
libinput_timer_init(&tp->gesture.finger_count_switch_timer,
tp->device->base.seat->libinput,
tp_gesture_finger_count_switch_timeout, tp);
return 0;
}
void
tp_remove_gesture(struct tp_dispatch *tp)
{
libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
}