diff --git a/doc/gestures.dox b/doc/gestures.dox index 02ef09ab..8632b8e6 100644 --- a/doc/gestures.dox +++ b/doc/gestures.dox @@ -88,4 +88,20 @@ thus suggesting a window movement. libinput only has knowledge of the finger coordinates (and even then only in device coordinates, not in screen coordinates) and thus cannot differentiate the two. +@section gestures_softbuttons Gestures with enabled software buttons + +If the touchpad device is a @ref touchpads_buttons_clickpads "Clickpad", it +is recommended that a caller switches to @ref clickfinger. +Usually fingers placed in a @ref software_buttons "software button area" is not +considered for gestures, resulting in some gestures to be interpreted as +pointer motion or two-finger scroll events. + +@image html pinch-gestures-softbuttons.svg "Interference of software buttons and pinch gestures" + +In the example above, the software button area is highlighted in red. The +user executes a three-finger pinch gesture, with the thumb remaining in the +software button area. libinput ignores fingers within the software button +areas, the movement of the remaining fingers is thus interpreted as a +two-finger scroll motion. + */ diff --git a/doc/svg/pinch-gestures-softbuttons.svg b/doc/svg/pinch-gestures-softbuttons.svg new file mode 100644 index 00000000..959cb4f4 --- /dev/null +++ b/doc/svg/pinch-gestures-softbuttons.svg @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index c676caac..e7b75ac2 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -40,6 +40,7 @@ gesture_state_to_str(enum tp_gesture_state state) CASE_RETURN_STRING(GESTURE_STATE_UNKNOWN); CASE_RETURN_STRING(GESTURE_STATE_SCROLL); CASE_RETURN_STRING(GESTURE_STATE_PINCH); + CASE_RETURN_STRING(GESTURE_STATE_SWIPE); } return NULL; } @@ -94,34 +95,30 @@ tp_gesture_start(struct tp_dispatch *tp, uint64_t time) if (tp->gesture.started) return; - switch (tp->gesture.finger_count) { - case 2: - switch (tp->gesture.state) { - case GESTURE_STATE_NONE: - case GESTURE_STATE_UNKNOWN: - log_bug_libinput(libinput, - "%s in unknown gesture mode\n", - __func__); - break; - case GESTURE_STATE_SCROLL: - /* NOP */ - break; - case GESTURE_STATE_PINCH: - gesture_notify_pinch(&tp->device->base, time, - LIBINPUT_EVENT_GESTURE_PINCH_BEGIN, - tp->gesture.finger_count, - &zero, &zero, 1.0, 0.0); - break; - } + switch (tp->gesture.state) { + case GESTURE_STATE_NONE: + case GESTURE_STATE_UNKNOWN: + log_bug_libinput(libinput, + "%s in unknown gesture mode\n", + __func__); break; - case 3: - case 4: + case GESTURE_STATE_SCROLL: + /* NOP */ + break; + case GESTURE_STATE_PINCH: + gesture_notify_pinch(&tp->device->base, time, + LIBINPUT_EVENT_GESTURE_PINCH_BEGIN, + tp->gesture.finger_count, + &zero, &zero, 1.0, 0.0); + break; + case GESTURE_STATE_SWIPE: gesture_notify_swipe(&tp->device->base, time, LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN, tp->gesture.finger_count, &zero, &zero); break; } + tp->gesture.started = true; } @@ -247,19 +244,54 @@ tp_gesture_set_scroll_buildup(struct tp_dispatch *tp) } static enum tp_gesture_state -tp_gesture_twofinger_handle_state_none(struct tp_dispatch *tp, uint64_t time) +tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *first, *second; + struct tp_touch *touches[4]; + unsigned int ntouches; + unsigned int i; - if (tp_gesture_get_active_touches(tp, tp->gesture.touches, 2) != 2) + ntouches = tp_gesture_get_active_touches(tp, touches, 4); + if (ntouches < 2) return GESTURE_STATE_NONE; - first = tp->gesture.touches[0]; - second = tp->gesture.touches[1]; + first = touches[0]; + second = touches[1]; + + /* For 3+ finger gestures we cheat. A human hand's finger + * arrangement means that for a 3 or 4 finger swipe gesture, the + * fingers are roughly arranged in a horizontal line. + * They will all move in the same direction, so we can simply look + * at the left and right-most ones only. If we have fake touches, we + * just take the left/right-most real touch position, since the fake + * touch has the same location as one of those. + * + * For a 3 or 4 finger pinch gesture, 2 or 3 fingers are roughly in + * a horizontal line, with the thumb below and left (right-handed + * users) or right (left-handed users). Again, the row of non-thumb + * fingers moves identically so we can look at the left and + * right-most only and then treat it like a two-finger + * gesture. + */ + if (ntouches > 2) { + second = touches[0]; + + for (i = 1; i < ntouches && i < tp->num_slots; i++) { + if (touches[i]->point.x < first->point.x) + first = touches[i]; + else if (touches[i]->point.x > second->point.x) + second = touches[i]; + } + + if (first == second) + return GESTURE_STATE_NONE; + } tp->gesture.initial_time = time; first->gesture.initial = first->point; second->gesture.initial = second->point; + tp->gesture.touches[0] = first; + tp->gesture.touches[1] = second; return GESTURE_STATE_UNKNOWN; } @@ -281,14 +313,16 @@ tp_gesture_same_directions(int dir1, int dir2) } static enum tp_gesture_state -tp_gesture_twofinger_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) +tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *first = tp->gesture.touches[0], *second = tp->gesture.touches[1]; int dir1, dir2; - /* if fingers stay unmoving for a while, assume (slow) scroll */ - if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) { + /* for two-finger gestures, if the fingers stay unmoving for a + * while, assume (slow) scroll */ + if (tp->gesture.finger_count == 2 && + time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) { tp_gesture_set_scroll_buildup(tp); return GESTURE_STATE_SCROLL; } @@ -299,10 +333,15 @@ tp_gesture_twofinger_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) if (dir1 == UNDEFINED_DIRECTION || dir2 == UNDEFINED_DIRECTION) return GESTURE_STATE_UNKNOWN; - /* If both touches are moving in the same direction assume scroll */ + /* If both touches are moving in the same direction assume + * scroll or swipe */ if (tp_gesture_same_directions(dir1, dir2)) { - tp_gesture_set_scroll_buildup(tp); - return GESTURE_STATE_SCROLL; + if (tp->gesture.finger_count == 2) { + tp_gesture_set_scroll_buildup(tp); + return GESTURE_STATE_SCROLL; + } else if (tp->gesture.enabled) { + return GESTURE_STATE_SWIPE; + } } else if (tp->gesture.enabled) { tp_gesture_get_pinch_info(tp, &tp->gesture.initial_distance, @@ -316,7 +355,7 @@ tp_gesture_twofinger_handle_state_unknown(struct tp_dispatch *tp, uint64_t time) } static enum tp_gesture_state -tp_gesture_twofinger_handle_state_scroll(struct tp_dispatch *tp, uint64_t time) +tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time) { struct normalized_coords delta; @@ -350,7 +389,26 @@ tp_gesture_twofinger_handle_state_scroll(struct tp_dispatch *tp, uint64_t time) } static enum tp_gesture_state -tp_gesture_twofinger_handle_state_pinch(struct tp_dispatch *tp, uint64_t time) +tp_gesture_handle_state_swipe(struct tp_dispatch *tp, uint64_t time) +{ + struct normalized_coords delta, unaccel; + + unaccel = tp_get_average_touches_delta(tp); + delta = tp_filter_motion(tp, &unaccel, time); + + if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) { + tp_gesture_start(tp, time); + gesture_notify_swipe(&tp->device->base, time, + LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, + tp->gesture.finger_count, + &delta, &unaccel); + } + + return GESTURE_STATE_SWIPE; +} + +static enum tp_gesture_state +tp_gesture_handle_state_pinch(struct tp_dispatch *tp, uint64_t time) { double angle, angle_delta, distance, scale; struct device_float_coords center, fdelta; @@ -388,25 +446,29 @@ tp_gesture_twofinger_handle_state_pinch(struct tp_dispatch *tp, uint64_t time) } static void -tp_gesture_post_twofinger(struct tp_dispatch *tp, uint64_t time) +tp_gesture_post_gesture(struct tp_dispatch *tp, uint64_t time) { enum tp_gesture_state oldstate = tp->gesture.state; if (tp->gesture.state == GESTURE_STATE_NONE) tp->gesture.state = - tp_gesture_twofinger_handle_state_none(tp, time); + tp_gesture_handle_state_none(tp, time); if (tp->gesture.state == GESTURE_STATE_UNKNOWN) tp->gesture.state = - tp_gesture_twofinger_handle_state_unknown(tp, time); + tp_gesture_handle_state_unknown(tp, time); if (tp->gesture.state == GESTURE_STATE_SCROLL) tp->gesture.state = - tp_gesture_twofinger_handle_state_scroll(tp, time); + tp_gesture_handle_state_scroll(tp, time); + + if (tp->gesture.state == GESTURE_STATE_SWIPE) + tp->gesture.state = + tp_gesture_handle_state_swipe(tp, time); if (tp->gesture.state == GESTURE_STATE_PINCH) tp->gesture.state = - tp_gesture_twofinger_handle_state_pinch(tp, time); + tp_gesture_handle_state_pinch(tp, time); log_debug(tp_libinput_context(tp), "gesture state: %s → %s\n", @@ -414,23 +476,6 @@ tp_gesture_post_twofinger(struct tp_dispatch *tp, uint64_t time) gesture_state_to_str(tp->gesture.state)); } -static void -tp_gesture_post_swipe(struct tp_dispatch *tp, uint64_t time) -{ - struct normalized_coords delta, unaccel; - - unaccel = tp_get_average_touches_delta(tp); - delta = tp_filter_motion(tp, &unaccel, time); - - if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) { - tp_gesture_start(tp, time); - gesture_notify_swipe(&tp->device->base, time, - LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, - tp->gesture.finger_count, - &delta, &unaccel); - } -} - void tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time) { @@ -453,11 +498,9 @@ 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(tp, time); - break; case 3: case 4: - tp_gesture_post_swipe(tp, time); + tp_gesture_post_gesture(tp, time); break; } } @@ -484,32 +527,30 @@ tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled) if (!tp->gesture.started) return; - switch (tp->gesture.finger_count) { - case 2: - switch (state) { - case GESTURE_STATE_NONE: - case GESTURE_STATE_UNKNOWN: - log_bug_libinput(libinput, - "%s in unknown gesture mode\n", - __func__); - break; - case GESTURE_STATE_SCROLL: - tp_gesture_stop_twofinger_scroll(tp, time); - break; - case GESTURE_STATE_PINCH: - gesture_notify_pinch_end(&tp->device->base, time, - tp->gesture.finger_count, - tp->gesture.prev_scale, - cancelled); - break; - } + switch (state) { + case GESTURE_STATE_NONE: + case GESTURE_STATE_UNKNOWN: + log_bug_libinput(libinput, + "%s in unknown gesture mode\n", + __func__); break; - case 3: - case 4: - gesture_notify_swipe_end(&tp->device->base, time, - tp->gesture.finger_count, cancelled); + case GESTURE_STATE_SCROLL: + tp_gesture_stop_twofinger_scroll(tp, time); + break; + case GESTURE_STATE_PINCH: + gesture_notify_pinch_end(&tp->device->base, time, + tp->gesture.finger_count, + tp->gesture.prev_scale, + cancelled); + break; + case GESTURE_STATE_SWIPE: + gesture_notify_swipe_end(&tp->device->base, + time, + tp->gesture.finger_count, + cancelled); break; } + tp->gesture.started = false; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 690feb55..20456f52 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -135,6 +135,7 @@ enum tp_gesture_state { GESTURE_STATE_UNKNOWN, GESTURE_STATE_SCROLL, GESTURE_STATE_PINCH, + GESTURE_STATE_SWIPE, }; enum tp_thumb_state {