/*
 * 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"

enum gesture_cancelled {
	END_GESTURE = 0,
	CANCEL_GESTURE = 1,
};

#define QUICK_GESTURE_HOLD_TIMEOUT usec_from_millis(40)
#define DEFAULT_GESTURE_HOLD_TIMEOUT usec_from_millis(180)
#define DEFAULT_GESTURE_SWITCH_TIMEOUT usec_from_millis(100)
#define DEFAULT_GESTURE_SWIPE_TIMEOUT usec_from_millis(150)
#define DEFAULT_GESTURE_PINCH_TIMEOUT usec_from_millis(300)
#define DRAG_3FG_OR_SWIPE_TIMEOUT usec_from_millis(80)

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

enum gesture_event {
	GESTURE_EVENT_RESET,
	GESTURE_EVENT_END,
	GESTURE_EVENT_CANCEL,
	GESTURE_EVENT_FINGER_DETECTED,
	GESTURE_EVENT_FINGER_SWITCH_TIMEOUT,
	GESTURE_EVENT_TAP_TIMEOUT,
	GESTURE_EVENT_HOLD_TIMEOUT,
	GESTURE_EVENT_HOLD_AND_MOTION_START,
	GESTURE_EVENT_POINTER_MOTION_START,
	GESTURE_EVENT_SCROLL_START,
	GESTURE_EVENT_SWIPE_START,
	GESTURE_EVENT_PINCH_START,
	GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
	GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT,
	GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT,
};

/*****************************************
 * 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_START);
	CASE_RETURN_STRING(GESTURE_STATE_SCROLL);
	CASE_RETURN_STRING(GESTURE_STATE_PINCH_START);
	CASE_RETURN_STRING(GESTURE_STATE_PINCH);
	CASE_RETURN_STRING(GESTURE_STATE_SWIPE_START);
	CASE_RETURN_STRING(GESTURE_STATE_SWIPE);
	CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_OR_SWIPE_START);
	CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_OR_SWIPE);
	CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_START);
	CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG);
	CASE_RETURN_STRING(GESTURE_STATE_3FG_DRAG_RELEASED);
	}
	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_END);
	CASE_RETURN_STRING(GESTURE_EVENT_CANCEL);
	CASE_RETURN_STRING(GESTURE_EVENT_FINGER_DETECTED);
	CASE_RETURN_STRING(GESTURE_EVENT_FINGER_SWITCH_TIMEOUT);
	CASE_RETURN_STRING(GESTURE_EVENT_TAP_TIMEOUT);
	CASE_RETURN_STRING(GESTURE_EVENT_HOLD_TIMEOUT);
	CASE_RETURN_STRING(GESTURE_EVENT_HOLD_AND_MOTION_START);
	CASE_RETURN_STRING(GESTURE_EVENT_POINTER_MOTION_START);
	CASE_RETURN_STRING(GESTURE_EVENT_SCROLL_START);
	CASE_RETURN_STRING(GESTURE_EVENT_SWIPE_START);
	CASE_RETURN_STRING(GESTURE_EVENT_PINCH_START);
	CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START);
	CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT);
	CASE_RETURN_STRING(GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT);
	}
	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 = usec_from_uint64_t(0);
	tp->scroll.duration.v = usec_from_uint64_t(0);
	tp->scroll.vector = zero;
	tp->scroll.time_prev = usec_from_uint64_t(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 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, usec_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, usec_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 = rad2deg(atan2(normalized.y, normalized.x));

	*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,
				    usec_t time)
{
	usec_t tdelta = usec_from_millis(0);
	struct phys_coords delta_mm, vector;
	double vector_decay, vector_length, slope;

	const usec_t ACTIVE_THRESHOLD = usec_from_millis(100),
		     INACTIVE_THRESHOLD = usec_from_millis(50),
		     EVENT_TIMEOUT = usec_from_millis(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 (!usec_is_zero(tp->scroll.time_prev)) {
		usec_t diff = usec_delta(time, tp->scroll.time_prev);
		if (usec_cmp(diff, EVENT_TIMEOUT) <= 0)
			tdelta = diff;
	}

	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 (usec_gt(tdelta, 0)) {
		uint64_t delta = usec_as_uint64_t(tdelta);
		double recent, later;
		uint64_t timeout_us = usec_as_uint64_t(EVENT_TIMEOUT);
		recent = ((timeout_us / 2.0) - delta) / (timeout_us / 2.0);
		later = (timeout_us - delta) / (timeout_us * 2.0);
		vector_decay = delta <= (0.33 * timeout_us) ? 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 = usec_add(tp->scroll.duration.v, tdelta);
		if (usec_cmp(tp->scroll.duration.v, ACTIVE_THRESHOLD) > 0)
			tp->scroll.duration.v = ACTIVE_THRESHOLD;
		if (slope >= DEGREE_75) {
			if (usec_cmp(tp->scroll.duration.h, tdelta) > 0)
				tp->scroll.duration.h =
					usec_sub(tp->scroll.duration.h, tdelta);
			else
				tp->scroll.duration.h = usec_from_uint64_t(0);
		}
	}
	if (slope < DEGREE_60 && vector_length > MIN_VECTOR) {
		tp->scroll.duration.h = usec_add(tp->scroll.duration.h, tdelta);
		if (usec_cmp(tp->scroll.duration.h, ACTIVE_THRESHOLD) > 0)
			tp->scroll.duration.h = ACTIVE_THRESHOLD;
		if (slope < DEGREE_15) {
			if (usec_cmp(tp->scroll.duration.v, tdelta) > 0)
				tp->scroll.duration.v =
					usec_sub(tp->scroll.duration.v, tdelta);
			else
				tp->scroll.duration.v = usec_from_uint64_t(0);
		}
	}

	if (usec_cmp(tp->scroll.duration.h, ACTIVE_THRESHOLD) == 0) {
		tp->scroll.active.h = true;
		if (usec_cmp(tp->scroll.duration.v, INACTIVE_THRESHOLD) < 0)
			tp->scroll.active.v = false;
	}
	if (usec_cmp(tp->scroll.duration.v, ACTIVE_THRESHOLD) == 0) {
		tp->scroll.active.v = true;
		if (usec_cmp(tp->scroll.duration.h, INACTIVE_THRESHOLD) < 0)
			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, usec_t time)
{
	usec_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, usec_add(time, timeout));
	}
}

static void
tp_gesture_handle_event_on_state_none(struct tp_dispatch *tp,
				      enum gesture_event event,
				      usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		break;
	case GESTURE_EVENT_FINGER_DETECTED:
		/* Note: this makes 3fg drag more responsive but disables
		 * 3fg pinch/hold. Those are niche enough to not worry about
		 * for now.
		 */
		if (!tp->tap.enabled &&
		    tp->drag_3fg.nfingers == tp->gesture.finger_count) {
			tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
		} else {
			tp_gesture_set_hold_timer(tp, time);
			tp->gesture.state = GESTURE_STATE_UNKNOWN;
		}
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
		break;
	case GESTURE_EVENT_POINTER_MOTION_START:
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_SCROLL_START:
		tp->gesture.state = GESTURE_STATE_SCROLL_START;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_set_3fg_drag_3fg_or_swipe_timer(struct tp_dispatch *tp, usec_t time)
{
	usec_t expire = usec_add(tp->gesture.initial_time, DRAG_3FG_OR_SWIPE_TIMEOUT);

	/* This is a hack to avoid the state machine getting even more complicated.
	 * For a slow drag/fast swipe we want the time from the *initial* touch point,
	 * not the time from when we realised the fingers are moving. IOW
	 * putting 3fg down, resting for 80ms and then moving fast must trigger
	 * a drag, not a swipe.
	 *
	 * In theory we should set the timer in the NONE/UNKNOWN states but that would
	 * require a whole parallel set of states like
	 * NONE_BUT_TIMEOUT_FOR_FAST_STATE_EXPIRED. Let's not do that, instead we set a
	 * negative timer and let the normal state proceed. Either we moved by the
	 * threshold already (in which case we shouldn't ever get here anyway) or
	 * we didn't in which case the neg timer will do the right thing too when it
	 * fires.
	 */
	if (usec_cmp(expire, time) < 0)
		libinput_timer_set_flags(&tp->gesture.drag_3fg_or_swipe_timer,
					 expire,
					 TIMER_FLAG_ALLOW_NEGATIVE);
	else
		libinput_timer_set(&tp->gesture.drag_3fg_or_swipe_timer, expire);
}

