diff --git a/doc/touchpad-tap-state-machine.svg b/doc/touchpad-tap-state-machine.svg
new file mode 100644
index 00000000..50ebc713
--- /dev/null
+++ b/doc/touchpad-tap-state-machine.svg
@@ -0,0 +1,771 @@
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 8c6d9354..579ed25f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,6 +11,9 @@ libinput_la_SOURCES = \
libinput-util.h \
evdev.c \
evdev.h \
+ evdev-mt-touchpad.c \
+ evdev-mt-touchpad.h \
+ evdev-mt-touchpad-tap.c \
evdev-touchpad.c \
filter.c \
filter.h \
diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c
new file mode 100644
index 00000000..bc7acbd0
--- /dev/null
+++ b/src/evdev-mt-touchpad-tap.c
@@ -0,0 +1,607 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission. Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "evdev-mt-touchpad.h"
+
+#define CASE_RETURN_STRING(a) case a: return #a;
+
+#define DEFAULT_TAP_TIMEOUT_PERIOD 180
+#define DEFAULT_TAP_MOVE_THRESHOLD 30
+
+enum tap_event {
+ TAP_EVENT_TOUCH = 12,
+ TAP_EVENT_MOTION,
+ TAP_EVENT_RELEASE,
+ TAP_EVENT_BUTTON,
+ TAP_EVENT_TIMEOUT,
+};
+
+/*****************************************
+ * DO NOT EDIT THIS FILE!
+ *
+ * Look at the state diagram in doc/touchpad-tap-state-machine.svg, or
+ * online at
+ * https://drive.google.com/file/d/0B1NwWmji69noYTdMcU1kTUZuUVE/edit?usp=sharing
+ * (it's a http://draw.io diagram)
+ *
+ * Any changes in this file must be represented in the diagram.
+ */
+
+static inline const char*
+tap_state_to_str(enum tp_tap_state state) {
+
+ switch(state) {
+ CASE_RETURN_STRING(TAP_STATE_IDLE);
+ CASE_RETURN_STRING(TAP_STATE_HOLD);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH);
+ CASE_RETURN_STRING(TAP_STATE_TAPPED);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_2);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_2_HOLD);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_3);
+ CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD);
+ CASE_RETURN_STRING(TAP_STATE_DRAGGING);
+ CASE_RETURN_STRING(TAP_STATE_DRAGGING_WAIT);
+ CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_DOUBLETAP);
+ CASE_RETURN_STRING(TAP_STATE_DRAGGING_2);
+ CASE_RETURN_STRING(TAP_STATE_DEAD);
+ }
+ return NULL;
+}
+
+static inline const char*
+tap_event_to_str(enum tap_event event) {
+
+ switch(event) {
+ CASE_RETURN_STRING(TAP_EVENT_TOUCH);
+ CASE_RETURN_STRING(TAP_EVENT_MOTION);
+ CASE_RETURN_STRING(TAP_EVENT_RELEASE);
+ CASE_RETURN_STRING(TAP_EVENT_TIMEOUT);
+ CASE_RETURN_STRING(TAP_EVENT_BUTTON);
+ }
+ return NULL;
+}
+#undef CASE_RETURN_STRING
+
+static void
+tp_tap_notify(struct tp_dispatch *tp,
+ uint32_t time,
+ int nfingers,
+ enum libinput_pointer_button_state state)
+{
+ int32_t button;
+
+ switch (nfingers) {
+ case 1: button = BTN_LEFT; break;
+ case 2: button = BTN_RIGHT; break;
+ case 3: button = BTN_MIDDLE; break;
+ default:
+ return;
+ }
+
+ pointer_notify_button(&tp->device->base,
+ time,
+ button,
+ state);
+}
+
+static void
+tp_tap_set_timer(struct tp_dispatch *tp, uint32_t time)
+{
+ uint32_t timeout = time + DEFAULT_TAP_TIMEOUT_PERIOD;
+ struct itimerspec its;
+
+ its.it_interval.tv_sec = 0;
+ its.it_interval.tv_nsec = 0;
+ its.it_value.tv_sec = timeout / 1000;
+ its.it_value.tv_nsec = (timeout % 1000) * 1000 * 1000;
+ timerfd_settime(tp->tap.timer_fd, TFD_TIMER_ABSTIME, &its, NULL);
+
+ tp->tap.timeout = timeout;
+}
+
+static void
+tp_tap_clear_timer(struct tp_dispatch *tp)
+{
+ tp->tap.timeout = 0;
+}
+
+static void
+tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_TOUCH;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ case TAP_EVENT_MOTION:
+ log_info("invalid event, no fingers are down\n");
+ break;
+ case TAP_EVENT_TIMEOUT:
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_TOUCH_2;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_TAPPED;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_TIMEOUT:
+ case TAP_EVENT_MOTION:
+ tp->tap.state = TAP_STATE_HOLD;
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_TOUCH_2;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_IDLE;
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_RELEASE:
+ log_info("invalid event when fingers are up\n");
+ break;
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_TIMEOUT:
+ tp->tap.state = TAP_STATE_IDLE;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ }
+}
+
+static void
+tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_TOUCH_3;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_HOLD;
+ tp_tap_notify(tp, time, 2, LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ tp_tap_notify(tp, time, 2, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_MOTION:
+ tp_tap_clear_timer(tp);
+ case TAP_EVENT_TIMEOUT:
+ tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_TOUCH_3;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_HOLD;
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ tp->tap.state = TAP_STATE_TOUCH_3_HOLD;
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
+ tp_tap_notify(tp, time, 3, LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ tp_tap_notify(tp, time, 3, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ break;
+ }
+}
+
+static void
+tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DRAGGING_2;
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_IDLE;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ tp->tap.state = TAP_STATE_DRAGGING;
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ }
+}
+
+static void
+tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DRAGGING_2;
+ break;
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_DRAGGING_WAIT;
+ tp_tap_set_timer(tp, time);
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ /* noop */
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ }
+}
+
+static void
+tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DRAGGING;
+ tp_tap_clear_timer(tp);
+ break;
+ case TAP_EVENT_RELEASE:
+ case TAP_EVENT_MOTION:
+ break;
+ case TAP_EVENT_TIMEOUT:
+ tp->tap.state = TAP_STATE_IDLE;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ }
+}
+
+static void
+tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_RELEASE:
+ tp->tap.state = TAP_STATE_DRAGGING;
+ break;
+ case TAP_EVENT_TOUCH:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ /* noop */
+ break;
+ case TAP_EVENT_BUTTON:
+ tp->tap.state = TAP_STATE_DEAD;
+ tp_tap_notify(tp, time, 1, LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+ break;
+ }
+}
+
+static void
+tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+
+ switch (event) {
+ case TAP_EVENT_RELEASE:
+ if (tp->nfingers_down == 0)
+ tp->tap.state = TAP_STATE_IDLE;
+ break;
+ case TAP_EVENT_TOUCH:
+ case TAP_EVENT_MOTION:
+ case TAP_EVENT_TIMEOUT:
+ case TAP_EVENT_BUTTON:
+ break;
+ }
+}
+
+static void
+tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time)
+{
+ enum tp_tap_state current;
+ if (!tp->tap.enabled)
+ return;
+
+ current = tp->tap.state;
+
+ switch(tp->tap.state) {
+ case TAP_STATE_IDLE:
+ tp_tap_idle_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_TOUCH:
+ tp_tap_touch_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_HOLD:
+ tp_tap_hold_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_TAPPED:
+ tp_tap_tapped_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_TOUCH_2:
+ tp_tap_touch2_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_TOUCH_2_HOLD:
+ tp_tap_touch2_hold_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_TOUCH_3:
+ tp_tap_touch3_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_TOUCH_3_HOLD:
+ tp_tap_touch3_hold_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_DRAGGING_OR_DOUBLETAP:
+ tp_tap_dragging_or_doubletap_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_DRAGGING:
+ tp_tap_dragging_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_DRAGGING_WAIT:
+ tp_tap_dragging_wait_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_DRAGGING_2:
+ tp_tap_dragging2_handle_event(tp, event, time);
+ break;
+ case TAP_STATE_DEAD:
+ tp_tap_dead_handle_event(tp, event, time);
+ break;
+ }
+
+ if (tp->tap.state == TAP_STATE_IDLE || tp->tap.state == TAP_STATE_DEAD)
+ tp_tap_clear_timer(tp);
+
+ log_debug("%s → %s → %s\n", tap_state_to_str(current), tap_event_to_str(event), tap_state_to_str(tp->tap.state));
+}
+
+static bool
+tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t)
+{
+ int threshold = DEFAULT_TAP_MOVE_THRESHOLD;
+ double dx, dy;
+
+ tp_get_delta(t, &dx, &dy);
+
+ return dx * dx + dy * dy > threshold * threshold;
+}
+
+int
+tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time)
+{
+ struct tp_touch *t;
+ int filter_motion = 0;
+
+ if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
+ tp_tap_handle_event(tp, TAP_EVENT_BUTTON, time);
+
+ tp_for_each_touch(tp, t) {
+ if (!t->dirty || t->state == TOUCH_NONE)
+ continue;
+
+ if (t->state == TOUCH_BEGIN)
+ tp_tap_handle_event(tp, TAP_EVENT_TOUCH, time);
+ else if (t->state == TOUCH_END)
+ tp_tap_handle_event(tp, TAP_EVENT_RELEASE, time);
+ else if (tp->tap.state != TAP_STATE_IDLE &&
+ tp_tap_exceeds_motion_threshold(tp, t))
+ tp_tap_handle_event(tp, TAP_EVENT_MOTION, time);
+ }
+
+ /**
+ * In any state where motion exceeding the move threshold would
+ * move to the next state, filter that motion until we actually
+ * exceed it. This prevents small motion events while we're waiting
+ * on a decision if a tap is a tap.
+ */
+ switch (tp->tap.state) {
+ case TAP_STATE_TOUCH:
+ case TAP_STATE_TAPPED:
+ case TAP_STATE_DRAGGING_OR_DOUBLETAP:
+ case TAP_STATE_TOUCH_2:
+ case TAP_STATE_TOUCH_3:
+ filter_motion = 1;
+ break;
+
+ default:
+ break;
+
+ }
+
+ return filter_motion;
+}
+
+static void
+tp_tap_timeout_handler(void *data)
+{
+ struct tp_dispatch *touchpad = data;
+ uint64_t expires;
+ int len;
+ struct timespec ts;
+ uint32_t now;
+
+ len = read(touchpad->tap.timer_fd, &expires, sizeof expires);
+ if (len != sizeof expires)
+ /* This will only happen if the application made the fd
+ * non-blocking, but this function should only be called
+ * upon the timeout, so lets continue anyway. */
+ fprintf(stderr, "timerfd read error: %m\n");
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+
+ tp_tap_handle_timeout(touchpad, now);
+}
+
+unsigned int
+tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time)
+{
+ if (!tp->tap.enabled)
+ return 0;
+
+ if (tp->tap.timeout && tp->tap.timeout <= time) {
+ tp_tap_clear_timer(tp);
+ tp_tap_handle_event(tp, TAP_EVENT_TIMEOUT, time);
+ }
+
+ return tp->tap.timeout;
+}
+
+int
+tp_init_tap(struct tp_dispatch *tp)
+{
+ tp->tap.state = TAP_STATE_IDLE;
+ tp->tap.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+
+ if (tp->tap.timer_fd == -1)
+ return -1;
+
+ tp->tap.source =
+ libinput_add_fd(tp->device->base.seat->libinput,
+ tp->tap.timer_fd,
+ tp_tap_timeout_handler,
+ tp);
+
+ if (tp->tap.source == NULL) {
+ close(tp->tap.timer_fd);
+ return -1;
+ }
+
+ tp->tap.enabled = 1; /* FIXME */
+
+ return 0;
+}
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
new file mode 100644
index 00000000..37920798
--- /dev/null
+++ b/src/evdev-mt-touchpad.c
@@ -0,0 +1,806 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#include "evdev-mt-touchpad.h"
+
+#define DEFAULT_CONSTANT_ACCEL_NUMERATOR 50
+#define DEFAULT_MIN_ACCEL_FACTOR 0.16
+#define DEFAULT_MAX_ACCEL_FACTOR 1.0
+#define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0
+
+static inline int
+tp_hysteresis(int in, int center, int margin)
+{
+ int diff = in - center;
+ if (abs(diff) <= margin)
+ return center;
+
+ if (diff > margin)
+ return center + diff - margin;
+ else if (diff < -margin)
+ return center + diff + margin;
+ return center + diff;
+}
+
+static double
+tp_accel_profile(struct motion_filter *filter,
+ void *data,
+ double velocity,
+ uint32_t time)
+{
+ struct tp_dispatch *tp =
+ (struct tp_dispatch *) data;
+
+ double accel_factor;
+
+ accel_factor = velocity * tp->accel.constant_factor;
+
+ if (accel_factor > tp->accel.max_factor)
+ accel_factor = tp->accel.max_factor;
+ else if (accel_factor < tp->accel.min_factor)
+ accel_factor = tp->accel.min_factor;
+
+ return accel_factor;
+}
+
+static inline struct tp_motion *
+tp_motion_history_offset(struct tp_touch *t, int offset)
+{
+ int offset_index =
+ (t->history.index - offset + TOUCHPAD_HISTORY_LENGTH) %
+ TOUCHPAD_HISTORY_LENGTH;
+
+ return &t->history.samples[offset_index];
+}
+
+static void
+tp_filter_motion(struct tp_dispatch *tp,
+ double *dx, double *dy, uint32_t time)
+{
+ struct motion_params motion;
+
+ motion.dx = *dx;
+ motion.dy = *dy;
+
+ filter_dispatch(tp->filter, &motion, tp, time);
+
+ *dx = motion.dx;
+ *dy = motion.dy;
+}
+
+static inline void
+tp_motion_history_push(struct tp_touch *t)
+{
+ int motion_index = (t->history.index + 1) % TOUCHPAD_HISTORY_LENGTH;
+
+ if (t->history.count < TOUCHPAD_HISTORY_LENGTH)
+ t->history.count++;
+
+ t->history.samples[motion_index].x = t->x;
+ t->history.samples[motion_index].y = t->y;
+ t->history.index = motion_index;
+}
+
+static inline void
+tp_motion_hysteresis(struct tp_dispatch *tp,
+ struct tp_touch *t)
+{
+ int x = t->x,
+ y = t->y;
+
+ if (t->history.count == 0) {
+ t->hysteresis.center_x = t->x;
+ t->hysteresis.center_y = t->y;
+ } else {
+ x = tp_hysteresis(x,
+ t->hysteresis.center_x,
+ tp->hysteresis.margin_x);
+ y = tp_hysteresis(y,
+ t->hysteresis.center_y,
+ tp->hysteresis.margin_y);
+ t->hysteresis.center_x = x;
+ t->hysteresis.center_y = y;
+ t->x = x;
+ t->y = y;
+ }
+}
+
+static inline void
+tp_motion_history_reset(struct tp_touch *t)
+{
+ t->history.count = 0;
+}
+
+static inline struct tp_touch *
+tp_current_touch(struct tp_dispatch *tp)
+{
+ return &tp->touches[min(tp->slot, tp->ntouches)];
+}
+
+static inline struct tp_touch *
+tp_get_touch(struct tp_dispatch *tp, unsigned int slot)
+{
+ assert(slot < tp->ntouches);
+ return &tp->touches[slot];
+}
+
+static inline void
+tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t)
+{
+ struct tp_touch *tmp;
+
+ if (t->state != TOUCH_UPDATE) {
+ tp_motion_history_reset(t);
+ t->dirty = true;
+ t->state = TOUCH_BEGIN;
+ tp->nfingers_down++;
+ assert(tp->nfingers_down >= 1);
+ tp->queued |= TOUCHPAD_EVENT_MOTION;
+
+ tp_for_each_touch(tp, tmp) {
+ if (tmp->is_pointer)
+ break;
+ }
+
+ if (!tmp->is_pointer) {
+ t->is_pointer = true;
+ }
+ }
+}
+
+static inline void
+tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t)
+{
+ if (t->state == TOUCH_NONE)
+ return;
+
+ t->dirty = true;
+ t->is_pointer = false;
+ t->state = TOUCH_END;
+ assert(tp->nfingers_down >= 1);
+ tp->nfingers_down--;
+ tp->queued |= TOUCHPAD_EVENT_MOTION;
+}
+
+static double
+tp_estimate_delta(int x0, int x1, int x2, int x3)
+{
+ return (x0 + x1 - x2 - x3) / 4;
+}
+
+void
+tp_get_delta(struct tp_touch *t, double *dx, double *dy)
+{
+ if (t->history.count < 4) {
+ *dx = 0;
+ *dy = 0;
+ return;
+ }
+
+ *dx = tp_estimate_delta(tp_motion_history_offset(t, 0)->x,
+ tp_motion_history_offset(t, 1)->x,
+ tp_motion_history_offset(t, 2)->x,
+ tp_motion_history_offset(t, 3)->x);
+ *dy = tp_estimate_delta(tp_motion_history_offset(t, 0)->y,
+ tp_motion_history_offset(t, 1)->y,
+ tp_motion_history_offset(t, 2)->y,
+ tp_motion_history_offset(t, 3)->y);
+}
+
+static void
+tp_process_absolute(struct tp_dispatch *tp,
+ const struct input_event *e,
+ uint32_t time)
+{
+ struct tp_touch *t = tp_current_touch(tp);
+
+ switch(e->code) {
+ case ABS_MT_POSITION_X:
+ t->x = e->value;
+ t->millis = time;
+ t->dirty = true;
+ tp->queued |= TOUCHPAD_EVENT_MOTION;
+ break;
+ case ABS_MT_POSITION_Y:
+ t->y = e->value;
+ t->millis = time;
+ t->dirty = true;
+ tp->queued |= TOUCHPAD_EVENT_MOTION;
+ break;
+ case ABS_MT_SLOT:
+ tp->slot = e->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ t->millis = time;
+ if (e->value != -1)
+ tp_begin_touch(tp, t);
+ else
+ tp_end_touch(tp, t);
+ }
+}
+
+static void
+tp_process_absolute_st(struct tp_dispatch *tp,
+ const struct input_event *e,
+ uint32_t time)
+{
+ struct tp_touch *t = tp_current_touch(tp);
+
+ switch(e->code) {
+ case ABS_X:
+ t->x = e->value;
+ t->millis = time;
+ t->dirty = true;
+ break;
+ case ABS_Y:
+ t->y = e->value;
+ t->millis = time;
+ t->dirty = true;
+ tp->queued |= TOUCHPAD_EVENT_MOTION;
+ break;
+ }
+}
+
+static void
+tp_process_fake_touch(struct tp_dispatch *tp,
+ const struct input_event *e,
+ uint32_t time)
+{
+ struct tp_touch *t;
+ unsigned int fake_touches;
+ unsigned int nfake_touches;
+ unsigned int i;
+ unsigned int shift;
+
+ if (e->code != BTN_TOUCH &&
+ (e->code < BTN_TOOL_DOUBLETAP || e->code > BTN_TOOL_QUADTAP))
+ return;
+
+ shift = e->code == BTN_TOUCH ? 0 : (e->code - BTN_TOOL_DOUBLETAP + 1);
+
+ if (e->value)
+ tp->fake_touches |= 1 << shift;
+ else
+ tp->fake_touches &= ~(0x1 << shift);
+
+ fake_touches = tp->fake_touches;
+ nfake_touches = 0;
+ while (fake_touches) {
+ nfake_touches++;
+ fake_touches >>= 1;
+ }
+
+ for (i = 0; i < tp->ntouches; i++) {
+ t = tp_get_touch(tp, i);
+ if (i >= nfake_touches) {
+ if (t->state != TOUCH_NONE) {
+ tp_end_touch(tp, t);
+ t->millis = time;
+ }
+ } else if (t->state != TOUCH_UPDATE &&
+ t->state != TOUCH_BEGIN) {
+ t->state = TOUCH_NONE;
+ tp_begin_touch(tp, t);
+ t->millis = time;
+ t->fake =true;
+ }
+ }
+
+ assert(tp->nfingers_down == nfake_touches);
+}
+
+static void
+tp_process_key(struct tp_dispatch *tp,
+ const struct input_event *e,
+ uint32_t time)
+{
+ uint32_t mask;
+
+ switch (e->code) {
+ case BTN_LEFT:
+ case BTN_MIDDLE:
+ case BTN_RIGHT:
+ mask = 1 << (e->code - BTN_LEFT);
+ if (e->value) {
+ tp->buttons.state |= mask;
+ tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS;
+ } else {
+ tp->buttons.state &= ~mask;
+ tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE;
+ }
+ break;
+ case BTN_TOUCH:
+ case BTN_TOOL_DOUBLETAP:
+ case BTN_TOOL_TRIPLETAP:
+ case BTN_TOOL_QUADTAP:
+ if (!tp->has_mt)
+ tp_process_fake_touch(tp, e, time);
+ break;
+ }
+}
+
+static void
+tp_unpin_finger(struct tp_dispatch *tp)
+{
+ struct tp_touch *t;
+ tp_for_each_touch(tp, t) {
+ if (t->is_pinned) {
+ t->is_pinned = false;
+
+ if (t->state != TOUCH_END &&
+ tp->nfingers_down == 1)
+ t->is_pointer = true;
+ break;
+ }
+ }
+}
+
+static void
+tp_pin_finger(struct tp_dispatch *tp)
+{
+ struct tp_touch *t,
+ *pinned = NULL;
+
+ tp_for_each_touch(tp, t) {
+ if (t->is_pinned) {
+ pinned = t;
+ break;
+ }
+ }
+
+ assert(!pinned);
+
+ pinned = tp_current_touch(tp);
+
+ if (tp->nfingers_down != 1) {
+ tp_for_each_touch(tp, t) {
+ if (t == pinned)
+ continue;
+
+ if (t->y > pinned->y)
+ pinned = t;
+ }
+ }
+
+ pinned->is_pinned = true;
+ pinned->is_pointer = false;
+}
+
+static void
+tp_process_state(struct tp_dispatch *tp, uint32_t time)
+{
+ struct tp_touch *t;
+ struct tp_touch *first = tp_get_touch(tp, 0);
+
+ tp_for_each_touch(tp, t) {
+ if (!tp->has_mt && t != first && first->fake) {
+ t->x = first->x;
+ t->y = first->y;
+ if (!t->dirty)
+ t->dirty = first->dirty;
+ } else if (!t->dirty)
+ continue;
+
+ tp_motion_hysteresis(tp, t);
+ tp_motion_history_push(t);
+ }
+
+ /* We have a physical button down event on a clickpad. For drag and
+ drop, this means we try to identify which finger pressed the
+ physical button and "pin" it, i.e. remove pointer-moving
+ capabilities from it.
+ */
+ if ((tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) &&
+ !tp->buttons.has_buttons)
+ tp_pin_finger(tp);
+}
+
+static void
+tp_post_process_state(struct tp_dispatch *tp, uint32_t time)
+{
+ struct tp_touch *t;
+
+ tp_for_each_touch(tp, t) {
+ if (!t->dirty)
+ continue;
+
+ if (t->state == TOUCH_END) {
+ t->state = TOUCH_NONE;
+ t->fake = false;
+ } else if (t->state == TOUCH_BEGIN)
+ t->state = TOUCH_UPDATE;
+
+ t->dirty = false;
+ }
+
+ tp->buttons.old_state = tp->buttons.state;
+
+ if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE)
+ tp_unpin_finger(tp);
+
+ tp->queued = TOUCHPAD_EVENT_NONE;
+}
+
+static void
+tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time)
+{
+ struct tp_touch *t;
+ int nchanged = 0;
+ double dx = 0, dy =0;
+ double tmpx, tmpy;
+
+ tp_for_each_touch(tp, t) {
+ if (t->dirty) {
+ nchanged++;
+ tp_get_delta(t, &tmpx, &tmpy);
+
+ dx += tmpx;
+ dy += tmpy;
+ }
+ }
+
+ if (nchanged == 0)
+ return;
+
+ dx /= nchanged;
+ dy /= nchanged;
+
+ tp_filter_motion(tp, &dx, &dy, time);
+
+ if (tp->scroll.state == SCROLL_STATE_NONE) {
+ /* Require at least one px scrolling to start */
+ if (dx <= -1.0 || dx >= 1.0) {
+ tp->scroll.state = SCROLL_STATE_SCROLLING;
+ tp->scroll.direction |= (1 << LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL);
+ }
+
+ if (dy <= -1.0 || dy >= 1.0) {
+ tp->scroll.state = SCROLL_STATE_SCROLLING;
+ tp->scroll.direction |= (1 << LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL);
+ }
+
+ if (tp->scroll.state == SCROLL_STATE_NONE)
+ return;
+ }
+
+ if (dy != 0.0 &&
+ (tp->scroll.direction & (1 << LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL))) {
+ pointer_notify_axis(&tp->device->base,
+ time,
+ LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL,
+ li_fixed_from_double(dy));
+ }
+
+ if (dx != 0.0 &&
+ (tp->scroll.direction & (1 << LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL))) {
+ pointer_notify_axis(&tp->device->base,
+ time,
+ LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL,
+ li_fixed_from_double(dx));
+ }
+}
+
+static int
+tp_post_scroll_events(struct tp_dispatch *tp, uint32_t time)
+{
+ /* don't scroll if a clickpad is held down */
+ if (!tp->buttons.has_buttons &&
+ (tp->buttons.state || tp->buttons.old_state))
+ return 0;
+
+ if (tp->nfingers_down != 2) {
+ /* terminate scrolling with a zero scroll event to notify
+ * caller that it really ended now */
+ if (tp->scroll.state != SCROLL_STATE_NONE) {
+ tp->scroll.state = SCROLL_STATE_NONE;
+ tp->scroll.direction = 0;
+ if (tp->scroll.direction & LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL)
+ pointer_notify_axis(&tp->device->base,
+ time,
+ LIBINPUT_POINTER_AXIS_VERTICAL_SCROLL,
+ 0);
+ if (tp->scroll.direction & LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL)
+ pointer_notify_axis(&tp->device->base,
+ time,
+ LIBINPUT_POINTER_AXIS_HORIZONTAL_SCROLL,
+ 0);
+ }
+ } else {
+ tp_post_twofinger_scroll(tp, time);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+tp_post_clickfinger_buttons(struct tp_dispatch *tp, uint32_t time)
+{
+ uint32_t current, old, button;
+ enum libinput_pointer_button_state state;
+
+ current = tp->buttons.state;
+ old = tp->buttons.old_state;
+
+ if (current == old)
+ return 0;
+
+ switch (tp->nfingers_down) {
+ case 1: button = BTN_LEFT; break;
+ case 2: button = BTN_RIGHT; break;
+ case 3: button = BTN_MIDDLE; break;
+ default:
+ return 0;
+ }
+
+ if (current)
+ state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED;
+ else
+ state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED;
+
+ pointer_notify_button(&tp->device->base,
+ time,
+ button,
+ state);
+ return 1;
+}
+
+static int
+tp_post_physical_buttons(struct tp_dispatch *tp, uint32_t time)
+{
+ uint32_t current, old, button;
+
+ current = tp->buttons.state;
+ old = tp->buttons.old_state;
+ button = BTN_LEFT;
+
+ while (current || old) {
+ enum libinput_pointer_button_state state;
+
+ if ((current & 0x1) ^ (old & 0x1)) {
+ if (!!(current & 0x1))
+ state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED;
+ else
+ state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED;
+
+ pointer_notify_button(&tp->device->base,
+ time,
+ button,
+ state);
+ }
+
+ button++;
+ current >>= 1;
+ old >>= 1;
+ }
+
+ return 0;
+}
+
+static int
+tp_post_button_events(struct tp_dispatch *tp, uint32_t time)
+{
+ int rc;
+
+ if ((tp->queued &
+ (TOUCHPAD_EVENT_BUTTON_PRESS|TOUCHPAD_EVENT_BUTTON_RELEASE)) == 0)
+ return 0;
+
+ if (tp->buttons.has_buttons)
+ rc = tp_post_physical_buttons(tp, time);
+ else
+ rc = tp_post_clickfinger_buttons(tp, time);
+
+ return rc;
+}
+
+static void
+tp_post_events(struct tp_dispatch *tp, uint32_t time)
+{
+ struct tp_touch *t = tp_current_touch(tp);
+ double dx, dy;
+
+ if (tp_post_button_events(tp, time) != 0)
+ return;
+
+ if (tp_tap_handle_state(tp, time) != 0)
+ return;
+
+ if (tp_post_scroll_events(tp, time) != 0)
+ return;
+
+ if (t->history.count >= TOUCHPAD_MIN_SAMPLES) {
+ if (!t->is_pointer) {
+ tp_for_each_touch(tp, t) {
+ if (t->is_pointer)
+ break;
+ }
+ }
+
+ if (!t->is_pointer)
+ return;
+
+ tp_get_delta(t, &dx, &dy);
+ tp_filter_motion(tp, &dx, &dy, time);
+
+ if (dx != 0 || dy != 0)
+ pointer_notify_motion(
+ &tp->device->base,
+ time,
+ li_fixed_from_double(dx),
+ li_fixed_from_double(dy));
+ }
+}
+
+static void
+tp_process(struct evdev_dispatch *dispatch,
+ struct evdev_device *device,
+ struct input_event *e,
+ uint32_t time)
+{
+ struct tp_dispatch *tp =
+ (struct tp_dispatch *)dispatch;
+
+ switch (e->type) {
+ case EV_ABS:
+ if (tp->has_mt)
+ tp_process_absolute(tp, e, time);
+ else
+ tp_process_absolute_st(tp, e, time);
+ break;
+ case EV_KEY:
+ tp_process_key(tp, e, time);
+ break;
+ case EV_SYN:
+ tp_process_state(tp, time);
+ tp_post_events(tp, time);
+ tp_post_process_state(tp, time);
+ break;
+ }
+}
+
+static void
+tp_destroy(struct evdev_dispatch *dispatch)
+{
+ struct tp_dispatch *tp =
+ (struct tp_dispatch*)dispatch;
+
+ if (tp->filter)
+ tp->filter->interface->destroy(tp->filter);
+ free(tp->touches);
+ free(tp);
+}
+
+static struct evdev_dispatch_interface tp_interface = {
+ tp_process,
+ tp_destroy
+};
+
+static int
+tp_init_slots(struct tp_dispatch *tp,
+ struct evdev_device *device)
+{
+ const struct input_absinfo *absinfo;
+
+ absinfo = libevdev_get_abs_info(device->evdev, ABS_MT_SLOT);
+ if (absinfo) {
+ tp->ntouches = absinfo->maximum + 1;
+ tp->slot = absinfo->value;
+ tp->has_mt = true;
+ } else {
+ tp->ntouches = 5; /* FIXME: based on DOUBLETAP, etc. */
+ tp->slot = 0;
+ tp->has_mt = false;
+ }
+ tp->touches = calloc(tp->ntouches,
+ sizeof(struct tp_touch));
+
+ return 0;
+}
+
+static int
+tp_init_accel(struct tp_dispatch *touchpad, double diagonal)
+{
+ struct motion_filter *accel;
+
+ touchpad->accel.constant_factor =
+ DEFAULT_CONSTANT_ACCEL_NUMERATOR / diagonal;
+ touchpad->accel.min_factor = DEFAULT_MIN_ACCEL_FACTOR;
+ touchpad->accel.max_factor = DEFAULT_MAX_ACCEL_FACTOR;
+
+ accel = create_pointer_accelator_filter(tp_accel_profile);
+ if (accel == NULL)
+ return -1;
+
+ touchpad->filter = accel;
+
+ return 0;
+}
+
+static int
+tp_init_scroll(struct tp_dispatch *tp)
+{
+ tp->scroll.direction = 0;
+ tp->scroll.state = SCROLL_STATE_NONE;
+
+ return 0;
+}
+
+static int
+tp_init(struct tp_dispatch *tp,
+ struct evdev_device *device)
+{
+ int width, height;
+ double diagonal;
+
+ tp->base.interface = &tp_interface;
+ tp->device = device;
+
+ if (tp_init_slots(tp, device) != 0)
+ return -1;
+
+ width = abs(device->abs.max_x - device->abs.min_x);
+ height = abs(device->abs.max_y - device->abs.min_y);
+ diagonal = sqrt(width*width + height*height);
+
+ tp->hysteresis.margin_x =
+ diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
+ tp->hysteresis.margin_y =
+ diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
+
+ if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT) ||
+ libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT))
+ tp->buttons.has_buttons = true;
+
+ if (tp_init_scroll(tp) != 0)
+ return -1;
+
+ if (tp_init_accel(tp, diagonal) != 0)
+ return -1;
+
+ if (tp_init_tap(tp) != 0)
+ return -1;
+
+ return 0;
+}
+
+struct evdev_dispatch *
+evdev_mt_touchpad_create(struct evdev_device *device)
+{
+ struct tp_dispatch *tp;
+
+ tp = zalloc(sizeof *tp);
+ if (!tp)
+ return NULL;
+
+ if (tp_init(tp, device) != 0) {
+ tp_destroy(&tp->base);
+ return NULL;
+ }
+
+ return &tp->base;
+}
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
new file mode 100644
index 00000000..c30dc9e4
--- /dev/null
+++ b/src/evdev-mt-touchpad.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#ifndef EVDEV_MT_TOUCHPAD_H
+#define EVDEV_MT_TOUCHPAD_H
+
+#include
+
+#include "evdev.h"
+#include "filter.h"
+
+#define TOUCHPAD_HISTORY_LENGTH 4
+#define TOUCHPAD_MIN_SAMPLES 4
+
+enum touchpad_event {
+ TOUCHPAD_EVENT_NONE = 0,
+ TOUCHPAD_EVENT_MOTION = (1 << 0),
+ TOUCHPAD_EVENT_BUTTON_PRESS = (1 << 1),
+ TOUCHPAD_EVENT_BUTTON_RELEASE = (1 << 2),
+};
+
+enum touch_state {
+ TOUCH_NONE = 0,
+ TOUCH_BEGIN,
+ TOUCH_UPDATE,
+ TOUCH_END
+};
+
+enum scroll_state {
+ SCROLL_STATE_NONE,
+ SCROLL_STATE_SCROLLING
+};
+
+enum tp_tap_state {
+ TAP_STATE_IDLE = 4,
+ TAP_STATE_TOUCH,
+ TAP_STATE_HOLD,
+ TAP_STATE_TAPPED,
+ TAP_STATE_TOUCH_2,
+ TAP_STATE_TOUCH_2_HOLD,
+ TAP_STATE_TOUCH_3,
+ TAP_STATE_TOUCH_3_HOLD,
+ TAP_STATE_DRAGGING_OR_DOUBLETAP,
+ TAP_STATE_DRAGGING,
+ TAP_STATE_DRAGGING_WAIT,
+ TAP_STATE_DRAGGING_2,
+ TAP_STATE_DEAD, /**< finger count exceeded */
+};
+
+struct tp_motion {
+ int32_t x;
+ int32_t y;
+};
+
+struct tp_touch {
+ enum touch_state state;
+ bool dirty;
+ bool fake; /* a fake touch */
+ bool is_pointer; /* the pointer-controlling touch */
+ bool is_pinned; /* holds the phys. button */
+ int32_t x;
+ int32_t y;
+ uint32_t millis;
+
+ struct {
+ struct tp_motion samples[TOUCHPAD_HISTORY_LENGTH];
+ unsigned int index;
+ unsigned int count;
+ } history;
+
+ struct {
+ int32_t center_x;
+ int32_t center_y;
+ } hysteresis;
+};
+
+struct tp_dispatch {
+ struct evdev_dispatch base;
+ struct evdev_device *device;
+ unsigned int nfingers_down; /* number of fingers down */
+ unsigned int slot; /* current slot */
+ bool has_mt;
+
+ unsigned int ntouches; /* number of slots */
+ struct tp_touch *touches; /* len == ntouches */
+ unsigned int fake_touches; /* fake touch mask */
+
+ struct {
+ int32_t margin_x;
+ int32_t margin_y;
+ } hysteresis;
+
+ struct motion_filter *filter;
+
+ struct {
+ double constant_factor;
+ double min_factor;
+ double max_factor;
+ } accel;
+
+ struct {
+ bool has_buttons; /* true for physical LMR buttons */
+ uint32_t state;
+ uint32_t old_state;
+ } buttons; /* physical buttons */
+
+ struct {
+ enum scroll_state state;
+ enum libinput_pointer_axis direction;
+ } scroll;
+
+ enum touchpad_event queued;
+
+ struct {
+ bool enabled;
+ int timer_fd;
+ struct libinput_source *source;
+ unsigned int timeout;
+ enum tp_tap_state state;
+ } tap;
+};
+
+#define tp_for_each_touch(_tp, _t) \
+ for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++)
+
+void
+tp_get_delta(struct tp_touch *t, double *dx, double *dy);
+
+int
+tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time);
+
+unsigned int
+tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time);
+
+int
+tp_init_tap(struct tp_dispatch *tp);
+
+#endif
diff --git a/src/evdev.c b/src/evdev.c
index ba7c8b34..72e4086e 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -600,7 +600,7 @@ evdev_configure_device(struct evdev_device *device)
if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_FINGER) &&
!libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_PEN) &&
(has_abs || has_mt)) {
- device->dispatch = evdev_touchpad_create(device);
+ device->dispatch = evdev_mt_touchpad_create(device);
}
for (i = KEY_ESC; i < KEY_MAX; i++) {
if (i >= BTN_MISC && i < KEY_OK)
diff --git a/src/evdev.h b/src/evdev.h
index b83a2f9d..0ab95720 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -118,6 +118,9 @@ evdev_device_create(struct libinput_seat *seat,
struct evdev_dispatch *
evdev_touchpad_create(struct evdev_device *device);
+struct evdev_dispatch *
+evdev_mt_touchpad_create(struct evdev_device *device);
+
void
evdev_device_proces_event(struct libinput_event *event);
diff --git a/test/Makefile.am b/test/Makefile.am
index a986c0b5..7bf02d18 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -17,7 +17,7 @@ liblitest_la_SOURCES = \
litest-generic-highres-touch.c \
litest.c
-run_tests = test-udev test-path test-pointer test-touch test-log
+run_tests = test-udev test-path test-pointer test-touch test-log test-touchpad
build_tests = test-build-linker test-build-pedantic-c99 test-build-std-gnuc90
noinst_PROGRAMS = $(build_tests) $(run_tests)
@@ -48,6 +48,11 @@ test_log_CFLAGS = $(AM_CPPFLAGS)
test_log_LDADD = $(TEST_LIBS)
test_log_LDFLAGS = -static
+test_touchpad_SOURCES = touchpad.c
+test_touchpad_CFLAGS = $(AM_CPPFLAGS)
+test_touchpad_LDADD = $(TEST_LIBS)
+test_touchpad_LDFLAGS = -static
+
# build-test only
test_build_pedantic_c99_SOURCES = build-pedantic.c
test_build_pedantic_c99_CFLAGS = $(AM_CPPFLAGS) -std=c99 -pedantic -Werror
diff --git a/test/litest-bcm5974.c b/test/litest-bcm5974.c
index 4c4ab00d..5a8ce8a3 100644
--- a/test/litest-bcm5974.c
+++ b/test/litest-bcm5974.c
@@ -42,6 +42,8 @@ litest_bcm5974_touch_down(struct litest_device *d,
static int tracking_id;
struct input_event *ev;
struct input_event down[] = {
+ { .type = EV_KEY, .code = BTN_TOOL_FINGER, .value = 1 },
+ { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
{ .type = EV_ABS, .code = ABS_X, .value = x },
{ .type = EV_ABS, .code = ABS_Y, .value = y },
{ .type = EV_ABS, .code = ABS_PRESSURE, .value = 30 },
@@ -52,10 +54,10 @@ litest_bcm5974_touch_down(struct litest_device *d,
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
};
- down[0].value = litest_scale(d, ABS_X, x);
- down[1].value = litest_scale(d, ABS_Y, y);
- down[5].value = litest_scale(d, ABS_X, x);
- down[6].value = litest_scale(d, ABS_Y, y);
+ down[2].value = litest_scale(d, ABS_X, x);
+ down[3].value = litest_scale(d, ABS_Y, y);
+ down[7].value = litest_scale(d, ABS_X, x);
+ down[8].value = litest_scale(d, ABS_Y, y);
ARRAY_FOR_EACH(down, ev)
litest_event(d, ev->type, ev->code, ev->value);
diff --git a/test/litest-keyboard.c b/test/litest-keyboard.c
index 5c142adf..dd911587 100644
--- a/test/litest-keyboard.c
+++ b/test/litest-keyboard.c
@@ -106,7 +106,7 @@ litest_create_keyboard(struct litest_device *d)
struct litest_test_device litest_keyboard_device = {
.type = LITEST_KEYBOARD,
- .features = LITEST_KEYBOARD,
+ .features = LITEST_KEYS,
.shortname = "default keyboard",
.setup = litest_keyboard_setup,
.teardown = litest_generic_device_teardown,
diff --git a/test/litest-synaptics.c b/test/litest-synaptics.c
index f6fa84e2..e4a47835 100644
--- a/test/litest-synaptics.c
+++ b/test/litest-synaptics.c
@@ -42,6 +42,8 @@ litest_synaptics_clickpad_touch_down(struct litest_device *d,
static int tracking_id;
struct input_event *ev;
struct input_event down[] = {
+ { .type = EV_KEY, .code = BTN_TOOL_FINGER, .value = 1 },
+ { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
{ .type = EV_ABS, .code = ABS_X, .value = x },
{ .type = EV_ABS, .code = ABS_Y, .value = y },
{ .type = EV_ABS, .code = ABS_PRESSURE, .value = 30 },
@@ -52,10 +54,10 @@ litest_synaptics_clickpad_touch_down(struct litest_device *d,
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
};
- down[0].value = litest_scale(d, ABS_X, x);
- down[1].value = litest_scale(d, ABS_Y, y);
- down[5].value = litest_scale(d, ABS_X, x);
- down[6].value = litest_scale(d, ABS_Y, y);
+ down[2].value = litest_scale(d, ABS_X, x);
+ down[3].value = litest_scale(d, ABS_Y, y);
+ down[7].value = litest_scale(d, ABS_X, x);
+ down[8].value = litest_scale(d, ABS_Y, y);
ARRAY_FOR_EACH(down, ev)
litest_event(d, ev->type, ev->code, ev->value);
diff --git a/test/pointer.c b/test/pointer.c
index e864169c..59fe8183 100644
--- a/test/pointer.c
+++ b/test/pointer.c
@@ -186,7 +186,7 @@ END_TEST
int main (int argc, char **argv) {
litest_add("pointer:motion", pointer_motion_relative, LITEST_POINTER, LITEST_ANY);
- litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_ANY);
+ litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD);
litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_ANY);
return litest_run(argc, argv);
diff --git a/test/touchpad.c b/test/touchpad.c
new file mode 100644
index 00000000..d32a296a
--- /dev/null
+++ b/test/touchpad.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "libinput-util.h"
+#include "litest.h"
+
+START_TEST(touchpad_1fg_motion)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5);
+ litest_touch_up(dev, 0);
+
+ libinput_dispatch(li);
+
+ event = libinput_get_event(li);
+ ck_assert(event != NULL);
+
+ while (event) {
+ ck_assert_int_eq(libinput_event_get_type(event),
+ LIBINPUT_EVENT_POINTER_MOTION);
+
+ ptrev = libinput_event_get_pointer_event(event);
+ ck_assert_int_ge(libinput_event_pointer_get_dx(ptrev), 0);
+ ck_assert_int_eq(libinput_event_pointer_get_dy(ptrev), 0);
+ libinput_event_destroy(event);
+ event = libinput_get_event(li);
+ }
+}
+END_TEST
+
+START_TEST(touchpad_2fg_no_motion)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_down(dev, 1, 70, 70);
+ litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5);
+ litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5);
+ litest_touch_up(dev, 0);
+ litest_touch_up(dev, 1);
+
+ libinput_dispatch(li);
+
+ event = libinput_get_event(li);
+ while (event) {
+ ck_assert_int_ne(libinput_event_get_type(event),
+ LIBINPUT_EVENT_POINTER_MOTION);
+ libinput_event_destroy(event);
+ event = libinput_get_event(li);
+ }
+}
+END_TEST
+
+static void
+assert_button_event(struct libinput *li, int button,
+ enum libinput_pointer_button_state state)
+{
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+
+ ck_assert(event != NULL);
+ ck_assert_int_eq(libinput_event_get_type(event),
+ LIBINPUT_EVENT_POINTER_BUTTON);
+ ptrev = libinput_event_get_pointer_event(event);
+ ck_assert_int_eq(libinput_event_pointer_get_button(ptrev),
+ button);
+ ck_assert_int_eq(libinput_event_pointer_get_button_state(ptrev),
+ state);
+ libinput_event_destroy(event);
+}
+
+START_TEST(touchpad_1fg_tap)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_up(dev, 0);
+
+ libinput_dispatch(li);
+
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ usleep(300000); /* tap-n-drag timeout */
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ ck_assert(event == NULL);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_up(dev, 0);
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5);
+ litest_touch_up(dev, 0);
+
+ libinput_dispatch(li);
+
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+
+ libinput_dispatch(li);
+ while (libinput_next_event_type(li) == LIBINPUT_EVENT_POINTER_MOTION) {
+ event = libinput_get_event(li);
+ libinput_event_destroy(event);
+ libinput_dispatch(li);
+ }
+
+ ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+
+ /* lift finger, set down again, should continue dragging */
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5);
+ litest_touch_up(dev, 0);
+
+ libinput_dispatch(li);
+ while (libinput_next_event_type(li) == LIBINPUT_EVENT_POINTER_MOTION) {
+ event = libinput_get_event(li);
+ libinput_event_destroy(event);
+ libinput_dispatch(li);
+ }
+
+ ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+
+ usleep(300000); /* tap-n-drag timeout */
+
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ ck_assert(event == NULL);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+
+ litest_drain_events(dev->libinput);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_down(dev, 1, 70, 70);
+ litest_touch_up(dev, 0);
+ litest_touch_up(dev, 1);
+
+ libinput_dispatch(li);
+
+ assert_button_event(li, BTN_RIGHT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ usleep(300000); /* tap-n-drag timeout */
+ assert_button_event(li, BTN_RIGHT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ ck_assert(event == NULL);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_clickfinger)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_event(dev, EV_KEY, BTN_LEFT, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_event(dev, EV_KEY, BTN_LEFT, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_touch_up(dev, 0);
+
+ libinput_dispatch(li);
+
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_clickfinger)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_touch_down(dev, 1, 70, 70);
+ litest_event(dev, EV_KEY, BTN_LEFT, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_event(dev, EV_KEY, BTN_LEFT, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_touch_up(dev, 0);
+ litest_touch_up(dev, 1);
+
+ libinput_dispatch(li);
+
+ assert_button_event(li, BTN_RIGHT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ assert_button_event(li, BTN_RIGHT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_btn_left)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+ enum libinput_pointer_button_state btnstate;
+
+ litest_drain_events(li);
+
+ litest_event(dev, EV_KEY, BTN_LEFT, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_event(dev, EV_KEY, BTN_LEFT, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+ libinput_dispatch(li);
+
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(clickpad_btn_left)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+
+ litest_drain_events(li);
+
+ /* A clickpad always needs a finger down to tell where the
+ click happens */
+ litest_event(dev, EV_KEY, BTN_LEFT, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_event(dev, EV_KEY, BTN_LEFT, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+ libinput_dispatch(li);
+ ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+}
+END_TEST
+
+START_TEST(clickpad_click_n_drag)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 50, 50);
+ litest_event(dev, EV_KEY, BTN_LEFT, 1);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+ libinput_dispatch(li);
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_PRESSED);
+
+ libinput_dispatch(li);
+ ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+
+ /* now put a second finger down */
+ litest_touch_down(dev, 1, 70, 70);
+ litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5);
+ litest_touch_up(dev, 1);
+
+ libinput_dispatch(li);
+ ck_assert_int_eq(libinput_next_event_type(li),
+ LIBINPUT_EVENT_POINTER_MOTION);
+ do {
+ event = libinput_get_event(li);
+ libinput_event_destroy(event);
+ libinput_dispatch(li);
+ } while (libinput_next_event_type(li) == LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_event(dev, EV_KEY, BTN_LEFT, 0);
+ litest_event(dev, EV_SYN, SYN_REPORT, 0);
+ litest_touch_up(dev, 0);
+
+ assert_button_event(li, BTN_LEFT,
+ LIBINPUT_POINTER_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+int main(int argc, char **argv) {
+
+ litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_ANY);
+
+ litest_add("touchpad:tap", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:tap", touchpad_1fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:tap", touchpad_2fg_tap, LITEST_TOUCHPAD, LITEST_ANY);
+
+ litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_TOUCHPAD, LITEST_ANY);
+
+ litest_add("touchpad:click", touchpad_btn_left, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+ litest_add("touchpad:click", clickpad_btn_left, LITEST_CLICKPAD, LITEST_ANY);
+ litest_add("touchpad:click", clickpad_click_n_drag, LITEST_CLICKPAD, LITEST_ANY);
+
+ return litest_run(argc, argv);
+}