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 @@ +IDLETOUCHfirstfinger downfinger upbutton +1presstimeoutmove > thresholdsecondfinger downTOUCH_2secondfinger upbutton +2pressmove > +thresholdtimeoutbutton 1releasebutton +2releaseTAPPEDtimeoutfirstfinger downDRAGGINGfirstfinger upbtn1releaseIDLEthirdfinger downTOUCH_3secondfinger upbutton 3pressbutton 3releasemove > thresholdIDLEtimeoutthirdfinger upfirstfinger upIDLEfourthfinger downDRAGGING_OR_DOUBLETAPtimeoutfirstfinger upbutton +1releasebutton +1pressbtn1releasesecondfinger downmove > thresholdHOLDfirstfinger upsecondfinger downTOUCH_2_HOLDsecondfinger upfirstfinger upthirdfinger downTOUCH_3_HOLDsecondfinger upthirdfinger upfourthfinger downDEADsecondfinger upthirdfinger upfourthfinger downfirstfinger upfirstfinger upfirstfinger upIDLEif fingercount == 0secondfinger upDRAGGING_2firstfinger upsecondfinger downthirdfinger downbtn1releasephysbuttonpressfourthfinger downphysbuttonpressbutton 1releaseDRAGGING_WAITtimeoutfirstfinger down 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); +}