/*
 * Copyright © 2015 Red Hat, Inc.
 *
 * 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:
 *
 * 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.
 */

#include "config.h"

#include <math.h>
#include <stdbool.h>

#include "evdev-mt-touchpad.h"

#define QUICK_GESTURE_HOLD_TIMEOUT ms2us(40)
#define DEFAULT_GESTURE_HOLD_TIMEOUT ms2us(180)
#define DEFAULT_GESTURE_SWITCH_TIMEOUT ms2us(100)
#define DEFAULT_GESTURE_SWIPE_TIMEOUT ms2us(150)
#define DEFAULT_GESTURE_PINCH_TIMEOUT ms2us(300)

#define HOLD_AND_MOTION_THRESHOLD 0.5 /* mm */
#define PINCH_DISAMBIGUATION_MOVE_THRESHOLD 1.5 /* mm */

enum gesture_event {
	GESTURE_EVENT_RESET,
	GESTURE_EVENT_FINGER_DETECTED,
	GESTURE_EVENT_HOLD_TIMEOUT,
	GESTURE_EVENT_HOLD_AND_MOTION,
	GESTURE_EVENT_POINTER_MOTION,
	GESTURE_EVENT_SCROLL,
	GESTURE_EVENT_SWIPE,
	GESTURE_EVENT_PINCH,
};

/*****************************************
 * DO NOT EDIT THIS FILE!
 *
 * Look at the state diagram in doc/touchpad-gestures-state-machine.svg
 * (generated with https://www.diagrams.net)
 *
 * Any changes in this file must be represented in the diagram.
 */

static inline const char*
gesture_state_to_str(enum tp_gesture_state state)
{
	switch (state) {
	CASE_RETURN_STRING(GESTURE_STATE_NONE);
	CASE_RETURN_STRING(GESTURE_STATE_UNKNOWN);
	CASE_RETURN_STRING(GESTURE_STATE_HOLD);
	CASE_RETURN_STRING(GESTURE_STATE_HOLD_AND_MOTION);
	CASE_RETURN_STRING(GESTURE_STATE_POINTER_MOTION);
	CASE_RETURN_STRING(GESTURE_STATE_SCROLL);
	CASE_RETURN_STRING(GESTURE_STATE_PINCH);
	CASE_RETURN_STRING(GESTURE_STATE_SWIPE);
	}
	return NULL;
}

static inline const char*
gesture_event_to_str(enum gesture_event event)
{
	switch(event) {
	CASE_RETURN_STRING(GESTURE_EVENT_RESET);
	CASE_RETURN_STRING(GESTURE_EVENT_FINGER_DETECTED);
	CASE_RETURN_STRING(GESTURE_EVENT_HOLD_TIMEOUT);
	CASE_RETURN_STRING(GESTURE_EVENT_HOLD_AND_MOTION);
	CASE_RETURN_STRING(GESTURE_EVENT_POINTER_MOTION);
	CASE_RETURN_STRING(GESTURE_EVENT_SCROLL);
	CASE_RETURN_STRING(GESTURE_EVENT_SWIPE);
	CASE_RETURN_STRING(GESTURE_EVENT_PINCH);
	}
	return NULL;
}

static struct device_float_coords
tp_get_touches_delta(struct tp_dispatch *tp, bool average)
{
	struct tp_touch *t;
	unsigned int i, nactive = 0;
	struct device_float_coords delta = {0.0, 0.0};

	for (i = 0; i < tp->num_slots; i++) {
		t = &tp->touches[i];

		if (!tp_touch_active_for_gesture(tp, t))
			continue;

		nactive++;

		if (t->dirty) {
			struct device_coords d;

			d = tp_get_delta(t);

			delta.x += d.x;
			delta.y += d.y;
		}
	}

	if (!average || nactive == 0)
		return delta;

	delta.x /= nactive;
	delta.y /= nactive;

	return delta;
}

static void
tp_gesture_init_scroll(struct tp_dispatch *tp)
{
	struct phys_coords zero = {0.0, 0.0};
	tp->scroll.active.h = false;
	tp->scroll.active.v = false;
	tp->scroll.duration.h = 0;
	tp->scroll.duration.v = 0;
	tp->scroll.vector = zero;
	tp->scroll.time_prev = 0;
}

static inline struct device_float_coords
tp_get_combined_touches_delta(struct tp_dispatch *tp)
{
	return tp_get_touches_delta(tp, false);
}

static inline struct device_float_coords
tp_get_average_touches_delta(struct tp_dispatch *tp)
{
	return tp_get_touches_delta(tp, true);
}

static void
tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
{
	const struct normalized_coords zero = { 0.0, 0.0 };

	if (tp->gesture.started)
		return;

	switch (tp->gesture.state) {
	case GESTURE_STATE_NONE:
	case GESTURE_STATE_UNKNOWN:
		evdev_log_bug_libinput(tp->device,
				       "%s in unknown gesture mode\n",
				       __func__);
		break;
	case GESTURE_STATE_HOLD:
	case GESTURE_STATE_HOLD_AND_MOTION:
		gesture_notify_hold(&tp->device->base, time,
				    tp->gesture.finger_count);
		break;
	case GESTURE_STATE_SCROLL:
		tp_gesture_init_scroll(tp);
		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;
	case GESTURE_STATE_POINTER_MOTION:
		break;
	}

	tp->gesture.started = true;
}

