[PATCH libinput 2/2] touchpad: Implement pinch gesture support
Hans de Goede
hdegoede at redhat.com
Mon Mar 16 03:22:43 PDT 2015
Implement touchpad pinch (and rotate) gesture support.
Note that two two-finger scrolling tests are slightly tweaked to assure that
there is enough touch movement to allow the scroll-or-pinch detect code to do
its works.
Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
src/evdev-mt-touchpad-gestures.c | 280 ++++++++++++++++++++++++++++++++++++++-
src/evdev-mt-touchpad.h | 18 +++
test/touchpad.c | 4 +-
3 files changed, 294 insertions(+), 8 deletions(-)
diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c
index 9948098..ceb24be 100644
--- a/src/evdev-mt-touchpad-gestures.c
+++ b/src/evdev-mt-touchpad-gestures.c
@@ -22,7 +22,6 @@
#include "config.h"
-#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <limits.h>
@@ -30,6 +29,7 @@
#include "evdev-mt-touchpad.h"
#define DEFAULT_GESTURE_SWITCH_TIMEOUT 100 /* ms */
+#define DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT 1000 /* ms */
static void
tp_get_touches_delta(struct tp_dispatch *tp, double *dx, double *dy, bool average)
@@ -75,12 +75,28 @@ tp_get_average_touches_delta(struct tp_dispatch *tp, double *dx, double *dy)
static void
tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+
if (tp->gesture.started)
return;
switch (tp->gesture.finger_count) {
case 2:
- /* NOP */
+ switch (tp->gesture.twofinger_state) {
+ case GESTURE_2FG_STATE_NEW:
+ case GESTURE_2FG_STATE_UNKNOWN:
+ log_bug_libinput(libinput,
+ "%s in unknown gesture mode\n", __func__);
+ break;
+ case GESTURE_2FG_STATE_SCROLL:
+ /* NOP */
+ break;
+ case GESTURE_2FG_STATE_PINCH:
+ gesture_notify_pinch(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_PINCH_START,
+ 0, 0, 0, 0, 0, 0);
+ break;
+ }
break;
case 3:
case 4:
@@ -113,10 +129,178 @@ tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
}
}
+static int
+tp_gesture_get_active_touches(struct tp_dispatch *tp,
+ struct tp_touch **touches,
+ unsigned int count)
+{
+ unsigned int i, n = 0;
+ struct tp_touch *t;
+
+ memset(touches, 0, count * sizeof(struct tp_touch *));
+
+ for (i = 0; i < tp->real_touches; i++) {
+ t = &tp->touches[i];
+ if (tp_touch_active(tp, t)) {
+ touches[n++] = t;
+ if (n == count)
+ return 0;
+ }
+ }
+
+ /*
+ * This can happen when the user does .e.g:
+ * 1) Put down 1st finger in center (so active)
+ * 2) Put down 2nd finger in a button area (so inactive)
+ * 3) Put down 3th finger somewhere, gets reported as a fake finger,
+ * so gets same coordinates as 1st -> active
+ *
+ * We could avoid this by looking at all touches, be we really only
+ * want to look at real touches.
+ */
+ return -1;
+}
+
+static int
+tp_gesture_get_direction(struct tp_dispatch *tp, int touch)
+{
+ double dx, dy, move_threshold;
+
+ /*
+ * Semi-mt touchpads have somewhat inaccurate coordinates when
+ * 2 fingers are down, so use a slightly larger threshold.
+ */
+ if (tp->semi_mt)
+ move_threshold = TP_MM_TO_DPI_NORMALIZED(4);
+ else
+ move_threshold = TP_MM_TO_DPI_NORMALIZED(3);
+
+ dx = tp->gesture.touches[touch]->x -
+ tp->gesture.touches[touch]->gesture.initial_x;
+ dy = tp->gesture.touches[touch]->y -
+ tp->gesture.touches[touch]->gesture.initial_y;
+ tp_normalize_delta(tp, &dx, &dy);
+ if (hypot(dx, dy) < move_threshold)
+ return UNDEFINED_DIRECTION;
+
+ return vector_get_direction(dx, dy);
+}
+
+static void
+tp_gesture_get_pinch_info(struct tp_dispatch *tp,
+ double *distance,
+ double *angle,
+ double *center_x,
+ double *center_y)
+{
+ double dx, dy;
+
+ dx = tp->gesture.touches[0]->x - tp->gesture.touches[1]->x;
+ dy = tp->gesture.touches[0]->y - tp->gesture.touches[1]->y;
+ tp_normalize_delta(tp, &dx, &dy);
+
+ *distance = hypot(dx, dy);
+ if (!tp->semi_mt)
+ *angle = atan2(dy, dx) * 180.0 / M_PI;
+ else
+ *angle = 0.0;
+
+ *center_x = (tp->gesture.touches[0]->x + tp->gesture.touches[1]->x) / 2;
+ *center_y = (tp->gesture.touches[0]->y + tp->gesture.touches[1]->y) / 2;
+}
+
+static void
+tp_gesture_set_scroll_buildup(struct tp_dispatch *tp)
+{
+ double dx, dy;
+
+ dx = tp->gesture.touches[0]->x -
+ tp->gesture.touches[0]->gesture.initial_x;
+ dy = tp->gesture.touches[0]->y -
+ tp->gesture.touches[0]->gesture.initial_y;
+ dx += tp->gesture.touches[1]->x -
+ tp->gesture.touches[1]->gesture.initial_x;
+ dy += tp->gesture.touches[1]->y -
+ tp->gesture.touches[1]->gesture.initial_y;
+
+ tp->device->scroll.buildup_horizontal = dx / 2.0;
+ tp->device->scroll.buildup_vertical = dy / 2.0;
+}
+
+static void
+tp_gesture_twofinger_state_new(struct tp_dispatch *tp, uint64_t time)
+{
+ if (tp_gesture_get_active_touches(tp, tp->gesture.touches, 2))
+ return;
+
+ tp->gesture.initial_time = time;
+ tp->gesture.touches[0]->gesture.initial_x =
+ tp->gesture.touches[0]->x;
+ tp->gesture.touches[0]->gesture.initial_y =
+ tp->gesture.touches[0]->y;
+ tp->gesture.touches[1]->gesture.initial_x =
+ tp->gesture.touches[1]->x;
+ tp->gesture.touches[1]->gesture.initial_y =
+ tp->gesture.touches[1]->y;
+
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_UNKNOWN;
+}
+
+static void
+tp_gesture_twofinger_state_unknown(struct tp_dispatch *tp, uint64_t time)
+{
+ double dx, dy;
+ int dir0, dir1;
+
+ dx = tp->gesture.touches[0]->x - tp->gesture.touches[1]->x;
+ dy = tp->gesture.touches[0]->y - tp->gesture.touches[1]->y;
+ tp_normalize_delta(tp, &dx, &dy);
+
+ /* If fingers are further then 3 cm apart assume pinch */
+ if (hypot(dx, dy) > TP_MM_TO_DPI_NORMALIZED(30)) {
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_PINCH;
+ tp_gesture_get_pinch_info(tp,
+ &tp->gesture.distance,
+ &tp->gesture.angle,
+ &tp->gesture.center_x,
+ &tp->gesture.center_y);
+ return;
+ }
+
+ /* Elif fingers have been close together for a while, scroll */
+ if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) {
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_SCROLL;
+ tp_gesture_set_scroll_buildup(tp);
+ return;
+ }
+
+ /* Else wait for both fingers to have moved */
+ dir0 = tp_gesture_get_direction(tp, 0);
+ dir1 = tp_gesture_get_direction(tp, 1);
+ if (dir0 == UNDEFINED_DIRECTION || dir1 == UNDEFINED_DIRECTION)
+ return;
+
+ /* If both touches are moving in the same direction assume scroll */
+ if (((dir0 | (dir0 >> 1)) & dir1) ||
+ ((dir1 | (dir1 >> 1)) & dir0) ||
+ ((dir0 & 0x80) && (dir1 & 0x01)) ||
+ ((dir1 & 0x80) && (dir0 & 0x01))) {
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_SCROLL;
+ tp_gesture_set_scroll_buildup(tp);
+ } else {
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_PINCH;
+ tp_gesture_get_pinch_info(tp,
+ &tp->gesture.distance,
+ &tp->gesture.angle,
+ &tp->gesture.center_x,
+ &tp->gesture.center_y);
+ }
+}
+
static void
-tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
+tp_gesture_twofinger_state_scroll(struct tp_dispatch *tp, uint64_t time)
{
- double dx = 0, dy =0;
+ double dx = 0.0, dy = 0.0;
tp_get_average_touches_delta(tp, &dx, &dy);
tp_filter_motion(tp, &dx, &dy, NULL, NULL, time);
@@ -132,6 +316,69 @@ tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
}
static void
+tp_gesture_twofinger_state_pinch(struct tp_dispatch *tp, uint64_t time)
+{
+ double dx, dy, distance, angle, center_x, center_y, delta;
+ double dx_unaccel, dy_unaccel;
+
+ tp_gesture_get_pinch_info(tp, &distance, &angle,
+ ¢er_x, ¢er_y);
+
+ delta = distance - tp->gesture.distance;
+ tp->gesture.distance = distance;
+ distance = delta;
+
+ delta = angle - tp->gesture.angle;
+ if (delta > 180.0)
+ delta -= 360.0;
+ else if (delta < -180.0)
+ delta += 360.0;
+ tp->gesture.angle = angle;
+ angle = delta;
+
+ dx = center_x - tp->gesture.center_x;
+ dy = center_y - tp->gesture.center_y;
+ tp->gesture.center_x = center_x;
+ tp->gesture.center_y = center_y;
+ tp_normalize_delta(tp, &dx, &dy);
+ tp_filter_motion(tp, &dx, &dy, &dx_unaccel, &dy_unaccel, time);
+
+ if (dx == 0.0 && dy == 0.0 &&
+ dx_unaccel == 0.0 && dy_unaccel == 0.0 &&
+ distance == 0.0 && angle == 0.0)
+ return;
+
+ tp_gesture_start(tp, time);
+ gesture_notify_pinch(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ dx, dy, dx_unaccel, dy_unaccel,
+ distance, angle);
+}
+
+static void
+tp_gesture_post_twofinger(struct tp_dispatch *tp, uint64_t time)
+{
+ switch (tp->gesture.twofinger_state) {
+ case GESTURE_2FG_STATE_NEW:
+ tp_gesture_twofinger_state_new(tp, time);
+ if (tp->gesture.twofinger_state != GESTURE_2FG_STATE_UNKNOWN)
+ break;
+ /* fall through */
+ case GESTURE_2FG_STATE_UNKNOWN:
+ tp_gesture_twofinger_state_unknown(tp, time);
+ if (tp->gesture.twofinger_state != GESTURE_2FG_STATE_SCROLL)
+ break;
+ /* fall through */
+ case GESTURE_2FG_STATE_SCROLL:
+ tp_gesture_twofinger_state_scroll(tp, time);
+ break;
+ case GESTURE_2FG_STATE_PINCH:
+ tp_gesture_twofinger_state_pinch(tp, time);
+ break;
+ }
+}
+
+static void
tp_gesture_post_swipe(struct tp_dispatch *tp, uint64_t time)
{
double dx, dy, dx_unaccel, dy_unaccel;
@@ -170,7 +417,7 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
tp_gesture_post_pointer_motion(tp, time);
break;
case 2:
- tp_gesture_post_twofinger_scroll(tp, time);
+ tp_gesture_post_twofinger(tp, time);
break;
case 3:
case 4:
@@ -190,12 +437,31 @@ tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
void
tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+ enum tp_gesture_2fg_state twofinger_state = tp->gesture.twofinger_state;
+
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_NEW;
+
if (!tp->gesture.started)
return;
switch (tp->gesture.finger_count) {
case 2:
- tp_gesture_stop_twofinger_scroll(tp, time);
+ switch (twofinger_state) {
+ case GESTURE_2FG_STATE_NEW:
+ case GESTURE_2FG_STATE_UNKNOWN:
+ log_bug_libinput(libinput,
+ "%s in unknown gesture mode\n", __func__);
+ break;
+ case GESTURE_2FG_STATE_SCROLL:
+ tp_gesture_stop_twofinger_scroll(tp, time);
+ break;
+ case GESTURE_2FG_STATE_PINCH:
+ gesture_notify_pinch(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ 0, 0, 0, 0, 0, 0);
+ break;
+ }
break;
case 3:
case 4:
@@ -255,6 +521,8 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
int
tp_init_gesture(struct tp_dispatch *tp)
{
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_NEW;
+
libinput_timer_init(&tp->gesture.finger_count_switch_timer,
tp->device->base.seat->libinput,
tp_gesture_finger_count_switch_timeout, tp);
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index aa6de69..b8e2164 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -122,6 +122,13 @@ enum tp_edge_scroll_touch_state {
EDGE_SCROLL_TOUCH_STATE_AREA,
};
+enum tp_gesture_2fg_state {
+ GESTURE_2FG_STATE_NEW,
+ GESTURE_2FG_STATE_UNKNOWN,
+ GESTURE_2FG_STATE_SCROLL,
+ GESTURE_2FG_STATE_PINCH,
+};
+
struct tp_motion {
int32_t x;
int32_t y;
@@ -185,6 +192,10 @@ struct tp_touch {
in device coordinates */
uint32_t time; /* first timestamp if is_palm == true */
} palm;
+
+ struct {
+ int32_t initial_x, initial_y; /* in device coordinates */
+ } gesture;
};
struct tp_dispatch {
@@ -222,6 +233,13 @@ struct tp_dispatch {
unsigned int finger_count;
unsigned int finger_count_pending;
struct libinput_timer finger_count_switch_timer;
+ enum tp_gesture_2fg_state twofinger_state;
+ struct tp_touch *touches[2];
+ uint64_t initial_time;
+ double distance;
+ double angle;
+ double center_x;
+ double center_y;
} gesture;
struct {
diff --git a/test/touchpad.c b/test/touchpad.c
index 6fa2301..62b5381 100644
--- a/test/touchpad.c
+++ b/test/touchpad.c
@@ -1872,7 +1872,7 @@ START_TEST(touchpad_2fg_scroll_slow_distance)
y_move = 7.0 * y->resolution /
(y->maximum - y->minimum) * 100;
} else {
- y_move = 10.0;
+ y_move = 20.0;
}
litest_drain_events(li);
@@ -1921,7 +1921,7 @@ START_TEST(touchpad_2fg_scroll_source)
litest_drain_events(li);
- test_2fg_scroll(dev, 0, 20, 0);
+ test_2fg_scroll(dev, 0, 30, 0);
litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
while ((event = libinput_get_event(li))) {
--
2.3.1
More information about the wayland-devel
mailing list