libinput/src/evdev-tablet-pad.c
Peter Hutterer f325ca921d evdev: add a rectangle to the touch arbitration
This enables us to specify the location that needs to be arbitrated, rather
than just disabling the whole device altogether. This patch just adds the
hooks, no implementation.

This is internal API only, one backend can specify an area in mm which gets
converted to device coordinates in the target device and arbitrated there.
Right now, everything simply passes NULL.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2019-01-31 05:17:28 +00:00

759 lines
18 KiB
C

/*
* Copyright © 2016 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 "evdev-tablet-pad.h"
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#if HAVE_LIBWACOM
#include <libwacom/libwacom.h>
#endif
#define pad_set_status(pad_,s_) (pad_)->status |= (s_)
#define pad_unset_status(pad_,s_) (pad_)->status &= ~(s_)
#define pad_has_status(pad_,s_) (!!((pad_)->status & (s_)))
static void
pad_get_buttons_pressed(struct pad_dispatch *pad,
struct button_state *buttons)
{
struct button_state *state = &pad->button_state;
struct button_state *prev_state = &pad->prev_button_state;
unsigned int i;
for (i = 0; i < sizeof(buttons->bits); i++)
buttons->bits[i] = state->bits[i] & ~(prev_state->bits[i]);
}
static void
pad_get_buttons_released(struct pad_dispatch *pad,
struct button_state *buttons)
{
struct button_state *state = &pad->button_state;
struct button_state *prev_state = &pad->prev_button_state;
unsigned int i;
for (i = 0; i < sizeof(buttons->bits); i++)
buttons->bits[i] = prev_state->bits[i] & ~(state->bits[i]);
}
static inline bool
pad_button_is_down(const struct pad_dispatch *pad,
uint32_t button)
{
return bit_is_set(pad->button_state.bits, button);
}
static inline bool
pad_any_button_down(const struct pad_dispatch *pad)
{
const struct button_state *state = &pad->button_state;
unsigned int i;
for (i = 0; i < sizeof(state->bits); i++)
if (state->bits[i] != 0)
return true;
return false;
}
static inline void
pad_button_set_down(struct pad_dispatch *pad,
uint32_t button,
bool is_down)
{
struct button_state *state = &pad->button_state;
if (is_down) {
set_bit(state->bits, button);
pad_set_status(pad, PAD_BUTTONS_PRESSED);
} else {
clear_bit(state->bits, button);
pad_set_status(pad, PAD_BUTTONS_RELEASED);
}
}
static void
pad_process_absolute(struct pad_dispatch *pad,
struct evdev_device *device,
struct input_event *e,
uint64_t time)
{
switch (e->code) {
case ABS_WHEEL:
pad->changed_axes |= PAD_AXIS_RING1;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
case ABS_THROTTLE:
pad->changed_axes |= PAD_AXIS_RING2;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
case ABS_RX:
pad->changed_axes |= PAD_AXIS_STRIP1;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
case ABS_RY:
pad->changed_axes |= PAD_AXIS_STRIP2;
pad_set_status(pad, PAD_AXES_UPDATED);
break;
case ABS_MISC:
/* The wacom driver always sends a 0 axis event on finger
up, but we also get an ABS_MISC 15 on touch down and
ABS_MISC 0 on touch up, on top of the actual event. This
is kernel behavior for xf86-input-wacom backwards
compatibility after the 3.17 wacom HID move.
We use that event to tell when we truly went a full
rotation around the wheel vs. a finger release.
FIXME: On the Intuos5 and later the kernel merges all
states into that event, so if any finger is down on any
button, the wheel release won't trigger the ABS_MISC 0
but still send a 0 event. We can't currently detect this.
*/
pad->have_abs_misc_terminator = true;
break;
default:
evdev_log_info(device,
"Unhandled EV_ABS event code %#x\n",
e->code);
break;
}
}
static inline double
normalize_ring(const struct input_absinfo *absinfo)
{
/* libinput has 0 as the ring's northernmost point in the device's
current logical rotation, increasing clockwise to 1. Wacom has
0 on the left-most wheel position.
*/
double range = absinfo->maximum - absinfo->minimum + 1;
double value = (absinfo->value - absinfo->minimum) / range - 0.25;
if (value < 0.0)
value += 1.0;
return value;
}
static inline double
normalize_strip(const struct input_absinfo *absinfo)
{
/* strip axes don't use a proper value, they just shift the bit left
* for each position. 0 isn't a real value either, it's only sent on
* finger release */
double min = 0,
max = log2(absinfo->maximum);
double range = max - min;
double value = (log2(absinfo->value) - min) / range;
return value;
}
static inline double
pad_handle_ring(struct pad_dispatch *pad,
struct evdev_device *device,
unsigned int code)
{
const struct input_absinfo *absinfo;
double degrees;
absinfo = libevdev_get_abs_info(device->evdev, code);
assert(absinfo);
degrees = normalize_ring(absinfo) * 360;
if (device->left_handed.enabled)
degrees = fmod(degrees + 180, 360);
return degrees;
}
static inline double
pad_handle_strip(struct pad_dispatch *pad,
struct evdev_device *device,
unsigned int code)
{
const struct input_absinfo *absinfo;
double pos;
absinfo = libevdev_get_abs_info(device->evdev, code);
assert(absinfo);
if (absinfo->value == 0)
return 0.0;
pos = normalize_strip(absinfo);
if (device->left_handed.enabled)
pos = 1.0 - pos;
return pos;
}
static inline struct libinput_tablet_pad_mode_group *
pad_ring_get_mode_group(struct pad_dispatch *pad,
unsigned int ring)
{
struct libinput_tablet_pad_mode_group *group;
list_for_each(group, &pad->modes.mode_group_list, link) {
if (libinput_tablet_pad_mode_group_has_ring(group, ring))
return group;
}
assert(!"Unable to find ring mode group");
return NULL;
}
static inline struct libinput_tablet_pad_mode_group *
pad_strip_get_mode_group(struct pad_dispatch *pad,
unsigned int strip)
{
struct libinput_tablet_pad_mode_group *group;
list_for_each(group, &pad->modes.mode_group_list, link) {
if (libinput_tablet_pad_mode_group_has_strip(group, strip))
return group;
}
assert(!"Unable to find strip mode group");
return NULL;
}
static void
pad_check_notify_axes(struct pad_dispatch *pad,
struct evdev_device *device,
uint64_t time)
{
struct libinput_device *base = &device->base;
struct libinput_tablet_pad_mode_group *group;
double value;
bool send_finger_up = false;
/* Suppress the reset to 0 on finger up. See the
comment in pad_process_absolute */
if (pad->have_abs_misc_terminator &&
libevdev_get_event_value(device->evdev, EV_ABS, ABS_MISC) == 0)
send_finger_up = true;
if (pad->changed_axes & PAD_AXIS_RING1) {
value = pad_handle_ring(pad, device, ABS_WHEEL);
if (send_finger_up)
value = -1.0;
group = pad_ring_get_mode_group(pad, 0);
tablet_pad_notify_ring(base,
time,
0,
value,
LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
group);
}
if (pad->changed_axes & PAD_AXIS_RING2) {
value = pad_handle_ring(pad, device, ABS_THROTTLE);
if (send_finger_up)
value = -1.0;
group = pad_ring_get_mode_group(pad, 1);
tablet_pad_notify_ring(base,
time,
1,
value,
LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
group);
}
if (pad->changed_axes & PAD_AXIS_STRIP1) {
value = pad_handle_strip(pad, device, ABS_RX);
if (send_finger_up)
value = -1.0;
group = pad_strip_get_mode_group(pad, 0);
tablet_pad_notify_strip(base,
time,
0,
value,
LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
group);
}
if (pad->changed_axes & PAD_AXIS_STRIP2) {
value = pad_handle_strip(pad, device, ABS_RY);
if (send_finger_up)
value = -1.0;
group = pad_strip_get_mode_group(pad, 1);
tablet_pad_notify_strip(base,
time,
1,
value,
LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
group);
}
pad->changed_axes = PAD_AXIS_NONE;
pad->have_abs_misc_terminator = false;
}
static void
pad_process_key(struct pad_dispatch *pad,
struct evdev_device *device,
struct input_event *e,
uint64_t time)
{
uint32_t button = e->code;
uint32_t is_press = e->value != 0;
pad_button_set_down(pad, button, is_press);
}
static inline struct libinput_tablet_pad_mode_group *
pad_button_get_mode_group(struct pad_dispatch *pad,
unsigned int button)
{
struct libinput_tablet_pad_mode_group *group;
list_for_each(group, &pad->modes.mode_group_list, link) {
if (libinput_tablet_pad_mode_group_has_button(group, button))
return group;
}
assert(!"Unable to find button mode group\n");
return NULL;
}
static void
pad_notify_button_mask(struct pad_dispatch *pad,
struct evdev_device *device,
uint64_t time,
const struct button_state *buttons,
enum libinput_button_state state)
{
struct libinput_device *base = &device->base;
struct libinput_tablet_pad_mode_group *group;
int32_t code;
unsigned int i;
for (i = 0; i < sizeof(buttons->bits); i++) {
unsigned char buttons_slice = buttons->bits[i];
code = i * 8;
while (buttons_slice) {
int enabled;
char map;
code++;
enabled = (buttons_slice & 1);
buttons_slice >>= 1;
if (!enabled)
continue;
map = pad->button_map[code - 1];
if (map != -1) {
group = pad_button_get_mode_group(pad, map);
pad_button_update_mode(group, map, state);
tablet_pad_notify_button(base, time, map, state, group);
}
}
}
}
static void
pad_notify_buttons(struct pad_dispatch *pad,
struct evdev_device *device,
uint64_t time,
enum libinput_button_state state)
{
struct button_state buttons;
if (state == LIBINPUT_BUTTON_STATE_PRESSED)
pad_get_buttons_pressed(pad, &buttons);
else
pad_get_buttons_released(pad, &buttons);
pad_notify_button_mask(pad, device, time, &buttons, state);
}
static void
pad_change_to_left_handed(struct evdev_device *device)
{
struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
if (device->left_handed.enabled == device->left_handed.want_enabled)
return;
if (pad_any_button_down(pad))
return;
device->left_handed.enabled = device->left_handed.want_enabled;
}
static void
pad_flush(struct pad_dispatch *pad,
struct evdev_device *device,
uint64_t time)
{
if (pad_has_status(pad, PAD_AXES_UPDATED)) {
pad_check_notify_axes(pad, device, time);
pad_unset_status(pad, PAD_AXES_UPDATED);
}
if (pad_has_status(pad, PAD_BUTTONS_RELEASED)) {
pad_notify_buttons(pad,
device,
time,
LIBINPUT_BUTTON_STATE_RELEASED);
pad_unset_status(pad, PAD_BUTTONS_RELEASED);
pad_change_to_left_handed(device);
}
if (pad_has_status(pad, PAD_BUTTONS_PRESSED)) {
pad_notify_buttons(pad,
device,
time,
LIBINPUT_BUTTON_STATE_PRESSED);
pad_unset_status(pad, PAD_BUTTONS_PRESSED);
}
/* Update state */
memcpy(&pad->prev_button_state,
&pad->button_state,
sizeof(pad->button_state));
}
static void
pad_process(struct evdev_dispatch *dispatch,
struct evdev_device *device,
struct input_event *e,
uint64_t time)
{
struct pad_dispatch *pad = pad_dispatch(dispatch);
switch (e->type) {
case EV_ABS:
pad_process_absolute(pad, device, e, time);
break;
case EV_KEY:
pad_process_key(pad, device, e, time);
break;
case EV_SYN:
pad_flush(pad, device, time);
break;
case EV_MSC:
/* The EKR sends the serial as MSC_SERIAL, ignore this for
* now */
break;
default:
evdev_log_error(device,
"Unexpected event type %s (%#x)\n",
libevdev_event_type_get_name(e->type),
e->type);
break;
}
}
static void
pad_suspend(struct evdev_dispatch *dispatch,
struct evdev_device *device)
{
struct pad_dispatch *pad = pad_dispatch(dispatch);
struct libinput *libinput = pad_libinput_context(pad);
unsigned int code;
for (code = KEY_ESC; code < KEY_CNT; code++) {
if (pad_button_is_down(pad, code))
pad_button_set_down(pad, code, false);
}
pad_flush(pad, device, libinput_now(libinput));
}
static void
pad_destroy(struct evdev_dispatch *dispatch)
{
struct pad_dispatch *pad = pad_dispatch(dispatch);
pad_destroy_leds(pad);
free(pad);
}
static struct evdev_dispatch_interface pad_interface = {
.process = pad_process,
.suspend = pad_suspend,
.remove = NULL,
.destroy = pad_destroy,
.device_added = NULL,
.device_removed = NULL,
.device_suspended = NULL,
.device_resumed = NULL,
.post_added = NULL,
.touch_arbitration_toggle = NULL,
.touch_arbitration_update_rect = NULL,
.get_switch_state = NULL,
};
static bool
pad_init_buttons_from_libwacom(struct pad_dispatch *pad,
struct evdev_device *device)
{
bool rc = false;
#if HAVE_LIBWACOM_GET_BUTTON_EVDEV_CODE
WacomDeviceDatabase *db = NULL;
WacomDevice *tablet = NULL;
int num_buttons;
int map = 0;
db = libwacom_database_new();
if (!db) {
evdev_log_info(device,
"Failed to initialize libwacom context.\n");
goto out;
}
tablet = libwacom_new_from_usbid(db,
evdev_device_get_id_vendor(device),
evdev_device_get_id_product(device),
NULL);
if (!tablet)
goto out;
num_buttons = libwacom_get_num_buttons(tablet);
for (int i = 0; i < num_buttons; i++) {
unsigned int code;
code = libwacom_get_button_evdev_code(tablet, 'A' + i);
if (code == 0)
continue;
pad->button_map[code] = map++;
}
pad->nbuttons = map;
rc = true;
out:
if (tablet)
libwacom_destroy(tablet);
if (db)
libwacom_database_destroy(db);
#endif
return rc;
}
static void
pad_init_buttons_from_kernel(struct pad_dispatch *pad,
struct evdev_device *device)
{
unsigned int code;
int map = 0;
/* we match wacom_report_numbered_buttons() from the kernel */
for (code = BTN_0; code < BTN_0 + 10; code++) {
if (libevdev_has_event_code(device->evdev, EV_KEY, code))
pad->button_map[code] = map++;
}
for (code = BTN_BASE; code < BTN_BASE + 2; code++) {
if (libevdev_has_event_code(device->evdev, EV_KEY, code))
pad->button_map[code] = map++;
}
for (code = BTN_A; code < BTN_A + 6; code++) {
if (libevdev_has_event_code(device->evdev, EV_KEY, code))
pad->button_map[code] = map++;
}
for (code = BTN_LEFT; code < BTN_LEFT + 7; code++) {
if (libevdev_has_event_code(device->evdev, EV_KEY, code))
pad->button_map[code] = map++;
}
pad->nbuttons = map;
}
static void
pad_init_buttons(struct pad_dispatch *pad,
struct evdev_device *device)
{
size_t i;
for (i = 0; i < ARRAY_LENGTH(pad->button_map); i++)
pad->button_map[i] = -1;
if (!pad_init_buttons_from_libwacom(pad, device))
pad_init_buttons_from_kernel(pad, device);
}
static void
pad_init_left_handed(struct evdev_device *device)
{
if (evdev_tablet_has_left_handed(device))
evdev_init_left_handed(device,
pad_change_to_left_handed);
}
static int
pad_init(struct pad_dispatch *pad, struct evdev_device *device)
{
pad->base.dispatch_type = DISPATCH_TABLET_PAD;
pad->base.interface = &pad_interface;
pad->device = device;
pad->status = PAD_NONE;
pad->changed_axes = PAD_AXIS_NONE;
pad_init_buttons(pad, device);
pad_init_left_handed(device);
if (pad_init_leds(pad, device) != 0)
return 1;
return 0;
}
static uint32_t
pad_sendevents_get_modes(struct libinput_device *device)
{
return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
}
static enum libinput_config_status
pad_sendevents_set_mode(struct libinput_device *device,
enum libinput_config_send_events_mode mode)
{
struct evdev_device *evdev = evdev_device(device);
struct pad_dispatch *pad = (struct pad_dispatch*)evdev->dispatch;
if (mode == pad->sendevents.current_mode)
return LIBINPUT_CONFIG_STATUS_SUCCESS;
switch(mode) {
case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED:
break;
case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED:
pad_suspend(evdev->dispatch, evdev);
break;
default:
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
}
pad->sendevents.current_mode = mode;
return LIBINPUT_CONFIG_STATUS_SUCCESS;
}
static enum libinput_config_send_events_mode
pad_sendevents_get_mode(struct libinput_device *device)
{
struct evdev_device *evdev = evdev_device(device);
struct pad_dispatch *dispatch = (struct pad_dispatch*)evdev->dispatch;
return dispatch->sendevents.current_mode;
}
static enum libinput_config_send_events_mode
pad_sendevents_get_default_mode(struct libinput_device *device)
{
return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
}
struct evdev_dispatch *
evdev_tablet_pad_create(struct evdev_device *device)
{
struct pad_dispatch *pad;
pad = zalloc(sizeof *pad);
if (pad_init(pad, device) != 0) {
pad_destroy(&pad->base);
return NULL;
}
device->base.config.sendevents = &pad->sendevents.config;
pad->sendevents.current_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
pad->sendevents.config.get_modes = pad_sendevents_get_modes;
pad->sendevents.config.set_mode = pad_sendevents_set_mode;
pad->sendevents.config.get_mode = pad_sendevents_get_mode;
pad->sendevents.config.get_default_mode = pad_sendevents_get_default_mode;
return &pad->base;
}
int
evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device)
{
struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
return -1;
return pad->nbuttons;
}
int
evdev_device_tablet_pad_get_num_rings(struct evdev_device *device)
{
int nrings = 0;
if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
return -1;
if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_WHEEL)) {
nrings++;
if (libevdev_has_event_code(device->evdev,
EV_ABS,
ABS_THROTTLE))
nrings++;
}
return nrings;
}
int
evdev_device_tablet_pad_get_num_strips(struct evdev_device *device)
{
int nstrips = 0;
if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
return -1;
if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_RX)) {
nstrips++;
if (libevdev_has_event_code(device->evdev,
EV_ABS,
ABS_RY))
nstrips++;
}
return nstrips;
}