static struct device_float_coords
tp_get_raw_pointer_motion(struct tp_dispatch *tp)
{
	struct device_float_coords raw;

	/* When a clickpad is clicked, combine motion of all active touches */
	if (tp->buttons.is_clickpad && tp->buttons.state)
		raw = tp_get_combined_touches_delta(tp);
	else
		raw = tp_get_average_touches_delta(tp);

	return raw;
}

static bool
tp_has_pending_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
	struct device_float_coords raw;

	if (!(tp->queued & TOUCHPAD_EVENT_MOTION))
		return false;

	/* Checking for raw pointer motion is enough in this case.
	 * Calling tp_filter_motion is intentionally omitted to avoid calling
	 * it twice (here and in tp_gesture_post_pointer_motion) with the same
	 * event.
	 */
	raw = tp_get_raw_pointer_motion(tp);
	return !device_float_is_zero(raw);
}

static void
tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
	struct device_float_coords raw;
	struct normalized_coords delta;

	raw = tp_get_raw_pointer_motion(tp);
	delta = tp_filter_motion(tp, &raw, time);

	if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
		struct device_float_coords unaccel;

		unaccel = tp_scale_to_xaxis(tp, raw);
		pointer_notify_motion(&tp->device->base,
				      time,
				      &delta,
				      &unaccel);
	}
}

