/*
 * Copyright © 2014-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 <limits.h>
#include <math.h>
#include <string.h>
#include "linux/input.h"

#include "util-input-event.h"
#include "evdev-mt-touchpad.h"

#define DEFAULT_BUTTON_ENTER_TIMEOUT ms2us(100)
#define DEFAULT_BUTTON_LEAVE_TIMEOUT ms2us(300)

/*****************************************
 * BEFORE YOU EDIT THIS FILE, look at the state diagram in
 * doc/touchpad-softbutton-state-machine.svg (generated with
 * https://www.diagrams.net).
 * Any changes in this file must be represented in the diagram.
 *
 * The state machine only affects the soft button area code.
 */

static inline const char*
button_state_to_str(enum button_state state)
{
	switch(state) {
	CASE_RETURN_STRING(BUTTON_STATE_NONE);
	CASE_RETURN_STRING(BUTTON_STATE_AREA);
	CASE_RETURN_STRING(BUTTON_STATE_BOTTOM);
	CASE_RETURN_STRING(BUTTON_STATE_TOP);
	CASE_RETURN_STRING(BUTTON_STATE_TOP_NEW);
	CASE_RETURN_STRING(BUTTON_STATE_TOP_TO_IGNORE);
	CASE_RETURN_STRING(BUTTON_STATE_IGNORE);
	}
	return NULL;
}

static inline const char*
button_event_to_str(enum button_event event)
{
	switch(event) {
	CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_R);
	CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_M);
	CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_L);
	CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_R);
	CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_M);
	CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_L);
	CASE_RETURN_STRING(BUTTON_EVENT_IN_AREA);
	CASE_RETURN_STRING(BUTTON_EVENT_UP);
	CASE_RETURN_STRING(BUTTON_EVENT_PRESS);
	CASE_RETURN_STRING(BUTTON_EVENT_RELEASE);
	CASE_RETURN_STRING(BUTTON_EVENT_TIMEOUT);
	}
	return NULL;
}

static inline bool
is_inside_bottom_button_area(const struct tp_dispatch *tp,
			     const struct tp_touch *t)
{
	return t->point.y >= tp->buttons.bottom_area.top_edge;
}

static inline bool
is_inside_bottom_right_area(const struct tp_dispatch *tp,
			    const struct tp_touch *t)
{
	return is_inside_bottom_button_area(tp, t) &&
	       t->point.x > tp->buttons.bottom_area.rightbutton_left_edge;
}

static inline bool
is_inside_bottom_middle_area(const struct tp_dispatch *tp,
			   const struct tp_touch *t)
{
	return is_inside_bottom_button_area(tp, t) &&
	       !is_inside_bottom_right_area(tp, t) &&
	       t->point.x > tp->buttons.bottom_area.middlebutton_left_edge;
}

static inline bool
is_inside_top_button_area(const struct tp_dispatch *tp,
			  const struct tp_touch *t)
{
	return t->point.y <= tp->buttons.top_area.bottom_edge;
}

static inline bool
is_inside_top_right_area(const struct tp_dispatch *tp,
			 const struct tp_touch *t)
{
	return is_inside_top_button_area(tp, t) &&
	       t->point.x > tp->buttons.top_area.rightbutton_left_edge;
}

static inline bool
is_inside_top_middle_area(const struct tp_dispatch *tp,
			  const struct tp_touch *t)
{
	return is_inside_top_button_area(tp, t) &&
	       t->point.x >= tp->buttons.top_area.leftbutton_right_edge &&
	       t->point.x <= tp->buttons.top_area.rightbutton_left_edge;
}

static void
tp_button_set_enter_timer(struct tp_dispatch *tp,
			  struct tp_touch *t,
			  uint64_t time)
{
	libinput_timer_set(&t->button.timer,
			   time + DEFAULT_BUTTON_ENTER_TIMEOUT);
}

static void
tp_button_set_leave_timer(struct tp_dispatch *tp,
			  struct tp_touch *t,
			  uint64_t time)
{
	libinput_timer_set(&t->button.timer,
			   time + DEFAULT_BUTTON_LEAVE_TIMEOUT);
}

/*
 * tp_button_set_state, change state and implement on-entry behavior
 * as described in the state machine diagram.
 */
