2014-02-07 15:18:17 +10:00
|
|
|
/*
|
2015-05-28 08:23:59 +10:00
|
|
|
* Copyright © 2013-2015 Red Hat, Inc.
|
2014-02-07 15:18:17 +10:00
|
|
|
*
|
2015-06-11 12:09:18 +10:00
|
|
|
* 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:
|
2014-02-07 15:18:17 +10:00
|
|
|
*
|
2015-06-11 12:09:18 +10:00
|
|
|
* 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.
|
2014-02-07 15:18:17 +10:00
|
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <assert.h>
|
2014-03-25 11:43:52 +10:00
|
|
|
#include <errno.h>
|
2014-02-07 15:18:17 +10:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdio.h>
|
2014-03-25 11:43:52 +10:00
|
|
|
#include <string.h>
|
2014-02-07 15:18:17 +10:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include "evdev-mt-touchpad.h"
|
|
|
|
|
|
2015-07-27 17:51:52 +08:00
|
|
|
#define DEFAULT_TAP_TIMEOUT_PERIOD ms2us(180)
|
|
|
|
|
#define DEFAULT_DRAG_TIMEOUT_PERIOD ms2us(300)
|
2015-03-04 08:24:36 +10:00
|
|
|
#define DEFAULT_TAP_MOVE_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3)
|
2014-02-07 15:18:17 +10:00
|
|
|
|
|
|
|
|
enum tap_event {
|
|
|
|
|
TAP_EVENT_TOUCH = 12,
|
|
|
|
|
TAP_EVENT_MOTION,
|
|
|
|
|
TAP_EVENT_RELEASE,
|
|
|
|
|
TAP_EVENT_BUTTON,
|
|
|
|
|
TAP_EVENT_TIMEOUT,
|
2015-07-06 15:22:45 +10:00
|
|
|
TAP_EVENT_THUMB,
|
2014-02-07 15:18:17 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*****************************************
|
|
|
|
|
* 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*
|
2015-05-01 11:41:12 +10:00
|
|
|
tap_state_to_str(enum tp_tap_state state)
|
|
|
|
|
{
|
2014-02-07 15:18:17 +10:00
|
|
|
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);
|
2015-08-31 11:10:58 +10:00
|
|
|
CASE_RETURN_STRING(TAP_STATE_TOUCH_2_RELEASE);
|
2014-02-07 15:18:17 +10:00
|
|
|
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);
|
2015-04-30 00:13:51 +02:00
|
|
|
CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_TAP);
|
2014-02-07 15:18:17 +10:00
|
|
|
CASE_RETURN_STRING(TAP_STATE_DRAGGING_2);
|
2015-04-16 10:19:11 +10:00
|
|
|
CASE_RETURN_STRING(TAP_STATE_MULTITAP);
|
|
|
|
|
CASE_RETURN_STRING(TAP_STATE_MULTITAP_DOWN);
|
2014-02-07 15:18:17 +10:00
|
|
|
CASE_RETURN_STRING(TAP_STATE_DEAD);
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline const char*
|
2015-05-01 11:41:12 +10:00
|
|
|
tap_event_to_str(enum tap_event event)
|
|
|
|
|
{
|
2014-02-07 15:18:17 +10:00
|
|
|
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);
|
2015-07-06 15:22:45 +10:00
|
|
|
CASE_RETURN_STRING(TAP_EVENT_THUMB);
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
tp_tap_notify(struct tp_dispatch *tp,
|
2014-04-08 12:29:45 +02:00
|
|
|
uint64_t time,
|
2014-02-07 15:18:17 +10:00
|
|
|
int nfingers,
|
2014-06-03 20:08:02 -04:00
|
|
|
enum libinput_button_state state)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-28 13:21:04 +02:00
|
|
|
if (state == LIBINPUT_BUTTON_STATE_PRESSED)
|
|
|
|
|
tp->tap.buttons_pressed |= (1 << nfingers);
|
|
|
|
|
else
|
|
|
|
|
tp->tap.buttons_pressed &= ~(1 << nfingers);
|
|
|
|
|
|
2014-07-27 15:54:49 +02:00
|
|
|
evdev_pointer_notify_button(tp->device,
|
|
|
|
|
time,
|
|
|
|
|
button,
|
|
|
|
|
state);
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-04-08 12:29:45 +02:00
|
|
|
tp_tap_set_timer(struct tp_dispatch *tp, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
2014-06-06 17:01:07 +02:00
|
|
|
libinput_timer_set(&tp->tap.timer, time + DEFAULT_TAP_TIMEOUT_PERIOD);
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
2015-04-30 11:12:26 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
static void
|
|
|
|
|
tp_tap_clear_timer(struct tp_dispatch *tp)
|
|
|
|
|
{
|
2014-06-06 17:01:07 +02:00
|
|
|
libinput_timer_cancel(&tp->tap.timer);
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_idle_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
2015-05-22 15:16:31 +10:00
|
|
|
struct libinput *libinput = tp_libinput_context(tp);
|
2014-02-07 15:18:17 +10:00
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_TOUCH;
|
2016-01-22 17:59:19 +10:00
|
|
|
tp->tap.first_press_time = time;
|
2014-02-07 15:18:17 +10:00
|
|
|
tp_tap_set_timer(tp, time);
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2015-05-04 16:22:36 +10:00
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
case TAP_EVENT_MOTION:
|
2014-06-18 19:51:19 +10:00
|
|
|
log_bug_libinput(libinput,
|
2015-04-15 15:33:41 +10:00
|
|
|
"invalid tap event, no fingers are down\n");
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
|
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
log_bug_libinput(libinput,
|
|
|
|
|
"invalid tap event, no fingers down, no thumb\n");
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_TOUCH_2;
|
|
|
|
|
tp_tap_set_timer(tp, time);
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2016-01-22 17:59:19 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2014-02-07 15:18:17 +10:00
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
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;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_hold_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
tp->tap.state = TAP_STATE_IDLE;
|
|
|
|
|
t->tap.is_thumb = true;
|
|
|
|
|
t->tap.state = TAP_TOUCH_STATE_DEAD;
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_tapped_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
2015-05-22 15:16:31 +10:00
|
|
|
struct libinput *libinput = tp_libinput_context(tp);
|
2014-02-07 15:18:17 +10:00
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_MOTION:
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2014-06-18 19:51:19 +10:00
|
|
|
log_bug_libinput(libinput,
|
2015-04-15 15:33:41 +10:00
|
|
|
"invalid tap event when fingers are up\n");
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
|
2015-04-28 09:50:02 +10:00
|
|
|
tp_tap_set_timer(tp, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
tp->tap.state = TAP_STATE_IDLE;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch2_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_TOUCH_3;
|
|
|
|
|
tp_tap_set_timer(tp, time);
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2015-08-31 11:10:58 +10:00
|
|
|
tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
|
|
|
|
|
tp_tap_set_timer(tp, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_MOTION:
|
|
|
|
|
tp_tap_clear_timer(tp);
|
2014-08-22 14:58:48 +10:00
|
|
|
/* fallthrough */
|
2014-02-07 15:18:17 +10:00
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
|
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-31 11:10:58 +10:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch3_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
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;
|
2014-11-06 11:15:27 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
|
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_DRAGGING_2;
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2015-04-16 10:19:11 +10:00
|
|
|
tp->tap.state = TAP_STATE_MULTITAP;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
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;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_DRAGGING_2;
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2015-06-22 11:06:25 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_MOTION:
|
|
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
/* noop */
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
2015-04-30 00:13:51 +02:00
|
|
|
tp->tap.state = TAP_STATE_DRAGGING_OR_TAP;
|
|
|
|
|
tp_tap_set_timer(tp, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
|
|
|
|
case TAP_EVENT_MOTION:
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
tp->tap.state = TAP_STATE_IDLE;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-30 00:13:51 +02:00
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2015-04-30 00:13:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging2_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
|
|
|
|
tp->tap.state = TAP_STATE_DRAGGING;
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_MOTION:
|
|
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
/* noop */
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
tp->tap.state = TAP_STATE_DEAD;
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 10:19:11 +10:00
|
|
|
static void
|
|
|
|
|
tp_tap_multitap_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event, uint64_t time)
|
|
|
|
|
{
|
2015-05-22 15:16:31 +10:00
|
|
|
struct libinput *libinput = tp_libinput_context(tp);
|
2015-04-16 10:19:11 +10:00
|
|
|
|
|
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2015-04-16 10:19:11 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2015-04-16 10:19:11 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dead_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event,
|
|
|
|
|
uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
|
case TAP_EVENT_RELEASE:
|
2015-05-04 16:22:36 +10:00
|
|
|
if (tp->nfingers_down == 0)
|
2014-02-07 15:18:17 +10:00
|
|
|
tp->tap.state = TAP_STATE_IDLE;
|
|
|
|
|
break;
|
|
|
|
|
case TAP_EVENT_TOUCH:
|
|
|
|
|
case TAP_EVENT_MOTION:
|
|
|
|
|
case TAP_EVENT_TIMEOUT:
|
|
|
|
|
case TAP_EVENT_BUTTON:
|
|
|
|
|
break;
|
2015-07-06 15:22:45 +10:00
|
|
|
case TAP_EVENT_THUMB:
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_handle_event(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t,
|
|
|
|
|
enum tap_event event,
|
|
|
|
|
uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
2015-05-22 15:16:31 +10:00
|
|
|
struct libinput *libinput = tp_libinput_context(tp);
|
2014-02-07 15:18:17 +10:00
|
|
|
enum tp_tap_state current;
|
2014-06-18 19:51:19 +10:00
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
current = tp->tap.state;
|
|
|
|
|
|
|
|
|
|
switch(tp->tap.state) {
|
|
|
|
|
case TAP_STATE_IDLE:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_idle_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_TOUCH:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_HOLD:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_hold_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_TAPPED:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_tapped_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_TOUCH_2:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch2_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_TOUCH_2_HOLD:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch2_hold_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-08-31 11:10:58 +10:00
|
|
|
case TAP_STATE_TOUCH_2_RELEASE:
|
|
|
|
|
tp_tap_touch2_release_handle_event(tp, t, event, time);
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
case TAP_STATE_TOUCH_3:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch3_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_TOUCH_3_HOLD:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_touch3_hold_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_DRAGGING_OR_DOUBLETAP:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging_or_doubletap_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_DRAGGING:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
case TAP_STATE_DRAGGING_WAIT:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging_wait_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-04-30 00:13:51 +02:00
|
|
|
case TAP_STATE_DRAGGING_OR_TAP:
|
|
|
|
|
tp_tap_dragging_tap_handle_event(tp, t, event, time);
|
|
|
|
|
break;
|
2014-02-07 15:18:17 +10:00
|
|
|
case TAP_STATE_DRAGGING_2:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dragging2_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
2015-04-16 10:19:11 +10:00
|
|
|
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;
|
2014-02-07 15:18:17 +10:00
|
|
|
case TAP_STATE_DEAD:
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_dead_handle_event(tp, t, event, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tp->tap.state == TAP_STATE_IDLE || tp->tap.state == TAP_STATE_DEAD)
|
|
|
|
|
tp_tap_clear_timer(tp);
|
|
|
|
|
|
2014-06-18 19:51:19 +10:00
|
|
|
log_debug(libinput,
|
|
|
|
|
"tap state: %s → %s → %s\n",
|
2014-06-05 16:04:00 +10:00
|
|
|
tap_state_to_str(current),
|
|
|
|
|
tap_event_to_str(event),
|
|
|
|
|
tap_state_to_str(tp->tap.state));
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2015-03-04 08:24:36 +10:00
|
|
|
tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp,
|
|
|
|
|
struct tp_touch *t)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
2015-03-24 13:14:19 +01:00
|
|
|
struct normalized_coords norm =
|
|
|
|
|
tp_normalize_delta(tp, device_delta(t->point,
|
|
|
|
|
t->tap.initial));
|
2014-02-07 15:18:17 +10:00
|
|
|
|
2015-03-24 13:14:19 +01:00
|
|
|
return normalized_length(norm) > DEFAULT_TAP_MOVE_THRESHOLD;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
2014-11-05 10:48:59 +01:00
|
|
|
static bool
|
|
|
|
|
tp_tap_enabled(struct tp_dispatch *tp)
|
|
|
|
|
{
|
|
|
|
|
return tp->tap.enabled && !tp->tap.suspended;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
int
|
2014-04-08 12:29:45 +02:00
|
|
|
tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
|
|
|
|
struct tp_touch *t;
|
2014-02-10 07:44:59 +10:00
|
|
|
int filter_motion = 0;
|
2014-02-07 15:18:17 +10:00
|
|
|
|
2014-11-05 10:48:59 +01:00
|
|
|
if (!tp_tap_enabled(tp))
|
2014-09-28 13:21:05 +02:00
|
|
|
return 0;
|
|
|
|
|
|
2014-07-27 23:28:31 +02:00
|
|
|
/* 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)
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_handle_event(tp, NULL, TAP_EVENT_BUTTON, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
|
|
|
|
|
tp_for_each_touch(tp, t) {
|
|
|
|
|
if (!t->dirty || t->state == TOUCH_NONE)
|
|
|
|
|
continue;
|
|
|
|
|
|
2014-09-28 12:51:09 +02:00
|
|
|
if (tp->buttons.is_clickpad &&
|
|
|
|
|
tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
|
2014-06-20 14:16:13 +10:00
|
|
|
t->tap.state = TAP_TOUCH_STATE_DEAD;
|
|
|
|
|
|
2015-05-27 18:25:49 +10:00
|
|
|
/* If a touch was considered thumb for tapping once, we
|
|
|
|
|
* ignore it for the rest of lifetime */
|
|
|
|
|
if (t->tap.is_thumb)
|
|
|
|
|
continue;
|
|
|
|
|
|
2014-06-20 14:16:13 +10:00
|
|
|
if (t->state == TOUCH_BEGIN) {
|
2015-05-27 18:25:49 +10:00
|
|
|
/* 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 */
|
2015-07-17 11:17:22 +10:00
|
|
|
if (t->thumb.state == THUMB_STATE_YES) {
|
2015-05-27 18:25:49 +10:00
|
|
|
t->tap.is_thumb = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-20 14:16:13 +10:00
|
|
|
t->tap.state = TAP_TOUCH_STATE_TOUCH;
|
2015-03-11 09:24:52 +10:00
|
|
|
t->tap.initial = t->point;
|
2014-06-20 14:16:13 +10:00
|
|
|
tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time);
|
2015-04-15 15:21:08 +10:00
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2014-06-20 14:16:13 +10:00
|
|
|
} else if (t->state == TOUCH_END) {
|
|
|
|
|
tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time);
|
2014-09-28 12:49:06 +02:00
|
|
|
t->tap.state = TAP_TOUCH_STATE_IDLE;
|
2014-06-20 14:16:13 +10:00
|
|
|
} else if (tp->tap.state != TAP_STATE_IDLE &&
|
2015-02-23 09:01:08 +10:00
|
|
|
tp_tap_exceeds_motion_threshold(tp, t)) {
|
2014-06-20 14:16:13 +10:00
|
|
|
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);
|
2015-07-06 15:22:45 +10:00
|
|
|
} else if (tp->tap.state != TAP_STATE_IDLE &&
|
2015-07-17 11:17:22 +10:00
|
|
|
t->thumb.state == THUMB_STATE_YES &&
|
2015-07-06 15:22:45 +10:00
|
|
|
!t->tap.is_thumb) {
|
|
|
|
|
tp_tap_handle_event(tp, t, TAP_EVENT_THUMB, time);
|
2014-06-20 14:16:13 +10:00
|
|
|
}
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
2014-02-10 07:44:59 +10:00
|
|
|
/**
|
|
|
|
|
* 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:
|
2015-04-30 00:13:51 +02:00
|
|
|
case TAP_STATE_DRAGGING_OR_TAP:
|
2014-02-10 07:44:59 +10:00
|
|
|
case TAP_STATE_TOUCH_2:
|
|
|
|
|
case TAP_STATE_TOUCH_3:
|
2015-04-16 10:19:11 +10:00
|
|
|
case TAP_STATE_MULTITAP_DOWN:
|
2014-02-10 07:44:59 +10:00
|
|
|
filter_motion = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return filter_motion;
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2014-06-06 17:01:07 +02:00
|
|
|
tp_tap_handle_timeout(uint64_t time, void *data)
|
2014-02-07 15:18:17 +10:00
|
|
|
{
|
2014-06-06 17:01:07 +02:00
|
|
|
struct tp_dispatch *tp = data;
|
2014-06-20 14:16:13 +10:00
|
|
|
struct tp_touch *t;
|
|
|
|
|
|
|
|
|
|
tp_tap_handle_event(tp, NULL, TAP_EVENT_TIMEOUT, time);
|
2014-02-07 15:18:17 +10:00
|
|
|
|
2014-06-20 14:16:13 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2014-02-07 15:18:17 +10:00
|
|
|
}
|
|
|
|
|
|
2014-11-05 10:48:59 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-04 10:38:21 +10:00
|
|
|
static int
|
|
|
|
|
tp_tap_config_count(struct libinput_device *device)
|
|
|
|
|
{
|
|
|
|
|
struct evdev_dispatch *dispatch;
|
2014-08-22 15:15:17 +10:00
|
|
|
struct tp_dispatch *tp = NULL;
|
2014-02-04 10:38:21 +10:00
|
|
|
|
|
|
|
|
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
|
2014-07-21 11:07:25 +10:00
|
|
|
tp_tap_config_set_enabled(struct libinput_device *device,
|
|
|
|
|
enum libinput_config_tap_state enabled)
|
2014-02-04 10:38:21 +10:00
|
|
|
{
|
2014-11-05 10:48:59 +01:00
|
|
|
struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch;
|
2015-01-06 09:34:06 +10:00
|
|
|
struct tp_dispatch *tp = NULL;
|
2014-02-04 10:38:21 +10:00
|
|
|
|
2015-01-06 09:34:06 +10:00
|
|
|
tp = container_of(dispatch, tp, base);
|
2014-11-05 10:48:59 +01:00
|
|
|
tp_tap_enabled_update(tp, tp->tap.suspended,
|
|
|
|
|
(enabled == LIBINPUT_CONFIG_TAP_ENABLED),
|
|
|
|
|
libinput_now(device->seat->libinput));
|
2014-02-04 10:38:21 +10:00
|
|
|
|
|
|
|
|
return LIBINPUT_CONFIG_STATUS_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-21 11:07:25 +10:00
|
|
|
static enum libinput_config_tap_state
|
2014-02-04 10:38:21 +10:00
|
|
|
tp_tap_config_is_enabled(struct libinput_device *device)
|
|
|
|
|
{
|
|
|
|
|
struct evdev_dispatch *dispatch;
|
2014-08-22 15:15:17 +10:00
|
|
|
struct tp_dispatch *tp = NULL;
|
2014-02-04 10:38:21 +10:00
|
|
|
|
|
|
|
|
dispatch = ((struct evdev_device *) device)->dispatch;
|
|
|
|
|
tp = container_of(dispatch, tp, base);
|
|
|
|
|
|
2014-07-21 11:07:25 +10:00
|
|
|
return tp->tap.enabled ? LIBINPUT_CONFIG_TAP_ENABLED :
|
|
|
|
|
LIBINPUT_CONFIG_TAP_DISABLED;
|
2014-02-04 10:38:21 +10:00
|
|
|
}
|
|
|
|
|
|
2014-07-21 11:07:25 +10:00
|
|
|
static enum libinput_config_tap_state
|
2015-03-04 10:00:52 +10:00
|
|
|
tp_tap_default(struct evdev_device *evdev)
|
2014-02-04 10:38:21 +10:00
|
|
|
{
|
2015-03-04 10:00:52 +10:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
|
2014-02-04 10:38:21 +10:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2014-07-21 11:07:25 +10:00
|
|
|
return LIBINPUT_CONFIG_TAP_DISABLED;
|
2014-02-04 10:38:21 +10:00
|
|
|
}
|
|
|
|
|
|
2015-03-04 10:00:52 +10:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 17:59:19 +10:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-16 17:02:02 +10:00
|
|
|
static enum libinput_config_status
|
|
|
|
|
tp_tap_config_set_draglock_enabled(struct libinput_device *device,
|
|
|
|
|
enum libinput_config_drag_lock_state enabled)
|
|
|
|
|
{
|
2015-06-22 11:06:25 +10:00
|
|
|
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;
|
2015-06-16 17:02:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum libinput_config_drag_lock_state
|
|
|
|
|
tp_tap_config_get_draglock_enabled(struct libinput_device *device)
|
2015-06-22 11:06:25 +10:00
|
|
|
{
|
|
|
|
|
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)
|
2015-06-16 17:02:02 +10:00
|
|
|
{
|
2015-06-26 16:18:29 +10:00
|
|
|
return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
|
2015-06-16 17:02:02 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum libinput_config_drag_lock_state
|
|
|
|
|
tp_tap_config_get_default_draglock_enabled(struct libinput_device *device)
|
|
|
|
|
{
|
2015-06-22 11:06:25 +10:00
|
|
|
struct evdev_device *evdev = (struct evdev_device *)device;
|
|
|
|
|
|
|
|
|
|
return tp_drag_lock_default(evdev);
|
2015-06-16 17:02:02 +10:00
|
|
|
}
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
int
|
|
|
|
|
tp_init_tap(struct tp_dispatch *tp)
|
|
|
|
|
{
|
2014-02-04 10:38:21 +10:00
|
|
|
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;
|
2016-01-22 17:59:19 +10:00
|
|
|
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;
|
2015-06-16 17:02:02 +10:00
|
|
|
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;
|
2014-02-04 10:38:21 +10:00
|
|
|
tp->device->base.config.tap = &tp->tap.config;
|
|
|
|
|
|
2014-02-07 15:18:17 +10:00
|
|
|
tp->tap.state = TAP_STATE_IDLE;
|
2015-03-04 10:00:52 +10:00
|
|
|
tp->tap.enabled = tp_tap_default(tp->device);
|
2016-01-22 17:59:19 +10:00
|
|
|
tp->tap.drag_enabled = tp_drag_default(tp->device);
|
2015-06-22 11:06:25 +10:00
|
|
|
tp->tap.drag_lock_enabled = tp_drag_lock_default(tp->device);
|
2014-02-07 15:18:17 +10:00
|
|
|
|
2014-06-06 17:01:07 +02:00
|
|
|
libinput_timer_init(&tp->tap.timer,
|
2015-05-22 15:16:31 +10:00
|
|
|
tp_libinput_context(tp),
|
2014-06-06 17:01:07 +02:00
|
|
|
tp_tap_handle_timeout, tp);
|
2014-02-07 15:18:17 +10:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2014-03-25 12:14:39 +10:00
|
|
|
|
|
|
|
|
void
|
2014-12-05 12:50:39 +01:00
|
|
|
tp_remove_tap(struct tp_dispatch *tp)
|
2014-03-25 12:14:39 +10:00
|
|
|
{
|
2014-06-06 17:01:07 +02:00
|
|
|
libinput_timer_cancel(&tp->tap.timer);
|
2014-03-25 12:14:39 +10:00
|
|
|
}
|
2014-09-01 17:17:18 +10:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
tp_release_all_taps(struct tp_dispatch *tp, uint64_t now)
|
|
|
|
|
{
|
2014-09-28 13:21:04 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 1; i <= 3; i++) {
|
|
|
|
|
if (tp->tap.buttons_pressed & (1 << i))
|
2014-11-06 11:15:27 +01:00
|
|
|
tp_tap_notify(tp, now, i, LIBINPUT_BUTTON_STATE_RELEASED);
|
2014-09-28 13:21:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tp->tap.state = tp->nfingers_down ? TAP_STATE_DEAD : TAP_STATE_IDLE;
|
2014-09-01 17:17:18 +10:00
|
|
|
}
|
2014-09-28 13:21:05 +02:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
tp_tap_suspend(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
2014-11-05 10:48:59 +01:00
|
|
|
tp_tap_enabled_update(tp, true, tp->tap.enabled, time);
|
2014-09-28 13:21:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
tp_tap_resume(struct tp_dispatch *tp, uint64_t time)
|
|
|
|
|
{
|
2014-11-05 10:48:59 +01:00
|
|
|
tp_tap_enabled_update(tp, false, tp->tap.enabled, time);
|
2014-09-28 13:21:05 +02:00
|
|
|
}
|
2014-09-25 15:58:04 +02:00
|
|
|
|
|
|
|
|
bool
|
2016-01-07 13:04:58 +10:00
|
|
|
tp_tap_dragging(const struct tp_dispatch *tp)
|
2014-09-25 15:58:04 +02:00
|
|
|
{
|
|
|
|
|
switch (tp->tap.state) {
|
|
|
|
|
case TAP_STATE_DRAGGING:
|
|
|
|
|
case TAP_STATE_DRAGGING_2:
|
|
|
|
|
case TAP_STATE_DRAGGING_WAIT:
|
2015-04-30 00:13:51 +02:00
|
|
|
case TAP_STATE_DRAGGING_OR_TAP:
|
2014-09-25 15:58:04 +02:00
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|