static unsigned int
tp_gesture_get_active_touches(const struct tp_dispatch *tp,
			      struct tp_touch **touches,
			      unsigned int count)
{
	unsigned int n = 0;
	struct tp_touch *t;

	memset(touches, 0, count * sizeof(struct tp_touch *));

	tp_for_each_touch(tp, t) {
		if (tp_touch_active_for_gesture(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 inline int
tp_gesture_same_directions(int dir1, int dir2)
{
	/*
	 * 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.
	 */
	return ((dir1 | (dir1 >> 1)) & dir2) ||
		((dir2 | (dir2 >> 1)) & dir1) ||
		((dir1 & 0x80) && (dir2 & 0x01)) ||
		((dir2 & 0x80) && (dir1 & 0x01));
}

static struct phys_coords
tp_gesture_mm_moved(struct tp_dispatch *tp, struct tp_touch *t)
{
	struct device_coords delta;

	delta.x = abs(t->point.x - t->gesture.initial.x);
	delta.y = abs(t->point.y - t->gesture.initial.y);

	return evdev_device_unit_delta_to_mm(tp->device, &delta);
}

static uint32_t
tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch)
{
	struct phys_coords mm;
	struct device_float_coords delta;

	delta = device_delta(touch->point, touch->gesture.initial);
	mm = tp_phys_delta(tp, delta);

	return phys_get_direction(mm);
}

static void
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);
	*angle = atan2(normalized.y, normalized.x) * 180.0 / M_PI;

	*center = device_average(first->point, second->point);
}

static inline void
tp_gesture_init_pinch(struct tp_dispatch *tp)
{
	tp_gesture_get_pinch_info(tp,
				  &tp->gesture.initial_distance,
				  &tp->gesture.angle,
				  &tp->gesture.center);
	tp->gesture.prev_scale = 1.0;
}

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 void
tp_gesture_apply_scroll_constraints(struct tp_dispatch *tp,
				  struct device_float_coords *raw,
				  struct normalized_coords *delta,
				  uint64_t time)
{
	uint64_t tdelta = 0;
	struct phys_coords delta_mm, vector;
	double vector_decay, vector_length, slope;

	const uint64_t ACTIVE_THRESHOLD = ms2us(100),
		       INACTIVE_THRESHOLD = ms2us(50),
		       EVENT_TIMEOUT = ms2us(100);

	/* Both axes active == true means free scrolling is enabled */
	if (tp->scroll.active.h && tp->scroll.active.v)
		return;

	/* Determine time delta since last movement event */
	if (tp->scroll.time_prev != 0)
		tdelta = time - tp->scroll.time_prev;
	if (tdelta > EVENT_TIMEOUT)
		tdelta = 0;
	tp->scroll.time_prev = time;

	/* Delta since last movement event in mm */
	delta_mm = tp_phys_delta(tp, *raw);

	/* Old vector data "fades" over time. This is a two-part linear
	 * approximation of an exponential function - for example, for
	 * EVENT_TIMEOUT of 100, vector_decay = (0.97)^tdelta. This linear
	 * approximation allows easier tweaking of EVENT_TIMEOUT and is faster.
	 */
	if (tdelta > 0) {
		double recent, later;
		recent = ((EVENT_TIMEOUT / 2.0) - tdelta) /
			 (EVENT_TIMEOUT / 2.0);
		later = (EVENT_TIMEOUT - tdelta) /
			(EVENT_TIMEOUT * 2.0);
		vector_decay = tdelta <= (0.33 * EVENT_TIMEOUT) ?
			       recent : later;
	} else {
		vector_decay = 0.0;
	}

	/* Calculate windowed vector from delta + weighted historic data */
	vector.x = (tp->scroll.vector.x * vector_decay) + delta_mm.x;
	vector.y = (tp->scroll.vector.y * vector_decay) + delta_mm.y;
	vector_length = hypot(vector.x, vector.y);
	tp->scroll.vector = vector;

	/* We care somewhat about distance and speed, but more about
	 * consistency of direction over time. Keep track of the time spent
	 * primarily along each axis. If one axis is active, time spent NOT
	 * moving much in the other axis is subtracted, allowing a switch of
	 * axes in a single scroll + ability to "break out" and go diagonal.
	 *
	 * Slope to degree conversions (infinity = 90°, 0 = 0°):
	 */
	const double DEGREE_75 = 3.73;
	const double DEGREE_60 = 1.73;
	const double DEGREE_30 = 0.57;
	const double DEGREE_15 = 0.27;
	slope = (vector.x != 0) ? fabs(vector.y / vector.x) : INFINITY;

	/* Ensure vector is big enough (in mm per EVENT_TIMEOUT) to be confident
	 * of direction. Larger = harder to enable diagonal/free scrolling.
	 */
	const double MIN_VECTOR = 0.15;

	if (slope >= DEGREE_30 && vector_length > MIN_VECTOR) {
		tp->scroll.duration.v += tdelta;
		if (tp->scroll.duration.v > ACTIVE_THRESHOLD)
			tp->scroll.duration.v = ACTIVE_THRESHOLD;
		if (slope >= DEGREE_75) {
			if (tp->scroll.duration.h > tdelta)
				tp->scroll.duration.h -= tdelta;
			else
				tp->scroll.duration.h = 0;
		}
	}
	if (slope < DEGREE_60  && vector_length > MIN_VECTOR) {
		tp->scroll.duration.h += tdelta;
		if (tp->scroll.duration.h > ACTIVE_THRESHOLD)
			tp->scroll.duration.h = ACTIVE_THRESHOLD;
		if (slope < DEGREE_15) {
			if (tp->scroll.duration.v > tdelta)
				tp->scroll.duration.v -= tdelta;
			else
				tp->scroll.duration.v = 0;
		}
	}

	if (tp->scroll.duration.h == ACTIVE_THRESHOLD) {
		tp->scroll.active.h = true;
		if (tp->scroll.duration.v < INACTIVE_THRESHOLD)
			tp->scroll.active.v = false;
	}
	if (tp->scroll.duration.v == ACTIVE_THRESHOLD) {
		tp->scroll.active.v = true;
		if (tp->scroll.duration.h < INACTIVE_THRESHOLD)
			tp->scroll.active.h = false;
	}

	/* If vector is big enough in a diagonal direction, always unlock
	 * both axes regardless of thresholds
	 */
	if (vector_length > 5.0 && slope < 1.73 && slope >= 0.57) {
		tp->scroll.active.v = true;
		tp->scroll.active.h = true;
	}

	/* If only one axis is active, constrain motion accordingly. If both
	 * are set, we've detected deliberate diagonal movement; enable free
	 * scrolling for the life of the gesture.
	 */
	if (!tp->scroll.active.h && tp->scroll.active.v)
		delta->x = 0.0;
	if (tp->scroll.active.h && !tp->scroll.active.v)
		delta->y = 0.0;

	/* If we haven't determined an axis, use the slope in the meantime */
	if (!tp->scroll.active.h && !tp->scroll.active.v) {
		delta->x = (slope >= DEGREE_60) ? 0.0 : delta->x;
		delta->y = (slope < DEGREE_30) ? 0.0 : delta->y;
	}
}

static inline void
log_gesture_bug(struct tp_dispatch *tp, enum gesture_event event)
{
	evdev_log_bug_libinput(tp->device,
			       "invalid gesture event %s in state %s\n",
			       gesture_event_to_str(event),
			       gesture_state_to_str(tp->gesture.state));
}

static bool
tp_gesture_is_quick_hold(struct tp_dispatch *tp)
{
	/* When 1 or 2 fingers are used to hold, always use a "quick" hold to
	 * make the hold to stop kinetic scrolling user interaction feel more
	 * natural.
	 */
	return (tp->gesture.finger_count == 1) ||
	       (tp->gesture.finger_count == 2);
}

static bool
tp_gesture_use_hold_timer(struct tp_dispatch *tp)
{
	/* When tap is not enabled, always use the timer */
	if (!tp->tap.enabled)
		return true;

	/* Always use the timer if it is a quick hold */
	if (tp_gesture_is_quick_hold(tp))
		return true;

	/* If the number of fingers on the touchpad exceeds the number of
	 * allowed fingers to tap, use the timer.
	 */
	if (tp->gesture.finger_count > 3)
		return true;

	/* If the tap state machine is already in a hold status, for example
	 * when holding with 3 fingers and then holding with 2, use the timer.
	 */
	if (tp->tap.state == TAP_STATE_HOLD ||
	    tp->tap.state == TAP_STATE_TOUCH_2_HOLD ||
	    tp->tap.state == TAP_STATE_TOUCH_3_HOLD)
		return true;

	/* If the tap state machine is in dead status, use the timer. This
	 * happens when the user holds after cancelling a gesture/scroll.
	 */
	if (tp->tap.state == TAP_STATE_DEAD)
		return true;

	/* Otherwise, sync the hold notification with the tap state machine */
	return false;
}

static void
tp_gesture_set_hold_timer(struct tp_dispatch *tp, uint64_t time)
{
	uint64_t timeout;

	if (!tp->gesture.hold_enabled)
		return;

	if (tp_gesture_use_hold_timer(tp)) {
		timeout = tp_gesture_is_quick_hold(tp) ?
			  QUICK_GESTURE_HOLD_TIMEOUT :
			  DEFAULT_GESTURE_HOLD_TIMEOUT;

		libinput_timer_set(&tp->gesture.hold_timer, time + timeout);
	}
}

static void
tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp,
				      enum gesture_event event,
				      uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		break;
	case GESTURE_EVENT_FINGER_DETECTED:
		tp_gesture_set_hold_timer(tp, time);
		tp->gesture.state = GESTURE_STATE_UNKNOWN;
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
		break;
	case GESTURE_EVENT_POINTER_MOTION:
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_SCROLL:
		tp->gesture.state = GESTURE_STATE_SCROLL;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_SWIPE:
	case GESTURE_EVENT_PINCH:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp,
					 enum gesture_event event,
					 uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
		tp->gesture.state = GESTURE_STATE_HOLD;
		tp_gesture_start(tp, time);
		break;
	case GESTURE_EVENT_POINTER_MOTION:
		/* Don't cancel the hold timer. This pointer motion can end up
		 * being recognised as hold and motion. */
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_SCROLL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp_gesture_set_scroll_buildup(tp);
		tp->gesture.state = GESTURE_STATE_SCROLL;
		break;
	case GESTURE_EVENT_SWIPE:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_SWIPE;
		break;
	case GESTURE_EVENT_PINCH:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp_gesture_init_pinch(tp);
		tp->gesture.state = GESTURE_STATE_PINCH;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_FINGER_DETECTED:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp,
				      enum gesture_event event,
				      uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
		tp->gesture.state = GESTURE_STATE_HOLD_AND_MOTION;
		break;
	case GESTURE_EVENT_POINTER_MOTION:
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_SCROLL:
		tp_gesture_set_scroll_buildup(tp);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_SCROLL;
		break;
	case GESTURE_EVENT_SWIPE:
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_SWIPE;
		break;
	case GESTURE_EVENT_PINCH:
		tp_gesture_init_pinch(tp);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_PINCH;
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_FINGER_DETECTED:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_hold_and_motion(struct tp_dispatch *tp,
						 enum gesture_event event,
						 uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_POINTER_MOTION:
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_SCROLL:
	case GESTURE_EVENT_SWIPE:
	case GESTURE_EVENT_PINCH:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_pointer_motion(struct tp_dispatch *tp,
						enum gesture_event event,
						uint64_t time)
{
	struct tp_touch *first;
	struct phys_coords first_moved;
	double first_mm;

	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
		if (tp->gesture.finger_count != 1)
			break;

		first = tp->gesture.touches[0];
		first_moved = tp_gesture_mm_moved(tp, first);
		first_mm = hypot(first_moved.x, first_moved.y);

		if (first_mm < HOLD_AND_MOTION_THRESHOLD) {
			tp->gesture.state = GESTURE_STATE_HOLD_AND_MOTION;
			tp_gesture_start(tp, time);
		}
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_POINTER_MOTION:
	case GESTURE_EVENT_SCROLL:
	case GESTURE_EVENT_SWIPE:
	case GESTURE_EVENT_PINCH:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_scroll(struct tp_dispatch *tp,
					enum gesture_event event,
					uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_PINCH:
		tp_gesture_init_pinch(tp);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_PINCH;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION:
	case GESTURE_EVENT_SCROLL:
	case GESTURE_EVENT_SWIPE:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_pinch(struct tp_dispatch *tp,
				       enum gesture_event event,
				       uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION:
	case GESTURE_EVENT_SCROLL:
	case GESTURE_EVENT_SWIPE:
	case GESTURE_EVENT_PINCH:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_swipe(struct tp_dispatch *tp,
				       enum gesture_event event,
				       uint64_t time)
{
	switch(event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION:
	case GESTURE_EVENT_SCROLL:
	case GESTURE_EVENT_SWIPE:
	case GESTURE_EVENT_PINCH:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event(struct tp_dispatch *tp,
			enum gesture_event event,
			uint64_t time)
{
	enum tp_gesture_state oldstate;

	oldstate = tp->gesture.state;

	switch(tp->gesture.state) {
	case GESTURE_STATE_NONE:
		tp_gesture_handle_event_on_state_none(tp, event, time);
		break;
	case GESTURE_STATE_UNKNOWN:
		tp_gesture_handle_event_on_state_unknown(tp, event, time);
		break;
	case GESTURE_STATE_HOLD:
		tp_gesture_handle_event_on_state_hold(tp, event, time);
		break;
	case GESTURE_STATE_HOLD_AND_MOTION:
		tp_gesture_handle_event_on_state_hold_and_motion(tp, event, time);
		break;
	case GESTURE_STATE_POINTER_MOTION:
		tp_gesture_handle_event_on_state_pointer_motion(tp, event, time);
		break;
	case GESTURE_STATE_SCROLL:
		tp_gesture_handle_event_on_state_scroll(tp, event, time);
		break;
	case GESTURE_STATE_PINCH:
		tp_gesture_handle_event_on_state_pinch(tp, event, time);
		break;
	case GESTURE_STATE_SWIPE:
		tp_gesture_handle_event_on_state_swipe(tp, event, time);
		break;
	}

	if (oldstate != tp->gesture.state) {
		evdev_log_debug(tp->device,
				"gesture state %s → %s → %s\n",
				gesture_state_to_str(oldstate),
				gesture_event_to_str(event),
				gesture_state_to_str(tp->gesture.state));
	}
}

static void
tp_gesture_hold_timeout(uint64_t now, void *data)
{
	struct tp_dispatch *tp = data;

	if (tp_tap_dragging_or_double_tapping(tp) || tp_tap_dragging(tp))
		return;

	tp_gesture_handle_event(tp, GESTURE_EVENT_HOLD_TIMEOUT, now);
}

void
tp_gesture_tap_timeout(struct tp_dispatch *tp, uint64_t time)
{
	if (!tp->gesture.hold_enabled)
		return;

	if (!tp_gesture_is_quick_hold(tp))
		tp_gesture_handle_event(tp, GESTURE_EVENT_HOLD_TIMEOUT, time);
}

static void
tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, uint64_t time)
{
	struct tp_touch *first = tp->gesture.touches[0],
			*second = tp->gesture.touches[1],
			*thumb;
	uint32_t dir1, dir2;
	struct device_coords delta;
	struct phys_coords first_moved, second_moved, distance_mm;
	double first_mm, second_mm; /* movement since gesture start in mm */
	double thumb_mm, finger_mm;
	double min_move = 1.5; /* min movement threshold in mm - count this touch */
	double max_move = 4.0; /* max movement threshold in mm - ignore other touch */
	bool is_hold_and_motion;

	first_moved = tp_gesture_mm_moved(tp, first);
	first_mm = hypot(first_moved.x, first_moved.y);

	if (tp->gesture.finger_count == 1) {
		if (!tp_has_pending_pointer_motion(tp, time))
			return;

		is_hold_and_motion = (first_mm < HOLD_AND_MOTION_THRESHOLD);

		if (tp->gesture.state == GESTURE_STATE_HOLD &&
		    is_hold_and_motion) {
			tp_gesture_handle_event(tp,
						GESTURE_EVENT_HOLD_AND_MOTION,
						time);
			return;
		}

		if (tp->gesture.state == GESTURE_STATE_HOLD_AND_MOTION &&
		    is_hold_and_motion)
			return;

		tp_gesture_handle_event(tp,
					GESTURE_EVENT_POINTER_MOTION,
					time);
		return;
	}

	/* If we have more fingers than slots, we don't know where the
	 * fingers are. Default to swipe */
	if (tp->gesture.enabled && tp->gesture.finger_count > 2 &&
	    tp->gesture.finger_count > tp->num_slots) {
		tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE, time);
		return;
	}

	/* Need more margin for error when there are more fingers */
	max_move += 2.0 * (tp->gesture.finger_count - 2);
	min_move += 0.5 * (tp->gesture.finger_count - 2);

	second_moved = tp_gesture_mm_moved(tp, second);
	second_mm = hypot(second_moved.x, second_moved.y);

	delta.x = abs(first->point.x - second->point.x);
	delta.y = abs(first->point.y - second->point.y);
	distance_mm = evdev_device_unit_delta_to_mm(tp->device, &delta);

	/* If both touches moved less than a mm, we cannot decide yet */
	if (first_mm < 1 && second_mm < 1)
		return;

	/* Pick the thumb as the lowest point on the touchpad */
	if (first->point.y > second->point.y) {
		thumb = first;
		thumb_mm = first_mm;
		finger_mm = second_mm;
	} else {
		thumb = second;
		thumb_mm = second_mm;
		finger_mm = first_mm;
	}

	/* If both touches are within 7mm vertically and 40mm horizontally
	 * past the timeout, assume scroll/swipe */
	if ((!tp->gesture.enabled ||
	     (distance_mm.x < 40.0 && distance_mm.y < 7.0)) &&
	    time > (tp->gesture.initial_time + DEFAULT_GESTURE_SWIPE_TIMEOUT)) {
		if (tp->gesture.finger_count == 2)
			tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
		else
			tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE, time);

		return;
	}

	/* If one touch exceeds the max_move threshold while the other has not
	 * yet passed the min_move threshold, there is either a resting thumb,
	 * or the user is doing "one-finger-scroll," where one touch stays in
	 * place while the other moves.
	 */
	if (first_mm >= max_move || second_mm >= max_move) {
		/* If thumb detection is enabled, and thumb is still while
		 * finger moves, cancel gestures and mark lower as thumb.
		 * This applies to all gestures (2, 3, 4+ fingers), but allows
		 * more thumb motion on >2 finger gestures during detection.
		 */
		if (tp->thumb.detect_thumbs && thumb_mm < min_move) {
			tp_thumb_suppress(tp, thumb);
			tp_gesture_cancel(tp, time);
			return;
		}

		/* If gestures detection is disabled, or if finger is still
		 * while thumb moves, assume this is "one-finger scrolling."
		 * This applies only to 2-finger gestures.
		 */
		if ((!tp->gesture.enabled || finger_mm < min_move) &&
		    tp->gesture.finger_count == 2) {
			tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
			return;
		}

		/* If more than 2 fingers are involved, and the thumb moves
		 * while the fingers stay still, assume a pinch if eligible.
		 */
		if (finger_mm < min_move &&
		    tp->gesture.finger_count > 2 &&
		    tp->gesture.enabled &&
		    tp->thumb.pinch_eligible) {
			tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH, time);
			return;
		}
	}

	/* If either touch is still below the min_move threshold, we can't
	 * tell what kind of gesture this is.
	 */
	if ((first_mm < min_move) || (second_mm < min_move))
		return;

	/* Both touches have exceeded the min_move threshold, so we have a
	 * valid gesture. Update gesture initial time and get directions so
	 * we know if it's a pinch or swipe/scroll.
	 */
	dir1 = tp_gesture_get_direction(tp, first);
	dir2 = tp_gesture_get_direction(tp, second);

	/* If we can't accurately detect pinches, or if the touches are moving
	 * the same way, this is a scroll or swipe.
	 */
	if (tp->gesture.finger_count > tp->num_slots ||
	    tp_gesture_same_directions(dir1, dir2)) {
		if (tp->gesture.finger_count == 2) {
			tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);
			return;
		}

		if (tp->gesture.enabled) {
			tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE, time);
			return;
		}
	}

	/* If the touches are moving away from each other, this is a pinch */
	tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH, time);
}