static void
tp_button_set_state(struct tp_dispatch *tp,
		    struct tp_touch *t,
		    enum button_state new_state,
		    enum button_event event,
		    uint64_t time)
{
	libinput_timer_cancel(&t->button.timer);

	t->button.state = new_state;

	switch (t->button.state) {
	case BUTTON_STATE_NONE:
		t->button.current = 0;
		break;
	case BUTTON_STATE_AREA:
		t->button.current = BUTTON_EVENT_IN_AREA;
		break;
	case BUTTON_STATE_BOTTOM:
		t->button.current = event;
		break;
	case BUTTON_STATE_TOP:
		break;
	case BUTTON_STATE_TOP_NEW:
		t->button.current = event;
		tp_button_set_enter_timer(tp, t, time);
		break;
	case BUTTON_STATE_TOP_TO_IGNORE:
		tp_button_set_leave_timer(tp, t, time);
		break;
	case BUTTON_STATE_IGNORE:
		t->button.current = 0;
		break;
	}
}

static void
tp_button_none_handle_event(struct tp_dispatch *tp,
			    struct tp_touch *t,
			    enum button_event event,
			    uint64_t time)
{
	switch (event) {
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
		tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, event, time);
		break;
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
		tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, event, time);
		break;
	case BUTTON_EVENT_IN_AREA:
		tp_button_set_state(tp, t, BUTTON_STATE_AREA, event, time);
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
	case BUTTON_EVENT_RELEASE:
	case BUTTON_EVENT_TIMEOUT:
		break;
	}
}

static void
tp_button_area_handle_event(struct tp_dispatch *tp,
			    struct tp_touch *t,
			    enum button_event event,
			    uint64_t time)
{
	switch (event) {
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
	case BUTTON_EVENT_IN_AREA:
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
	case BUTTON_EVENT_RELEASE:
	case BUTTON_EVENT_TIMEOUT:
		break;
	}
}

/**
 * Release any button in the bottom area, provided it started within a
 * threshold around start_time (i.e. simultaneously with the other touch
 * that triggered this call).
 */
static inline void
tp_button_release_other_bottom_touches(struct tp_dispatch *tp,
				       uint64_t other_start_time)
{
	struct tp_touch *t;

	tp_for_each_touch(tp, t) {
		uint64_t tdelta;

		if (t->button.state != BUTTON_STATE_BOTTOM ||
		    t->button.has_moved)
			continue;

		if (other_start_time > t->button.initial_time)
			tdelta = other_start_time - t->button.initial_time;
		else
			tdelta = t->button.initial_time - other_start_time;

		if (tdelta > ms2us(80))
			continue;

		t->button.has_moved = true;
	}
}

static void
tp_button_bottom_handle_event(struct tp_dispatch *tp,
			      struct tp_touch *t,
			      enum button_event event,
			      uint64_t time)
{
	switch (event) {
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
		if (event != t->button.current)
			tp_button_set_state(tp,
					    t,
					    BUTTON_STATE_BOTTOM,
					    event,
					    time);
		break;
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
	case BUTTON_EVENT_IN_AREA:
		tp_button_set_state(tp, t, BUTTON_STATE_AREA, event, time);

		/* We just transitioned one finger from BOTTOM to AREA,
		 * if there are other fingers in BOTTOM that started
		 * simultaneously with this finger, release those fingers
		 * because they're part of a gesture.
		 */
		tp_button_release_other_bottom_touches(tp,
						       t->button.initial_time);
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
	case BUTTON_EVENT_RELEASE:
	case BUTTON_EVENT_TIMEOUT:
		break;
	}
}

static void
tp_button_top_handle_event(struct tp_dispatch *tp,
			   struct tp_touch *t,
			   enum button_event event,
			   uint64_t time)
{
	switch (event) {
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
		tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, event, time);
		break;
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
		if (event != t->button.current)
			tp_button_set_state(tp,
					    t,
					    BUTTON_STATE_TOP_NEW,
					    event,
					    time);
		break;
	case BUTTON_EVENT_IN_AREA:
		tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, event, time);
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
	case BUTTON_EVENT_RELEASE:
	case BUTTON_EVENT_TIMEOUT:
		break;
	}
}

static void
tp_button_top_new_handle_event(struct tp_dispatch *tp,
			       struct tp_touch *t,
			       enum button_event event,
			       uint64_t time)
{
	switch(event) {
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
		tp_button_set_state(tp, t, BUTTON_STATE_AREA, event, time);
		break;
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
		if (event != t->button.current)
			tp_button_set_state(tp,
					    t,
					    BUTTON_STATE_TOP_NEW,
					    event,
					    time);
		break;
	case BUTTON_EVENT_IN_AREA:
		tp_button_set_state(tp, t, BUTTON_STATE_AREA, event, time);
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
		tp_button_set_state(tp, t, BUTTON_STATE_TOP, event, time);
		break;
	case BUTTON_EVENT_RELEASE:
		break;
	case BUTTON_EVENT_TIMEOUT:
		tp_button_set_state(tp, t, BUTTON_STATE_TOP, event, time);
		break;
	}
}

