[PATCH libinput 07/11] touchpad: Gesture support preparation
Hans de Goede
hdegoede at redhat.com
Wed Feb 18 04:26:50 PST 2015
Handle everything which is not handeld by the tap, (soft)button or edge-scroll
code/statemachines in a unified way. Everything is treated as a X-finger
gesture now, and the action to take on finger movement is decided by
the gesture.finger_mode setting. Pointer control now simply is seen as a
1 finger gesture, and 2fg scrolling as a 2fg gesture.
This removed the need for special casing things like switching back to
pointer mode when lifting a finger in 2fg scrolling mode, and also lays the
groundwork for adding 3+ fg gesture support.
Note that 1 test-case needs to be updated to wait for the finger mode
switching when switching mode while a gesture has already been started.
This is actually an improvement as this stops sending spurious pointer
motion events at the end of 2fg scrolling when not lifting both fingers at
exactly the same time.
Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
src/Makefile.am | 1 +
src/evdev-mt-touchpad-buttons.c | 1 -
src/evdev-mt-touchpad-edge-scroll.c | 1 -
src/evdev-mt-touchpad-gestures.c | 146 ++++++++++++++++++++++++++++++++++++
src/evdev-mt-touchpad.c | 129 ++++++-------------------------
src/evdev-mt-touchpad.h | 47 +++++++++---
test/litest.c | 6 ++
test/litest.h | 1 +
test/touchpad.c | 12 +++
9 files changed, 225 insertions(+), 119 deletions(-)
create mode 100644 src/evdev-mt-touchpad-gestures.c
diff --git a/src/Makefile.am b/src/Makefile.am
index b5eba73..ff65ff7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,7 @@ libinput_la_SOURCES = \
evdev-mt-touchpad-tap.c \
evdev-mt-touchpad-buttons.c \
evdev-mt-touchpad-edge-scroll.c \
+ evdev-mt-touchpad-gestures.c \
filter.c \
filter.h \
filter-private.h \
diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index 9dbb513..12f8023 100644
--- a/src/evdev-mt-touchpad-buttons.c
+++ b/src/evdev-mt-touchpad-buttons.c
@@ -156,7 +156,6 @@ tp_button_set_state(struct tp_dispatch *tp, struct tp_touch *t,
break;
case BUTTON_STATE_AREA:
t->button.curr = BUTTON_EVENT_IN_AREA;
- tp_set_pointer(tp, t);
break;
case BUTTON_STATE_BOTTOM:
t->button.curr = event;
diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c
index df181d5..28f29c2 100644
--- a/src/evdev-mt-touchpad-edge-scroll.c
+++ b/src/evdev-mt-touchpad-edge-scroll.c
@@ -86,7 +86,6 @@ tp_edge_scroll_set_state(struct tp_dispatch *tp,
break;
case EDGE_SCROLL_TOUCH_STATE_AREA:
t->scroll.edge = EDGE_NONE;
- tp_set_pointer(tp, t);
break;
}
}
diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c
new file mode 100644
index 0000000..28ac74b
--- /dev/null
+++ b/src/evdev-mt-touchpad-gestures.c
@@ -0,0 +1,146 @@
+/*
+ * 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 */
+
+void
+tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
+{
+ if (tp->gesture.started)
+ return;
+
+ switch (tp->gesture.finger_mode) {
+ case 2:
+ /* NOP */
+ break;
+ }
+ tp->gesture.started = true;
+}
+
+void
+tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
+{
+ if (tp->gesture.finger_mode == 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_mode = 1;
+ tp->gesture.finger_mode_pending = 0;
+ }
+
+ /* Don't send events when we're unsure in which mode we are */
+ if (tp->gesture.finger_mode_pending)
+ return;
+
+ switch (tp->gesture.finger_mode) {
+ case 1:
+ tp_gesture_post_pointer_motion(tp, time);
+ break;
+ case 2:
+ tp_gesture_post_twofinger_scroll(tp, time);
+ break;
+ }
+}
+
+void
+tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
+{
+ if (!tp->gesture.started)
+ return;
+
+ switch (tp->gesture.finger_mode) {
+ case 2:
+ tp_gesture_stop_twofinger_scroll(tp, time);
+ break;
+ }
+ tp->gesture.started = false;
+}
+
+static void
+tp_gesture_finger_mode_switch_timeout(uint64_t now, void *data)
+{
+ struct tp_dispatch *tp = data;
+
+ if (!tp->gesture.finger_mode_pending)
+ return;
+
+ tp_gesture_stop(tp, now); /* End current gesture */
+ tp->gesture.finger_mode = tp->gesture.finger_mode_pending;
+ tp->gesture.finger_mode_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_mode) {
+ /* If all fingers are lifted immediately end the gesture */
+ if (active_touches == 0) {
+ tp_gesture_stop(tp, time);
+ tp->gesture.finger_mode = 0;
+ tp->gesture.finger_mode_pending = 0;
+ /* Immediately switch to new mode to avoid initial latency */
+ } else if (!tp->gesture.started) {
+ tp->gesture.finger_mode = active_touches;
+ tp->gesture.finger_mode_pending = 0;
+ /* Else debounce finger changes */
+ } else if (active_touches != tp->gesture.finger_mode_pending) {
+ tp->gesture.finger_mode_pending = active_touches;
+ libinput_timer_set(&tp->gesture.finger_mode_switch_timer,
+ time + DEFAULT_GESTURE_SWITCH_TIMEOUT);
+ }
+ } else {
+ tp->gesture.finger_mode_pending = 0;
+ }
+}
+
+int
+tp_init_gesture(struct tp_dispatch *tp)
+{
+ libinput_timer_init(&tp->gesture.finger_mode_switch_timer,
+ tp->device->base.seat->libinput,
+ tp_gesture_finger_mode_switch_timeout, tp);
+ return 0;
+}
+
+void
+tp_remove_gesture(struct tp_dispatch *tp)
+{
+ libinput_timer_cancel(&tp->gesture.finger_mode_switch_timer);
+}
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index c6c48d3..947678a 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -227,7 +227,6 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
}
t->dirty = true;
- t->is_pointer = false;
t->palm.is_palm = false;
t->state = TOUCH_END;
t->pinned.is_pinned = false;
@@ -424,7 +423,6 @@ tp_unpin_finger(struct tp_dispatch *tp, struct tp_touch *t)
if (xdist * xdist + ydist * ydist >=
tp->buttons.motion_dist * tp->buttons.motion_dist) {
t->pinned.is_pinned = false;
- tp_set_pointer(tp, t);
return;
}
@@ -439,14 +437,13 @@ tp_pin_fingers(struct tp_dispatch *tp)
struct tp_touch *t;
tp_for_each_touch(tp, t) {
- t->is_pointer = false;
t->pinned.is_pinned = true;
t->pinned.center_x = t->x;
t->pinned.center_y = t->y;
}
}
-static int
+int
tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
{
return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) &&
@@ -456,21 +453,6 @@ tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
tp_edge_scroll_touch_active(tp, t);
}
-void
-tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t)
-{
- struct tp_touch *tmp = NULL;
-
- /* Only set the touch as pointer if we don't have one yet */
- tp_for_each_touch(tp, tmp) {
- if (tmp->is_pointer)
- return;
- }
-
- if (tp_touch_active(tp, t))
- t->is_pointer = true;
-}
-
static void
tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
{
@@ -487,7 +469,6 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
int dirs = vector_get_direction(t->x - t->palm.x, t->y - t->palm.y);
if ((dirs & DIRECTIONS) && !(dirs & ~DIRECTIONS)) {
t->palm.is_palm = false;
- tp_set_pointer(tp, t);
}
}
return;
@@ -530,8 +511,6 @@ tp_get_average_touches_delta(struct tp_dispatch *tp, double *dx, double *dy)
*dx += tmpx;
*dy += tmpy;
}
- /* Stop spurious MOTION events at the end of scrolling */
- t->is_pointer = false;
}
if (nchanged == 0)
@@ -541,80 +520,30 @@ tp_get_average_touches_delta(struct tp_dispatch *tp, double *dx, double *dy)
*dy /= nchanged;
}
-static void
-tp_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
+void
+tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
{
double dx = 0, dy =0;
tp_get_average_touches_delta(tp, &dx, &dy);
tp_filter_motion(tp, &dx, &dy, NULL, NULL, time);
+ if (dx == 0.0 && dy == 0.0)
+ return;
+
+ tp_gesture_start(tp, time);
evdev_post_scroll(tp->device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
dx, dy);
- tp->scroll.twofinger_state = TWOFINGER_SCROLL_STATE_ACTIVE;
}
-static void
-tp_twofinger_stop_scroll(struct tp_dispatch *tp, uint64_t time)
+void
+tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
{
- struct tp_touch *t, *ptr = NULL;
- int nfingers_down = 0;
-
evdev_stop_scroll(tp->device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
-
- /* If we were scrolling and now there's exactly 1 active finger,
- switch back to pointer movement */
- if (tp->scroll.twofinger_state == TWOFINGER_SCROLL_STATE_ACTIVE) {
- tp_for_each_touch(tp, t) {
- if (tp_touch_active(tp, t)) {
- nfingers_down++;
- if (ptr == NULL)
- ptr = t;
- }
- }
-
- if (nfingers_down == 1)
- tp_set_pointer(tp, ptr);
- }
-
- tp->scroll.twofinger_state = TWOFINGER_SCROLL_STATE_NONE;
-}
-
-static int
-tp_twofinger_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
-{
- struct tp_touch *t;
- int nfingers_down = 0;
-
- if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
- return 0;
-
- /* No 2fg scrolling during tap-n-drag */
- if (tp_tap_dragging(tp))
- return 0;
-
- /* No 2fg scrolling while a clickpad is clicked */
- if (tp->buttons.is_clickpad && tp->buttons.state)
- return 0;
-
- /* Only count active touches for 2 finger scrolling */
- tp_for_each_touch(tp, t) {
- if (tp_touch_active(tp, t))
- nfingers_down++;
- }
-
- if (nfingers_down == 2) {
- tp_post_twofinger_scroll(tp, time);
- return 1;
- }
-
- tp_twofinger_stop_scroll(tp, time);
-
- return 0;
}
static void
@@ -718,6 +647,8 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
if ((tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) &&
tp->buttons.is_clickpad)
tp_pin_fingers(tp);
+
+ tp_gesture_handle_state(tp, time);
}
static void
@@ -749,24 +680,6 @@ tp_post_process_state(struct tp_dispatch *tp, uint64_t time)
}
static void
-tp_get_pointer_delta(struct tp_dispatch *tp, double *dx, double *dy)
-{
- struct tp_touch *t = tp_current_touch(tp);
-
- if (!t->is_pointer) {
- tp_for_each_touch(tp, t) {
- if (t->is_pointer)
- break;
- }
- }
-
- if (!t->is_pointer || !t->dirty)
- return;
-
- tp_get_delta(t, dx, dy);
-}
-
-static void
tp_get_combined_touches_delta(struct tp_dispatch *tp, double *dx, double *dy)
{
struct tp_touch *t;
@@ -785,8 +698,8 @@ tp_get_combined_touches_delta(struct tp_dispatch *tp, double *dx, double *dy)
}
}
-static void
-tp_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
+void
+tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
double dx = 0.0, dy = 0.0;
double dx_unaccel, dy_unaccel;
@@ -795,7 +708,7 @@ tp_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
if (tp->buttons.is_clickpad && tp->buttons.state)
tp_get_combined_touches_delta(tp, &dx, &dy);
else
- tp_get_pointer_delta(tp, &dx, &dy);
+ tp_get_average_touches_delta(tp, &dx, &dy);
tp_filter_motion(tp, &dx, &dy, &dx_unaccel, &dy_unaccel, time);
@@ -821,16 +734,14 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time)
if (filter_motion || tp->sendevents.trackpoint_active) {
tp_edge_scroll_stop_events(tp, time);
- tp_twofinger_stop_scroll(tp, time);
+ tp_gesture_stop(tp, time);
return;
}
if (tp_edge_scroll_post_events(tp, time) != 0)
return;
- if (tp_twofinger_scroll_post_events(tp, time) != 0)
- return;
- tp_post_pointer_motion(tp, time);
+ tp_gesture_post_events(tp, time);
}
static void
@@ -887,6 +798,7 @@ tp_remove(struct evdev_dispatch *dispatch)
tp_remove_buttons(tp);
tp_remove_sendevents(tp);
tp_remove_edge_scroll(tp);
+ tp_remove_gesture(tp);
}
static void
@@ -980,7 +892,7 @@ tp_trackpoint_event(uint64_t time, struct libinput_event *event, void *data)
if (!tp->sendevents.trackpoint_active) {
tp_edge_scroll_stop_events(tp, time);
- tp_twofinger_stop_scroll(tp, time);
+ tp_gesture_stop(tp, time);
tp_tap_suspend(tp, time);
tp->sendevents.trackpoint_active = true;
}
@@ -1221,7 +1133,7 @@ tp_scroll_config_scroll_method_set_method(struct libinput_device *device,
return LIBINPUT_CONFIG_STATUS_SUCCESS;
tp_edge_scroll_stop_events(tp, time);
- tp_twofinger_stop_scroll(tp, time);
+ tp_gesture_stop_twofinger_scroll(tp, time);
tp->scroll.method = method;
@@ -1359,6 +1271,9 @@ tp_init(struct tp_dispatch *tp,
if (tp_init_scroll(tp, device) != 0)
return -1;
+ if (tp_init_gesture(tp) != 0)
+ return -1;
+
device->seat_caps |= EVDEV_DEVICE_POINTER;
return 0;
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 3479e5e..4d8e875 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -118,11 +118,6 @@ enum tp_edge_scroll_touch_state {
EDGE_SCROLL_TOUCH_STATE_AREA,
};
-enum tp_twofinger_scroll_state {
- TWOFINGER_SCROLL_STATE_NONE,
- TWOFINGER_SCROLL_STATE_ACTIVE,
-};
-
struct tp_motion {
int32_t x;
int32_t y;
@@ -133,7 +128,6 @@ struct tp_touch {
enum touch_state state;
bool has_ended; /* TRACKING_ID == -1 */
bool dirty;
- bool is_pointer; /* the pointer-controlling touch */
int32_t x;
int32_t y;
uint64_t millis;
@@ -217,6 +211,13 @@ struct tp_dispatch {
} accel;
struct {
+ bool started;
+ unsigned int finger_mode;
+ unsigned int finger_mode_pending;
+ struct libinput_timer finger_mode_switch_timer;
+ } gesture;
+
+ struct {
bool is_clickpad; /* true for clickpads */
bool has_topbuttons;
bool use_clickfinger; /* number of fingers decides button number */
@@ -253,7 +254,6 @@ struct tp_dispatch {
enum libinput_config_scroll_method method;
int32_t right_edge;
int32_t bottom_edge;
- enum tp_twofinger_scroll_state twofinger_state;
} scroll;
enum touchpad_event queued;
@@ -288,15 +288,15 @@ void
tp_get_delta(struct tp_touch *t, double *dx, double *dy);
void
-tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t);
-
-void
tp_filter_motion(struct tp_dispatch *tp,
double *dx, double *dy,
double *dx_unaccel, double *dy_unaccel,
uint64_t time);
int
+tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
+
+int
tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time);
int
@@ -368,4 +368,31 @@ tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time);
int
tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
+int
+tp_init_gesture(struct tp_dispatch *tp);
+
+void
+tp_remove_gesture(struct tp_dispatch *tp);
+
+void
+tp_gesture_start(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_stop(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time);
+
#endif
diff --git a/test/litest.c b/test/litest.c
index b938cce..cf12424 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -1385,6 +1385,12 @@ litest_timeout_buttonscroll(void)
}
void
+litest_timeout_finger_switch(void)
+{
+ msleep(120);
+}
+
+void
litest_push_event_frame(struct litest_device *dev)
{
assert(!dev->skip_ev_syn);
diff --git a/test/litest.h b/test/litest.h
index 60ec458..d78bf0e 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -180,6 +180,7 @@ struct libevdev_uinput * litest_create_uinput_abs_device(const char *name,
void litest_timeout_tap(void);
void litest_timeout_softbuttons(void);
void litest_timeout_buttonscroll(void);
+void litest_timeout_finger_switch(void);
void litest_push_event_frame(struct litest_device *dev);
void litest_pop_event_frame(struct litest_device *dev);
diff --git a/test/touchpad.c b/test/touchpad.c
index 9c34b50..3627352 100644
--- a/test/touchpad.c
+++ b/test/touchpad.c
@@ -1871,9 +1871,15 @@ START_TEST(touchpad_2fg_scroll_return_to_motion)
/* 2fg scroll */
litest_touch_down(dev, 1, 53, 50);
+ libinput_dispatch(li);
+ litest_timeout_finger_switch();
+ libinput_dispatch(li);
litest_touch_move_to(dev, 0, 47, 50, 47, 70, 5, 0);
litest_touch_move_to(dev, 1, 53, 50, 53, 70, 5, 0);
litest_touch_up(dev, 1);
+ libinput_dispatch(li);
+ litest_timeout_finger_switch();
+ libinput_dispatch(li);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
litest_touch_move_to(dev, 0, 47, 70, 47, 50, 10, 0);
@@ -1881,9 +1887,15 @@ START_TEST(touchpad_2fg_scroll_return_to_motion)
/* back to 2fg scroll, lifting the other finger */
litest_touch_down(dev, 1, 50, 50);
+ libinput_dispatch(li);
+ litest_timeout_finger_switch();
+ libinput_dispatch(li);
litest_touch_move_to(dev, 0, 47, 50, 47, 70, 5, 0);
litest_touch_move_to(dev, 1, 53, 50, 53, 70, 5, 0);
litest_touch_up(dev, 0);
+ libinput_dispatch(li);
+ litest_timeout_finger_switch();
+ libinput_dispatch(li);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
/* move with second finger */
--
2.1.0
More information about the wayland-devel
mailing list