static bool
tp_gesture_is_pinch(struct tp_dispatch *tp)
{
	struct tp_touch *first = tp->gesture.touches[0],
			*second = tp->gesture.touches[1];
	uint32_t dir1, dir2;
	struct phys_coords first_moved, second_moved;
	double first_mm, second_mm;

	dir1 = tp_gesture_get_direction(tp, first);
	dir2 = tp_gesture_get_direction(tp, second);
	if (tp_gesture_same_directions(dir1, dir2))
		return false;

	first_moved = tp_gesture_mm_moved(tp, first);
	first_mm = hypot(first_moved.x, first_moved.y);
	if (first_mm < PINCH_DISAMBIGUATION_MOVE_THRESHOLD)
		return false;

	second_moved = tp_gesture_mm_moved(tp, second);
	second_mm = hypot(second_moved.x, second_moved.y);
	if (second_mm < PINCH_DISAMBIGUATION_MOVE_THRESHOLD)
		return false;

	return true;
}

static void
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;

	ntouches = tp_gesture_get_active_touches(tp, touches, 4);

	first = touches[0];
	second = touches[1];

	if (ntouches == 0)
		return;

	if (ntouches == 1) {
		first->gesture.initial = first->point;
		tp->gesture.touches[0] = first;

		tp_gesture_handle_event(tp,
					GESTURE_EVENT_FINGER_DETECTED,
					time);
		return;
	}

	if (!tp->gesture.enabled) {
		if (ntouches == 2)
			tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL, time);

		return;
	}

	/* For 3+ finger gestures, we only really need to track two touches.
	 * The human hand's finger arrangement means that for a pinch, the
	 * bottom-most touch will always be the thumb, and the top-most touch
	 * will always be one of the fingers.
	 *
	 * For 3+ finger swipes, the fingers will likely (but not necessarily)
	 * be in a horizontal line. They all move together, regardless, so it
	 * doesn't really matter which two of those touches we track.
	 *
	 * Tracking top and bottom is a change from previous versions, where
	 * we tracked leftmost and rightmost. This change enables:
	 *
	 * - More accurate pinch detection if thumb is near the center
	 * - Better resting-thumb detection while two-finger scrolling
	 * - On capable hardware, allow 3- or 4-finger swipes with resting
	 *   thumb or held-down clickpad
	 */
	if (ntouches > 2) {
		second = touches[0];

		for (i = 1; i < ntouches && i < tp->num_slots; i++) {
			if (touches[i]->point.y < first->point.y)
				first = touches[i];
			else if (touches[i]->point.y >= second->point.y)
				second = touches[i];
		}

		if (first == second)
			return;

	}

	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;

	tp_gesture_handle_event(tp, GESTURE_EVENT_FINGER_DETECTED, time);
}

