libinput/src/evdev-mt-touchpad-tap.c
Friedrich Schöller af86152370 touchpad: fix tapping that happens after a moving thumb
When finger movement exceeded the motion threshold before the finger was
recognized as a thumb, it would never be regarded as a thumb by the tap system.
This prohibited tapping until the thumb was lifted.

This is fixed by moving the check for the thumb state up such that it
happens before the motion threshold check.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2018-05-10 14:35:54 +10:00

1408 lines
34 KiB
C

/*
* 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.
*/
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "evdev-mt-touchpad.h"
#define DEFAULT_TAP_TIMEOUT_PERIOD ms2us(180)
#define DEFAULT_DRAG_TIMEOUT_PERIOD ms2us(300)
#define DEFAULT_TAP_MOVE_THRESHOLD 1.3 /* mm */
enum tap_event {
TAP_EVENT_TOUCH = 12,
TAP_EVENT_MOTION,
TAP_EVENT_RELEASE,
TAP_EVENT_BUTTON,
TAP_EVENT_TIMEOUT,
TAP_EVENT_THUMB,
TAP_EVENT_PALM,
TAP_EVENT_PALM_UP,
};
/*****************************************
* 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_MULTITAP_PALM);
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);
CASE_RETURN_STRING(TAP_EVENT_PALM);
CASE_RETURN_STRING(TAP_EVENT_PALM_UP);
}
return NULL;
}
static inline void
log_tap_bug(struct tp_dispatch *tp, struct tp_touch *t, enum tap_event event)
{
evdev_log_bug_libinput(tp->device,
"%d: invalid tap event %s in state %s\n",
t->index,
tap_event_to_str(event),
tap_state_to_str(tp->tap.state));
}
static void
tp_tap_notify(struct tp_dispatch *tp,
uint64_t time,
int nfingers,
enum libinput_button_state state)
{
int32_t button;
int32_t button_map[2][3] = {
{ BTN_LEFT, BTN_RIGHT, BTN_MIDDLE },
{ BTN_LEFT, BTN_MIDDLE, BTN_RIGHT },
};
assert(tp->tap.map < ARRAY_LENGTH(button_map));
if (nfingers > 3)
return;
button = button_map[tp->tap.map][nfingers - 1];
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)
{
switch (event) {
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_TOUCH;
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
break;
case TAP_EVENT_MOTION:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_TIMEOUT:
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
break;
case TAP_EVENT_THUMB:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
t->tap.state = TAP_TOUCH_STATE_DEAD;
break;
case TAP_EVENT_PALM_UP:
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.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp_tap_notify(tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
if (tp->tap.drag_enabled) {
tp->tap.state = TAP_STATE_TAPPED;
tp->tap.saved_release_time = time;
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;
tp->tap.nfingers_down--;
t->tap.state = TAP_TOUCH_STATE_DEAD;
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
t->tap.state = TAP_TOUCH_STATE_DEAD;
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_PALM_UP:
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.saved_press_time = time;
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;
tp->tap.nfingers_down--;
t->tap.state = TAP_TOUCH_STATE_DEAD;
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
static void
tp_tap_tapped_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
case TAP_EVENT_MOTION:
case TAP_EVENT_RELEASE:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_TIMEOUT:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_DEAD;
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_THUMB:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_PALM:
case TAP_EVENT_PALM_UP:
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.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_RELEASE:
tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
tp->tap.saved_release_time = time;
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;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH;
tp_tap_set_timer(tp, time); /* overwrite timer */
break;
case TAP_EVENT_PALM_UP:
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.saved_press_time = time;
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;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_HOLD;
break;
case TAP_EVENT_PALM_UP:
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,
tp->tap.saved_press_time,
2,
LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp,
tp->tap.saved_release_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;
case TAP_EVENT_PALM:
/* There's only one saved press time and it's overwritten by
* the last touch down. So in the case of finger down, palm
* down, finger up, palm detected, we use the
* palm touch's press time here instead of the finger's press
* time. Let's wait and see if that's an issue.
*/
tp_tap_notify(tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
if (tp->tap.drag_enabled) {
tp->tap.state = TAP_STATE_TAPPED;
tp->tap.saved_release_time = time;
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_PALM_UP:
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,
tp->tap.saved_press_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;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH_2;
break;
case TAP_EVENT_PALM_UP:
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;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
break;
case TAP_EVENT_PALM_UP:
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,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.saved_release_time = time;
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,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_TAPPED;
break;
case TAP_EVENT_PALM_UP:
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;
case TAP_EVENT_PALM:
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_PALM_UP:
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:
case TAP_EVENT_PALM:
break;
case TAP_EVENT_PALM_UP:
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;
case TAP_EVENT_PALM:
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.state = TAP_STATE_IDLE;
break;
case TAP_EVENT_PALM_UP:
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;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
static void
tp_tap_multitap_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
switch (event) {
case TAP_EVENT_RELEASE:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_MULTITAP_DOWN;
tp_tap_notify(tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
tp->tap.saved_press_time = time;
tp_tap_set_timer(tp, time);
break;
case TAP_EVENT_MOTION:
log_tap_bug(tp, t, event);
break;
case TAP_EVENT_TIMEOUT:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_notify(tp,
tp->tap.saved_press_time,
1,
LIBINPUT_BUTTON_STATE_PRESSED);
tp_tap_notify(tp,
tp->tap.saved_release_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:
case TAP_EVENT_PALM:
break;
case TAP_EVENT_PALM_UP:
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,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp->tap.saved_release_time = time;
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,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
tp_tap_clear_timer(tp);
break;
case TAP_EVENT_THUMB:
break;
case TAP_EVENT_PALM:
tp->tap.state = TAP_STATE_MULTITAP_PALM;
break;
case TAP_EVENT_PALM_UP:
break;
}
}
static void
tp_tap_multitap_palm_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event,
uint64_t time)
{
switch (event) {
case TAP_EVENT_RELEASE:
/* This is the palm finger */
break;
case TAP_EVENT_TOUCH:
tp->tap.state = TAP_STATE_MULTITAP_DOWN;
break;
case TAP_EVENT_MOTION:
break;
case TAP_EVENT_TIMEOUT:
case TAP_EVENT_BUTTON:
tp->tap.state = TAP_STATE_IDLE;
tp_tap_clear_timer(tp);
tp_tap_notify(tp,
tp->tap.saved_release_time,
1,
LIBINPUT_BUTTON_STATE_RELEASED);
break;
case TAP_EVENT_THUMB:
case TAP_EVENT_PALM:
case TAP_EVENT_PALM_UP:
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->tap.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;
case TAP_EVENT_PALM:
case TAP_EVENT_PALM_UP:
if (tp->tap.nfingers_down == 0)
tp->tap.state = TAP_STATE_IDLE;
break;
}
}
static void
tp_tap_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event,
uint64_t time)
{
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_MULTITAP_PALM:
tp_tap_multitap_palm_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);
evdev_log_debug(tp->device,
"tap: touch %d state %s → %s → %s\n",
t ? (int)t->index : -1,
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 phys_coords mm =
tp_phys_delta(tp, device_delta(t->point, t->tap.initial));
/* if we have more fingers down than slots, we know that synaptics
* touchpads are likely to give us pointer jumps.
* This triggers the movement threshold, making three-finger taps
* less reliable (#101435)
*
* This uses the real nfingers_down, not the one for taps.
*/
if (tp->device->model_flags & EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD &&
(tp->nfingers_down > 2 || tp->old_nfingers_down > 2) &&
(tp->nfingers_down > tp->num_slots ||
tp->old_nfingers_down > tp->num_slots)) {
return false;
}
/* Semi-mt devices will give us large movements on finger release,
* depending which touch is released. Make sure we ignore any
* movement in the same frame as a finger change.
*/
if (tp->semi_mt && tp->nfingers_down != tp->old_nfingers_down)
return false;
return length_in_mm(mm) > 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;
/* A palm tap needs to be properly relased because we might
* be who-knows-where in the state machine. Otherwise, we
* ignore any event from it.
*/
if (t->tap.is_palm) {
if (t->state == TOUCH_END)
tp_tap_handle_event(tp,
t,
TAP_EVENT_PALM_UP,
time);
continue;
}
if (t->state == TOUCH_HOVERING)
continue;
if (t->palm.state != PALM_NONE) {
assert(!t->tap.is_palm);
tp_tap_handle_event(tp, t, TAP_EVENT_PALM, time);
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
if (t->state != TOUCH_BEGIN) {
assert(tp->tap.nfingers_down > 0);
tp->tap.nfingers_down--;
}
} else 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.nfingers_down++;
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) {
if (t->was_down) {
assert(tp->tap.nfingers_down >= 1);
tp->tap.nfingers_down--;
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 &&
t->thumb.state == THUMB_STATE_YES) {
tp_tap_handle_event(tp, t, TAP_EVENT_THUMB, time);
} 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);
}
}
/**
* 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;
}
assert(tp->tap.nfingers_down <= tp->nfingers_down);
if (tp->nfingers_down == 0)
assert(tp->tap.nfingers_down == 0);
return filter_motion;
}
static inline void
tp_tap_update_map(struct tp_dispatch *tp)
{
if (tp->tap.state != TAP_STATE_IDLE)
return;
if (tp->tap.map != tp->tap.want_map)
tp->tap.map = tp->tap.want_map;
}
void
tp_tap_post_process_state(struct tp_dispatch *tp)
{
tp_tap_update_map(tp);
}
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)) {
struct tp_touch *t;
/* On resume, all touches are considered palms */
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE)
continue;
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
}
tp->tap.state = TAP_STATE_IDLE;
tp->tap.nfingers_down = 0;
} else {
tp_release_all_taps(tp, time);
}
}
static int
tp_tap_config_count(struct libinput_device *device)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
return min(tp->ntouches, 3U); /* 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 = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
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 = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
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 = evdev_device(device);
return tp_tap_default(evdev);
}
static enum libinput_config_status
tp_tap_config_set_map(struct libinput_device *device,
enum libinput_config_tap_button_map map)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
tp->tap.want_map = map;
tp_tap_update_map(tp);
return LIBINPUT_CONFIG_STATUS_SUCCESS;
}
static enum libinput_config_tap_button_map
tp_tap_config_get_map(struct libinput_device *device)
{
struct evdev_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
return tp->tap.want_map;
}
static enum libinput_config_tap_button_map
tp_tap_config_get_default_map(struct libinput_device *device)
{
return LIBINPUT_CONFIG_TAP_MAP_LRM;
}
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 = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
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_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
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 = 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 = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
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_dispatch *dispatch = evdev_device(device)->dispatch;
struct tp_dispatch *tp = tp_dispatch(dispatch);
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 = evdev_device(device);
return tp_drag_lock_default(evdev);
}
void
tp_init_tap(struct tp_dispatch *tp)
{
char timer_name[64];
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_map = tp_tap_config_set_map;
tp->tap.config.get_map = tp_tap_config_get_map;
tp->tap.config.get_default_map = tp_tap_config_get_default_map;
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.map = LIBINPUT_CONFIG_TAP_MAP_LRM;
tp->tap.want_map = tp->tap.map;
tp->tap.drag_enabled = tp_drag_default(tp->device);
tp->tap.drag_lock_enabled = tp_drag_lock_default(tp->device);
snprintf(timer_name,
sizeof(timer_name),
"%s tap",
evdev_device_get_sysname(tp->device));
libinput_timer_init(&tp->tap.timer,
tp_libinput_context(tp),
timer_name,
tp_tap_handle_timeout, tp);
}
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)
{
struct tp_touch *t;
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);
}
/* To neutralize all current touches, we make them all palms */
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE)
continue;
if (t->tap.is_palm)
continue;
t->tap.is_palm = true;
t->tap.state = TAP_TOUCH_STATE_DEAD;
}
tp->tap.state = TAP_STATE_IDLE;
tp->tap.nfingers_down = 0;
}
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;
}
}