static void
tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp,
				     struct tp_touch *t,
				     enum button_event event,
				     uint64_t time)
{
	switch(event) {
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
		if (event == t->button.current)
			tp_button_set_state(tp,
					    t,
					    BUTTON_STATE_TOP,
					    event,
					    time);
		else
			tp_button_set_state(tp,
					    t,
					    BUTTON_STATE_TOP_NEW,
					    event,
					    time);
		break;
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
	case BUTTON_EVENT_IN_AREA:
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
	case BUTTON_EVENT_RELEASE:
		break;
	case BUTTON_EVENT_TIMEOUT:
		tp_button_set_state(tp, t, BUTTON_STATE_IGNORE, event, time);
		break;
	}
}

static void
tp_button_ignore_handle_event(struct tp_dispatch *tp,
			      struct tp_touch *t,
			      enum button_event event,
			      uint64_t time)
{
	switch (event) {
	case BUTTON_EVENT_IN_BOTTOM_R:
	case BUTTON_EVENT_IN_BOTTOM_M:
	case BUTTON_EVENT_IN_BOTTOM_L:
	case BUTTON_EVENT_IN_TOP_R:
	case BUTTON_EVENT_IN_TOP_M:
	case BUTTON_EVENT_IN_TOP_L:
	case BUTTON_EVENT_IN_AREA:
		break;
	case BUTTON_EVENT_UP:
		tp_button_set_state(tp, t, BUTTON_STATE_NONE, event, time);
		break;
	case BUTTON_EVENT_PRESS:
		t->button.current = BUTTON_EVENT_IN_AREA;
		break;
	case BUTTON_EVENT_RELEASE:
		break;
	case BUTTON_EVENT_TIMEOUT:
		break;
	}
}

static void
tp_button_handle_event(struct tp_dispatch *tp,
		       struct tp_touch *t,
		       enum button_event event,
		       uint64_t time)
{
	enum button_state current = t->button.state;

	switch(t->button.state) {
	case BUTTON_STATE_NONE:
		tp_button_none_handle_event(tp, t, event, time);
		break;
	case BUTTON_STATE_AREA:
		tp_button_area_handle_event(tp, t, event, time);
		break;
	case BUTTON_STATE_BOTTOM:
		tp_button_bottom_handle_event(tp, t, event, time);
		break;
	case BUTTON_STATE_TOP:
		tp_button_top_handle_event(tp, t, event, time);
		break;
	case BUTTON_STATE_TOP_NEW:
		tp_button_top_new_handle_event(tp, t, event, time);
		break;
	case BUTTON_STATE_TOP_TO_IGNORE:
		tp_button_top_to_ignore_handle_event(tp, t, event, time);
		break;
	case BUTTON_STATE_IGNORE:
		tp_button_ignore_handle_event(tp, t, event, time);
		break;
	}

	if (current != t->button.state)
		evdev_log_debug(tp->device,
				"button state: touch %d from %-20s event %-24s to %-20s\n",
				t->index,
				button_state_to_str(current),
				button_event_to_str(event),
				button_state_to_str(t->button.state));
}

static inline void
tp_button_check_for_movement(struct tp_dispatch *tp, struct tp_touch *t)
{
	struct device_coords delta;
	struct phys_coords mm;
	double vector_length;

	if (t->button.has_moved)
		return;

	switch (t->button.state) {
	case BUTTON_STATE_NONE:
	case BUTTON_STATE_AREA:
	case BUTTON_STATE_TOP:
	case BUTTON_STATE_TOP_NEW:
	case BUTTON_STATE_TOP_TO_IGNORE:
	case BUTTON_STATE_IGNORE:
		/* No point calculating if we're not going to use it */
		return;
	case BUTTON_STATE_BOTTOM:
		break;
	}

	delta.x = t->point.x - t->button.initial.x;
	delta.y = t->point.y - t->button.initial.y;
	mm = evdev_device_unit_delta_to_mm(tp->device, &delta);
	vector_length = hypot(mm.x, mm.y);

	if (vector_length > 5.0 /* mm */) {
		t->button.has_moved = true;

		tp_button_release_other_bottom_touches(tp,
						       t->button.initial_time);
	}
}

