/*
* Copyright © 2014-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.
*/
#include "config.h"
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <string.h>
#include <unistd.h>
#include "linux/input.h"
#include "evdev-mt-touchpad.h"
#define DEFAULT_BUTTON_ENTER_TIMEOUT ms2us(100)
#define DEFAULT_BUTTON_LEAVE_TIMEOUT ms2us(300)
/*****************************************
* BEFORE YOU EDIT THIS FILE, look at the state diagram in
* doc/touchpad-softbutton-state-machine.svg, or online at
* https://drive.google.com/file/d/0B1NwWmji69nocUs1cVJTbkdwMFk/edit?usp=sharing
* (it's a http://draw.io diagram)
*
* Any changes in this file must be represented in the diagram.
*
* The state machine only affects the soft button area code.
*/
static inline const char*
button_state_to_str(enum button_state state)
{
switch(state) {
CASE_RETURN_STRING(BUTTON_STATE_NONE);
CASE_RETURN_STRING(BUTTON_STATE_AREA);
CASE_RETURN_STRING(BUTTON_STATE_BOTTOM);
CASE_RETURN_STRING(BUTTON_STATE_TOP);
CASE_RETURN_STRING(BUTTON_STATE_TOP_NEW);
CASE_RETURN_STRING(BUTTON_STATE_TOP_TO_IGNORE);
CASE_RETURN_STRING(BUTTON_STATE_IGNORE);
}
return NULL;
}
static inline const char*
button_event_to_str(enum button_event event)
{
switch(event) {
CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_R);
CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_M);
CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_L);
CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_R);
CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_M);
CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_L);
CASE_RETURN_STRING(BUTTON_EVENT_IN_AREA);
CASE_RETURN_STRING(BUTTON_EVENT_UP);
CASE_RETURN_STRING(BUTTON_EVENT_PRESS);
CASE_RETURN_STRING(BUTTON_EVENT_RELEASE);
CASE_RETURN_STRING(BUTTON_EVENT_TIMEOUT);
}
return NULL;
}
static inline bool
is_inside_bottom_button_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return t->point.y >= tp->buttons.bottom_area.top_edge;
}
static inline bool
is_inside_bottom_right_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_bottom_button_area(tp, t) &&
t->point.x > tp->buttons.bottom_area.rightbutton_left_edge;
}
static inline bool
is_inside_bottom_middle_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_bottom_button_area(tp, t) &&
!is_inside_bottom_right_area(tp, t) &&
t->point.x > tp->buttons.bottom_area.middlebutton_left_edge;
}
static inline bool
is_inside_bottom_left_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_bottom_button_area(tp, t) &&
!is_inside_bottom_middle_area(tp, t) &&
!is_inside_bottom_right_area(tp, t);
}
static inline bool
is_inside_top_button_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return t->point.y <= tp->buttons.top_area.bottom_edge;
}
static inline bool
is_inside_top_right_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_top_button_area(tp, t) &&
t->point.x > tp->buttons.top_area.rightbutton_left_edge;
}
static inline bool
is_inside_top_left_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_top_button_area(tp, t) &&
t->point.x < tp->buttons.top_area.leftbutton_right_edge;
}
static inline bool
is_inside_top_middle_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_top_button_area(tp, t) &&
t->point.x >= tp->buttons.top_area.leftbutton_right_edge &&
t->point.x <= tp->buttons.top_area.rightbutton_left_edge;
}
static void
tp_button_set_enter_timer(struct tp_dispatch *tp, struct tp_touch *t)
{
libinput_timer_set(&t->button.timer,
t->time + DEFAULT_BUTTON_ENTER_TIMEOUT);
}
static void
tp_button_set_leave_timer(struct tp_dispatch *tp, struct tp_touch *t)
{
libinput_timer_set(&t->button.timer,
t->time + DEFAULT_BUTTON_LEAVE_TIMEOUT);
}
/*
* tp_button_set_state, change state and implement on-entry behavior
* as described in the state machine diagram.
*/
static void
tp_button_set_state(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_state new_state,
enum button_event event)
{
libinput_timer_cancel(&t->button.timer);
t->button.state = new_state;
switch (t->button.state) {
case BUTTON_STATE_NONE:
t->button.current = 0;
break;
case BUTTON_STATE_AREA:
t->button.current = BUTTON_EVENT_IN_AREA;
break;
case BUTTON_STATE_BOTTOM:
t->button.current = event;
break;
case BUTTON_STATE_TOP:
break;
case BUTTON_STATE_TOP_NEW:
t->button.current = event;
tp_button_set_enter_timer(tp, t);
break;
case BUTTON_STATE_TOP_TO_IGNORE:
tp_button_set_leave_timer(tp, t);
break;
case BUTTON_STATE_IGNORE:
t->button.current = 0;
break;
}
}
static void
tp_button_none_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, event);
break;
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, event);
break;
case BUTTON_EVENT_IN_AREA:
tp_button_set_state(tp, t, BUTTON_STATE_AREA, event);
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
case BUTTON_EVENT_RELEASE:
case BUTTON_EVENT_TIMEOUT:
break;
}
}
static void
tp_button_area_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
case BUTTON_EVENT_IN_AREA:
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
case BUTTON_EVENT_RELEASE:
case BUTTON_EVENT_TIMEOUT:
break;
}
}
/**
* Release any button in the bottom area, provided it started within a
* threshold around start_time (i.e. simultaneously with the other touch
* that triggered this call).
*/
static inline void
tp_button_release_other_bottom_touches(struct tp_dispatch *tp,
uint64_t other_start_time)
{
struct tp_touch *t;
tp_for_each_touch(tp, t) {
uint64_t tdelta;
if (t->button.state != BUTTON_STATE_BOTTOM ||
t->button.has_moved)
continue;
if (other_start_time > t->button.initial_time)
tdelta = other_start_time - t->button.initial_time;
else
tdelta = t->button.initial_time - other_start_time;
if (tdelta > ms2us(80))
continue;
t->button.has_moved = true;
}
}
static void
tp_button_bottom_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
if (event != t->button.current)
tp_button_set_state(tp,
t,
BUTTON_STATE_BOTTOM,
event);
break;
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
case BUTTON_EVENT_IN_AREA:
tp_button_set_state(tp, t, BUTTON_STATE_AREA, event);
/* We just transitioned one finger from BOTTOM to AREA,
* if there are other fingers in BOTTOM that started
* simultaneously with this finger, release those fingers
* because they're part of a gesture.
*/
tp_button_release_other_bottom_touches(tp,
t->button.initial_time);
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
case BUTTON_EVENT_RELEASE:
case BUTTON_EVENT_TIMEOUT:
break;
}
}
static void
tp_button_top_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, event);
break;
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
if (event != t->button.current)
tp_button_set_state(tp,
t,
BUTTON_STATE_TOP_NEW,
event);
break;
case BUTTON_EVENT_IN_AREA:
tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, event);
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
case BUTTON_EVENT_RELEASE:
case BUTTON_EVENT_TIMEOUT:
break;
}
}
static void
tp_button_top_new_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch(event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
tp_button_set_state(tp, t, BUTTON_STATE_AREA, event);
break;
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
if (event != t->button.current)
tp_button_set_state(tp,
t,
BUTTON_STATE_TOP_NEW,
event);
break;
case BUTTON_EVENT_IN_AREA:
tp_button_set_state(tp, t, BUTTON_STATE_AREA, event);
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
tp_button_set_state(tp, t, BUTTON_STATE_TOP, event);
break;
case BUTTON_EVENT_RELEASE:
break;
case BUTTON_EVENT_TIMEOUT:
tp_button_set_state(tp, t, BUTTON_STATE_TOP, event);
break;
}
}
static void
tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch(event) {
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
if (event == t->button.current)
tp_button_set_state(tp,
t,
BUTTON_STATE_TOP,
event);
else
tp_button_set_state(tp,
t,
BUTTON_STATE_TOP_NEW,
event);
break;
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
case BUTTON_EVENT_IN_AREA:
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
case BUTTON_EVENT_RELEASE:
break;
case BUTTON_EVENT_TIMEOUT:
tp_button_set_state(tp, t, BUTTON_STATE_IGNORE, event);
break;
}
}
static void
tp_button_ignore_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event)
{
switch (event) {
case BUTTON_EVENT_IN_BOTTOM_R:
case BUTTON_EVENT_IN_BOTTOM_M:
case BUTTON_EVENT_IN_BOTTOM_L:
case BUTTON_EVENT_IN_TOP_R:
case BUTTON_EVENT_IN_TOP_M:
case BUTTON_EVENT_IN_TOP_L:
case BUTTON_EVENT_IN_AREA:
break;
case BUTTON_EVENT_UP:
tp_button_set_state(tp, t, BUTTON_STATE_NONE, event);
break;
case BUTTON_EVENT_PRESS:
t->button.current = BUTTON_EVENT_IN_AREA;
break;
case BUTTON_EVENT_RELEASE:
break;
case BUTTON_EVENT_TIMEOUT:
break;
}
}
static void
tp_button_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum button_event event,
uint64_t time)
{
enum button_state current = t->button.state;
switch(t->button.state) {
case BUTTON_STATE_NONE:
tp_button_none_handle_event(tp, t, event);
break;
case BUTTON_STATE_AREA:
tp_button_area_handle_event(tp, t, event);
break;
case BUTTON_STATE_BOTTOM:
tp_button_bottom_handle_event(tp, t, event);
break;
case BUTTON_STATE_TOP:
tp_button_top_handle_event(tp, t, event);
break;
case BUTTON_STATE_TOP_NEW:
tp_button_top_new_handle_event(tp, t, event);
break;
case BUTTON_STATE_TOP_TO_IGNORE:
tp_button_top_to_ignore_handle_event(tp, t, event);
break;
case BUTTON_STATE_IGNORE:
tp_button_ignore_handle_event(tp, t, event);
break;
}
if (current != t->button.state)
evdev_log_debug(tp->device,
"button state: touch %d from %-20s event %-24s to %-20s\n",
t->index,
button_state_to_str(current),
button_event_to_str(event),
button_state_to_str(t->button.state));
}
static inline void
tp_button_check_for_movement(struct tp_dispatch *tp, struct tp_touch *t)
{
struct device_coords delta;
struct phys_coords mm;
double vector_length;
if (t->button.has_moved)
return;
switch (t->button.state) {
case BUTTON_STATE_NONE:
case BUTTON_STATE_AREA:
case BUTTON_STATE_TOP:
case BUTTON_STATE_TOP_NEW:
case BUTTON_STATE_TOP_TO_IGNORE:
case BUTTON_STATE_IGNORE:
/* No point calculating if we're not going to use it */
return;
case BUTTON_STATE_BOTTOM:
break;
}
delta.x = t->point.x - t->button.initial.x;
delta.y = t->point.y - t->button.initial.y;
mm = evdev_device_unit_delta_to_mm(tp->device, &delta);
vector_length = hypot(mm.x, mm.y);
if (vector_length > 5.0 /* mm */) {
t->button.has_moved = true;
tp_button_release_other_bottom_touches(tp,
t->button.initial_time);
}
}
void
tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *t;
tp_for_each_touch(tp, t) {
if (t->state == TOUCH_NONE || t->state == TOUCH_HOVERING)
continue;
if (t->state == TOUCH_BEGIN) {
t->button.initial = t->point;
t->button.initial_time = time;
t->button.has_moved = false;
}
if (t->state == TOUCH_END) {
tp_button_handle_event(tp, t, BUTTON_EVENT_UP, time);
} else if (t->dirty) {
enum button_event event;
if (is_inside_bottom_button_area(tp, t)) {
if (is_inside_bottom_right_area(tp, t))
event = BUTTON_EVENT_IN_BOTTOM_R;
else if (is_inside_bottom_middle_area(tp, t))
event = BUTTON_EVENT_IN_BOTTOM_M;
else
event = BUTTON_EVENT_IN_BOTTOM_L;
/* In the bottom area we check for movement
* within the area. Top area - meh */
tp_button_check_for_movement(tp, t);
} else if (is_inside_top_button_area(tp, t)) {
if (is_inside_top_right_area(tp, t))
event = BUTTON_EVENT_IN_TOP_R;
else if (is_inside_top_middle_area(tp, t))
event = BUTTON_EVENT_IN_TOP_M;
else
event = BUTTON_EVENT_IN_TOP_L;
} else {
event = BUTTON_EVENT_IN_AREA;
}
tp_button_handle_event(tp, t, event, time);
}
if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE)
tp_button_handle_event(tp, t, BUTTON_EVENT_RELEASE, time);
if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
tp_button_handle_event(tp, t, BUTTON_EVENT_PRESS, time);
}
}
static void
tp_button_handle_timeout(uint64_t now, void *data)
{
struct tp_touch *t = data;
tp_button_handle_event(t->tp, t, BUTTON_EVENT_TIMEOUT, now);
}
void
tp_process_button(struct tp_dispatch *tp,
const struct input_event *e,
uint64_t time)
{
uint32_t mask = 1 << (e->code - BTN_LEFT);
/* Ignore other buttons on clickpads */
if (tp->buttons.is_clickpad && e->code != BTN_LEFT) {
evdev_log_bug_kernel(tp->device,
"received %s button event on a clickpad\n",
libevdev_event_code_get_name(EV_KEY, e->code));
return;
}
if (e->value) {
tp->buttons.state |= mask;
tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS;
} else {
tp->buttons.state &= ~mask;
tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE;
}
}
void
tp_release_all_buttons(struct tp_dispatch *tp,
uint64_t time)
{
if (tp->buttons.state) {
tp->buttons.state = 0;
tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE;
}
}
static void
tp_init_softbuttons(struct tp_dispatch *tp,
struct evdev_device *device)
{
double width, height;
struct device_coords edges;
int mb_le, mb_re; /* middle button left/right edge */
struct phys_coords mm = { 0.0, 0.0 };
evdev_device_get_size(device, &width, &height);
/* button height: 10mm or 15% or the touchpad height,
whichever is smaller */
if (height * 0.15 > 10)
mm.y = height - 10;
else
mm.y = height * 0.85;
mm.x = width * 0.5;
edges = evdev_device_mm_to_units(device, &mm);
tp->buttons.bottom_area.top_edge = edges.y;
tp->buttons.bottom_area.rightbutton_left_edge = edges.x;
tp->buttons.bottom_area.middlebutton_left_edge = INT_MAX;
/* if middlebutton emulation is enabled, don't init a software area */
if (device->middlebutton.want_enabled)
return;
/* The middle button is 25% of the touchpad and centered. Many
* touchpads don't have markings for the middle button at all so we
* need to make it big enough to reliably hit it but not too big so
* it takes away all the space.
*
* On touchpads with visible markings we reduce the size of the
* middle button since users have a visual guide.
*/
if (evdev_device_has_model_quirk(device,
QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER)) {
mm.x = width/2 - 5; /* 10mm wide */
edges = evdev_device_mm_to_units(device, &mm);
mb_le = edges.x;
mm.x = width/2 + 5; /* 10mm wide */
edges = evdev_device_mm_to_units(device, &mm);
mb_re = edges.x;
} else {
mm.x = width * 0.375;
edges = evdev_device_mm_to_units(device, &mm);
mb_le = edges.x;
mm.x = width * 0.625;
edges = evdev_device_mm_to_units(device, &mm);
mb_re = edges.x;
}
tp->buttons.bottom_area.middlebutton_left_edge = mb_le;
tp->buttons.bottom_area.rightbutton_left_edge = mb_re;
}
void
tp_init_top_softbuttons(struct tp_dispatch *tp,
struct evdev_device *device,
double topbutton_size_mult)
{
struct device_coords edges;
if (tp->buttons.has_topbuttons) {
/* T440s has the top button line 5mm from the top, event
analysis has shown events to start down to ~10mm from the
top - which maps to 15%. We allow the caller to enlarge the
area using a multiplier for the touchpad disabled case. */
double topsize_mm = 10 * topbutton_size_mult;
struct phys_coords mm;
double width, height;
evdev_device_get_size(device, &width, &height);
mm.x = width * 0.60;
mm.y = topsize_mm;
edges = evdev_device_mm_to_units(device, &mm);
tp->buttons.top_area.bottom_edge = edges.y;
tp->buttons.top_area.rightbutton_left_edge = edges.x;
mm.x = width * 0.40;
edges = evdev_device_mm_to_units(device, &mm);
tp->buttons.top_area.leftbutton_right_edge = edges.x;
} else {
tp->buttons.top_area.bottom_edge = INT_MIN;
}
}
static inline uint32_t
tp_button_config_click_get_methods(struct libinput_device *device)
{
struct evdev_device *evdev = evdev_device(device);
struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
uint32_t methods = LIBINPUT_CONFIG_CLICK_METHOD_NONE;
if (tp->buttons.is_clickpad) {
methods |= LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
if (tp->has_mt)
methods |= LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
}
if (evdev->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON)
methods |= LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
return methods;
}
static void
tp_switch_click_method(struct tp_dispatch *tp)
{
/*
* All we need to do when switching click methods is to change the
* bottom_area.top_edge so that when in clickfinger mode the bottom
* touchpad area is not dead wrt finger movement starting there.
*
* We do not need to take any state into account, fingers which are
* already down will simply keep the state / area they have assigned
* until they are released, and the post_button_events path is state
* agnostic.
*/
switch (tp->buttons.click_method) {
case LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS:
tp_init_softbuttons(tp, tp->device);
break;
case LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER:
case LIBINPUT_CONFIG_CLICK_METHOD_NONE:
tp->buttons.bottom_area.top_edge = INT_MAX;
break;
}
}
static enum libinput_config_status
tp_button_config_click_set_method(struct libinput_device *device,
enum libinput_config_click_method method)
{
struct evdev_device *evdev = evdev_device(device);
struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
tp->buttons.click_method = method;
tp_switch_click_method(tp);
return LIBINPUT_CONFIG_STATUS_SUCCESS;
}
static enum libinput_config_click_method
tp_button_config_click_get_method(struct libinput_device *device)
{
struct evdev_device *evdev = evdev_device(device);
struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
return tp->buttons.click_method;
}
static enum libinput_config_click_method
tp_click_get_default_method(struct tp_dispatch *tp)
{
struct evdev_device *device = tp->device;
if (evdev_device_has_model_quirk(device, QUIRK_MODEL_CHROMEBOOK) ||
evdev_device_has_model_quirk(device, QUIRK_MODEL_SYSTEM76_BONOBO) ||
evdev_device_has_model_quirk(device, QUIRK_MODEL_SYSTEM76_GALAGO) ||
evdev_device_has_model_quirk(device, QUIRK_MODEL_SYSTEM76_KUDU) ||
evdev_device_has_model_quirk(device, QUIRK_MODEL_CLEVO_W740SU) ||
evdev_device_has_model_quirk(device, QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON))
return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
if (!tp->buttons.is_clickpad)
return LIBINPUT_CONFIG_CLICK_METHOD_NONE;
else if (evdev_device_has_model_quirk(device, QUIRK_MODEL_APPLE_TOUCHPAD))
return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
return LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
}
static enum libinput_config_click_method
tp_button_config_click_get_default_method(struct libinput_device *device)
{
struct evdev_device *evdev = evdev_device(device);
struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
return tp_click_get_default_method(tp);
}
void
tp_clickpad_middlebutton_apply_config(struct evdev_device *device)
{
struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch;
if (!tp->buttons.is_clickpad ||
tp->buttons.state != 0)
return;
if (device->middlebutton.want_enabled ==
device->middlebutton.enabled)
return;
device->middlebutton.enabled = device->middlebutton.want_enabled;
if (tp->buttons.click_method ==
LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
tp_init_softbuttons(tp, device);
}
static int
tp_clickpad_middlebutton_is_available(struct libinput_device *device)
{
return evdev_middlebutton_is_available(device);
}
static enum libinput_config_status
tp_clickpad_middlebutton_set(struct libinput_device *device,
enum libinput_config_middle_emulation_state enable)
{
struct evdev_device *evdev = evdev_device(device);
switch (enable) {
case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED:
evdev->middlebutton.want_enabled = true;
break;
case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED:
evdev->middlebutton.want_enabled = false;
break;
default:
return LIBINPUT_CONFIG_STATUS_INVALID;
}
tp_clickpad_middlebutton_apply_config(evdev);
return LIBINPUT_CONFIG_STATUS_SUCCESS;
}
static enum libinput_config_middle_emulation_state
tp_clickpad_middlebutton_get(struct libinput_device *device)
{
return evdev_middlebutton_get(device);
}
static enum libinput_config_middle_emulation_state
tp_clickpad_middlebutton_get_default(struct libinput_device *device)
{
return evdev_middlebutton_get_default(device);
}
static inline void
tp_init_clickpad_middlebutton_emulation(struct tp_dispatch *tp,
struct evdev_device *device)
{
device->middlebutton.enabled_default = false;
device->middlebutton.want_enabled = false;
device->middlebutton.enabled = false;
device->middlebutton.config.available = tp_clickpad_middlebutton_is_available;
device->middlebutton.config.set = tp_clickpad_middlebutton_set;
device->middlebutton.config.get = tp_clickpad_middlebutton_get;
device->middlebutton.config.get_default = tp_clickpad_middlebutton_get_default;
device->base.config.middle_emulation = &device->middlebutton.config;
}
static inline void
tp_init_middlebutton_emulation(struct tp_dispatch *tp,
struct evdev_device *device)
{
bool enable_by_default,
want_config_option;
/* On clickpads we provide the config option but disable by default.
When enabled, the middle software button disappears */
if (tp->buttons.is_clickpad) {
tp_init_clickpad_middlebutton_emulation(tp, device);
return;
}
/* init middle button emulation on non-clickpads, but only if we
* don't have a middle button. Exception: ALPS touchpads don't know
* if they have a middle button, so we always want the option there
* and enabled by default.
*/
if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE)) {
enable_by_default = true;
want_config_option = false;
} else if (evdev_device_has_model_quirk(device,
QUIRK_MODEL_ALPS_TOUCHPAD)) {
enable_by_default = true;
want_config_option = true;
} else
return;
evdev_init_middlebutton(tp->device,
enable_by_default,
want_config_option);
}
void
tp_init_buttons(struct tp_dispatch *tp,
struct evdev_device *device)
{
struct tp_touch *t;
const struct input_absinfo *absinfo_x, *absinfo_y;
int i;
tp->buttons.is_clickpad = libevdev_has_property(device->evdev,
INPUT_PROP_BUTTONPAD);
tp->buttons.has_topbuttons = libevdev_has_property(device->evdev,
INPUT_PROP_TOPBUTTONPAD);
if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE) ||
libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) {
if (tp->buttons.is_clickpad)
evdev_log_bug_kernel(device,
"clickpad advertising right button\n");
} else if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_LEFT) &&
!tp->buttons.is_clickpad &&
libevdev_get_id_vendor(device->evdev) != VENDOR_ID_APPLE) {
evdev_log_bug_kernel(device,
"non clickpad without right button?\n");
}
absinfo_x = device->abs.absinfo_x;
absinfo_y = device->abs.absinfo_y;
/* pinned-finger motion threshold, see tp_unpin_finger. */
tp->buttons.motion_dist.x_scale_coeff = 1.0/absinfo_x->resolution;
tp->buttons.motion_dist.y_scale_coeff = 1.0/absinfo_y->resolution;
tp->buttons.config_method.get_methods = tp_button_config_click_get_methods;
tp->buttons.config_method.set_method = tp_button_config_click_set_method;
tp->buttons.config_method.get_method = tp_button_config_click_get_method;
tp->buttons.config_method.get_default_method = tp_button_config_click_get_default_method;
tp->device->base.config.click_method = &tp->buttons.config_method;
tp->buttons.click_method = tp_click_get_default_method(tp);
tp_switch_click_method(tp);
tp_init_top_softbuttons(tp, device, 1.0);
tp_init_middlebutton_emulation(tp, device);
i = 0;
tp_for_each_touch(tp, t) {
char timer_name[64];
i++;
snprintf(timer_name,
sizeof(timer_name),
"%s (%d) button",
evdev_device_get_sysname(device),
i);
t->button.state = BUTTON_STATE_NONE;
libinput_timer_init(&t->button.timer,
tp_libinput_context(tp),
timer_name,
tp_button_handle_timeout, t);
}
}
void
tp_remove_buttons(struct tp_dispatch *tp)
{
struct tp_touch *t;
tp_for_each_touch(tp, t) {
libinput_timer_cancel(&t->button.timer);
libinput_timer_destroy(&t->button.timer);
}
}
static int
tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
{
uint32_t current, old, button;
current = tp->buttons.state;
old = tp->buttons.old_state;
button = BTN_LEFT;
while (current || old) {
enum libinput_button_state state;
if ((current & 0x1) ^ (old & 0x1)) {
uint32_t b;
if (!!(current & 0x1))
state = LIBINPUT_BUTTON_STATE_PRESSED;
else
state = LIBINPUT_BUTTON_STATE_RELEASED;
b = evdev_to_left_handed(tp->device, button);
evdev_pointer_notify_physical_button(tp->device,
time,
b,
state);
}
button++;
current >>= 1;
old >>= 1;
}
return 0;
}
static inline bool
tp_clickfinger_within_distance(struct tp_dispatch *tp,
struct tp_touch *t1,
struct tp_touch *t2)
{
double x, y;
bool within_distance = false;
int xres, yres;
int bottom_threshold;
if (!t1 || !t2)
return 0;
if (tp_thumb_ignored(tp, t1) || tp_thumb_ignored(tp, t2))
return 0;
x = abs(t1->point.x - t2->point.x);
y = abs(t1->point.y - t2->point.y);
xres = tp->device->abs.absinfo_x->resolution;
yres = tp->device->abs.absinfo_y->resolution;
x /= xres;
y /= yres;
/* maximum horiz spread is 40mm horiz, 30mm vert, anything wider
* than that is probably a gesture. */
if (x > 40 || y > 30)
goto out;
within_distance = true;
/* if y spread is <= 20mm, they're definitely together. */
if (y <= 20)
goto out;
/* if they're vertically spread between 20-40mm, they're not
* together if:
* - the touchpad's vertical size is >50mm, anything smaller is
* unlikely to have a thumb resting on it
* - and one of the touches is in the bottom 20mm of the touchpad
* and the other one isn't
*/
if (tp->device->abs.dimensions.y/yres < 50)
goto out;
bottom_threshold = tp->device->abs.absinfo_y->maximum - 20 * yres;
if ((t1->point.y > bottom_threshold) !=
(t2->point.y > bottom_threshold))
within_distance = 0;
out:
return within_distance;
}
static uint32_t
tp_clickfinger_set_button(struct tp_dispatch *tp)
{
uint32_t button;
unsigned int nfingers = 0;
struct tp_touch *t;
struct tp_touch *first = NULL,
*second = NULL;
tp_for_each_touch(tp, t) {
if (t->state != TOUCH_BEGIN && t->state != TOUCH_UPDATE)
continue;
if (tp_thumb_ignored(tp, t))
continue;
if (t->palm.state != PALM_NONE)
continue;
nfingers++;
if (!first)
first = t;
else if (!second)
second = t;
}
/* Only check for finger distance when there are 2 fingers on the
* touchpad */
if (nfingers != 2)
goto out;
if (tp_clickfinger_within_distance(tp, first, second))
nfingers = 2;
else
nfingers = 1;
out:
switch (nfingers) {
case 0:
case 1: button = BTN_LEFT; break;
case 2: button = BTN_RIGHT; break;
default:
button = BTN_MIDDLE; break;
break;
}
return button;
}
static int
tp_notify_clickpadbutton(struct tp_dispatch *tp,
uint64_t time,
uint32_t button,
uint32_t is_topbutton,
enum libinput_button_state state)
{
/* If we've a trackpoint, send top buttons through the trackpoint */
if (tp->buttons.trackpoint) {
if (is_topbutton) {
struct evdev_dispatch *dispatch = tp->buttons.trackpoint->dispatch;
struct input_event event;
struct input_event syn_report = {{ 0, 0 }, EV_SYN, SYN_REPORT, 0 };
event.time = us2tv(time);
event.type = EV_KEY;
event.code = button;
event.value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0;
syn_report.time = event.time;
dispatch->interface->process(dispatch,
tp->buttons.trackpoint,
&event,
time);
dispatch->interface->process(dispatch,
tp->buttons.trackpoint,
&syn_report,
time);
return 1;
}
/* Ignore button events not for the trackpoint while suspended */
if (tp->device->is_suspended)
return 0;
}
/* A button click always terminates edge scrolling, even if we
* don't end up sending a button event. */
tp_edge_scroll_stop_events(tp, time);
/*
* If the user has requested clickfinger replace the button chosen
* by the softbutton code with one based on the number of fingers.
*/
if (tp->buttons.click_method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER &&
state == LIBINPUT_BUTTON_STATE_PRESSED) {
button = tp_clickfinger_set_button(tp);
tp->buttons.active = button;
if (!button)
return 0;
}
evdev_pointer_notify_button(tp->device, time, button, state);
return 1;
}
static int
tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time)
{
uint32_t current, old, button, is_top;
enum libinput_button_state state;
enum { AREA = 0x01, LEFT = 0x02, MIDDLE = 0x04, RIGHT = 0x08 };
bool want_left_handed = true;
current = tp->buttons.state;
old = tp->buttons.old_state;
is_top = 0;
if (!tp->buttons.click_pending && current == old)
return 0;
if (current) {
struct tp_touch *t;
uint32_t area = 0;
tp_for_each_touch(tp, t) {
switch (t->button.current) {
case BUTTON_EVENT_IN_AREA:
area |= AREA;
break;
case BUTTON_EVENT_IN_TOP_L:
is_top = 1;
/* fallthrough */
case BUTTON_EVENT_IN_BOTTOM_L:
area |= LEFT;
break;
case BUTTON_EVENT_IN_TOP_M:
is_top = 1;
/* fallthrough */
case BUTTON_EVENT_IN_BOTTOM_M:
area |= MIDDLE;
break;
case BUTTON_EVENT_IN_TOP_R:
is_top = 1;
/* fallthrough */
case BUTTON_EVENT_IN_BOTTOM_R:
area |= RIGHT;
break;
default:
break;
}
}
if (area == 0 &&
tp->buttons.click_method != LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) {
/* No touches, wait for a touch before processing */
tp->buttons.click_pending = true;
return 0;
}
if ((tp->device->middlebutton.enabled || is_top) &&
(area & LEFT) && (area & RIGHT)) {
button = BTN_MIDDLE;
} else if (area & MIDDLE) {
button = BTN_MIDDLE;
} else if (area & RIGHT) {
button = BTN_RIGHT;
} else if (area & LEFT) {
button = BTN_LEFT;
} else { /* main or no area (for clickfinger) is always BTN_LEFT */
button = BTN_LEFT;
want_left_handed = false;
}
if (is_top)
want_left_handed = false;
if (want_left_handed)
button = evdev_to_left_handed(tp->device, button);
tp->buttons.active = button;
tp->buttons.active_is_topbutton = is_top;
state = LIBINPUT_BUTTON_STATE_PRESSED;
} else {
button = tp->buttons.active;
is_top = tp->buttons.active_is_topbutton;
tp->buttons.active = 0;
tp->buttons.active_is_topbutton = 0;
state = LIBINPUT_BUTTON_STATE_RELEASED;
}
tp->buttons.click_pending = false;
if (button)
return tp_notify_clickpadbutton(tp,
time,
button,
is_top,
state);
return 0;
}
int
tp_post_button_events(struct tp_dispatch *tp, uint64_t time)
{
if (tp->buttons.is_clickpad ||
tp->device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD_ONEBUTTON)
return tp_post_clickpadbutton_buttons(tp, time);
else
return tp_post_physical_buttons(tp, time);
}
bool
tp_button_touch_active(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return t->button.state == BUTTON_STATE_AREA || t->button.has_moved;
}
bool
tp_button_is_inside_softbutton_area(const struct tp_dispatch *tp,
const struct tp_touch *t)
{
return is_inside_top_button_area(tp, t) ||
is_inside_bottom_button_area(tp, t);
}