diff options
author | Hans de Goede <hdegoede@redhat.com> | 2015-03-05 12:44:13 +0100 |
---|---|---|
committer | Hans de Goede <hdegoede@redhat.com> | 2015-03-24 11:42:42 +0100 |
commit | 1990e33dad10c08542f5a6b11799415b55efa07e (patch) | |
tree | d8de90c159931b78451e2971d7001e15db3b5b16 | |
parent | 1280737772d3d3531234311e56179e5ef45cb5a8 (diff) |
touchpad: Implement pinch gesture supportgestures-broken
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@redhat.com>
-rw-r--r-- | src/evdev-mt-touchpad-gestures.c | 286 | ||||
-rw-r--r-- | src/evdev-mt-touchpad.h | 18 | ||||
-rw-r--r-- | test/touchpad.c | 4 |
3 files changed, 301 insertions, 7 deletions
diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index c92a170..caeb2dd 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 struct normalized_coords tp_get_touches_delta(struct tp_dispatch *tp, bool average) @@ -75,12 +75,28 @@ tp_get_average_touches_delta(struct tp_dispatch *tp) 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_NONE: + 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,8 +129,184 @@ tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time) } } +static unsigned 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 count; + } + } + + /* + * 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 n; +} + +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_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time) +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_none(struct tp_dispatch *tp, uint64_t time) +{ + if (tp_gesture_get_active_touches(tp, tp->gesture.touches, 2) != 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 than 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. + * + * In some cases (semi-mt touchpads) We may seen one finger move + * e.g. N/NE and the other W/NW so we not only check for overlapping + * directions, but also for neighboring bits being set. + * The ((dira & 0x80) && (dirb & 0x01)) checks are to check for bit 0 + * and 7 being set as they also represent neighboring directions. + */ + 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_twofinger_state_scroll(struct tp_dispatch *tp, uint64_t time) { struct normalized_coords delta; @@ -132,6 +324,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_NONE: + tp_gesture_twofinger_state_none(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 +425,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 +445,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_NONE; + 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_NONE: + 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 +529,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_NONE; + 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 9980f90..4bdad08 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -120,6 +120,13 @@ enum tp_edge_scroll_touch_state { EDGE_SCROLL_TOUCH_STATE_AREA, }; +enum tp_gesture_2fg_state { + GESTURE_2FG_STATE_NONE, + GESTURE_2FG_STATE_UNKNOWN, + GESTURE_2FG_STATE_SCROLL, + GESTURE_2FG_STATE_PINCH, +}; + struct tp_touch { struct tp_dispatch *tp; enum touch_state state; @@ -171,6 +178,10 @@ struct tp_touch { struct device_coords first; /* first coordinates if is_palm == true */ uint32_t time; /* first timestamp if is_palm == true */ } palm; + + struct { + int32_t initial_x, initial_y; /* in device coordinates */ + } gesture; }; struct tp_dispatch { @@ -205,6 +216,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))) { |