void
tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
{
	struct tp_touch *t;

	tp_for_each_touch(tp, t) {
		if (t->state == TOUCH_NONE || t->state == TOUCH_HOVERING)
			continue;

		if (t->state == TOUCH_BEGIN) {
			t->button.initial = t->point;
			t->button.initial_time = time;
			t->button.has_moved = false;
		}

		if (t->state == TOUCH_END) {
			tp_button_handle_event(tp, t, BUTTON_EVENT_UP, time);
		} else if (t->dirty) {
			enum button_event event;

			if (is_inside_bottom_button_area(tp, t)) {
				if (is_inside_bottom_right_area(tp, t))
					event = BUTTON_EVENT_IN_BOTTOM_R;
				else if (is_inside_bottom_middle_area(tp, t))
					event = BUTTON_EVENT_IN_BOTTOM_M;
				else
					event = BUTTON_EVENT_IN_BOTTOM_L;

				/* In the bottom area we check for movement
				 * within the area. Top area - meh */
				tp_button_check_for_movement(tp, t);
			} else if (is_inside_top_button_area(tp, t)) {
				if (is_inside_top_right_area(tp, t))
					event = BUTTON_EVENT_IN_TOP_R;
				else if (is_inside_top_middle_area(tp, t))
					event = BUTTON_EVENT_IN_TOP_M;
				else
					event = BUTTON_EVENT_IN_TOP_L;
			} else {
				event = BUTTON_EVENT_IN_AREA;
			}

			tp_button_handle_event(tp, t, event, time);
		}
		if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE)
			tp_button_handle_event(tp, t, BUTTON_EVENT_RELEASE, time);
		if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
			tp_button_handle_event(tp, t, BUTTON_EVENT_PRESS, time);
	}
}

static void
tp_button_handle_timeout(uint64_t now, void *data)
{
	struct tp_touch *t = data;

	tp_button_handle_event(t->tp, t, BUTTON_EVENT_TIMEOUT, now);
}

void
tp_process_button(struct tp_dispatch *tp,
		  const struct input_event *e,
		  uint64_t time)
{
	uint32_t mask = bit(e->code - BTN_LEFT);

	/* Ignore other buttons on clickpads */
	if (tp->buttons.is_clickpad && e->code != BTN_LEFT) {
		evdev_log_bug_kernel(tp->device,
				     "received %s button event on a clickpad\n",
				     libevdev_event_code_get_name(EV_KEY, e->code));
		return;
	}

	if (e->value) {
		tp->buttons.state |= mask;
		tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS;
	} else {
		tp->buttons.state &= ~mask;
		tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE;
	}
}

void
tp_release_all_buttons(struct tp_dispatch *tp,
		       uint64_t time)
{
	if (tp->buttons.state) {
		tp->buttons.state = 0;
		tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE;
	}
}

static void
tp_init_softbuttons(struct tp_dispatch *tp,
		    struct evdev_device *device)
{
	double width, height;
	struct device_coords edges;
	int mb_le, mb_re; /* middle button left/right edge */
	struct phys_coords mm = { 0.0, 0.0 };

	evdev_device_get_size(device, &width, &height);

	/* button height: 10mm or 15% or the touchpad height,
	   whichever is smaller */
	if (height * 0.15 > 10)
		mm.y = height - 10;
	else
		mm.y = height * 0.85;

	mm.x = width * 0.5;
	edges = evdev_device_mm_to_units(device, &mm);
	tp->buttons.bottom_area.top_edge = edges.y;
	tp->buttons.bottom_area.rightbutton_left_edge = edges.x;

	tp->buttons.bottom_area.middlebutton_left_edge = INT_MAX;

	/* if middlebutton emulation is enabled, don't init a software area */
	if (device->middlebutton.want_enabled)
		return;

	/* The middle button is 25% of the touchpad and centered. Many
	 * touchpads don't have markings for the middle button at all so we
	 * need to make it big enough to reliably hit it but not too big so
	 * it takes away all the space.
	 *
	 * On touchpads with visible markings we reduce the size of the
	 * middle button since users have a visual guide.
	 */
	if (evdev_device_has_model_quirk(device,
					 QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER)) {
		mm.x = width/2 - 5; /* 10mm wide */
		edges = evdev_device_mm_to_units(device, &mm);
		mb_le = edges.x;

		mm.x = width/2 + 5; /* 10mm wide */
		edges = evdev_device_mm_to_units(device, &mm);
		mb_re = edges.x;
	} else {
		mm.x = width * 0.375;
		edges = evdev_device_mm_to_units(device, &mm);
		mb_le = edges.x;

		mm.x = width * 0.625;
		edges = evdev_device_mm_to_units(device, &mm);
		mb_re = edges.x;
	}

	tp->buttons.bottom_area.middlebutton_left_edge = mb_le;
	tp->buttons.bottom_area.rightbutton_left_edge = mb_re;
}