static void
tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time,
				bool ignore_motion)
{
	if (!ignore_motion)
		tp_gesture_detect_motion_gestures(tp, time);
}

static void
tp_gesture_handle_state_hold(struct tp_dispatch *tp, uint64_t time,
			     bool ignore_motion)
{
	tp_gesture_start(tp, time);

	if (!ignore_motion)
		tp_gesture_detect_motion_gestures(tp, time);
}

static void
tp_gesture_handle_state_hold_and_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
	if (tp->queued & TOUCHPAD_EVENT_MOTION)
		tp_gesture_post_pointer_motion(tp, time);

	tp_gesture_detect_motion_gestures(tp, time);
}

static void
tp_gesture_handle_state_pointer_motion(struct tp_dispatch *tp, uint64_t time)
{
	if (tp->queued & TOUCHPAD_EVENT_MOTION)
		tp_gesture_post_pointer_motion(tp, time);
}

static void
tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
{
	struct device_float_coords raw;
	struct normalized_coords delta;

	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
		return;

	/* We may confuse a pinch for a scroll initially,
	 * allow ourselves to correct our guess.
	 */
	if (time < (tp->gesture.initial_time + DEFAULT_GESTURE_PINCH_TIMEOUT) &&
	    tp_gesture_is_pinch(tp)) {
		tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH, time);
		return;
	}

	raw = tp_get_average_touches_delta(tp);

	/* scroll is not accelerated by default */
	delta = tp_filter_scroll(tp, &raw, time);

	if (normalized_is_zero(delta))
		return;

	tp_gesture_start(tp, time);
	tp_gesture_apply_scroll_constraints(tp, &raw, &delta, time);
	evdev_post_scroll(tp->device,
			  time,
			  LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
			  &delta);
}

