mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-25 12:50:05 +01:00
For external tablets like the Intuos series we now expose the area rectangle configuration and the (minimum) implementation required to make this work. Because an area configuration may apply late and tablet events usually get scaled by the compositor we need to store the current axis extents in each event. This is to behave correctly in this events sequence: 1. tool proximity in 2. caller changes config, config is pending 3. tool moves, generates events 4. tool goes out of prox, new config applies 5. caller processes motion events from step 3 If the caller in step five uses any of the get_x_transformed calls these need to be scaled relative to the original area, not the one set in step 2. The current implementation merely clips into the area so moving a stylus outside the area will be equivalent to moving it along the respective edge of the area. It's not a true dead zone yet. Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1013>
858 lines
23 KiB
C
858 lines
23 KiB
C
/*
|
|
* Copyright © 2018 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.h"
|
|
|
|
enum totem_slot_state {
|
|
SLOT_STATE_NONE,
|
|
SLOT_STATE_BEGIN,
|
|
SLOT_STATE_UPDATE,
|
|
SLOT_STATE_END,
|
|
};
|
|
|
|
struct totem_slot {
|
|
bool dirty;
|
|
unsigned int index;
|
|
enum totem_slot_state state;
|
|
struct libinput_tablet_tool *tool;
|
|
struct tablet_axes axes;
|
|
unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)];
|
|
|
|
struct device_coords last_point;
|
|
};
|
|
|
|
struct totem_dispatch {
|
|
struct evdev_dispatch base;
|
|
struct evdev_device *device;
|
|
|
|
int slot; /* current slot */
|
|
struct totem_slot *slots;
|
|
size_t nslots;
|
|
|
|
struct evdev_device *touch_device;
|
|
|
|
/* We only have one button */
|
|
bool button_state_now;
|
|
bool button_state_previous;
|
|
|
|
enum evdev_arbitration_state arbitration_state;
|
|
};
|
|
|
|
static inline struct totem_dispatch*
|
|
totem_dispatch(struct evdev_dispatch *totem)
|
|
{
|
|
evdev_verify_dispatch_type(totem, DISPATCH_TOTEM);
|
|
|
|
return container_of(totem, struct totem_dispatch, base);
|
|
}
|
|
|
|
static inline struct libinput *
|
|
totem_libinput_context(const struct totem_dispatch *totem)
|
|
{
|
|
return evdev_libinput_context(totem->device);
|
|
}
|
|
|
|
static struct libinput_tablet_tool *
|
|
totem_new_tool(struct totem_dispatch *totem)
|
|
{
|
|
struct libinput *libinput = totem_libinput_context(totem);
|
|
struct libinput_tablet_tool *tool;
|
|
|
|
tool = zalloc(sizeof *tool);
|
|
|
|
*tool = (struct libinput_tablet_tool) {
|
|
.type = LIBINPUT_TABLET_TOOL_TYPE_TOTEM,
|
|
.serial = 0,
|
|
.tool_id = 0,
|
|
.refcount = 1,
|
|
};
|
|
|
|
tool->pressure.offset = 0;
|
|
tool->pressure.has_offset = false;
|
|
tool->pressure.threshold.lower = 0;
|
|
tool->pressure.threshold.upper = 1;
|
|
|
|
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X);
|
|
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y);
|
|
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z);
|
|
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR);
|
|
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR);
|
|
set_bit(tool->buttons, BTN_0);
|
|
|
|
list_insert(&libinput->tool_list, &tool->link);
|
|
|
|
return tool;
|
|
}
|
|
|
|
static inline void
|
|
totem_set_touch_device_enabled(struct totem_dispatch *totem,
|
|
bool enable_touch_device,
|
|
uint64_t time)
|
|
{
|
|
struct evdev_device *touch_device = totem->touch_device;
|
|
struct evdev_dispatch *dispatch;
|
|
struct phys_rect r, *rect = NULL;
|
|
enum evdev_arbitration_state state = ARBITRATION_NOT_ACTIVE;
|
|
|
|
if (touch_device == NULL)
|
|
return;
|
|
|
|
/* We just pick the coordinates of the first touch we find. The
|
|
* totem only does one tool right now despite being nominally an MT
|
|
* device, so let's not go too hard on ourselves*/
|
|
for (size_t i = 0; !enable_touch_device && i < totem->nslots; i++) {
|
|
struct totem_slot *slot = &totem->slots[i];
|
|
struct phys_coords mm;
|
|
|
|
if (slot->state == SLOT_STATE_NONE)
|
|
continue;
|
|
|
|
/* Totem size is ~70mm. We could calculate the real size but
|
|
until we need that, hardcoding it is enough */
|
|
mm = evdev_device_units_to_mm(totem->device, &slot->axes.point);
|
|
r.x = mm.x - 30;
|
|
r.y = mm.y - 30;
|
|
r.w = 100;
|
|
r.h = 100;
|
|
|
|
rect = &r;
|
|
|
|
state = ARBITRATION_IGNORE_RECT;
|
|
break;
|
|
}
|
|
|
|
dispatch = touch_device->dispatch;
|
|
|
|
if (enable_touch_device) {
|
|
if (dispatch->interface->touch_arbitration_toggle)
|
|
dispatch->interface->touch_arbitration_toggle(dispatch,
|
|
touch_device,
|
|
state,
|
|
rect,
|
|
time);
|
|
} else {
|
|
switch (totem->arbitration_state) {
|
|
case ARBITRATION_IGNORE_ALL:
|
|
abort();
|
|
case ARBITRATION_NOT_ACTIVE:
|
|
if (dispatch->interface->touch_arbitration_toggle)
|
|
dispatch->interface->touch_arbitration_toggle(dispatch,
|
|
touch_device,
|
|
state,
|
|
rect,
|
|
time);
|
|
break;
|
|
case ARBITRATION_IGNORE_RECT:
|
|
if (dispatch->interface->touch_arbitration_update_rect)
|
|
dispatch->interface->touch_arbitration_update_rect(dispatch,
|
|
touch_device,
|
|
rect,
|
|
time);
|
|
break;
|
|
}
|
|
}
|
|
totem->arbitration_state = state;
|
|
}
|
|
|
|
static void
|
|
totem_process_key(struct totem_dispatch *totem,
|
|
struct evdev_device *device,
|
|
struct input_event *e,
|
|
uint64_t time)
|
|
{
|
|
/* ignore kernel key repeat */
|
|
if (e->value == 2)
|
|
return;
|
|
|
|
switch(e->code) {
|
|
case BTN_0:
|
|
totem->button_state_now = !!e->value;
|
|
break;
|
|
default:
|
|
evdev_log_info(device,
|
|
"Unhandled KEY event code %#x\n",
|
|
e->code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
totem_process_abs(struct totem_dispatch *totem,
|
|
struct evdev_device *device,
|
|
struct input_event *e,
|
|
uint64_t time)
|
|
{
|
|
struct totem_slot *slot = &totem->slots[totem->slot];
|
|
|
|
switch(e->code) {
|
|
case ABS_MT_SLOT:
|
|
if ((size_t)e->value >= totem->nslots) {
|
|
evdev_log_bug_libinput(device,
|
|
"exceeded slot count (%d of max %zd)\n",
|
|
e->value,
|
|
totem->nslots);
|
|
e->value = totem->nslots - 1;
|
|
}
|
|
totem->slot = e->value;
|
|
return;
|
|
case ABS_MT_TRACKING_ID:
|
|
/* If the totem is already down on init, we currently
|
|
ignore it */
|
|
if (e->value >= 0)
|
|
slot->state = SLOT_STATE_BEGIN;
|
|
else if (slot->state != SLOT_STATE_NONE)
|
|
slot->state = SLOT_STATE_END;
|
|
break;
|
|
case ABS_MT_POSITION_X:
|
|
set_bit(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X);
|
|
break;
|
|
case ABS_MT_POSITION_Y:
|
|
set_bit(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y);
|
|
break;
|
|
case ABS_MT_TOUCH_MAJOR:
|
|
set_bit(slot->changed_axes,
|
|
LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR);
|
|
break;
|
|
case ABS_MT_TOUCH_MINOR:
|
|
set_bit(slot->changed_axes,
|
|
LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR);
|
|
break;
|
|
case ABS_MT_ORIENTATION:
|
|
set_bit(slot->changed_axes,
|
|
LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z);
|
|
break;
|
|
case ABS_MT_TOOL_TYPE:
|
|
if (e->value != MT_TOOL_DIAL) {
|
|
evdev_log_info(device,
|
|
"Unexpected tool type %#x, changing to dial\n",
|
|
e->code);
|
|
}
|
|
break;
|
|
default:
|
|
evdev_log_info(device,
|
|
"Unhandled ABS event code %#x\n",
|
|
e->code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
totem_slot_fetch_axes(struct totem_dispatch *totem,
|
|
struct totem_slot *slot,
|
|
struct libinput_tablet_tool *tool,
|
|
struct tablet_axes *axes_out,
|
|
uint64_t time)
|
|
{
|
|
struct evdev_device *device = totem->device;
|
|
const char tmp[sizeof(slot->changed_axes)] = {0};
|
|
struct tablet_axes axes = {0};
|
|
struct device_float_coords delta;
|
|
bool rc = false;
|
|
|
|
if (memcmp(tmp, slot->changed_axes, sizeof(tmp)) == 0) {
|
|
axes = slot->axes;
|
|
goto out;
|
|
}
|
|
|
|
if (bit_is_set(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X) ||
|
|
bit_is_set(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y)) {
|
|
slot->axes.point.x = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_POSITION_X);
|
|
slot->axes.point.y = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_POSITION_Y);
|
|
}
|
|
|
|
if (bit_is_set(slot->changed_axes,
|
|
LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z)) {
|
|
int angle = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_ORIENTATION);
|
|
/* The kernel gives us ±90 degrees off neutral */
|
|
slot->axes.rotation = (360 - angle) % 360;
|
|
}
|
|
|
|
if (bit_is_set(slot->changed_axes,
|
|
LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR) ||
|
|
bit_is_set(slot->changed_axes,
|
|
LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR)) {
|
|
int major, minor;
|
|
unsigned int rmajor, rminor;
|
|
|
|
major = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_TOUCH_MAJOR);
|
|
minor = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_TOUCH_MINOR);
|
|
rmajor = libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MAJOR);
|
|
rminor = libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MINOR);
|
|
slot->axes.size.major = (double)major/rmajor;
|
|
slot->axes.size.minor = (double)minor/rminor;
|
|
}
|
|
|
|
axes.point = slot->axes.point;
|
|
axes.rotation = slot->axes.rotation;
|
|
axes.size = slot->axes.size;
|
|
|
|
delta.x = slot->axes.point.x - slot->last_point.x;
|
|
delta.y = slot->axes.point.y - slot->last_point.y;
|
|
axes.delta = filter_dispatch(device->pointer.filter, &delta, tool, time);
|
|
|
|
rc = true;
|
|
out:
|
|
*axes_out = axes;
|
|
return rc;
|
|
|
|
}
|
|
|
|
static void
|
|
totem_slot_mark_all_axes_changed(struct totem_dispatch *totem,
|
|
struct totem_slot *slot,
|
|
struct libinput_tablet_tool *tool)
|
|
{
|
|
static_assert(sizeof(slot->changed_axes) ==
|
|
sizeof(tool->axis_caps),
|
|
"Mismatching array sizes");
|
|
|
|
memcpy(slot->changed_axes,
|
|
tool->axis_caps,
|
|
sizeof(slot->changed_axes));
|
|
}
|
|
|
|
static inline void
|
|
totem_slot_reset_changed_axes(struct totem_dispatch *totem,
|
|
struct totem_slot *slot)
|
|
{
|
|
memset(slot->changed_axes, 0, sizeof(slot->changed_axes));
|
|
}
|
|
|
|
static inline void
|
|
slot_axes_initialize(struct totem_dispatch *totem,
|
|
struct totem_slot *slot)
|
|
{
|
|
struct evdev_device *device = totem->device;
|
|
|
|
slot->axes.point.x = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_POSITION_X);
|
|
slot->axes.point.y = libevdev_get_slot_value(device->evdev,
|
|
slot->index,
|
|
ABS_MT_POSITION_Y);
|
|
slot->last_point.x = slot->axes.point.x;
|
|
slot->last_point.y = slot->axes.point.y;
|
|
}
|
|
|
|
static enum totem_slot_state
|
|
totem_handle_slot_state(struct totem_dispatch *totem,
|
|
struct totem_slot *slot,
|
|
uint64_t time)
|
|
{
|
|
struct evdev_device *device = totem->device;
|
|
struct tablet_axes axes;
|
|
enum libinput_tablet_tool_tip_state tip_state;
|
|
bool updated;
|
|
|
|
switch (slot->state) {
|
|
case SLOT_STATE_BEGIN:
|
|
if (!slot->tool)
|
|
slot->tool = totem_new_tool(totem);
|
|
slot_axes_initialize(totem, slot);
|
|
totem_slot_mark_all_axes_changed(totem, slot, slot->tool);
|
|
break;
|
|
case SLOT_STATE_UPDATE:
|
|
case SLOT_STATE_END:
|
|
assert(slot->tool);
|
|
break;
|
|
case SLOT_STATE_NONE:
|
|
return SLOT_STATE_NONE;
|
|
}
|
|
|
|
tip_state = LIBINPUT_TABLET_TOOL_TIP_UP;
|
|
updated = totem_slot_fetch_axes(totem, slot, slot->tool, &axes, time);
|
|
|
|
switch (slot->state) {
|
|
case SLOT_STATE_BEGIN:
|
|
tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN;
|
|
tablet_notify_proximity(&device->base,
|
|
time,
|
|
slot->tool,
|
|
LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
totem_slot_reset_changed_axes(totem, slot);
|
|
tablet_notify_tip(&device->base,
|
|
time,
|
|
slot->tool,
|
|
tip_state,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
slot->state = SLOT_STATE_UPDATE;
|
|
break;
|
|
case SLOT_STATE_UPDATE:
|
|
tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN;
|
|
if (updated) {
|
|
tablet_notify_axis(&device->base,
|
|
time,
|
|
slot->tool,
|
|
tip_state,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
}
|
|
break;
|
|
case SLOT_STATE_END:
|
|
/* prox out is handled after button events */
|
|
break;
|
|
case SLOT_STATE_NONE:
|
|
abort();
|
|
break;
|
|
}
|
|
|
|
/* We only have one button but possibly multiple totems. It's not
|
|
* clear how the firmware will work, so for now we just handle the
|
|
* button state in the first slot.
|
|
*
|
|
* Due to the design of the totem we're also less fancy about
|
|
* button handling than the tablet code. Worst case, you might get
|
|
* tip up before button up but meh.
|
|
*/
|
|
if (totem->button_state_now != totem->button_state_previous) {
|
|
enum libinput_button_state btn_state;
|
|
|
|
if (totem->button_state_now)
|
|
btn_state = LIBINPUT_BUTTON_STATE_PRESSED;
|
|
else
|
|
btn_state = LIBINPUT_BUTTON_STATE_RELEASED;
|
|
|
|
tablet_notify_button(&device->base,
|
|
time,
|
|
slot->tool,
|
|
tip_state,
|
|
&axes,
|
|
BTN_0,
|
|
btn_state,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
|
|
totem->button_state_previous = totem->button_state_now;
|
|
}
|
|
|
|
switch(slot->state) {
|
|
case SLOT_STATE_BEGIN:
|
|
case SLOT_STATE_UPDATE:
|
|
break;
|
|
case SLOT_STATE_END:
|
|
tip_state = LIBINPUT_TABLET_TOOL_TIP_UP;
|
|
tablet_notify_tip(&device->base,
|
|
time,
|
|
slot->tool,
|
|
tip_state,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
totem_slot_reset_changed_axes(totem, slot);
|
|
tablet_notify_proximity(&device->base,
|
|
time,
|
|
slot->tool,
|
|
LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
|
|
slot->state = SLOT_STATE_NONE;
|
|
break;
|
|
case SLOT_STATE_NONE:
|
|
abort();
|
|
break;
|
|
}
|
|
|
|
slot->last_point = slot->axes.point;
|
|
totem_slot_reset_changed_axes(totem, slot);
|
|
|
|
return slot->state;
|
|
}
|
|
|
|
static enum totem_slot_state
|
|
totem_handle_state(struct totem_dispatch *totem,
|
|
uint64_t time)
|
|
{
|
|
enum totem_slot_state global_state = SLOT_STATE_NONE;
|
|
|
|
for (size_t i = 0; i < totem->nslots; i++) {
|
|
enum totem_slot_state s;
|
|
|
|
s = totem_handle_slot_state(totem,
|
|
&totem->slots[i],
|
|
time);
|
|
|
|
/* If one slot is active, the totem is active */
|
|
if (s != SLOT_STATE_NONE)
|
|
global_state = SLOT_STATE_UPDATE;
|
|
}
|
|
|
|
return global_state;
|
|
}
|
|
|
|
static void
|
|
totem_interface_process(struct evdev_dispatch *dispatch,
|
|
struct evdev_device *device,
|
|
struct input_event *e,
|
|
uint64_t time)
|
|
{
|
|
struct totem_dispatch *totem = totem_dispatch(dispatch);
|
|
enum totem_slot_state global_state;
|
|
bool enable_touch;
|
|
|
|
switch(e->type) {
|
|
case EV_ABS:
|
|
totem_process_abs(totem, device, e, time);
|
|
break;
|
|
case EV_KEY:
|
|
totem_process_key(totem, device, e, time);
|
|
break;
|
|
case EV_MSC:
|
|
/* timestamp, ignore */
|
|
break;
|
|
case EV_SYN:
|
|
global_state = totem_handle_state(totem, time);
|
|
enable_touch = (global_state == SLOT_STATE_NONE);
|
|
totem_set_touch_device_enabled(totem,
|
|
enable_touch,
|
|
time);
|
|
break;
|
|
default:
|
|
evdev_log_error(device,
|
|
"Unexpected event type %s (%#x)\n",
|
|
libevdev_event_type_get_name(e->type),
|
|
e->type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
totem_interface_suspend(struct evdev_dispatch *dispatch,
|
|
struct evdev_device *device)
|
|
{
|
|
struct totem_dispatch *totem = totem_dispatch(dispatch);
|
|
uint64_t now = libinput_now(evdev_libinput_context(device));
|
|
|
|
for (size_t i = 0; i < totem->nslots; i++) {
|
|
struct totem_slot *slot = &totem->slots[i];
|
|
struct tablet_axes axes;
|
|
enum libinput_tablet_tool_tip_state tip_state;
|
|
|
|
/* If we never initialized a tool, we can skip everything */
|
|
if (!slot->tool)
|
|
continue;
|
|
|
|
totem_slot_fetch_axes(totem, slot, slot->tool, &axes, now);
|
|
totem_slot_reset_changed_axes(totem, slot);
|
|
|
|
if (slot->state == SLOT_STATE_NONE)
|
|
tip_state = LIBINPUT_TABLET_TOOL_TIP_UP;
|
|
else
|
|
tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN;
|
|
|
|
if (totem->button_state_now) {
|
|
tablet_notify_button(&device->base,
|
|
now,
|
|
slot->tool,
|
|
tip_state,
|
|
&axes,
|
|
BTN_0,
|
|
LIBINPUT_BUTTON_STATE_RELEASED,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
|
|
totem->button_state_now = false;
|
|
totem->button_state_previous = false;
|
|
}
|
|
|
|
if (slot->state != SLOT_STATE_NONE) {
|
|
tablet_notify_tip(&device->base,
|
|
now,
|
|
slot->tool,
|
|
LIBINPUT_TABLET_TOOL_TIP_UP,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
}
|
|
tablet_notify_proximity(&device->base,
|
|
now,
|
|
slot->tool,
|
|
LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
}
|
|
totem_set_touch_device_enabled(totem, true, now);
|
|
}
|
|
|
|
static void
|
|
totem_interface_destroy(struct evdev_dispatch *dispatch)
|
|
{
|
|
struct totem_dispatch *totem = totem_dispatch(dispatch);
|
|
|
|
free(totem->slots);
|
|
free(totem);
|
|
}
|
|
|
|
static void
|
|
totem_interface_device_added(struct evdev_device *device,
|
|
struct evdev_device *added_device)
|
|
{
|
|
struct totem_dispatch *totem = totem_dispatch(device->dispatch);
|
|
struct libinput_device_group *g1, *g2;
|
|
|
|
if ((evdev_device_get_id_vendor(added_device) !=
|
|
evdev_device_get_id_vendor(device)) ||
|
|
(evdev_device_get_id_product(added_device) !=
|
|
evdev_device_get_id_product(device)))
|
|
return;
|
|
|
|
/* virtual devices don't have device groups, so check for that
|
|
libinput replay */
|
|
g1 = libinput_device_get_device_group(&device->base);
|
|
g2 = libinput_device_get_device_group(&added_device->base);
|
|
if (g1 && g2 && g1->identifier != g2->identifier)
|
|
return;
|
|
|
|
if (totem->touch_device != NULL) {
|
|
evdev_log_bug_libinput(device,
|
|
"already has a paired touch device, ignoring (%s)\n",
|
|
added_device->devname);
|
|
return;
|
|
}
|
|
|
|
totem->touch_device = added_device;
|
|
evdev_log_info(device, "%s: is the totem touch device\n", added_device->devname);
|
|
}
|
|
|
|
static void
|
|
totem_interface_device_removed(struct evdev_device *device,
|
|
struct evdev_device *removed_device)
|
|
{
|
|
struct totem_dispatch *totem = totem_dispatch(device->dispatch);
|
|
|
|
if (totem->touch_device != removed_device)
|
|
return;
|
|
|
|
totem_set_touch_device_enabled(totem, true,
|
|
libinput_now(evdev_libinput_context(device)));
|
|
totem->touch_device = NULL;
|
|
}
|
|
|
|
static void
|
|
totem_interface_initial_proximity(struct evdev_device *device,
|
|
struct evdev_dispatch *dispatch)
|
|
{
|
|
struct totem_dispatch *totem = totem_dispatch(dispatch);
|
|
uint64_t now = libinput_now(evdev_libinput_context(device));
|
|
bool enable_touch = true;
|
|
|
|
for (size_t i = 0; i < totem->nslots; i++) {
|
|
struct totem_slot *slot = &totem->slots[i];
|
|
struct tablet_axes axes;
|
|
int tracking_id;
|
|
|
|
tracking_id = libevdev_get_slot_value(device->evdev,
|
|
i,
|
|
ABS_MT_TRACKING_ID);
|
|
if (tracking_id == -1)
|
|
continue;
|
|
|
|
slot->tool = totem_new_tool(totem);
|
|
slot_axes_initialize(totem, slot);
|
|
totem_slot_mark_all_axes_changed(totem, slot, slot->tool);
|
|
totem_slot_fetch_axes(totem, slot, slot->tool, &axes, now);
|
|
tablet_notify_proximity(&device->base,
|
|
now,
|
|
slot->tool,
|
|
LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
totem_slot_reset_changed_axes(totem, slot);
|
|
tablet_notify_tip(&device->base,
|
|
now,
|
|
slot->tool,
|
|
LIBINPUT_TABLET_TOOL_TIP_DOWN,
|
|
slot->changed_axes,
|
|
&axes,
|
|
device->abs.absinfo_x,
|
|
device->abs.absinfo_y);
|
|
slot->state = SLOT_STATE_UPDATE;
|
|
enable_touch = false;
|
|
}
|
|
|
|
totem_set_touch_device_enabled(totem, enable_touch, now);
|
|
}
|
|
|
|
static struct evdev_dispatch_interface totem_interface = {
|
|
.process = totem_interface_process,
|
|
.suspend = totem_interface_suspend,
|
|
.remove = NULL,
|
|
.destroy = totem_interface_destroy,
|
|
.device_added = totem_interface_device_added,
|
|
.device_removed = totem_interface_device_removed,
|
|
.device_suspended = totem_interface_device_removed, /* treat as remove */
|
|
.device_resumed = totem_interface_device_added, /* treat as add */
|
|
.post_added = totem_interface_initial_proximity,
|
|
.touch_arbitration_toggle = NULL,
|
|
.touch_arbitration_update_rect = NULL,
|
|
.get_switch_state = NULL,
|
|
};
|
|
|
|
static bool
|
|
totem_reject_device(struct evdev_device *device)
|
|
{
|
|
struct libevdev *evdev = device->evdev;
|
|
bool has_xy, has_slot, has_tool_dial, has_size, has_touch_size;
|
|
double w, h;
|
|
|
|
has_xy = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) &&
|
|
libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y);
|
|
has_slot = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT);
|
|
has_tool_dial = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_TOOL_TYPE) &&
|
|
libevdev_get_abs_maximum(evdev, ABS_MT_TOOL_TYPE) >= MT_TOOL_DIAL;
|
|
has_size = evdev_device_get_size(device, &w, &h) == 0;
|
|
has_touch_size =
|
|
libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MAJOR) > 0 ||
|
|
libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MINOR) > 0;
|
|
|
|
if (has_xy && has_slot && has_tool_dial && has_size && has_touch_size)
|
|
return false;
|
|
|
|
evdev_log_bug_libinput(device,
|
|
"missing totem capabilities:%s%s%s%s%s. "
|
|
"Ignoring this device.\n",
|
|
has_xy ? "" : " xy",
|
|
has_slot ? "" : " slot",
|
|
has_tool_dial ? "" : " dial",
|
|
has_size ? "" : " resolutions",
|
|
has_touch_size ? "" : " touch-size");
|
|
return true;
|
|
}
|
|
|
|
static uint32_t
|
|
totem_accel_config_get_profiles(struct libinput_device *libinput_device)
|
|
{
|
|
return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
|
|
}
|
|
|
|
static enum libinput_config_status
|
|
totem_accel_config_set_profile(struct libinput_device *libinput_device,
|
|
enum libinput_config_accel_profile profile)
|
|
{
|
|
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
|
|
}
|
|
|
|
static enum libinput_config_accel_profile
|
|
totem_accel_config_get_profile(struct libinput_device *libinput_device)
|
|
{
|
|
return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
|
|
}
|
|
|
|
static enum libinput_config_accel_profile
|
|
totem_accel_config_get_default_profile(struct libinput_device *libinput_device)
|
|
{
|
|
return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
|
|
}
|
|
|
|
static int
|
|
totem_init_accel(struct totem_dispatch *totem, struct evdev_device *device)
|
|
{
|
|
const struct input_absinfo *x, *y;
|
|
struct motion_filter *filter;
|
|
|
|
x = device->abs.absinfo_x;
|
|
y = device->abs.absinfo_y;
|
|
|
|
/* same filter as the tablet */
|
|
filter = create_pointer_accelerator_filter_tablet(x->resolution,
|
|
y->resolution);
|
|
if (!filter)
|
|
return -1;
|
|
|
|
evdev_device_init_pointer_acceleration(device, filter);
|
|
|
|
/* we override the profile hooks for accel configuration with hooks
|
|
* that don't allow selection of profiles */
|
|
device->pointer.config.get_profiles = totem_accel_config_get_profiles;
|
|
device->pointer.config.set_profile = totem_accel_config_set_profile;
|
|
device->pointer.config.get_profile = totem_accel_config_get_profile;
|
|
device->pointer.config.get_default_profile = totem_accel_config_get_default_profile;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct evdev_dispatch *
|
|
evdev_totem_create(struct evdev_device *device)
|
|
{
|
|
struct totem_dispatch *totem;
|
|
struct totem_slot *slots;
|
|
int num_slots;
|
|
|
|
if (totem_reject_device(device))
|
|
return NULL;
|
|
|
|
totem = zalloc(sizeof *totem);
|
|
totem->device = device;
|
|
totem->base.dispatch_type = DISPATCH_TOTEM;
|
|
totem->base.interface = &totem_interface;
|
|
|
|
num_slots = libevdev_get_num_slots(device->evdev);
|
|
if (num_slots <= 0)
|
|
goto error;
|
|
|
|
totem->slot = libevdev_get_current_slot(device->evdev);
|
|
slots = zalloc(num_slots * sizeof(*totem->slots));
|
|
|
|
for (int slot = 0; slot < num_slots; ++slot) {
|
|
slots[slot].index = slot;
|
|
}
|
|
|
|
totem->slots = slots;
|
|
totem->nslots = num_slots;
|
|
|
|
evdev_init_sendevents(device, &totem->base);
|
|
totem_init_accel(totem, device);
|
|
|
|
return &totem->base;
|
|
error:
|
|
totem_interface_destroy(&totem->base);
|
|
return NULL;
|
|
}
|