void
tp_init_top_softbuttons(struct tp_dispatch *tp,
			struct evdev_device *device,
			double topbutton_size_mult)
{
	struct device_coords edges;

	if (tp->buttons.has_topbuttons) {
		/* T440s has the top button line 5mm from the top, event
		   analysis has shown events to start down to ~10mm from the
		   top - which maps to 15%.  We allow the caller to enlarge the
		   area using a multiplier for the touchpad disabled case. */
		double topsize_mm = 10 * topbutton_size_mult;
		struct phys_coords mm;
		double width, height;

		evdev_device_get_size(device, &width, &height);

		mm.x = width * 0.60;
		mm.y = topsize_mm;
		edges = evdev_device_mm_to_units(device, &mm);
		tp->buttons.top_area.bottom_edge = edges.y;
		tp->buttons.top_area.rightbutton_left_edge = edges.x;

		mm.x = width * 0.40;
		edges = evdev_device_mm_to_units(device, &mm);
		tp->buttons.top_area.leftbutton_right_edge = edges.x;
	} else {
		tp->buttons.top_area.bottom_edge = INT_MIN;
	}
}

static inline uint32_t
tp_button_config_click_get_methods(struct libinput_device *device)
{
	struct evdev_device *evdev = evdev_device(device);
	struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
	uint32_t methods = LIBINPUT_CONFIG_CLICK_METHOD_NONE;

	if (tp->buttons.is_clickpad) {
		methods |= LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
		if (tp->has_mt)
			methods |= LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
	}

	if (evdev->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON)
		methods |= LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;

	return methods;
}

static void
tp_switch_click_method(struct tp_dispatch *tp)
{
	/*
	 * All we need to do when switching click methods is to change the
	 * bottom_area.top_edge so that when in clickfinger mode the bottom
	 * touchpad area is not dead wrt finger movement starting there.
	 *
	 * We do not need to take any state into account, fingers which are
	 * already down will simply keep the state / area they have assigned
	 * until they are released, and the post_button_events path is state
	 * agnostic.
	 */

	switch (tp->buttons.click_method) {
	case LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS:
		tp_init_softbuttons(tp, tp->device);
		break;
	case LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER:
	case LIBINPUT_CONFIG_CLICK_METHOD_NONE:
		tp->buttons.bottom_area.top_edge = INT_MAX;
		break;
	}
}

static enum libinput_config_status
tp_button_config_click_set_method(struct libinput_device *device,
				  enum libinput_config_click_method method)
{
	struct evdev_device *evdev = evdev_device(device);
	struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;

	tp->buttons.click_method = method;
	tp_switch_click_method(tp);

	return LIBINPUT_CONFIG_STATUS_SUCCESS;
}

static enum libinput_config_click_method
tp_button_config_click_get_method(struct libinput_device *device)
{
	struct evdev_device *evdev = evdev_device(device);
	struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;

	return tp->buttons.click_method;
}

static enum libinput_config_click_method
tp_click_get_default_method(struct tp_dispatch *tp)
{
	struct evdev_device *device = tp->device;

	if (evdev_device_has_model_quirk(device, QUIRK_MODEL_CHROMEBOOK) ||
	    evdev_device_has_model_quirk(device, QUIRK_MODEL_SYSTEM76_BONOBO) ||
	    evdev_device_has_model_quirk(device, QUIRK_MODEL_SYSTEM76_GALAGO) ||
	    evdev_device_has_model_quirk(device, QUIRK_MODEL_SYSTEM76_KUDU) ||
	    evdev_device_has_model_quirk(device, QUIRK_MODEL_CLEVO_W740SU) ||
	    evdev_device_has_model_quirk(device, QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON))
		return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;

	if (!tp->buttons.is_clickpad)
		return LIBINPUT_CONFIG_CLICK_METHOD_NONE;

	if (evdev_device_has_model_quirk(device, QUIRK_MODEL_APPLE_TOUCHPAD))
		return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;

	return LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
}

static enum libinput_config_click_method
tp_button_config_click_get_default_method(struct libinput_device *device)
{
	struct evdev_device *evdev = evdev_device(device);
	struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;

	return tp_click_get_default_method(tp);
}

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

	if (!tp->buttons.is_clickpad ||
	    tp->buttons.state != 0)
		return;

	if (device->middlebutton.want_enabled ==
	    device->middlebutton.enabled)
		return;

	device->middlebutton.enabled = device->middlebutton.want_enabled;
	if (tp->buttons.click_method ==
	    LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
		tp_init_softbuttons(tp, device);
}

static int
tp_clickpad_middlebutton_is_available(struct libinput_device *device)
{
	return evdev_middlebutton_is_available(device);
}

static enum libinput_config_status
tp_clickpad_middlebutton_set(struct libinput_device *device,
		     enum libinput_config_middle_emulation_state enable)
{
	struct evdev_device *evdev = evdev_device(device);

	switch (enable) {
	case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED:
		evdev->middlebutton.want_enabled = true;
		break;
	case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED:
		evdev->middlebutton.want_enabled = false;
		break;
	default:
		return LIBINPUT_CONFIG_STATUS_INVALID;
	}