static void
tp_gesture_handle_state_swipe(struct tp_dispatch *tp, uint64_t time)
{
	struct device_float_coords raw;
	struct normalized_coords delta, unaccel;

	raw = tp_get_average_touches_delta(tp);
	delta = tp_filter_motion(tp, &raw, time);

	if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
		unaccel = tp_filter_motion_unaccelerated(tp, &raw, time);
		tp_gesture_start(tp, time);
		gesture_notify_swipe(&tp->device->base, time,
				     LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
				     tp->gesture.finger_count,
				     &delta, &unaccel);
	}
}

static void
tp_gesture_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, &center);

	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;

	delta = tp_filter_motion(tp, &fdelta, time);

	if (normalized_is_zero(delta) && device_float_is_zero(fdelta) &&
	    scale == tp->gesture.prev_scale && angle_delta == 0.0)
		return;

	unaccel = tp_filter_motion_unaccelerated(tp, &fdelta, time);
	tp_gesture_start(tp, time);
	gesture_notify_pinch(&tp->device->base, time,
			     LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
			     tp->gesture.finger_count,
			     &delta, &unaccel, scale, angle_delta);

	tp->gesture.prev_scale = scale;
}

static void
tp_gesture_post_gesture(struct tp_dispatch *tp, uint64_t time,
			bool ignore_motion)
{
	if (tp->gesture.state == GESTURE_STATE_NONE)
		tp_gesture_handle_state_none(tp, time);

	if (tp->gesture.state == GESTURE_STATE_UNKNOWN)
		tp_gesture_handle_state_unknown(tp, time, ignore_motion);

	if (tp->gesture.state == GESTURE_STATE_HOLD)
		tp_gesture_handle_state_hold(tp, time, ignore_motion);

	if (tp->gesture.state == GESTURE_STATE_POINTER_MOTION)
		tp_gesture_handle_state_pointer_motion(tp, time);

	if (tp->gesture.state == GESTURE_STATE_HOLD_AND_MOTION)
		tp_gesture_handle_state_hold_and_pointer_motion(tp, time);

	if (tp->gesture.state == GESTURE_STATE_SCROLL)
		tp_gesture_handle_state_scroll(tp, time);

	if (tp->gesture.state == GESTURE_STATE_SWIPE)
		tp_gesture_handle_state_swipe(tp, time);

	if (tp->gesture.state == GESTURE_STATE_PINCH)
		tp_gesture_handle_state_pinch(tp, time);
}

