2015-02-16 13:30:24 +01:00
|
|
|
/*
|
|
|
|
|
* Copyright © 2015 Red Hat, Inc.
|
|
|
|
|
*
|
2015-06-11 12:09:18 +10:00
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
2015-02-16 13:30:24 +01:00
|
|
|
*
|
2015-06-11 12:09:18 +10:00
|
|
|
* The above copyright notice and this permission notice (including the next
|
|
|
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
|
|
|
* Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
|
* DEALINGS IN THE SOFTWARE.
|
2015-02-16 13:30:24 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
|
|
|
|
|
#include "evdev-mt-touchpad.h"
|
|
|
|
|
|
|
|
|
|
#define DEFAULT_GESTURE_SWITCH_TIMEOUT 100 /* ms */
|
2015-03-05 12:44:13 +01:00
|
|
|
#define DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT 1000 /* ms */
|
2015-02-16 13:30:24 +01:00
|
|
|
|
2015-07-10 15:03:42 +10:00
|
|
|
#define CASE_RETURN_STRING(a) case a: return #a
|
|
|
|
|
|
|
|
|
|
static inline const char*
|
|
|
|
|
gesture_state_to_str(enum tp_gesture_2fg_state state)
|
|
|
|
|
{
|
|
|
|
|
switch (state) {
|
|
|
|
|
CASE_RETURN_STRING(GESTURE_2FG_STATE_NONE);
|
|
|
|
|
CASE_RETURN_STRING(GESTURE_2FG_STATE_UNKNOWN);
|
|
|
|
|
CASE_RETURN_STRING(GESTURE_2FG_STATE_SCROLL);
|
|
|
|
|
CASE_RETURN_STRING(GESTURE_2FG_STATE_PINCH);
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-11 09:58:11 +10:00
|
|
|
static struct normalized_coords
|
|
|
|
|
tp_get_touches_delta(struct tp_dispatch *tp, bool average)
|
2015-02-17 11:40:17 +01:00
|
|
|
{
|
|
|
|
|
struct tp_touch *t;
|
2015-02-17 11:40:57 +01:00
|
|
|
unsigned int i, nchanged = 0;
|
2015-03-11 09:58:11 +10:00
|
|
|
struct normalized_coords normalized;
|
|
|
|
|
struct normalized_coords delta = {0.0, 0.0};
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-04-09 15:27:34 +10:00
|
|
|
for (i = 0; i < tp->num_slots; i++) {
|
2015-02-17 11:40:57 +01:00
|
|
|
t = &tp->touches[i];
|
|
|
|
|
|
2015-02-17 11:40:17 +01:00
|
|
|
if (tp_touch_active(tp, t) && t->dirty) {
|
|
|
|
|
nchanged++;
|
2015-03-11 09:58:11 +10:00
|
|
|
normalized = tp_get_delta(t);
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-03-11 09:58:11 +10:00
|
|
|
delta.x += normalized.x;
|
|
|
|
|
delta.y += normalized.y;
|
2015-02-17 11:40:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:44:06 +01:00
|
|
|
if (!average || nchanged == 0)
|
2015-03-11 09:58:11 +10:00
|
|
|
return delta;
|
|
|
|
|
|
|
|
|
|
delta.x /= nchanged;
|
|
|
|
|
delta.y /= nchanged;
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-03-11 09:58:11 +10:00
|
|
|
return delta;
|
2015-02-17 11:40:17 +01:00
|
|
|
}
|
|
|
|
|
|
2015-03-11 09:58:11 +10:00
|
|
|
static inline struct normalized_coords
|
|
|
|
|
tp_get_combined_touches_delta(struct tp_dispatch *tp)
|
2015-02-17 11:40:17 +01:00
|
|
|
{
|
2015-03-11 09:58:11 +10:00
|
|
|
return tp_get_touches_delta(tp, false);
|
2015-02-17 11:44:06 +01:00
|
|
|
}
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-03-11 09:58:11 +10:00
|
|
|
static inline struct normalized_coords
|
|
|
|
|
tp_get_average_touches_delta(struct tp_dispatch *tp)
|
2015-02-17 11:44:06 +01:00
|
|
|
{
|
2015-03-11 09:58:11 +10:00
|
|
|
return tp_get_touches_delta(tp, true);
|
2015-02-17 11:40:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-02-16 13:30:24 +01:00
|
|
|
tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
2015-03-05 12:44:13 +01:00
|
|
|
struct libinput *libinput = tp->device->base.seat->libinput;
|
2015-02-18 09:00:25 +01:00
|
|
|
const struct normalized_coords zero = { 0.0, 0.0 };
|
|
|
|
|
|
2015-02-16 13:30:24 +01:00
|
|
|
if (tp->gesture.started)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
switch (tp->gesture.finger_count) {
|
|
|
|
|
case 2:
|
2015-03-05 12:44:13 +01:00
|
|
|
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_BEGIN,
|
2015-05-22 10:58:56 +10:00
|
|
|
&zero, &zero, 1.0, 0.0);
|
2015-03-05 12:44:13 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2015-02-16 13:30:24 +01:00
|
|
|
break;
|
2015-02-18 09:00:25 +01:00
|
|
|
case 3:
|
|
|
|
|
case 4:
|
|
|
|
|
gesture_notify_swipe(&tp->device->base, time,
|
|
|
|
|
LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
|
|
|
|
|
tp->gesture.finger_count,
|
|
|
|
|
&zero, &zero);
|
|
|
|
|
break;
|
2015-02-16 13:30:24 +01:00
|
|
|
}
|
|
|
|
|
tp->gesture.started = true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:40:17 +01:00
|
|
|
static void
|
|
|
|
|
tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
2015-03-11 10:43:18 +10:00
|
|
|
struct normalized_coords delta, unaccel;
|
2015-06-26 09:07:24 +10:00
|
|
|
struct device_float_coords raw;
|
2015-02-17 11:40:17 +01:00
|
|
|
|
|
|
|
|
/* When a clickpad is clicked, combine motion of all active touches */
|
|
|
|
|
if (tp->buttons.is_clickpad && tp->buttons.state)
|
2015-03-24 16:43:24 +01:00
|
|
|
unaccel = tp_get_combined_touches_delta(tp);
|
2015-02-17 11:40:17 +01:00
|
|
|
else
|
2015-03-24 16:43:24 +01:00
|
|
|
unaccel = tp_get_average_touches_delta(tp);
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-03-24 16:43:24 +01:00
|
|
|
delta = tp_filter_motion(tp, &unaccel, time);
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-03-24 16:33:29 +01:00
|
|
|
if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) {
|
2015-06-26 09:07:24 +10:00
|
|
|
raw = tp_unnormalize_for_xaxis(tp, unaccel);
|
|
|
|
|
pointer_notify_motion(&tp->device->base,
|
|
|
|
|
time,
|
|
|
|
|
&delta,
|
|
|
|
|
&raw);
|
2015-02-17 11:40:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-05 12:44:13 +01:00
|
|
|
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->num_slots; 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, struct tp_touch *touch)
|
|
|
|
|
{
|
|
|
|
|
struct normalized_coords normalized;
|
|
|
|
|
struct device_float_coords delta;
|
|
|
|
|
double 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);
|
|
|
|
|
|
|
|
|
|
delta = device_delta(touch->point, touch->gesture.initial);
|
|
|
|
|
normalized = tp_normalize_delta(tp, delta);
|
|
|
|
|
|
|
|
|
|
if (normalized_length(normalized) < move_threshold)
|
|
|
|
|
return UNDEFINED_DIRECTION;
|
|
|
|
|
|
|
|
|
|
return normalized_get_direction(normalized);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:40:17 +01:00
|
|
|
static void
|
2015-03-05 12:44:13 +01:00
|
|
|
tp_gesture_get_pinch_info(struct tp_dispatch *tp,
|
|
|
|
|
double *distance,
|
|
|
|
|
double *angle,
|
|
|
|
|
struct device_float_coords *center)
|
|
|
|
|
{
|
|
|
|
|
struct normalized_coords normalized;
|
|
|
|
|
struct device_float_coords delta;
|
|
|
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
|
|
|
*second = tp->gesture.touches[1];
|
|
|
|
|
|
|
|
|
|
delta = device_delta(first->point, second->point);
|
|
|
|
|
normalized = tp_normalize_delta(tp, delta);
|
|
|
|
|
*distance = normalized_length(normalized);
|
|
|
|
|
|
|
|
|
|
if (!tp->semi_mt)
|
|
|
|
|
*angle = atan2(normalized.y, normalized.x) * 180.0 / M_PI;
|
|
|
|
|
else
|
|
|
|
|
*angle = 0.0;
|
|
|
|
|
|
|
|
|
|
*center = device_average(first->point, second->point);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tp_gesture_set_scroll_buildup(struct tp_dispatch *tp)
|
|
|
|
|
{
|
|
|
|
|
struct device_float_coords d0, d1;
|
|
|
|
|
struct device_float_coords average;
|
|
|
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
|
|
|
*second = tp->gesture.touches[1];
|
|
|
|
|
|
|
|
|
|
d0 = device_delta(first->point, first->gesture.initial);
|
|
|
|
|
d1 = device_delta(second->point, second->gesture.initial);
|
|
|
|
|
|
|
|
|
|
average = device_float_average(d0, d1);
|
|
|
|
|
tp->device->scroll.buildup = tp_normalize_delta(tp, average);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum tp_gesture_2fg_state
|
|
|
|
|
tp_gesture_twofinger_handle_state_none(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
|
|
|
|
struct tp_touch *first, *second;
|
|
|
|
|
|
|
|
|
|
if (tp_gesture_get_active_touches(tp, tp->gesture.touches, 2) != 2)
|
|
|
|
|
return GESTURE_2FG_STATE_NONE;
|
|
|
|
|
|
|
|
|
|
first = tp->gesture.touches[0];
|
|
|
|
|
second = tp->gesture.touches[1];
|
|
|
|
|
|
|
|
|
|
tp->gesture.initial_time = time;
|
|
|
|
|
first->gesture.initial = first->point;
|
|
|
|
|
second->gesture.initial = second->point;
|
|
|
|
|
|
|
|
|
|
return GESTURE_2FG_STATE_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum tp_gesture_2fg_state
|
|
|
|
|
tp_gesture_twofinger_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
|
|
|
|
struct normalized_coords normalized;
|
|
|
|
|
struct device_float_coords delta;
|
|
|
|
|
struct tp_touch *first = tp->gesture.touches[0],
|
|
|
|
|
*second = tp->gesture.touches[1];
|
|
|
|
|
int dir1, dir2;
|
|
|
|
|
|
|
|
|
|
delta = device_delta(first->point, second->point);
|
|
|
|
|
normalized = tp_normalize_delta(tp, delta);
|
|
|
|
|
|
|
|
|
|
/* If fingers are further than 3 cm apart assume pinch */
|
|
|
|
|
if (normalized_length(normalized) > TP_MM_TO_DPI_NORMALIZED(30)) {
|
|
|
|
|
tp_gesture_get_pinch_info(tp,
|
|
|
|
|
&tp->gesture.initial_distance,
|
|
|
|
|
&tp->gesture.angle,
|
|
|
|
|
&tp->gesture.center);
|
|
|
|
|
tp->gesture.prev_scale = 1.0;
|
|
|
|
|
return GESTURE_2FG_STATE_PINCH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Elif fingers have been close together for a while, scroll */
|
|
|
|
|
if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) {
|
|
|
|
|
tp_gesture_set_scroll_buildup(tp);
|
|
|
|
|
return GESTURE_2FG_STATE_SCROLL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Else wait for both fingers to have moved */
|
|
|
|
|
dir1 = tp_gesture_get_direction(tp, first);
|
|
|
|
|
dir2 = tp_gesture_get_direction(tp, second);
|
|
|
|
|
if (dir1 == UNDEFINED_DIRECTION || dir2 == UNDEFINED_DIRECTION)
|
|
|
|
|
return GESTURE_2FG_STATE_UNKNOWN;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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 (((dir1 | (dir1 >> 1)) & dir2) ||
|
|
|
|
|
((dir2 | (dir2 >> 1)) & dir1) ||
|
|
|
|
|
((dir1 & 0x80) && (dir2 & 0x01)) ||
|
|
|
|
|
((dir2 & 0x80) && (dir1 & 0x01))) {
|
|
|
|
|
tp_gesture_set_scroll_buildup(tp);
|
|
|
|
|
return GESTURE_2FG_STATE_SCROLL;
|
|
|
|
|
} else {
|
|
|
|
|
tp_gesture_get_pinch_info(tp,
|
|
|
|
|
&tp->gesture.initial_distance,
|
|
|
|
|
&tp->gesture.angle,
|
|
|
|
|
&tp->gesture.center);
|
|
|
|
|
tp->gesture.prev_scale = 1.0;
|
|
|
|
|
return GESTURE_2FG_STATE_PINCH;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum tp_gesture_2fg_state
|
|
|
|
|
tp_gesture_twofinger_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
|
2015-02-17 11:40:17 +01:00
|
|
|
{
|
2015-03-11 09:58:11 +10:00
|
|
|
struct normalized_coords delta;
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-04-21 12:07:55 +10:00
|
|
|
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
|
2015-03-05 12:44:13 +01:00
|
|
|
return GESTURE_2FG_STATE_SCROLL;
|
2015-04-21 12:07:55 +10:00
|
|
|
|
2015-04-24 16:49:36 +02:00
|
|
|
/* 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)
|
2015-03-05 12:44:13 +01:00
|
|
|
return GESTURE_2FG_STATE_SCROLL;
|
2015-04-24 16:49:36 +02:00
|
|
|
|
|
|
|
|
delta = tp_get_delta(&tp->touches[0]);
|
|
|
|
|
} else {
|
|
|
|
|
delta = tp_get_average_touches_delta(tp);
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-24 16:43:24 +01:00
|
|
|
delta = tp_filter_motion(tp, &delta, time);
|
2015-02-17 11:40:17 +01:00
|
|
|
|
2015-03-24 16:33:29 +01:00
|
|
|
if (normalized_is_zero(delta))
|
2015-03-05 12:44:13 +01:00
|
|
|
return GESTURE_2FG_STATE_SCROLL;
|
2015-02-17 11:40:17 +01:00
|
|
|
|
|
|
|
|
tp_gesture_start(tp, time);
|
|
|
|
|
evdev_post_scroll(tp->device,
|
|
|
|
|
time,
|
|
|
|
|
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
|
2015-03-11 10:36:44 +10:00
|
|
|
&delta);
|
2015-03-05 12:44:13 +01:00
|
|
|
|
|
|
|
|
return GESTURE_2FG_STATE_SCROLL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum tp_gesture_2fg_state
|
|
|
|
|
tp_gesture_twofinger_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
|
|
|
|
double angle, angle_delta, distance, scale;
|
|
|
|
|
struct device_float_coords center, fdelta;
|
|
|
|
|
struct normalized_coords delta, unaccel;
|
|
|
|
|
|
|
|
|
|
tp_gesture_get_pinch_info(tp, &distance, &angle, ¢er);
|
|
|
|
|
|
|
|
|
|
scale = distance / tp->gesture.initial_distance;
|
|
|
|
|
|
|
|
|
|
angle_delta = angle - tp->gesture.angle;
|
|
|
|
|
tp->gesture.angle = angle;
|
|
|
|
|
if (angle_delta > 180.0)
|
|
|
|
|
angle_delta -= 360.0;
|
|
|
|
|
else if (angle_delta < -180.0)
|
|
|
|
|
angle_delta += 360.0;
|
|
|
|
|
|
|
|
|
|
fdelta = device_float_delta(center, tp->gesture.center);
|
|
|
|
|
tp->gesture.center = center;
|
|
|
|
|
unaccel = tp_normalize_delta(tp, fdelta);
|
|
|
|
|
delta = tp_filter_motion(tp, &unaccel, time);
|
|
|
|
|
|
|
|
|
|
if (normalized_is_zero(delta) && normalized_is_zero(unaccel) &&
|
|
|
|
|
scale == tp->gesture.prev_scale && angle_delta == 0.0)
|
|
|
|
|
return GESTURE_2FG_STATE_PINCH;
|
|
|
|
|
|
|
|
|
|
tp_gesture_start(tp, time);
|
|
|
|
|
gesture_notify_pinch(&tp->device->base, time,
|
|
|
|
|
LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
|
|
|
|
|
&delta, &unaccel, scale, angle_delta);
|
|
|
|
|
|
|
|
|
|
tp->gesture.prev_scale = scale;
|
|
|
|
|
|
|
|
|
|
return GESTURE_2FG_STATE_PINCH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tp_gesture_post_twofinger(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
2015-07-10 15:03:42 +10:00
|
|
|
enum tp_gesture_2fg_state oldstate = tp->gesture.twofinger_state;
|
|
|
|
|
|
2015-03-05 12:44:13 +01:00
|
|
|
if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_NONE)
|
|
|
|
|
tp->gesture.twofinger_state =
|
|
|
|
|
tp_gesture_twofinger_handle_state_none(tp, time);
|
|
|
|
|
|
|
|
|
|
if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_UNKNOWN)
|
|
|
|
|
tp->gesture.twofinger_state =
|
|
|
|
|
tp_gesture_twofinger_handle_state_unknown(tp, time);
|
|
|
|
|
|
|
|
|
|
if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_SCROLL)
|
|
|
|
|
tp->gesture.twofinger_state =
|
|
|
|
|
tp_gesture_twofinger_handle_state_scroll(tp, time);
|
|
|
|
|
|
|
|
|
|
if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_PINCH)
|
|
|
|
|
tp->gesture.twofinger_state =
|
|
|
|
|
tp_gesture_twofinger_handle_state_pinch(tp, time);
|
2015-07-10 15:03:42 +10:00
|
|
|
|
|
|
|
|
log_debug(tp_libinput_context(tp),
|
|
|
|
|
"gesture state: %s → %s\n",
|
|
|
|
|
gesture_state_to_str(oldstate),
|
|
|
|
|
gesture_state_to_str(tp->gesture.twofinger_state));
|
2015-02-17 11:40:17 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-18 09:00:25 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-16 13:30:24 +01:00
|
|
|
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)) {
|
2015-05-22 10:09:22 +10:00
|
|
|
tp_gesture_cancel(tp, time);
|
2015-02-16 13:30:24 +01:00
|
|
|
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:
|
2015-03-05 12:44:13 +01:00
|
|
|
tp_gesture_post_twofinger(tp, time);
|
2015-02-16 13:30:24 +01:00
|
|
|
break;
|
2015-02-18 09:00:25 +01:00
|
|
|
case 3:
|
|
|
|
|
case 4:
|
|
|
|
|
tp_gesture_post_swipe(tp, time);
|
|
|
|
|
break;
|
2015-02-16 13:30:24 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:40:17 +01:00
|
|
|
void
|
|
|
|
|
tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
2015-04-21 12:07:55 +10:00
|
|
|
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
|
|
|
|
|
return;
|
|
|
|
|
|
2015-02-17 11:40:17 +01:00
|
|
|
evdev_stop_scroll(tp->device,
|
|
|
|
|
time,
|
|
|
|
|
LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-22 10:09:22 +10:00
|
|
|
static void
|
|
|
|
|
tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled)
|
2015-02-16 13:30:24 +01:00
|
|
|
{
|
2015-03-05 12:44:13 +01:00
|
|
|
struct libinput *libinput = tp->device->base.seat->libinput;
|
|
|
|
|
enum tp_gesture_2fg_state twofinger_state = tp->gesture.twofinger_state;
|
2015-02-18 09:00:25 +01:00
|
|
|
|
2015-03-05 12:44:13 +01:00
|
|
|
tp->gesture.twofinger_state = GESTURE_2FG_STATE_NONE;
|
|
|
|
|
|
2015-02-16 13:30:24 +01:00
|
|
|
if (!tp->gesture.started)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
switch (tp->gesture.finger_count) {
|
|
|
|
|
case 2:
|
2015-03-05 12:44:13 +01:00
|
|
|
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:
|
2015-04-29 13:19:51 +02:00
|
|
|
gesture_notify_pinch_end(&tp->device->base, time,
|
2015-05-22 10:58:56 +10:00
|
|
|
tp->gesture.prev_scale,
|
2015-04-29 13:19:51 +02:00
|
|
|
cancelled);
|
2015-03-05 12:44:13 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2015-02-16 13:30:24 +01:00
|
|
|
break;
|
2015-02-18 09:00:25 +01:00
|
|
|
case 3:
|
|
|
|
|
case 4:
|
2015-04-29 13:19:51 +02:00
|
|
|
gesture_notify_swipe_end(&tp->device->base, time,
|
|
|
|
|
tp->gesture.finger_count, cancelled);
|
2015-02-18 09:00:25 +01:00
|
|
|
break;
|
2015-02-16 13:30:24 +01:00
|
|
|
}
|
|
|
|
|
tp->gesture.started = false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-22 10:09:22 +10:00
|
|
|
void
|
|
|
|
|
tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
|
|
|
|
tp_gesture_end(tp, time, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
|
|
|
|
tp_gesture_end(tp, time, false);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-16 13:30:24 +01:00
|
|
|
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;
|
|
|
|
|
|
2015-05-22 10:09:22 +10:00
|
|
|
tp_gesture_cancel(tp, now); /* End current gesture */
|
2015-02-16 13:30:24 +01:00
|
|
|
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;
|
2015-05-27 18:25:49 +10:00
|
|
|
uint32_t old_thumb_mask, thumb_mask = 0;
|
|
|
|
|
int i = 0;
|
2015-02-16 13:30:24 +01:00
|
|
|
|
2015-05-27 18:25:49 +10:00
|
|
|
tp_for_each_touch(tp, t) {
|
2015-02-16 13:30:24 +01:00
|
|
|
if (tp_touch_active(tp, t))
|
|
|
|
|
active_touches++;
|
|
|
|
|
|
2015-05-27 18:25:49 +10:00
|
|
|
if (t->is_thumb)
|
|
|
|
|
thumb_mask |= 1 << i;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
old_thumb_mask = tp->gesture.thumb_mask;
|
|
|
|
|
tp->gesture.thumb_mask = thumb_mask;
|
|
|
|
|
|
|
|
|
|
/* active touches does not include thumb touches, need to count those
|
|
|
|
|
* separately, in a bitmask.
|
|
|
|
|
* then, if the finger count changes and/or the thumb count changes
|
|
|
|
|
* -> cancel gesture.
|
|
|
|
|
*/
|
|
|
|
|
if (thumb_mask != old_thumb_mask) {
|
|
|
|
|
/* if a thumb is detected during a gesture, that gesture is
|
|
|
|
|
* cancelled and the user effectively needs to restart. we
|
|
|
|
|
* could be smarter, but the complexity isn't worth it */
|
|
|
|
|
tp_gesture_cancel(tp, time);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-16 13:30:24 +01:00
|
|
|
if (active_touches != tp->gesture.finger_count) {
|
|
|
|
|
/* If all fingers are lifted immediately end the gesture */
|
|
|
|
|
if (active_touches == 0) {
|
2015-05-22 10:09:22 +10:00
|
|
|
tp_gesture_stop(tp, time);
|
2015-02-16 13:30:24 +01:00
|
|
|
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)
|
|
|
|
|
{
|
2015-03-05 12:44:13 +01:00
|
|
|
tp->gesture.twofinger_state = GESTURE_2FG_STATE_NONE;
|
|
|
|
|
|
2015-02-16 13:30:24 +01:00
|
|
|
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);
|
|
|
|
|
}
|