	tp_clickpad_middlebutton_apply_config(evdev);

	return LIBINPUT_CONFIG_STATUS_SUCCESS;
}

static enum libinput_config_middle_emulation_state
tp_clickpad_middlebutton_get(struct libinput_device *device)
{
	return evdev_middlebutton_get(device);
}

static enum libinput_config_middle_emulation_state
tp_clickpad_middlebutton_get_default(struct libinput_device *device)
{
	return evdev_middlebutton_get_default(device);
}

static inline void
tp_init_clickpad_middlebutton_emulation(struct tp_dispatch *tp,
					struct evdev_device *device)
{
	device->middlebutton.enabled_default = false;
	device->middlebutton.want_enabled = false;
	device->middlebutton.enabled = false;

	device->middlebutton.config.available = tp_clickpad_middlebutton_is_available;
	device->middlebutton.config.set = tp_clickpad_middlebutton_set;
	device->middlebutton.config.get = tp_clickpad_middlebutton_get;
	device->middlebutton.config.get_default = tp_clickpad_middlebutton_get_default;
	device->base.config.middle_emulation = &device->middlebutton.config;
}

static inline void
tp_init_middlebutton_emulation(struct tp_dispatch *tp,
			       struct evdev_device *device)
{
	bool enable_by_default,
	     want_config_option;

	/* On clickpads we provide the config option but disable by default.
	   When enabled, the middle software button disappears */
	if (tp->buttons.is_clickpad) {
		tp_init_clickpad_middlebutton_emulation(tp, device);
		return;
	}

	/* init middle button emulation on non-clickpads, but only if we
	 * don't have a middle button. Exception: ALPS touchpads don't know
	 * if they have a middle button, so we always want the option there
	 * and enabled by default.
	 */
	if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE)) {
		enable_by_default = true;
		want_config_option = false;
	} else if (evdev_device_has_model_quirk(device,
						QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD)) {
		enable_by_default = true;
		want_config_option = true;
	} else
		return;

	evdev_init_middlebutton(tp->device,
				enable_by_default,
				want_config_option);
}

static bool
tp_guess_clickpad(const struct tp_dispatch *tp, struct evdev_device *device)
{
	bool is_clickpad;
	bool has_left = libevdev_has_event_code(device->evdev, EV_KEY, BTN_LEFT),
	     has_middle = libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE),
	     has_right = libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT);

	is_clickpad = libevdev_has_property(device->evdev, INPUT_PROP_BUTTONPAD);

	/* A non-clickpad without a right button is a clickpad, assume the
	 * kernel is wrong.
	 * Exceptions here:
	 * - The one-button Apple touchpad (discontinued in 2008) has a
	 *   single physical button
	 * - Wacom touch devices have neither left nor right buttons
	 */
	if (!is_clickpad && has_left && !has_right &&
	    (tp->device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON) == 0) {
		evdev_log_bug_kernel(device,
				     "missing right button, assuming it is a clickpad.\n");
		is_clickpad = true;
	}

	if (has_middle || has_right) {
		if (is_clickpad)
			evdev_log_bug_kernel(device,
					     "clickpad advertising right button. "
					     "See %s/clickpad-with-right-button.html for details\n",
					     HTTP_DOC_LINK);
	} else if (has_left &
		   !is_clickpad &&
		   libevdev_get_id_vendor(device->evdev) != VENDOR_ID_APPLE) {
			evdev_log_bug_kernel(device,
					     "non clickpad without right button?\n");
	}

	return is_clickpad;
}

void
tp_init_buttons(struct tp_dispatch *tp,
		struct evdev_device *device)
{
	struct tp_touch *t;
	const struct input_absinfo *absinfo_x, *absinfo_y;
	int i;

	tp->buttons.is_clickpad = tp_guess_clickpad(tp, device);

	tp->buttons.has_topbuttons = libevdev_has_property(device->evdev,
						        INPUT_PROP_TOPBUTTONPAD);

	absinfo_x = device->abs.absinfo_x;
	absinfo_y = device->abs.absinfo_y;

	/* pinned-finger motion threshold, see tp_unpin_finger. */
	tp->buttons.motion_dist.x_scale_coeff = 1.0/absinfo_x->resolution;
	tp->buttons.motion_dist.y_scale_coeff = 1.0/absinfo_y->resolution;

	tp->buttons.config_method.get_methods = tp_button_config_click_get_methods;
	tp->buttons.config_method.set_method = tp_button_config_click_set_method;
	tp->buttons.config_method.get_method = tp_button_config_click_get_method;
	tp->buttons.config_method.get_default_method = tp_button_config_click_get_default_method;
	tp->device->base.config.click_method = &tp->buttons.config_method;