static bool
tp_gesture_thumb_moved(struct tp_dispatch *tp)
{
	struct tp_touch *thumb;
	struct phys_coords thumb_moved;
	double thumb_mm;

	thumb = tp_thumb_get_touch(tp);
	if (!thumb)
		return false;

	if (!tp_touch_active_for_gesture(tp, thumb))
		return false;

	thumb_moved = tp_gesture_mm_moved(tp, thumb);
	thumb_mm = hypot(thumb_moved.x, thumb_moved.y);
	return thumb_mm >= PINCH_DISAMBIGUATION_MOVE_THRESHOLD;
}

void
tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time,
		       bool ignore_motion)
{
	if (tp->gesture.finger_count == 0)
		return;

	/* When tap-and-dragging, force 1fg mode. On clickpads, if the
	 * physical button is down, don't allow gestures unless the button
	 * is held down by a *thumb*, specifically.
	 */
	if (tp_tap_dragging(tp) ||
	    (tp->buttons.is_clickpad && tp->buttons.state &&
	     tp->thumb.state == THUMB_STATE_FINGER)) {
		if (tp->gesture.state != GESTURE_STATE_POINTER_MOTION) {
			tp_gesture_cancel(tp, time);
			tp_gesture_handle_event(tp,
						GESTURE_EVENT_POINTER_MOTION,
						time);
		}
		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;

	/* When pinching, the thumb tends to move slower than the finger,
	 * so we may suppress it too early. Give it some time to move.
	 */
	if (time < (tp->gesture.initial_time + DEFAULT_GESTURE_PINCH_TIMEOUT) &&
	    tp_gesture_thumb_moved(tp))
		tp_thumb_reset(tp);

	if (tp->gesture.finger_count <= 4)
		tp_gesture_post_gesture(tp, time, ignore_motion);
}

void
tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
{
	if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
		return;

	evdev_stop_scroll(tp->device,
			  time,
			  LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
}

static void
tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled)
{
	enum tp_gesture_state state = tp->gesture.state;

	if (!tp->gesture.started) {
		tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
		return;
	}

	switch (state) {
	case GESTURE_STATE_NONE:
	case GESTURE_STATE_UNKNOWN:
		evdev_log_bug_libinput(tp->device,
				       "%s in unknown gesture mode\n",
				       __func__);
		break;
	case GESTURE_STATE_HOLD:
	case GESTURE_STATE_HOLD_AND_MOTION:
		gesture_notify_hold_end(&tp->device->base, time,
					tp->gesture.finger_count, cancelled);
		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;
	case GESTURE_STATE_SWIPE:
		gesture_notify_swipe_end(&tp->device->base,
					 time,
					 tp->gesture.finger_count,
					 cancelled);
		break;
	case GESTURE_STATE_POINTER_MOTION:
		break;
	}

	tp->gesture.started = false;
	tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
}

void
tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time)
{
	tp_gesture_end(tp, time, true);
}

