libinput/src/libinput-plugin-tablet-forced-tool.c
Peter Hutterer 49d9528bdf tablet: after three valid prox out events, unload the forced prox plugin
Once we receive three valid proximity out events for the pen, unload the
plugin that generates forced proximity in events for tablets that
don't send BTN_TOOL_PEN.

Closes #1222

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1370>
2025-11-26 10:55:03 +10:00

272 lines
7.4 KiB
C

/*
* Copyright © 2025 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 <assert.h>
#include <libevdev/libevdev.h>
#include "util-mem.h"
#include "util-strings.h"
#include "evdev-frame.h"
#include "libinput-log.h"
#include "libinput-plugin-tablet-forced-tool.h"
#include "libinput-plugin.h"
#include "libinput-util.h"
/*
* Handling for tools that never set BTN_TOOL_PEN.
*/
enum tools {
PEN = 0,
RUBBER,
BRUSH,
PENCIL,
AIRBRUSH,
MOUSE,
LENS,
};
struct plugin_device {
struct list link;
struct libinput_device *device;
bitmask_t tool_state;
bool pen_forced_into_proximity;
size_t pen_prox_out_events;
};
struct plugin_data {
struct list devices;
};
static void
plugin_device_destroy(struct plugin_device *device)
{
libinput_device_unref(device->device);
list_remove(&device->link);
free(device);
}
static void
plugin_data_destroy(void *d)
{
struct plugin_data *data = d;
struct plugin_device *device;
list_for_each_safe(device, &data->devices, link) {
plugin_device_destroy(device);
}
free(data);
}
DEFINE_DESTROY_CLEANUP_FUNC(plugin_data);
static void
plugin_destroy(struct libinput_plugin *libinput_plugin)
{
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
plugin_data_destroy(plugin);
}
static void
forced_tool_plugin_force_pen_out(struct libinput_plugin *libinput_plugin,
struct libinput_device *device,
struct evdev_frame *frame)
{
_unref_(evdev_frame) *prox_out_frame = evdev_frame_new(2);
evdev_frame_append_one(prox_out_frame, evdev_usage_from(EVDEV_BTN_TOOL_PEN), 0);
evdev_frame_set_time(prox_out_frame, evdev_frame_get_time(frame));
libinput_plugin_prepend_evdev_frame(libinput_plugin, device, prox_out_frame);
}
static void
forced_tool_plugin_device_handle_frame(struct libinput_plugin *libinput_plugin,
struct plugin_device *device,
struct evdev_frame *frame)
{
size_t nevents;
struct evdev_event *events = evdev_frame_get_events(frame, &nevents);
bool axis_change = false;
for (size_t i = 0; i < nevents; i++) {
struct evdev_event *event = &events[i];
switch (evdev_usage_enum(event->usage)) {
case EVDEV_BTN_TOOL_PEN:
if (event->value == 1) {
bitmask_set_bit(&device->tool_state, BTN_TOOL_PEN);
} else {
bitmask_clear_bit(&device->tool_state, BTN_TOOL_PEN);
device->pen_forced_into_proximity = false;
/* If we get three valid pen proximity out events, let's
* assume this device works fine and disable our plugin
*/
if (++device->pen_prox_out_events > 2) {
plugin_log_debug(
libinput_plugin,
"%s: forced tool handling unloaded\n",
libinput_device_get_name(
device->device));
libinput_plugin_enable_device_event_frame(
libinput_plugin,
device->device,
false);
plugin_device_destroy(device);
return;
}
}
return; /* Nothing to do */
case EVDEV_BTN_TOOL_RUBBER:
case EVDEV_BTN_TOOL_BRUSH:
case EVDEV_BTN_TOOL_PENCIL:
case EVDEV_BTN_TOOL_AIRBRUSH:
case EVDEV_BTN_TOOL_MOUSE:
case EVDEV_BTN_TOOL_LENS: {
int code = evdev_event_code(event) - BTN_TOOL_PEN;
if (event->value == 1) {
bitmask_set_bit(&device->tool_state, code);
if (device->pen_forced_into_proximity) {
forced_tool_plugin_force_pen_out(
libinput_plugin,
device->device,
frame);
device->pen_forced_into_proximity = false;
}
} else {
bitmask_clear_bit(&device->tool_state, code);
}
return; /* Keep the frame as-is */
}
case EVDEV_ABS_X:
case EVDEV_ABS_Y:
case EVDEV_ABS_Z: /* rotation */
/* not ABS_DISTANCE! */
case EVDEV_ABS_PRESSURE:
case EVDEV_ABS_TILT_X:
case EVDEV_ABS_TILT_Y:
case EVDEV_ABS_WHEEL: /* slider */
/* no early return here, the BTN_TOOL updates
* may come after the ABS_ events */
axis_change = true;
break;
case EVDEV_REL_WHEEL:
/* no early return here, the BTN_TOOL updates
* may come after the REL_ events */
axis_change = true;
break;
default:
break;
}
}
if (!axis_change)
return;
const bitmask_t all_tools =
bitmask_from_bits(PEN, RUBBER, BRUSH, PENCIL, AIRBRUSH, MOUSE, LENS);
if (bitmask_any(device->tool_state, all_tools))
return;
/* We need to force a BTN_TOOL_PEN if we get an axis event (i.e.
* stylus is def. in proximity). We don't do this for pure
* button events because we discard those.
*/
evdev_frame_append_one(frame,
evdev_usage_from(EVDEV_BTN_TOOL_PEN),
1); /* libinput's event frame will have space */
device->pen_forced_into_proximity = true;
}
static void
forced_tool_plugin_evdev_frame(struct libinput_plugin *libinput_plugin,
struct libinput_device *device,
struct evdev_frame *frame)
{
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
struct plugin_device *pd;
list_for_each(pd, &plugin->devices, link) {
if (pd->device == device) {
forced_tool_plugin_device_handle_frame(libinput_plugin,
pd,
frame);
break;
}
}
}
static void
forced_tool_plugin_device_added(struct libinput_plugin *libinput_plugin,
struct libinput_device *device)
{
if (!libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL))
return;
libinput_plugin_enable_device_event_frame(libinput_plugin, device, true);
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
struct plugin_device *pd = zalloc(sizeof(*pd));
pd->device = libinput_device_ref(device);
list_take_append(&plugin->devices, pd, link);
}
static void
forced_tool_plugin_device_removed(struct libinput_plugin *libinput_plugin,
struct libinput_device *device)
{
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
struct plugin_device *dev;
list_for_each_safe(dev, &plugin->devices, link) {
if (dev->device == device) {
plugin_device_destroy(dev);
return;
}
}
}
static const struct libinput_plugin_interface interface = {
.run = NULL,
.destroy = plugin_destroy,
.device_new = NULL,
.device_ignored = NULL,
.device_added = forced_tool_plugin_device_added,
.device_removed = forced_tool_plugin_device_removed,
.evdev_frame = forced_tool_plugin_evdev_frame,
};
void
libinput_tablet_plugin_forced_tool(struct libinput *libinput)
{
_destroy_(plugin_data) *plugin = zalloc(sizeof(*plugin));
list_init(&plugin->devices);
_unref_(libinput_plugin) *p = libinput_plugin_new(libinput,
"tablet-forced-tool",
&interface,
steal(&plugin));
}