	tp->buttons.click_method = tp_click_get_default_method(tp);
	tp_switch_click_method(tp);

	tp_init_top_softbuttons(tp, device, 1.0);

	tp_init_middlebutton_emulation(tp, device);

	i = 0;
	tp_for_each_touch(tp, t) {
		char timer_name[64];
		i++;

		snprintf(timer_name,
			 sizeof(timer_name),
			 "%s (%d) button",
			 evdev_device_get_sysname(device),
			 i);
		t->button.state = BUTTON_STATE_NONE;
		libinput_timer_init(&t->button.timer,
				    tp_libinput_context(tp),
				    timer_name,
				    tp_button_handle_timeout, t);
	}
}

void
tp_remove_buttons(struct tp_dispatch *tp)
{
	struct tp_touch *t;

	tp_for_each_touch(tp, t) {
		libinput_timer_cancel(&t->button.timer);
		libinput_timer_destroy(&t->button.timer);
	}
}

static int
tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
{
	uint32_t current, old, button;

	current = tp->buttons.state;
	old = tp->buttons.old_state;
	button = BTN_LEFT;

	while (current || old) {
		enum libinput_button_state state;

		if ((current & 0x1) ^ (old & 0x1)) {
			uint32_t b;

			if (!!(current & 0x1))
				state = LIBINPUT_BUTTON_STATE_PRESSED;
			else
				state = LIBINPUT_BUTTON_STATE_RELEASED;

			b = evdev_to_left_handed(tp->device, button);
			evdev_pointer_notify_physical_button(tp->device,
							     time,
							     b,
							     state);
		}

		button++;
		current >>= 1;
		old >>= 1;
	}

	return 0;
}

static inline bool
tp_clickfinger_within_distance(struct tp_dispatch *tp,
			       struct tp_touch *t1,
			       struct tp_touch *t2)
{
	double x, y;
	bool within_distance = false;
	int xres, yres;
	int bottom_threshold;

	if (!t1 || !t2)
		return 0;

	if (tp_thumb_ignored(tp, t1) || tp_thumb_ignored(tp, t2))
		return 0;

	x = abs(t1->point.x - t2->point.x);
	y = abs(t1->point.y - t2->point.y);

	xres = tp->device->abs.absinfo_x->resolution;
	yres = tp->device->abs.absinfo_y->resolution;
	x /= xres;
	y /= yres;

	/* maximum horiz spread is 40mm horiz, 30mm vert, anything wider
	 * than that is probably a gesture. */
	if (x > 40 || y > 30)
		goto out;

	within_distance = true;

	/* if y spread is <= 20mm, they're definitely together. */
	if (y <= 20)
		goto out;

	/* if they're vertically spread between 20-40mm, they're not
	 * together if:
	 * - the touchpad's vertical size is >50mm, anything smaller is
	 *   unlikely to have a thumb resting on it
	 * - and one of the touches is in the bottom 20mm of the touchpad
	 *   and the other one isn't
	 */

	if (tp->device->abs.dimensions.y/yres < 50)
		goto out;

	bottom_threshold = tp->device->abs.absinfo_y->maximum - 20 * yres;
	if ((t1->point.y > bottom_threshold) !=
		    (t2->point.y > bottom_threshold))
		within_distance = 0;

out:
	return within_distance;
}

static uint32_t
tp_clickfinger_set_button(struct tp_dispatch *tp)
{
	uint32_t button;
	unsigned int nfingers = 0;
	struct tp_touch *t;
	struct tp_touch *first = NULL,
			*second = NULL;

	tp_for_each_touch(tp, t) {
		if (t->state != TOUCH_BEGIN && t->state != TOUCH_UPDATE)
			continue;

		if (tp_thumb_ignored(tp, t))
			continue;

		if (t->palm.state != PALM_NONE)
			continue;

		nfingers++;

		if (!first)
			first = t;
		else if (!second)
			second = t;
	}

	/* Only check for finger distance when there are 2 fingers on the
	 * touchpad */
	if (nfingers != 2)
		goto out;

	if (tp_clickfinger_within_distance(tp, first, second))
		nfingers = 2;
	else
		nfingers = 1;

out:
	switch (nfingers) {
	case 0:
	case 1: button = BTN_LEFT; break;
	case 2: button = BTN_RIGHT; break;
	case 3: button = BTN_MIDDLE; break;
	default:
		button = 0;
		break;
	}

	return button;
}