void
tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, uint64_t time)
{
	if (tp->gesture.started && tp->gesture.state != GESTURE_STATE_HOLD)
		tp_gesture_end(tp, time, true);
}

void
tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
{
	tp_gesture_end(tp, time, false);
}

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;

	tp_gesture_cancel(tp, now); /* End current gesture */
	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;

	tp_for_each_touch(tp, t) {
		if (tp_touch_active_for_gesture(tp, t))
			active_touches++;
	}

	if (active_touches != tp->gesture.finger_count) {
		/* If all fingers are lifted immediately end the gesture */
		if (active_touches == 0) {
			tp_gesture_stop(tp, time);
			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;
			/* If in UNKNOWN or POINTER_MOTION state, go back to
			 * NONE to re-evaluate leftmost and rightmost touches
			 */
			if (tp->gesture.state == GESTURE_STATE_UNKNOWN ||
			    tp->gesture.state == GESTURE_STATE_POINTER_MOTION) {
				tp_gesture_handle_event(tp,
							GESTURE_EVENT_RESET,
							time);
			}
		/* 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;
	}
}

static bool
tp_gesture_are_gestures_enabled(struct tp_dispatch *tp)
{
	return (!tp->semi_mt && tp->num_slots > 1);
}

static enum libinput_config_status
tp_gesture_set_hold_enabled(struct libinput_device *device,
			    enum libinput_config_hold_state enabled)
{
	struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
	struct tp_dispatch *tp = tp_dispatch(dispatch);

	if (!tp_gesture_are_gestures_enabled(tp))
		return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;

	tp->gesture.hold_enabled = (enabled == LIBINPUT_CONFIG_HOLD_ENABLED);

	return LIBINPUT_CONFIG_STATUS_SUCCESS;
}

static enum libinput_config_hold_state
tp_gesture_is_hold_enabled(struct libinput_device *device)
{
	struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
	struct tp_dispatch *tp = tp_dispatch(dispatch);

	return tp->gesture.hold_enabled ? LIBINPUT_CONFIG_HOLD_ENABLED :
					  LIBINPUT_CONFIG_HOLD_DISABLED;
}

static enum libinput_config_hold_state
tp_gesture_get_hold_default(struct libinput_device *device)
{
	struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
	struct tp_dispatch *tp = tp_dispatch(dispatch);

	return tp_gesture_are_gestures_enabled(tp) ?
	       LIBINPUT_CONFIG_HOLD_ENABLED :
	       LIBINPUT_CONFIG_HOLD_DISABLED;
}

void
tp_init_gesture(struct tp_dispatch *tp)
{
	char timer_name[64];

	tp->gesture.config.set_hold_enabled = tp_gesture_set_hold_enabled;
	tp->gesture.config.get_hold_enabled = tp_gesture_is_hold_enabled;
	tp->gesture.config.get_hold_default = tp_gesture_get_hold_default;
	tp->device->base.config.gesture = &tp->gesture.config;

	/* two-finger scrolling is always enabled, this flag just
	 * decides whether we detect pinch. semi-mt devices are too
	 * unreliable to do pinch gestures. */
	tp->gesture.enabled = tp_gesture_are_gestures_enabled(tp);

	tp->gesture.state = GESTURE_STATE_NONE;
	tp->gesture.hold_enabled = tp_gesture_are_gestures_enabled(tp);

	snprintf(timer_name,
		 sizeof(timer_name),
		 "%s gestures",
		 evdev_device_get_sysname(tp->device));
	libinput_timer_init(&tp->gesture.finger_count_switch_timer,
			    tp_libinput_context(tp),
			    timer_name,
			    tp_gesture_finger_count_switch_timeout, tp);

	snprintf(timer_name,
		 sizeof(timer_name),
		 "%s hold",
		 evdev_device_get_sysname(tp->device));
	libinput_timer_init(&tp->gesture.hold_timer,
			    tp_libinput_context(tp),
			    timer_name,
			    tp_gesture_hold_timeout, tp);
}

void
tp_remove_gesture(struct tp_dispatch *tp)
{
	libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
	libinput_timer_cancel(&tp->gesture.hold_timer);
}
