libinput/src/evdev-tablet-pad.c
Peter Hutterer 96fd84ebc9 pad: use libwacom to get the evdev to button number mapping
Some of wacom's tablets, notably the Bamboo series, have a non-predictable
scheme of mapping the buttons to numeric button numbers in libwacom. Since we
promise sequential button numbers, we need to have those identical to
libwacom, otherwise it's impossible to map the two together.

Most tablets have a predictable mapping, so this does not affect the majority
of devices.

For the old-style bamboos, this swaps the buttons around with the buttons
being ordered vertically top-to-bottom in libwacom.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Acked-by: Jason Gerecke <jason.gerecke@wacom.com>
2018-03-05 15:00:03 +10:00

757 lines
18 KiB
C

/*
* Copyright © 2016 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the copyright holders not be used in
* advertising or publicity pertaining to distribution of the software
* without specific, written prior permission. The copyright holders make
* no representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS 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,
.toggle_touch = 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;
}