static int
tp_notify_clickpadbutton(struct tp_dispatch *tp,
			 uint64_t time,
			 uint32_t button,
			 uint32_t is_topbutton,
			 enum libinput_button_state state)
{
	/* If we've a trackpoint, send top buttons through the trackpoint */
	if (tp->buttons.trackpoint) {
		if (is_topbutton) {
			struct evdev_dispatch *dispatch = tp->buttons.trackpoint->dispatch;
			struct input_event event, syn_report;
			int value;

			value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0;
			event = input_event_init(time, EV_KEY, button, value);
			syn_report = input_event_init(time, EV_SYN, SYN_REPORT, 0);
			dispatch->interface->process(dispatch,
						     tp->buttons.trackpoint,
						     &event,
						     time);
			dispatch->interface->process(dispatch,
						     tp->buttons.trackpoint,
						     &syn_report,
						     time);
			return 1;
		}
		/* Ignore button events not for the trackpoint while suspended */
		if (tp->device->is_suspended)
			return 0;
	}

	/* A button click always terminates edge scrolling, even if we
	 * don't end up sending a button event. */
	tp_edge_scroll_stop_events(tp, time);

	/*
	 * If the user has requested clickfinger replace the button chosen
	 * by the softbutton code with one based on the number of fingers.
	 */
	if (tp->buttons.click_method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER &&
	    state == LIBINPUT_BUTTON_STATE_PRESSED) {
		button = tp_clickfinger_set_button(tp);
		tp->buttons.active = button;

		if (!button)
			return 0;
	}

	evdev_pointer_notify_button(tp->device, time, button, state);
	return 1;
}

static int
tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time)
{
	uint32_t current, old, button, is_top;
	enum libinput_button_state state;
	enum { AREA = 0x01, LEFT = 0x02, MIDDLE = 0x04, RIGHT = 0x08 };
	bool want_left_handed = true;

	current = tp->buttons.state;
	old = tp->buttons.old_state;
	is_top = 0;

	if (!tp->buttons.click_pending && current == old)
		return 0;

	if (current) {
		struct tp_touch *t;
		uint32_t area = 0;

		tp_for_each_touch(tp, t) {
			switch (t->button.current) {
			case BUTTON_EVENT_IN_AREA:
				area |= AREA;
				break;
			case BUTTON_EVENT_IN_TOP_L:
				is_top = 1;
				_fallthrough_;
			case BUTTON_EVENT_IN_BOTTOM_L:
				area |= LEFT;
				break;
			case BUTTON_EVENT_IN_TOP_M:
				is_top = 1;
				_fallthrough_;
			case BUTTON_EVENT_IN_BOTTOM_M:
				area |= MIDDLE;
				break;
			case BUTTON_EVENT_IN_TOP_R:
				is_top = 1;
				_fallthrough_;
			case BUTTON_EVENT_IN_BOTTOM_R:
				area |= RIGHT;
				break;
			default:
				break;
			}
		}

		if (area == 0 &&
		    tp->buttons.click_method != LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) {
			/* No touches, wait for a touch before processing */
			tp->buttons.click_pending = true;
			return 0;
		}

		if ((tp->device->middlebutton.enabled || is_top) &&
		    (area & LEFT) && (area & RIGHT)) {
			button = BTN_MIDDLE;
		} else if (area & MIDDLE) {
			button = BTN_MIDDLE;
		} else if (area & RIGHT) {
			button = BTN_RIGHT;
		} else if (area & LEFT) {
			button = BTN_LEFT;
		} else { /* main or no area (for clickfinger) is always BTN_LEFT */
			button = BTN_LEFT;
			want_left_handed = false;
		}

		if (is_top)
			want_left_handed = false;

		if (want_left_handed)
			button = evdev_to_left_handed(tp->device, button);

		tp->buttons.active = button;
		tp->buttons.active_is_topbutton = is_top;
		state = LIBINPUT_BUTTON_STATE_PRESSED;
	} else {
		button = tp->buttons.active;
		is_top = tp->buttons.active_is_topbutton;
		tp->buttons.active = 0;
		tp->buttons.active_is_topbutton = 0;
		state = LIBINPUT_BUTTON_STATE_RELEASED;
	}

	tp->buttons.click_pending = false;

	if (button)
		return tp_notify_clickpadbutton(tp,
						time,
						button,
						is_top,
						state);
	return 0;
}

int
tp_post_button_events(struct tp_dispatch *tp, uint64_t time)
{
	if (tp->buttons.is_clickpad ||
	    tp->device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON)
		return tp_post_clickpadbutton_buttons(tp, time);
	return tp_post_physical_buttons(tp, time);
}

bool
tp_button_touch_active(const struct tp_dispatch *tp,
		       const struct tp_touch *t)
{
	return t->button.state == BUTTON_STATE_AREA || t->button.has_moved;
}

bool
tp_button_is_inside_softbutton_area(const struct tp_dispatch *tp,
				    const struct tp_touch *t)
{
	return is_inside_top_button_area(tp, t) ||
	       is_inside_bottom_button_area(tp, t);
}
