libinput/src/evdev-tablet.c
Stephen Chandler Paul b5d6be3cd6 tablet: Include starting values of axes in proximity events
Having a motion event that's sent right after the original proximity event just
to give the values of each axis is somewhat redundant. Since we already include
the values of each axis with each type of event, we may as well use the
proximity event to give the client the starting values for each axis on the
tablet.

Signed-off-by: Stephen Chandler Paul <thatslyude@gmail.com>
Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2015-02-18 13:59:14 +10:00

630 lines
17 KiB
C

/*
* Copyright © 2014 Red Hat, Inc.
* Copyright © 2014 Stephen Chandler "Lyude" Paul
*
* 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.h"
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#define tablet_set_status(tablet_,s_) (tablet_)->status |= (s_)
#define tablet_unset_status(tablet_,s_) (tablet_)->status &= ~(s_)
#define tablet_has_status(tablet_,s_) (!!((tablet_)->status & (s_)))
#define tablet_get_pressed_buttons(tablet_,field_) \
((tablet_)->button_state.field_ & ~((tablet_)->prev_button_state.field_))
#define tablet_get_released_buttons(tablet_,field_) \
((tablet_)->prev_button_state.field_ & ~((tablet_)->button_state.field_))
static void
tablet_process_absolute(struct tablet_dispatch *tablet,
struct evdev_device *device,
struct input_event *e,
uint32_t time)
{
enum libinput_tablet_axis axis;
switch (e->code) {
case ABS_X:
case ABS_Y:
case ABS_PRESSURE:
case ABS_TILT_X:
case ABS_TILT_Y:
case ABS_DISTANCE:
axis = evcode_to_axis(e->code);
if (axis == LIBINPUT_TABLET_AXIS_NONE) {
log_bug_libinput(device->base.seat->libinput,
"Invalid ABS event code %#x\n",
e->code);
break;
}
set_bit(tablet->changed_axes, axis);
tablet_set_status(tablet, TABLET_AXES_UPDATED);
break;
default:
log_info(device->base.seat->libinput,
"Unhandled ABS event code %#x\n", e->code);
break;
}
}
static void
tablet_mark_all_axes_changed(struct tablet_dispatch *tablet,
struct evdev_device *device)
{
enum libinput_tablet_axis a;
for (a = 0; a < LIBINPUT_TABLET_AXIS_CNT; a++) {
if (libevdev_has_event_code(device->evdev,
EV_ABS,
axis_to_evcode(a)))
set_bit(tablet->changed_axes, a);
}
tablet_set_status(tablet, TABLET_AXES_UPDATED);
}
static void
tablet_change_to_left_handed(struct evdev_device *device)
{
struct tablet_dispatch *tablet =
(struct tablet_dispatch*)device->dispatch;
if (device->left_handed.enabled == device->left_handed.want_enabled)
return;
if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
return;
device->left_handed.enabled = device->left_handed.want_enabled;
}
static void
tablet_update_tool(struct tablet_dispatch *tablet,
struct evdev_device *device,
enum libinput_tool_type tool,
bool enabled)
{
assert(tool != LIBINPUT_TOOL_NONE);
if (enabled) {
tablet->current_tool_type = tool;
tablet_mark_all_axes_changed(tablet, device);
tablet_set_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
tablet_unset_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
}
else
tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
}
static inline double
normalize_pressure_or_dist(const struct input_absinfo * absinfo) {
double range = absinfo->maximum - absinfo->minimum;
double value = (absinfo->value - absinfo->minimum) / range;
return value;
}
static inline double
normalize_tilt(const struct input_absinfo * absinfo) {
double range = absinfo->maximum - absinfo->minimum;
double value = (absinfo->value - absinfo->minimum) / range;
/* Map to the (-1, 1) range */
return (value * 2) - 1;
}
static inline int32_t
invert_axis(const struct input_absinfo *absinfo)
{
return absinfo->maximum - (absinfo->value - absinfo->minimum);
}
static void
tablet_check_notify_axes(struct tablet_dispatch *tablet,
struct evdev_device *device,
uint32_t time,
struct libinput_tool *tool)
{
struct libinput_device *base = &device->base;
bool axis_update_needed = false;
int a;
for (a = 0; a < LIBINPUT_TABLET_AXIS_CNT; a++) {
const struct input_absinfo *absinfo;
if (!bit_is_set(tablet->changed_axes, a))
continue;
absinfo = libevdev_get_abs_info(device->evdev,
axis_to_evcode(a));
switch (a) {
case LIBINPUT_TABLET_AXIS_X:
case LIBINPUT_TABLET_AXIS_Y:
if (device->left_handed.enabled)
tablet->axes[a] = invert_axis(absinfo);
else
tablet->axes[a] = absinfo->value;
break;
case LIBINPUT_TABLET_AXIS_DISTANCE:
case LIBINPUT_TABLET_AXIS_PRESSURE:
tablet->axes[a] = normalize_pressure_or_dist(absinfo);
break;
case LIBINPUT_TABLET_AXIS_TILT_X:
case LIBINPUT_TABLET_AXIS_TILT_Y:
tablet->axes[a] = normalize_tilt(absinfo);
break;
default:
log_bug_libinput(device->base.seat->libinput,
"Invalid axis update: %d\n", a);
break;
}
axis_update_needed = true;
}
/* We need to make sure that we check that the tool is not out of
* proximity before we send any axis updates. This is because many
* tablets will send axis events with incorrect values if the tablet
* tool is close enough so that the tablet can partially detect that
* it's there, but can't properly receive any data from the tool. */
if (axis_update_needed &&
!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY) &&
!tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY))
tablet_notify_proximity(&device->base,
time,
tool,
LIBINPUT_TOOL_PROXIMITY_IN,
tablet->changed_axes,
tablet->axes);
else
tablet_notify_axis(base,
time,
tool,
tablet->changed_axes,
tablet->axes);
}
memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes));
}
static void
tablet_update_button(struct tablet_dispatch *tablet,
uint32_t evcode,
uint32_t enable)
{
uint32_t button, *mask;
/* XXX: This really depends on the expected buttons fitting in the mask */
if (evcode >= BTN_MISC && evcode <= BTN_TASK) {
return;
} else if (evcode >= BTN_TOUCH && evcode <= BTN_STYLUS2) {
mask = &tablet->button_state.stylus_buttons;
button = evcode - BTN_TOUCH;
} else {
log_info(tablet->device->base.seat->libinput,
"Unhandled button %s (%#x)\n",
libevdev_event_code_get_name(EV_KEY, evcode), evcode);
return;
}
assert(button < 32);
if (enable) {
(*mask) |= 1 << button;
tablet_set_status(tablet, TABLET_BUTTONS_PRESSED);
} else {
(*mask) &= ~(1 << button);
tablet_set_status(tablet, TABLET_BUTTONS_RELEASED);
}
}
static void
tablet_process_key(struct tablet_dispatch *tablet,
struct evdev_device *device,
struct input_event *e,
uint32_t time)
{
switch (e->code) {
case BTN_TOOL_PEN:
case BTN_TOOL_RUBBER:
case BTN_TOOL_BRUSH:
case BTN_TOOL_PENCIL:
case BTN_TOOL_AIRBRUSH:
case BTN_TOOL_FINGER:
case BTN_TOOL_MOUSE:
case BTN_TOOL_LENS:
/* These codes have an equivalent libinput_tool value */
tablet_update_tool(tablet, device, e->code, e->value);
break;
case BTN_TOUCH:
if (e->value)
tablet_set_status(tablet, TABLET_STYLUS_IN_CONTACT);
else
tablet_unset_status(tablet, TABLET_STYLUS_IN_CONTACT);
/* Fall through */
case BTN_STYLUS:
case BTN_STYLUS2:
default:
tablet_update_button(tablet, e->code, e->value);
break;
}
}
static void
tablet_process_misc(struct tablet_dispatch *tablet,
struct evdev_device *device,
struct input_event *e,
uint32_t time)
{
switch (e->code) {
case MSC_SERIAL:
if (e->value != -1)
tablet->current_tool_serial = e->value;
break;
default:
log_info(device->base.seat->libinput,
"Unhandled MSC event code %s (%#x)\n",
libevdev_event_code_get_name(EV_MSC, e->code),
e->code);
break;
}
}
static struct libinput_tool *
tablet_get_tool(struct tablet_dispatch *tablet,
enum libinput_tool_type type,
uint32_t serial)
{
struct libinput_tool *tool = NULL, *t;
struct list *tool_list;
if (serial) {
tool_list = &tablet->device->base.seat->libinput->tool_list;
/* Check if we already have the tool in our list of tools */
list_for_each(t, tool_list, link) {
if (type == t->type && serial == t->serial) {
tool = t;
break;
}
}
} else {
/* We can't guarantee that tools without serial numbers are
* unique, so we keep them local to the tablet that they come
* into proximity of instead of storing them in the global tool
* list */
tool_list = &tablet->tool_list;
/* Same as above, but don't bother checking the serial number */
list_for_each(t, tool_list, link) {
if (type == t->type) {
tool = t;
break;
}
}
}
/* If we didn't already have the new_tool in our list of tools,
* add it */
if (!tool) {
tool = zalloc(sizeof *tool);
*tool = (struct libinput_tool) {
.type = type,
.serial = serial,
.refcount = 1,
};
/* Determine the axis capabilities of the tool. Here's a break
* down of the heuristics used here:
* - The Wacom art pen supports all of the extra axes, along
* with rotation
* - All of normal pens and the airbrush support all of the
* extra axes if the tablet can report them
* - All of the mouse like devices don't really report any of
* the extra axes except for rotation.
* (as of writing this comment, rotation isn't supported, so you
* won't see the mouse or art pen here)
*/
switch (type) {
case LIBINPUT_TOOL_PEN:
case LIBINPUT_TOOL_ERASER:
case LIBINPUT_TOOL_PENCIL:
case LIBINPUT_TOOL_BRUSH:
case LIBINPUT_TOOL_AIRBRUSH:
if (bit_is_set(tablet->axis_caps,
LIBINPUT_TABLET_AXIS_PRESSURE))
set_bit(tool->axis_caps,
LIBINPUT_TABLET_AXIS_PRESSURE);
if (bit_is_set(tablet->axis_caps,
LIBINPUT_TABLET_AXIS_DISTANCE))
set_bit(tool->axis_caps,
LIBINPUT_TABLET_AXIS_DISTANCE);
if (bit_is_set(tablet->axis_caps,
LIBINPUT_TABLET_AXIS_TILT_X))
set_bit(tool->axis_caps,
LIBINPUT_TABLET_AXIS_TILT_X);
if (bit_is_set(tablet->axis_caps,
LIBINPUT_TABLET_AXIS_TILT_Y))
set_bit(tool->axis_caps,
LIBINPUT_TABLET_AXIS_TILT_Y);
break;
default:
break;
}
list_insert(tool_list, &tool->link);
}
return tool;
}
static void
tablet_notify_button_mask(struct tablet_dispatch *tablet,
struct evdev_device *device,
uint32_t time,
struct libinput_tool *tool,
uint32_t buttons,
uint32_t button_base,
enum libinput_button_state state)
{
struct libinput_device *base = &device->base;
int32_t num_button = 0;
while (buttons) {
int enabled;
num_button++;
enabled = (buttons & 1);
buttons >>= 1;
if (!enabled)
continue;
tablet_notify_button(base,
time,
tool,
tablet->axes,
num_button + button_base - 1,
state);
}
}
static void
tablet_notify_buttons(struct tablet_dispatch *tablet,
struct evdev_device *device,
uint32_t time,
struct libinput_tool *tool,
enum libinput_button_state state)
{
uint32_t stylus_buttons;
if (state == LIBINPUT_BUTTON_STATE_PRESSED)
stylus_buttons =
tablet_get_pressed_buttons(tablet, stylus_buttons);
else
stylus_buttons =
tablet_get_released_buttons(tablet, stylus_buttons);
tablet_notify_button_mask(tablet,
device,
time,
tool,
stylus_buttons,
BTN_TOUCH,
state);
}
static void
sanitize_tablet_axes(struct tablet_dispatch *tablet)
{
const struct input_absinfo *distance,
*pressure;
distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE);
pressure = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE);
/* Keep distance and pressure mutually exclusive */
if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_DISTANCE) &&
distance->value > distance->minimum &&
pressure->value > pressure->minimum) {
clear_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_DISTANCE);
tablet->axes[LIBINPUT_TABLET_AXIS_DISTANCE] = 0;
} else if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_PRESSURE) &&
!tablet_has_status(tablet, TABLET_STYLUS_IN_CONTACT)) {
/* Make sure that the last axis value sent to the caller is a 0 */
if (tablet->axes[LIBINPUT_TABLET_AXIS_PRESSURE] == 0)
clear_bit(tablet->changed_axes,
LIBINPUT_TABLET_AXIS_PRESSURE);
else
tablet->axes[LIBINPUT_TABLET_AXIS_PRESSURE] = 0;
}
}
static void
tablet_flush(struct tablet_dispatch *tablet,
struct evdev_device *device,
uint32_t time)
{
struct libinput_tool *tool =
tablet_get_tool(tablet,
tablet->current_tool_type,
tablet->current_tool_serial);
if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
/* Release all stylus buttons */
tablet->button_state.stylus_buttons = 0;
tablet_set_status(tablet, TABLET_BUTTONS_RELEASED);
} else if (tablet_has_status(tablet, TABLET_AXES_UPDATED) ||
tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) {
sanitize_tablet_axes(tablet);
tablet_check_notify_axes(tablet, device, time, tool);
tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
tablet_unset_status(tablet, TABLET_AXES_UPDATED);
}
if (tablet_has_status(tablet, TABLET_BUTTONS_RELEASED)) {
tablet_notify_buttons(tablet,
device,
time,
tool,
LIBINPUT_BUTTON_STATE_RELEASED);
tablet_unset_status(tablet, TABLET_BUTTONS_RELEASED);
}
if (tablet_has_status(tablet, TABLET_BUTTONS_PRESSED)) {
tablet_notify_buttons(tablet,
device,
time,
tool,
LIBINPUT_BUTTON_STATE_PRESSED);
tablet_unset_status(tablet, TABLET_BUTTONS_PRESSED);
}
if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
tablet_notify_proximity(&device->base,
time,
tool,
LIBINPUT_TOOL_PROXIMITY_OUT,
tablet->changed_axes,
tablet->axes);
tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
tablet_unset_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes));
tablet_change_to_left_handed(device);
}
/* Update state */
memcpy(&tablet->prev_button_state,
&tablet->button_state,
sizeof(tablet->button_state));
}
static void
tablet_process(struct evdev_dispatch *dispatch,
struct evdev_device *device,
struct input_event *e,
uint64_t time)
{
struct tablet_dispatch *tablet =
(struct tablet_dispatch *)dispatch;
switch (e->type) {
case EV_ABS:
tablet_process_absolute(tablet, device, e, time);
break;
case EV_KEY:
tablet_process_key(tablet, device, e, time);
break;
case EV_MSC:
tablet_process_misc(tablet, device, e, time);
break;
case EV_SYN:
tablet_flush(tablet, device, time);
break;
default:
log_error(device->base.seat->libinput,
"Unexpected event type %s (%#x)\n",
libevdev_event_type_get_name(e->type),
e->type);
break;
}
}
static void
tablet_destroy(struct evdev_dispatch *dispatch)
{
struct tablet_dispatch *tablet =
(struct tablet_dispatch*)dispatch;
struct libinput_tool *tool, *tmp;
list_for_each_safe(tool, tmp, &tablet->tool_list, link) {
libinput_tool_unref(tool);
}
free(tablet);
}
static struct evdev_dispatch_interface tablet_interface = {
tablet_process,
NULL, /* remove */
tablet_destroy,
NULL, /* device_added */
NULL, /* device_removed */
NULL, /* device_suspended */
NULL, /* device_resumed */
NULL, /* tag_device */
};
static int
tablet_init(struct tablet_dispatch *tablet,
struct evdev_device *device)
{
enum libinput_tablet_axis axis;
tablet->base.interface = &tablet_interface;
tablet->device = device;
tablet->status = TABLET_NONE;
tablet->current_tool_type = LIBINPUT_TOOL_NONE;
list_init(&tablet->tool_list);
for (axis = 0; axis < LIBINPUT_TABLET_AXIS_CNT; axis++) {
if (libevdev_has_event_code(device->evdev,
EV_ABS,
axis_to_evcode(axis)))
set_bit(tablet->axis_caps, axis);
}
tablet_mark_all_axes_changed(tablet, device);
tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
return 0;
}
struct evdev_dispatch *
evdev_tablet_create(struct evdev_device *device)
{
struct tablet_dispatch *tablet;
tablet = zalloc(sizeof *tablet);
if (!tablet)
return NULL;
if (tablet_init(tablet, device) != 0) {
tablet_destroy(&tablet->base);
return NULL;
}
evdev_init_left_handed(device, tablet_change_to_left_handed);
return &tablet->base;
}