static void
tp_gesture_handle_event_on_state_unknown(struct tp_dispatch *tp,
					 enum gesture_event event,
					 usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
		tp->gesture.state = GESTURE_STATE_HOLD;
		gesture_notify_hold_begin(&tp->device->base,
					  time,
					  tp->gesture.finger_count);
		break;
	case GESTURE_EVENT_POINTER_MOTION_START:
		/* 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_START:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp_gesture_set_scroll_buildup(tp);
		tp->gesture.state = GESTURE_STATE_SCROLL_START;
		break;
	case GESTURE_EVENT_SWIPE_START:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_SWIPE_START;
		break;
	case GESTURE_EVENT_PINCH_START:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp_gesture_init_pinch(tp);
		tp->gesture.state = GESTURE_STATE_PINCH_START;
		break;
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_hold(struct tp_dispatch *tp,
				      enum gesture_event event,
				      usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL: {
		bool cancelled = event == GESTURE_EVENT_CANCEL;
		gesture_notify_hold_end(&tp->device->base,
					time,
					tp->gesture.finger_count,
					cancelled);
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	}
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		tp_gesture_cancel(tp, time);
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
		tp->gesture.state = GESTURE_STATE_HOLD_AND_MOTION;
		break;
	case GESTURE_EVENT_POINTER_MOTION_START:
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_SCROLL_START:
		tp_gesture_set_scroll_buildup(tp);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_SCROLL_START;
		break;
	case GESTURE_EVENT_SWIPE_START:
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_SWIPE_START;
		break;
	case GESTURE_EVENT_PINCH_START:
		tp_gesture_cancel(tp, time);
		tp_gesture_init_pinch(tp);
		tp->gesture.state = GESTURE_STATE_PINCH_START;
		break;
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE_START;
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		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,
						 usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL: {
		bool cancelled = event == GESTURE_EVENT_CANCEL;
		gesture_notify_hold_end(&tp->device->base,
					time,
					tp->gesture.finger_count,
					cancelled);
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	}
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		tp_gesture_cancel(tp, time);
		break;
	case GESTURE_EVENT_POINTER_MOTION_START:
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

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

	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_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;
			gesture_notify_hold_begin(&tp->device->base,
						  time,
						  tp->gesture.finger_count);
		}
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_scroll_start(struct tp_dispatch *tp,
					      enum gesture_event event,
					      usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_PINCH_START:
		tp_gesture_init_pinch(tp);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_PINCH_START;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_scroll(struct tp_dispatch *tp,
					enum gesture_event event,
					usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		tp_gesture_stop_twofinger_scroll(tp, time);
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		tp_gesture_cancel(tp, time);
		break;
	case GESTURE_EVENT_PINCH_START:
		tp_gesture_init_pinch(tp);
		tp_gesture_cancel(tp, time);
		tp->gesture.state = GESTURE_STATE_PINCH_START;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_pinch_start(struct tp_dispatch *tp,
					     enum gesture_event event,
					     usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_pinch(struct tp_dispatch *tp,
				       enum gesture_event event,
				       usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL: {
		bool cancelled = event == GESTURE_EVENT_CANCEL;
		gesture_notify_pinch_end(&tp->device->base,
					 time,
					 tp->gesture.finger_count,
					 tp->gesture.prev_scale,
					 cancelled);
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	}
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		tp_gesture_cancel(tp, time);
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_swipe_start(struct tp_dispatch *tp,
					     enum gesture_event event,
					     usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_swipe(struct tp_dispatch *tp,
				       enum gesture_event event,
				       usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL: {
		bool cancelled = event == GESTURE_EVENT_CANCEL;
		gesture_notify_swipe_end(&tp->device->base,
					 time,
					 tp->gesture.finger_count,
					 cancelled);
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	}
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		tp_gesture_cancel(tp, time);
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_3fg_drag_or_swipe_start(struct tp_dispatch *tp,
							 enum gesture_event event,
							 usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_3fg_drag_or_swipe(struct tp_dispatch *tp,
						   enum gesture_event event,
						   usec_t time)
{
	struct tp_touch *first = tp->gesture.touches[0],
			*second = tp->gesture.touches[1];
	struct phys_coords first_moved, second_moved;
	double first_mm, second_mm;

	switch (event) {
	case GESTURE_EVENT_RESET:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL: {
		bool cancelled = event == GESTURE_EVENT_CANCEL;
		gesture_notify_swipe_end(&tp->device->base,
					 time,
					 tp->gesture.finger_count,
					 cancelled);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	}
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
		libinput_timer_cancel(&tp->gesture.drag_3fg_or_swipe_timer);

		first_moved = tp_gesture_mm_moved(tp, first);
		second_moved = tp_gesture_mm_moved(tp, second);
		first_mm = hypot(first_moved.x, first_moved.y);
		second_mm = hypot(second_moved.x, second_moved.y);
		if ((first_mm + second_mm) / 2.0 >= DRAG_3FG_OR_SWIPE_MOVE_THRESHOLD) {
			tp->gesture.state = GESTURE_STATE_SWIPE;
		} else {
			/* Cancel the swipe */
			tp_gesture_cancel(tp, time);
			tp->gesture.state = GESTURE_STATE_3FG_DRAG_START;
		}
		break;
	}
}

