mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-01-06 16:30:13 +01:00
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>
248 lines
6.2 KiB
C
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);
|
|
}
|