/* * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * 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 DEFAULT_TAP_TIMEOUT_PERIOD ms2us(180) #define DEFAULT_DRAG_TIMEOUT_PERIOD ms2us(300) #define DEFAULT_TAP_MOVE_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3) enum tap_event { TAP_EVENT_TOUCH = 12, TAP_EVENT_MOTION, TAP_EVENT_RELEASE, TAP_EVENT_BUTTON, TAP_EVENT_TIMEOUT, TAP_EVENT_THUMB, }; /***************************************** * 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_2_RELEASE); 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_OR_TAP); CASE_RETURN_STRING(TAP_STATE_DRAGGING_2); CASE_RETURN_STRING(TAP_STATE_MULTITAP); CASE_RETURN_STRING(TAP_STATE_MULTITAP_DOWN); 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); CASE_RETURN_STRING(TAP_EVENT_THUMB); } return NULL; } static void tp_tap_notify(struct tp_dispatch *tp, uint64_t time, int nfingers, enum libinput_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; } if (state == LIBINPUT_BUTTON_STATE_PRESSED) tp->tap.buttons_pressed |= (1 << nfingers); else tp->tap.buttons_pressed &= ~(1 << nfingers); evdev_pointer_notify_button(tp->device, time, button, state); } static void tp_tap_set_timer(struct tp_dispatch *tp, uint64_t time) { libinput_timer_set(&tp->tap.timer, time + DEFAULT_TAP_TIMEOUT_PERIOD); } static void tp_tap_set_drag_timer(struct tp_dispatch *tp, uint64_t time) { libinput_timer_set(&tp->tap.timer, time + DEFAULT_DRAG_TIMEOUT_PERIOD); } static void tp_tap_clear_timer(struct tp_dispatch *tp) { libinput_timer_cancel(&tp->tap.timer); } static void tp_tap_idle_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { struct libinput *libinput = tp_libinput_context(tp); switch (event) { case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_TOUCH; tp->tap.first_press_time = time; tp_tap_set_timer(tp, time); break; case TAP_EVENT_RELEASE: break; case TAP_EVENT_MOTION: log_bug_libinput(libinput, "invalid tap event, no fingers are down\n"); break; case TAP_EVENT_TIMEOUT: break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_THUMB: log_bug_libinput(libinput, "invalid tap event, no fingers down, no thumb\n"); break; } } static void tp_tap_touch_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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_notify(tp, tp->tap.first_press_time, 1, LIBINPUT_BUTTON_STATE_PRESSED); if (tp->tap.drag_enabled) { tp->tap.state = TAP_STATE_TAPPED; tp_tap_set_timer(tp, time); } else { tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); tp->tap.state = TAP_STATE_IDLE; } 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; case TAP_EVENT_THUMB: tp->tap.state = TAP_STATE_IDLE; t->tap.is_thumb = true; t->tap.state = TAP_TOUCH_STATE_DEAD; tp_tap_clear_timer(tp); break; } } static void tp_tap_hold_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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; case TAP_EVENT_THUMB: tp->tap.state = TAP_STATE_IDLE; t->tap.is_thumb = true; t->tap.state = TAP_TOUCH_STATE_DEAD; break; } } static void tp_tap_tapped_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { struct libinput *libinput = tp_libinput_context(tp); switch (event) { case TAP_EVENT_MOTION: case TAP_EVENT_RELEASE: log_bug_libinput(libinput, "invalid tap event when fingers are up\n"); break; case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP; tp_tap_set_timer(tp, time); break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_IDLE; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_touch2_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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_TOUCH_2_RELEASE; tp_tap_set_timer(tp, time); break; case TAP_EVENT_MOTION: tp_tap_clear_timer(tp); /* fallthrough */ case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_TOUCH_2_HOLD; break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_THUMB: break; } } static void tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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; case TAP_EVENT_THUMB: break; } } static void tp_tap_touch2_release_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { switch (event) { case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_TOUCH_2_HOLD; t->tap.state = TAP_TOUCH_STATE_DEAD; tp_tap_clear_timer(tp); break; case TAP_EVENT_RELEASE: tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_PRESSED); tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_RELEASED); tp->tap.state = TAP_STATE_IDLE; break; case TAP_EVENT_MOTION: case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_HOLD; break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_THUMB: break; } } static void tp_tap_touch3_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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; if (t->tap.state == TAP_TOUCH_STATE_TOUCH) { tp_tap_notify(tp, time, 3, LIBINPUT_BUTTON_STATE_PRESSED); tp_tap_notify(tp, time, 3, LIBINPUT_BUTTON_STATE_RELEASED); } break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; break; case TAP_EVENT_THUMB: break; } } static void tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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; case TAP_EVENT_THUMB: break; } } static void tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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_MULTITAP; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); 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_BUTTON_STATE_RELEASED); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_dragging_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { switch (event) { case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_DRAGGING_2; break; case TAP_EVENT_RELEASE: if (tp->tap.drag_lock_enabled) { tp->tap.state = TAP_STATE_DRAGGING_WAIT; tp_tap_set_drag_timer(tp, time); } else { tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); tp->tap.state = TAP_STATE_IDLE; } 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_BUTTON_STATE_RELEASED); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { switch (event) { case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_DRAGGING_OR_TAP; tp_tap_set_timer(tp, time); 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_BUTTON_STATE_RELEASED); break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_dragging_tap_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { switch (event) { case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_DRAGGING_2; tp_tap_clear_timer(tp); break; case TAP_EVENT_RELEASE: tp->tap.state = TAP_STATE_IDLE; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); 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_BUTTON_STATE_RELEASED); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_dragging2_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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_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_BUTTON_STATE_RELEASED); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_multitap_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { struct libinput *libinput = tp_libinput_context(tp); switch (event) { case TAP_EVENT_RELEASE: log_bug_libinput(libinput, "invalid tap event, no fingers are down\n"); break; case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_MULTITAP_DOWN; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); tp_tap_set_timer(tp, time); break; case TAP_EVENT_MOTION: log_bug_libinput(libinput, "invalid tap event, no fingers are down\n"); break; case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_IDLE; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED); tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_IDLE; tp_tap_clear_timer(tp); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_multitap_down_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { switch (event) { case TAP_EVENT_RELEASE: tp->tap.state = TAP_STATE_MULTITAP; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); break; case TAP_EVENT_TOUCH: tp->tap.state = TAP_STATE_DRAGGING_2; tp_tap_clear_timer(tp); break; case TAP_EVENT_MOTION: case TAP_EVENT_TIMEOUT: tp->tap.state = TAP_STATE_DRAGGING; tp_tap_clear_timer(tp); break; case TAP_EVENT_BUTTON: tp->tap.state = TAP_STATE_DEAD; tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED); tp_tap_clear_timer(tp); break; case TAP_EVENT_THUMB: break; } } static void tp_tap_dead_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_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; case TAP_EVENT_THUMB: break; } } static void tp_tap_handle_event(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event, uint64_t time) { struct libinput *libinput = tp_libinput_context(tp); enum tp_tap_state current; current = tp->tap.state; switch(tp->tap.state) { case TAP_STATE_IDLE: tp_tap_idle_handle_event(tp, t, event, time); break; case TAP_STATE_TOUCH: tp_tap_touch_handle_event(tp, t, event, time); break; case TAP_STATE_HOLD: tp_tap_hold_handle_event(tp, t, event, time); break; case TAP_STATE_TAPPED: tp_tap_tapped_handle_event(tp, t, event, time); break; case TAP_STATE_TOUCH_2: tp_tap_touch2_handle_event(tp, t, event, time); break; case TAP_STATE_TOUCH_2_HOLD: tp_tap_touch2_hold_handle_event(tp, t, event, time); break; case TAP_STATE_TOUCH_2_RELEASE: tp_tap_touch2_release_handle_event(tp, t, event, time); break; case TAP_STATE_TOUCH_3: tp_tap_touch3_handle_event(tp, t, event, time); break; case TAP_STATE_TOUCH_3_HOLD: tp_tap_touch3_hold_handle_event(tp, t, event, time); break; case TAP_STATE_DRAGGING_OR_DOUBLETAP: tp_tap_dragging_or_doubletap_handle_event(tp, t, event, time); break; case TAP_STATE_DRAGGING: tp_tap_dragging_handle_event(tp, t, event, time); break; case TAP_STATE_DRAGGING_WAIT: tp_tap_dragging_wait_handle_event(tp, t, event, time); break; case TAP_STATE_DRAGGING_OR_TAP: tp_tap_dragging_tap_handle_event(tp, t, event, time); break; case TAP_STATE_DRAGGING_2: tp_tap_dragging2_handle_event(tp, t, event, time); break; case TAP_STATE_MULTITAP: tp_tap_multitap_handle_event(tp, t, event, time); break; case TAP_STATE_MULTITAP_DOWN: tp_tap_multitap_down_handle_event(tp, t, event, time); break; case TAP_STATE_DEAD: tp_tap_dead_handle_event(tp, t, event, time); break; } if (tp->tap.state == TAP_STATE_IDLE || tp->tap.state == TAP_STATE_DEAD) tp_tap_clear_timer(tp); log_debug(libinput, "tap state: %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) { struct normalized_coords norm = tp_normalize_delta(tp, device_delta(t->point, t->tap.initial)); return normalized_length(norm) > DEFAULT_TAP_MOVE_THRESHOLD; } static bool tp_tap_enabled(struct tp_dispatch *tp) { return tp->tap.enabled && !tp->tap.suspended; } int tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; int filter_motion = 0; if (!tp_tap_enabled(tp)) return 0; /* Handle queued button pressed events from clickpads. For touchpads * with separate physical buttons, ignore button pressed events so they * don't interfere with tapping. */ if (tp->buttons.is_clickpad && tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) tp_tap_handle_event(tp, NULL, TAP_EVENT_BUTTON, time); tp_for_each_touch(tp, t) { if (!t->dirty || t->state == TOUCH_NONE) continue; if (tp->buttons.is_clickpad && tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) t->tap.state = TAP_TOUCH_STATE_DEAD; /* If a touch was considered thumb for tapping once, we * ignore it for the rest of lifetime */ if (t->tap.is_thumb) continue; if (t->state == TOUCH_BEGIN) { /* The simple version: if a touch is a thumb on * begin we ignore it. All other thumb touches * follow the normal tap state for now */ if (t->thumb.state == THUMB_STATE_YES) { t->tap.is_thumb = true; continue; } t->tap.state = TAP_TOUCH_STATE_TOUCH; t->tap.initial = t->point; tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time); /* If we think this is a palm, pretend there's a * motion event which will prevent tap clicks * without requiring extra states in the FSM. */ if (tp_palm_tap_is_palm(tp, t)) tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time); } else if (t->state == TOUCH_END) { tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time); t->tap.state = TAP_TOUCH_STATE_IDLE; } else if (tp->tap.state != TAP_STATE_IDLE && tp_tap_exceeds_motion_threshold(tp, t)) { struct tp_touch *tmp; /* Any touch exceeding the threshold turns all * touches into DEAD */ tp_for_each_touch(tp, tmp) { if (tmp->tap.state == TAP_TOUCH_STATE_TOUCH) tmp->tap.state = TAP_TOUCH_STATE_DEAD; } tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time); } else if (tp->tap.state != TAP_STATE_IDLE && t->thumb.state == THUMB_STATE_YES && !t->tap.is_thumb) { tp_tap_handle_event(tp, t, TAP_EVENT_THUMB, 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_DRAGGING_OR_TAP: case TAP_STATE_TOUCH_2: case TAP_STATE_TOUCH_3: case TAP_STATE_MULTITAP_DOWN: filter_motion = 1; break; default: break; } return filter_motion; } static void tp_tap_handle_timeout(uint64_t time, void *data) { struct tp_dispatch *tp = data; struct tp_touch *t; tp_tap_handle_event(tp, NULL, TAP_EVENT_TIMEOUT, time); tp_for_each_touch(tp, t) { if (t->state == TOUCH_NONE || t->tap.state == TAP_TOUCH_STATE_IDLE) continue; t->tap.state = TAP_TOUCH_STATE_DEAD; } } static void tp_tap_enabled_update(struct tp_dispatch *tp, bool suspended, bool enabled, uint64_t time) { bool was_enabled = tp_tap_enabled(tp); tp->tap.suspended = suspended; tp->tap.enabled = enabled; if (tp_tap_enabled(tp) == was_enabled) return; if (tp_tap_enabled(tp)) { /* Must restart in DEAD if fingers are down atm */ tp->tap.state = tp->nfingers_down ? TAP_STATE_DEAD : TAP_STATE_IDLE; } else { tp_release_all_taps(tp, time); } } static int tp_tap_config_count(struct libinput_device *device) { struct evdev_dispatch *dispatch; struct tp_dispatch *tp = NULL; dispatch = ((struct evdev_device *) device)->dispatch; tp = container_of(dispatch, tp, base); return min(tp->ntouches, 3); /* we only do up to 3 finger tap */ } static enum libinput_config_status tp_tap_config_set_enabled(struct libinput_device *device, enum libinput_config_tap_state enabled) { struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch; struct tp_dispatch *tp = NULL; tp = container_of(dispatch, tp, base); tp_tap_enabled_update(tp, tp->tap.suspended, (enabled == LIBINPUT_CONFIG_TAP_ENABLED), libinput_now(device->seat->libinput)); return LIBINPUT_CONFIG_STATUS_SUCCESS; } static enum libinput_config_tap_state tp_tap_config_is_enabled(struct libinput_device *device) { struct evdev_dispatch *dispatch; struct tp_dispatch *tp = NULL; dispatch = ((struct evdev_device *) device)->dispatch; tp = container_of(dispatch, tp, base); return tp->tap.enabled ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED; } static enum libinput_config_tap_state tp_tap_default(struct evdev_device *evdev) { /** * If we don't have a left button we must have tapping enabled by * default. */ if (!libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_LEFT)) return LIBINPUT_CONFIG_TAP_ENABLED; /** * Tapping is disabled by default for two reasons: * * if you don't know that tapping is a thing (or enabled by * default), you get spurious mouse events that make the desktop * feel buggy. * * if you do know what tapping is and you want it, you * usually know where to enable it, or at least you can search for * it. */ return LIBINPUT_CONFIG_TAP_DISABLED; } static enum libinput_config_tap_state tp_tap_config_get_default(struct libinput_device *device) { struct evdev_device *evdev = (struct evdev_device *)device; return tp_tap_default(evdev); } static enum libinput_config_status tp_tap_config_set_drag_enabled(struct libinput_device *device, enum libinput_config_drag_state enabled) { struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch; struct tp_dispatch *tp = NULL; tp = container_of(dispatch, tp, base); tp->tap.drag_enabled = enabled; return LIBINPUT_CONFIG_STATUS_SUCCESS; } static enum libinput_config_drag_state tp_tap_config_get_drag_enabled(struct libinput_device *device) { struct evdev_device *evdev = (struct evdev_device *)device; struct tp_dispatch *tp = NULL; tp = container_of(evdev->dispatch, tp, base); return tp->tap.drag_enabled; } static inline enum libinput_config_drag_state tp_drag_default(struct evdev_device *device) { return LIBINPUT_CONFIG_DRAG_ENABLED; } static enum libinput_config_drag_state tp_tap_config_get_default_drag_enabled(struct libinput_device *device) { struct evdev_device *evdev = (struct evdev_device *)device; return tp_drag_default(evdev); } static enum libinput_config_status tp_tap_config_set_draglock_enabled(struct libinput_device *device, enum libinput_config_drag_lock_state enabled) { struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch; struct tp_dispatch *tp = NULL; tp = container_of(dispatch, tp, base); tp->tap.drag_lock_enabled = enabled; return LIBINPUT_CONFIG_STATUS_SUCCESS; } static enum libinput_config_drag_lock_state tp_tap_config_get_draglock_enabled(struct libinput_device *device) { struct evdev_device *evdev = (struct evdev_device *)device; struct tp_dispatch *tp = NULL; tp = container_of(evdev->dispatch, tp, base); return tp->tap.drag_lock_enabled; } static inline enum libinput_config_drag_lock_state tp_drag_lock_default(struct evdev_device *device) { return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } static enum libinput_config_drag_lock_state tp_tap_config_get_default_draglock_enabled(struct libinput_device *device) { struct evdev_device *evdev = (struct evdev_device *)device; return tp_drag_lock_default(evdev); } int tp_init_tap(struct tp_dispatch *tp) { tp->tap.config.count = tp_tap_config_count; tp->tap.config.set_enabled = tp_tap_config_set_enabled; tp->tap.config.get_enabled = tp_tap_config_is_enabled; tp->tap.config.get_default = tp_tap_config_get_default; tp->tap.config.set_drag_enabled = tp_tap_config_set_drag_enabled; tp->tap.config.get_drag_enabled = tp_tap_config_get_drag_enabled; tp->tap.config.get_default_drag_enabled = tp_tap_config_get_default_drag_enabled; tp->tap.config.set_draglock_enabled = tp_tap_config_set_draglock_enabled; tp->tap.config.get_draglock_enabled = tp_tap_config_get_draglock_enabled; tp->tap.config.get_default_draglock_enabled = tp_tap_config_get_default_draglock_enabled; tp->device->base.config.tap = &tp->tap.config; tp->tap.state = TAP_STATE_IDLE; tp->tap.enabled = tp_tap_default(tp->device); tp->tap.drag_enabled = tp_drag_default(tp->device); tp->tap.drag_lock_enabled = tp_drag_lock_default(tp->device); libinput_timer_init(&tp->tap.timer, tp_libinput_context(tp), tp_tap_handle_timeout, tp); return 0; } void tp_remove_tap(struct tp_dispatch *tp) { libinput_timer_cancel(&tp->tap.timer); } void tp_release_all_taps(struct tp_dispatch *tp, uint64_t now) { int i; for (i = 1; i <= 3; i++) { if (tp->tap.buttons_pressed & (1 << i)) tp_tap_notify(tp, now, i, LIBINPUT_BUTTON_STATE_RELEASED); } tp->tap.state = tp->nfingers_down ? TAP_STATE_DEAD : TAP_STATE_IDLE; } void tp_tap_suspend(struct tp_dispatch *tp, uint64_t time) { tp_tap_enabled_update(tp, true, tp->tap.enabled, time); } void tp_tap_resume(struct tp_dispatch *tp, uint64_t time) { tp_tap_enabled_update(tp, false, tp->tap.enabled, time); } bool tp_tap_dragging(const struct tp_dispatch *tp) { switch (tp->tap.state) { case TAP_STATE_DRAGGING: case TAP_STATE_DRAGGING_2: case TAP_STATE_DRAGGING_WAIT: case TAP_STATE_DRAGGING_OR_TAP: return true; default: return false; } }