static void
tp_gesture_handle_event_on_state_3fg_drag_start(struct tp_dispatch *tp,
						enum gesture_event event,
						usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
		libinput_timer_cancel(&tp->gesture.hold_timer);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_TAP_TIMEOUT:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_set_3fg_drag_timer(struct tp_dispatch *tp, usec_t time)
{
	tp->gesture.drag_3fg_release_time = time;
	libinput_timer_set(&tp->gesture.drag_3fg_timer, usec_add_millis(time, 700));
}

static void
tp_gesture_handle_event_on_state_3fg_drag(struct tp_dispatch *tp,
					  enum gesture_event event,
					  usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_CANCEL:
		/* If the gesture is cancelled we release the button immediately */
		evdev_pointer_notify_button(tp->device,
					    tp->gesture.drag_3fg_release_time,
					    evdev_usage_from(EVDEV_BTN_LEFT),
					    LIBINPUT_BUTTON_STATE_RELEASED);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_END:
		/* If the gesture ends we start the timer so we
		 * can keep dragging */
		tp_gesture_set_3fg_drag_timer(tp, time);
		tp->gesture.state = GESTURE_STATE_3FG_DRAG_RELEASED;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
		if (tp->gesture.finger_count_pending < 2) {
			evdev_pointer_notify_button(tp->device,
						    tp->gesture.drag_3fg_release_time,
						    evdev_usage_from(EVDEV_BTN_LEFT),
						    LIBINPUT_BUTTON_STATE_RELEASED);
			tp->gesture.state = GESTURE_STATE_NONE;
		}
		break;
	case GESTURE_EVENT_TAP_TIMEOUT:
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_FINGER_DETECTED:
	case GESTURE_EVENT_HOLD_TIMEOUT:
	case GESTURE_EVENT_POINTER_MOTION_START:
	case GESTURE_EVENT_SCROLL_START:
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event_on_state_3fg_drag_released(struct tp_dispatch *tp,
						   enum gesture_event event,
						   usec_t time)
{
	switch (event) {
	case GESTURE_EVENT_RESET:
		log_gesture_bug(tp, event);
		break;
	case GESTURE_EVENT_END:
	case GESTURE_EVENT_CANCEL:
	case GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT:
		libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
		libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
		evdev_pointer_notify_button(tp->device,
					    tp->gesture.drag_3fg_release_time,
					    evdev_usage_from(EVDEV_BTN_LEFT),
					    LIBINPUT_BUTTON_STATE_RELEASED);
		tp->gesture.state = GESTURE_STATE_NONE;
		break;
	case GESTURE_EVENT_FINGER_SWITCH_TIMEOUT:
	case GESTURE_EVENT_TAP_TIMEOUT:
		if (tp->gesture.finger_count_pending == tp->drag_3fg.nfingers) {
			libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
			tp->gesture.state = GESTURE_STATE_3FG_DRAG;
		}
		break;
	case GESTURE_EVENT_FINGER_DETECTED:
		break;
	case GESTURE_EVENT_POINTER_MOTION_START:
		libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
		evdev_pointer_notify_button(tp->device,
					    tp->gesture.drag_3fg_release_time,
					    evdev_usage_from(EVDEV_BTN_LEFT),
					    LIBINPUT_BUTTON_STATE_RELEASED);
		tp->gesture.state = GESTURE_STATE_POINTER_MOTION;
		break;
	case GESTURE_EVENT_HOLD_AND_MOTION_START:
	case GESTURE_EVENT_HOLD_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	/* Anything that's detected as gesture in this state
	 * will be continue the current 3fg drag gesture */
	case GESTURE_EVENT_SCROLL_START:
		libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
		evdev_pointer_notify_button(tp->device,
					    tp->gesture.drag_3fg_release_time,
					    evdev_usage_from(EVDEV_BTN_LEFT),
					    LIBINPUT_BUTTON_STATE_RELEASED);
		tp->gesture.state = GESTURE_STATE_SCROLL_START;
		break;
	case GESTURE_EVENT_SWIPE_START:
	case GESTURE_EVENT_PINCH_START:
		libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
		tp->gesture.state = GESTURE_STATE_3FG_DRAG;
		break;
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START:
	case GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT:
		log_gesture_bug(tp, event);
		break;
	}
}

static void
tp_gesture_handle_event(struct tp_dispatch *tp, enum gesture_event event, usec_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_START:
		tp_gesture_handle_event_on_state_scroll_start(tp, event, time);
		break;
	case GESTURE_STATE_SCROLL:
		tp_gesture_handle_event_on_state_scroll(tp, event, time);
		break;
	case GESTURE_STATE_PINCH_START:
		tp_gesture_handle_event_on_state_pinch_start(tp, event, time);
		break;
	case GESTURE_STATE_PINCH:
		tp_gesture_handle_event_on_state_pinch(tp, event, time);
		break;
	case GESTURE_STATE_SWIPE_START:
		tp_gesture_handle_event_on_state_swipe_start(tp, event, time);
		break;
	case GESTURE_STATE_SWIPE:
		tp_gesture_handle_event_on_state_swipe(tp, event, time);
		break;
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
		tp_gesture_handle_event_on_state_3fg_drag_or_swipe_start(tp,
									 event,
									 time);
		break;
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
		tp_gesture_handle_event_on_state_3fg_drag_or_swipe(tp, event, time);
		break;
	case GESTURE_STATE_3FG_DRAG_START:
		tp_gesture_handle_event_on_state_3fg_drag_start(tp, event, time);
		break;
	case GESTURE_STATE_3FG_DRAG:
		tp_gesture_handle_event_on_state_3fg_drag(tp, event, time);
		break;
	case GESTURE_STATE_3FG_DRAG_RELEASED:
		tp_gesture_handle_event_on_state_3fg_drag_released(tp, event, time);
		break;
	}

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

static void
tp_gesture_hold_timeout(usec_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, usec_t time)
{
	if (!tp->gesture.hold_enabled)
		return;

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

static void
tp_gesture_3fg_drag_timeout(usec_t now, void *data)
{
	struct tp_dispatch *tp = data;

	tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_RELEASE_TIMEOUT, now);
}

static void
tp_gesture_3fg_drag_or_swipe_timeout(usec_t now, void *data)
{
	struct tp_dispatch *tp = data;

	tp_gesture_handle_event(tp, GESTURE_EVENT_3FG_DRAG_OR_SWIPE_TIMEOUT, now);
}

static void
tp_gesture_detect_motion_gestures(struct tp_dispatch *tp, usec_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 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_START,
						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_START, time);
		return;
	}

	/* If we have more fingers than slots, we don't know where the
	 * fingers are. Default to swipe/3fg drag */
	if (tp->gesture.enabled && tp->gesture.finger_count > 2 &&
	    tp->gesture.finger_count > tp->num_slots) {
		if (tp->drag_3fg.nfingers == tp->gesture.finger_count)
			tp_gesture_handle_event(tp,
						GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
						time);
		else
			tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, 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;

	/* 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)) &&
	    usec_cmp(time,
		     usec_add(tp->gesture.initial_time,
			      DEFAULT_GESTURE_SWIPE_TIMEOUT)) > 0) {
		if (tp->gesture.finger_count == 2)
			tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, time);
		else if (tp->drag_3fg.nfingers == tp->gesture.finger_count)
			tp_gesture_handle_event(tp,
						GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
						time);
		else
			tp_gesture_handle_event(tp, GESTURE_EVENT_SWIPE_START, time);

		return;
	}

	/* If 3fg dragging touches are within a 60x10mm box, start
	 * dragging (or swiping) immediately */
	if (tp->gesture.finger_count == tp->drag_3fg.nfingers && distance_mm.x < 60.0 &&
	    distance_mm.y < 10.0) {
		tp_gesture_handle_event(tp,
					GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
					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) {
		double thumb_mm, finger_mm;

		/* 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 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_START, 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_START, 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_START, time);
			return;
		}

		if (tp->drag_3fg.nfingers == tp->gesture.finger_count) {
			tp_gesture_handle_event(tp,
						GESTURE_EVENT_3FG_DRAG_OR_SWIPE_START,
						time);
			return;
		}

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

	/* If the touches are moving away from each other, this is a pinch */
	tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, 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, usec_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 && !tp->tap.enabled && ntouches == 2) {
		tp_gesture_handle_event(tp, GESTURE_EVENT_SCROLL_START, 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, usec_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, usec_t time, bool ignore_motion)
{
	if (!ignore_motion)
		tp_gesture_detect_motion_gestures(tp, time);
}

static void
tp_gesture_handle_state_hold_and_pointer_motion(struct tp_dispatch *tp, usec_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, usec_t time)
{
	if (tp->queued & TOUCHPAD_EVENT_MOTION)
		tp_gesture_post_pointer_motion(tp, time);
}

static void
tp_gesture_handle_state_scroll_start(struct tp_dispatch *tp, usec_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 (usec_cmp(time,
		     usec_add(tp->gesture.initial_time,
			      DEFAULT_GESTURE_PINCH_TIMEOUT)) < 0 &&
	    tp_gesture_is_pinch(tp)) {
		tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, 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_init_scroll(tp);
	tp->gesture.state = GESTURE_STATE_SCROLL;
}

static void
tp_gesture_handle_state_scroll(struct tp_dispatch *tp, usec_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 (usec_cmp(time,
		     usec_add(tp->gesture.initial_time,
			      DEFAULT_GESTURE_PINCH_TIMEOUT)) < 0 &&
	    tp_gesture_is_pinch(tp)) {
		tp_gesture_handle_event(tp, GESTURE_EVENT_PINCH_START, 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_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_start(struct tp_dispatch *tp, usec_t time)
{
	struct device_float_coords raw;
	struct normalized_coords delta;

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

	if (!normalized_is_zero(delta) || !device_float_is_zero(raw)) {
		const struct normalized_coords zero = { 0.0, 0.0 };
		gesture_notify_swipe(&tp->device->base,
				     time,
				     LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
				     tp->gesture.finger_count,
				     &zero,
				     &zero);
		tp->gesture.state = GESTURE_STATE_SWIPE;
	}
}

static void
tp_gesture_handle_state_swipe(struct tp_dispatch *tp, usec_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);
		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_start(struct tp_dispatch *tp, usec_t time)
{
	const struct normalized_coords zero = { 0.0, 0.0 };
	double angle, angle_delta, distance, scale;
	struct device_float_coords center, fdelta;
	struct normalized_coords delta;

	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;

	gesture_notify_pinch(&tp->device->base,
			     time,
			     LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
			     tp->gesture.finger_count,
			     &zero,
			     &zero,
			     1.0,
			     0.0);

	tp->gesture.prev_scale = scale;
	tp->gesture.state = GESTURE_STATE_PINCH;
}

static void
tp_gesture_handle_state_pinch(struct tp_dispatch *tp, usec_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);
	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_handle_state_3fg_drag_start(struct tp_dispatch *tp, usec_t time)
{
	evdev_pointer_notify_button(tp->device,
				    time,
				    evdev_usage_from(EVDEV_BTN_LEFT),
				    LIBINPUT_BUTTON_STATE_PRESSED);
	/* FIXME: immediately send a motion event? */
	tp->gesture.state = GESTURE_STATE_3FG_DRAG;
}

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

static void
tp_gesture_handle_state_3fg_drag_released(struct tp_dispatch *tp,
					  usec_t time,
					  bool ignore_motion)
{
	tp_gesture_detect_motion_gestures(tp, time);
}

static void
tp_gesture_handle_state_3fg_drag_or_swipe(struct tp_dispatch *tp, usec_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);
		gesture_notify_swipe(&tp->device->base,
				     time,
				     LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
				     tp->gesture.finger_count,
				     &delta,
				     &unaccel);
	}
}

static void
tp_gesture_handle_state_3fg_drag_or_swipe_start(struct tp_dispatch *tp, usec_t time)
{
	const struct normalized_coords zero = { 0.0, 0.0 };
	gesture_notify_swipe(&tp->device->base,
			     time,
			     LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
			     tp->gesture.finger_count,
			     &zero,
			     &zero);
	tp->gesture.state = GESTURE_STATE_3FG_DRAG_OR_SWIPE;
	tp_gesture_set_3fg_drag_3fg_or_swipe_timer(tp, time);
}

static void
tp_gesture_handle_state(struct tp_dispatch *tp, usec_t time, bool ignore_motion)
{
	enum tp_gesture_state oldstate = tp->gesture.state;
	enum tp_gesture_state transitions[18] = { 0 };
	enum tp_gesture_state *transition_state = transitions;

#define REMEMBER_TRANSITION(_ts, _state) { \
		if (*(_ts) != (_state)) { \
			++(_ts); \
			assert((_ts) < transitions + ARRAY_LENGTH(transitions)); \
			*(_ts) = _state; \
		} \
	}

	*transition_state = tp->gesture.state;
	if (tp->gesture.state == GESTURE_STATE_NONE) {
		tp_gesture_handle_state_none(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_UNKNOWN) {
		tp_gesture_handle_state_unknown(tp, time, ignore_motion);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_HOLD) {
		tp_gesture_handle_state_hold(tp, time, ignore_motion);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_POINTER_MOTION) {
		tp_gesture_handle_state_pointer_motion(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_HOLD_AND_MOTION) {
		tp_gesture_handle_state_hold_and_pointer_motion(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_SCROLL) {
		tp_gesture_handle_state_scroll(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_SCROLL_START) {
		tp_gesture_handle_state_scroll_start(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_SWIPE) {
		tp_gesture_handle_state_swipe(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_SWIPE_START) {
		tp_gesture_handle_state_swipe_start(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_PINCH) {
		tp_gesture_handle_state_pinch(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_PINCH_START) {
		tp_gesture_handle_state_pinch_start(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_3FG_DRAG) {
		tp_gesture_handle_state_3fg_drag(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_START) {
		tp_gesture_handle_state_3fg_drag_start(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_RELEASED) {
		tp_gesture_handle_state_3fg_drag_released(tp, time, ignore_motion);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_OR_SWIPE) {
		tp_gesture_handle_state_3fg_drag_or_swipe(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}
	if (tp->gesture.state == GESTURE_STATE_3FG_DRAG_OR_SWIPE_START) {
		tp_gesture_handle_state_3fg_drag_or_swipe_start(tp, time);
		REMEMBER_TRANSITION(transition_state, tp->gesture.state);
	}

#undef REMEMBER_TRANSITION

	if (oldstate != tp->gesture.state) {
		_autostrvfree_ char **states = NULL;
		states = strv_append_strdup(states, gesture_state_to_str(oldstate));
		for (enum tp_gesture_state *s = transitions + 1; s < transition_state;
		     s++) {
			states = strv_append_strdup(states, gesture_state_to_str(*s));
		}
		states = strv_append_strdup(states,
					    gesture_state_to_str(tp->gesture.state));
		_autofree_ char *str = strv_join(states, " → ");
		evdev_log_debug(tp->device,
				"gesture: [%dfg] state %s\n",
				tp->gesture.finger_count,
				str);
	}
}

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, usec_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_START,
						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 (usec_cmp(time,
		     usec_add(tp->gesture.initial_time,
			      DEFAULT_GESTURE_PINCH_TIMEOUT)) < 0 &&
	    tp_gesture_thumb_moved(tp))
		tp_thumb_reset(tp);

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

void
tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, usec_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, usec_t time, enum gesture_cancelled cancelled)
{
	switch (tp->gesture.state) {
	case GESTURE_STATE_NONE:
	case GESTURE_STATE_UNKNOWN:
	case GESTURE_STATE_SCROLL_START:
	case GESTURE_STATE_PINCH_START:
	case GESTURE_STATE_SWIPE_START:
	case GESTURE_STATE_3FG_DRAG_START:
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
		tp_gesture_handle_event(tp, GESTURE_EVENT_RESET, time);
		break;
	case GESTURE_STATE_HOLD:
	case GESTURE_STATE_HOLD_AND_MOTION:
	case GESTURE_STATE_POINTER_MOTION:
	case GESTURE_STATE_SCROLL:
	case GESTURE_STATE_PINCH:
	case GESTURE_STATE_SWIPE:
	case GESTURE_STATE_3FG_DRAG:
	case GESTURE_STATE_3FG_DRAG_RELEASED:
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
		switch (cancelled) {
		case CANCEL_GESTURE:
			tp_gesture_handle_event(tp, GESTURE_EVENT_CANCEL, time);
			break;
		case END_GESTURE:
			tp_gesture_handle_event(tp, GESTURE_EVENT_END, time);
			break;
		}
		break;
	}
}

void
tp_gesture_cancel(struct tp_dispatch *tp, usec_t time)
{
	tp_gesture_end(tp, time, CANCEL_GESTURE);
}

void
tp_gesture_cancel_motion_gestures(struct tp_dispatch *tp, usec_t time)
{

	switch (tp->gesture.state) {
	case GESTURE_STATE_NONE:
	case GESTURE_STATE_UNKNOWN:
	case GESTURE_STATE_SCROLL_START:
	case GESTURE_STATE_PINCH_START:
	case GESTURE_STATE_SWIPE_START:
	case GESTURE_STATE_3FG_DRAG_START:
		break;
	case GESTURE_STATE_HOLD:
		break;
	case GESTURE_STATE_HOLD_AND_MOTION:
	case GESTURE_STATE_POINTER_MOTION:
	case GESTURE_STATE_SCROLL:
	case GESTURE_STATE_PINCH:
	case GESTURE_STATE_SWIPE:
		evdev_log_debug(tp->device, "Cancelling motion gestures\n");
		tp_gesture_cancel(tp, time);
		break;
	case GESTURE_STATE_3FG_DRAG:
		break;
	case GESTURE_STATE_3FG_DRAG_RELEASED:
		break;
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
		evdev_log_debug(tp->device, "Cancelling motion gestures\n");
		tp_gesture_cancel(tp, time);
		break;
	}
}

void
tp_gesture_stop(struct tp_dispatch *tp, usec_t time)
{
	tp_gesture_end(tp, time, END_GESTURE);
}

static void
tp_gesture_finger_count_switch_timeout(usec_t now, void *data)
{
	struct tp_dispatch *tp = data;

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

	tp_gesture_handle_event(tp, GESTURE_EVENT_FINGER_SWITCH_TIMEOUT, now);
	tp->gesture.finger_count = tp->gesture.finger_count_pending;
	tp->gesture.finger_count_pending = 0;
}

static bool
tp_gesture_debounce_finger_changes(struct tp_dispatch *tp)
{
	switch (tp->gesture.state) {
	case GESTURE_STATE_NONE:
	case GESTURE_STATE_UNKNOWN:
	case GESTURE_STATE_SCROLL_START:
	case GESTURE_STATE_PINCH_START:
	case GESTURE_STATE_SWIPE_START:
	case GESTURE_STATE_POINTER_MOTION:
		return false;
	case GESTURE_STATE_HOLD:
	case GESTURE_STATE_HOLD_AND_MOTION:
	case GESTURE_STATE_SCROLL:
	case GESTURE_STATE_PINCH:
	case GESTURE_STATE_SWIPE:
	case GESTURE_STATE_3FG_DRAG_START:
	case GESTURE_STATE_3FG_DRAG_RELEASED:
	case GESTURE_STATE_3FG_DRAG:
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE:
	case GESTURE_STATE_3FG_DRAG_OR_SWIPE_START:
		return true;
	}

	abort();
}

void
tp_gesture_update_finger_state(struct tp_dispatch *tp, usec_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_debounce_finger_changes(tp)) {
			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,
				usec_add(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;
}

static int
tp_3fg_drag_count(struct libinput_device *device)
{
	struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
	struct tp_dispatch *tp = tp_dispatch(dispatch);

	/* If we can't to gestures we can't do 3fg drag */
	if (!tp_gesture_are_gestures_enabled(tp))
		return 0;

	/* For now return the number of MT slots until we need to figure out
	 * if we can implement this on a 2-finger BTN_TOOL_TRIPLETAP device */
	return tp->num_slots;
}

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

	if (tp_3fg_drag_count(device) < 3)
		return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;

	switch (enabled) {
	case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
		tp->drag_3fg.want_nfingers = 0;
		break;
	case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
		tp->drag_3fg.want_nfingers = 3;
		break;
	case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
		tp->drag_3fg.want_nfingers = 4;
		break;
	}

	tp_3fg_drag_apply_config(evdev_device(device));

	return LIBINPUT_CONFIG_STATUS_SUCCESS;
}

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

	switch (tp->drag_3fg.want_nfingers) {
	case 3:
		return LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG;
	case 4:
		return LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG;
	}
	return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
}

static enum libinput_config_3fg_drag_state
tp_3fg_drag_default(struct tp_dispatch *tp)
{
	return LIBINPUT_CONFIG_3FG_DRAG_DISABLED;
}

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

	return tp_3fg_drag_default(tp);
}

void
tp_3fg_drag_apply_config(struct evdev_device *device)
{
	struct tp_dispatch *tp = (struct tp_dispatch *)device->dispatch;

	if (tp->drag_3fg.want_nfingers == tp->drag_3fg.nfingers)
		return;

	if (tp->nfingers_down)
		return;

	tp->drag_3fg.nfingers = tp->drag_3fg.want_nfingers;

	evdev_log_debug(device,
			"touchpad-3fg-drag: drag is now for %zd fingers\n",
			tp->drag_3fg.nfingers);
}

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;

	tp->drag_3fg.config.count = tp_3fg_drag_count;
	tp->drag_3fg.config.set_enabled = tp_3fg_drag_set_enabled;
	tp->drag_3fg.config.get_enabled = tp_3fg_drag_get_enabled;
	tp->drag_3fg.config.get_default = tp_3fg_drag_get_default_enabled;
	tp->device->base.config.drag_3fg = &tp->drag_3fg.config;

	switch (tp_3fg_drag_default(tp)) {
	case LIBINPUT_CONFIG_3FG_DRAG_DISABLED:
		tp->drag_3fg.nfingers = 0;
		break;
	case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG:
		tp->drag_3fg.nfingers = 3;
		break;
	case LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG:
		tp->drag_3fg.nfingers = 4;
		break;
	}
	tp->drag_3fg.want_nfingers = tp->drag_3fg.nfingers;

	/* 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);
	snprintf(timer_name,
		 sizeof(timer_name),
		 "%s drag_3fg",
		 evdev_device_get_sysname(tp->device));
	libinput_timer_init(&tp->gesture.drag_3fg_timer,
			    tp_libinput_context(tp),
			    timer_name,
			    tp_gesture_3fg_drag_timeout,
			    tp);
	snprintf(timer_name,
		 sizeof(timer_name),
		 "%s drag_or_swipe",
		 evdev_device_get_sysname(tp->device));
	libinput_timer_init(&tp->gesture.drag_3fg_or_swipe_timer,
			    tp_libinput_context(tp),
			    timer_name,
			    tp_gesture_3fg_drag_or_swipe_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);
	libinput_timer_cancel(&tp->gesture.drag_3fg_timer);
	libinput_timer_cancel(&tp->gesture.drag_3fg_or_swipe_timer);
}
