2025-06-02 15:11:08 +10:00
|
|
|
/*
|
|
|
|
|
* 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-eraser-button.h"
|
|
|
|
|
#include "libinput-plugin.h"
|
|
|
|
|
#include "libinput-util.h"
|
|
|
|
|
|
2025-12-01 09:17:37 +10:00
|
|
|
static usec_t ERASER_BUTTON_DELAY = { 30 * 1000 }; /* µs */
|
2025-06-02 15:11:08 +10:00
|
|
|
|
|
|
|
|
enum frame_filter_state {
|
|
|
|
|
DISCARD,
|
|
|
|
|
PROCESS,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum eraser_button_state {
|
|
|
|
|
ERASER_BUTTON_NEUTRAL,
|
|
|
|
|
ERASER_BUTTON_PEN_PENDING_ERASER,
|
|
|
|
|
ERASER_BUTTON_BUTTON_HELD_DOWN,
|
|
|
|
|
ERASER_BUTTON_BUTTON_RELEASED,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum eraser_button_event {
|
|
|
|
|
ERASER_EVENT_PEN_ENTERING_PROX,
|
|
|
|
|
ERASER_EVENT_PEN_LEAVING_PROX,
|
|
|
|
|
ERASER_EVENT_ERASER_ENTERING_PROX,
|
|
|
|
|
ERASER_EVENT_ERASER_LEAVING_PROX,
|
|
|
|
|
ERASER_EVENT_TIMEOUT,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
eraser_button_state_str(enum eraser_button_state state)
|
|
|
|
|
{
|
|
|
|
|
switch (state) {
|
|
|
|
|
CASE_RETURN_STRING(ERASER_BUTTON_NEUTRAL);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_BUTTON_PEN_PENDING_ERASER);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_BUTTON_BUTTON_HELD_DOWN);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_BUTTON_BUTTON_RELEASED);
|
|
|
|
|
}
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
eraser_button_event_str(enum eraser_button_event event)
|
|
|
|
|
{
|
|
|
|
|
switch (event) {
|
|
|
|
|
CASE_RETURN_STRING(ERASER_EVENT_PEN_ENTERING_PROX);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_EVENT_PEN_LEAVING_PROX);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_EVENT_ERASER_ENTERING_PROX);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_EVENT_ERASER_LEAVING_PROX);
|
|
|
|
|
CASE_RETURN_STRING(ERASER_EVENT_TIMEOUT);
|
|
|
|
|
}
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct plugin_device {
|
|
|
|
|
struct list link;
|
|
|
|
|
struct plugin_data *parent;
|
|
|
|
|
struct libinput_device *device;
|
|
|
|
|
|
|
|
|
|
bool pen_in_prox;
|
|
|
|
|
bool eraser_in_prox;
|
|
|
|
|
|
|
|
|
|
struct evdev_frame *last_frame;
|
|
|
|
|
|
|
|
|
|
enum libinput_config_eraser_button_mode mode;
|
|
|
|
|
/* The evdev code of the button to send */
|
|
|
|
|
evdev_usage_t button;
|
|
|
|
|
struct libinput_plugin_timer *timer;
|
|
|
|
|
enum eraser_button_state state;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct plugin_data {
|
|
|
|
|
struct libinput_plugin *plugin;
|
|
|
|
|
struct list devices;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
plugin_device_destroy(struct plugin_device *device)
|
|
|
|
|
{
|
|
|
|
|
libinput_plugin_timer_cancel(device->timer);
|
|
|
|
|
libinput_plugin_timer_unref(device->timer);
|
|
|
|
|
libinput_device_unref(device->device);
|
|
|
|
|
evdev_frame_unref(device->last_frame);
|
|
|
|
|
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
|
|
|
|
|
eraser_button_set_state(struct plugin_device *device, enum eraser_button_state to)
|
|
|
|
|
{
|
|
|
|
|
enum eraser_button_state *state = &device->state;
|
|
|
|
|
|
|
|
|
|
*state = to;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2025-12-01 09:17:37 +10:00
|
|
|
eraser_button_set_timer(struct plugin_device *device, usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
2025-12-01 09:17:37 +10:00
|
|
|
libinput_plugin_timer_set(device->timer, usec_add(time, ERASER_BUTTON_DELAY));
|
2025-06-02 15:11:08 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_cancel_timer(struct plugin_device *device)
|
|
|
|
|
{
|
|
|
|
|
libinput_plugin_timer_cancel(device->timer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_state_bug(struct plugin_device *device, enum eraser_button_event event)
|
|
|
|
|
{
|
|
|
|
|
plugin_log_bug(device->parent->plugin,
|
|
|
|
|
"Invalid eraser button event %s in state %s\n",
|
|
|
|
|
eraser_button_event_str(event),
|
|
|
|
|
eraser_button_state_str(device->state));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum tool_filter {
|
|
|
|
|
SKIP_PEN = bit(1),
|
|
|
|
|
SKIP_ERASER = bit(2),
|
|
|
|
|
PEN_IN_PROX = bit(3),
|
|
|
|
|
PEN_OUT_OF_PROX = bit(4),
|
|
|
|
|
ERASER_IN_PROX = bit(5),
|
|
|
|
|
ERASER_OUT_OF_PROX = bit(6),
|
|
|
|
|
BUTTON_DOWN = bit(7),
|
|
|
|
|
BUTTON_UP = bit(8),
|
|
|
|
|
SKIP_BTN_TOUCH = bit(9),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_insert_frame(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame_in,
|
|
|
|
|
enum tool_filter filter,
|
|
|
|
|
evdev_usage_t *button)
|
|
|
|
|
{
|
|
|
|
|
size_t nevents;
|
|
|
|
|
const struct evdev_event *events = evdev_frame_get_events(frame_in, &nevents);
|
|
|
|
|
|
|
|
|
|
/* +2 because we may add BTN_TOOL_PEN and BTN_TOOL_RUBBER */
|
|
|
|
|
_unref_(evdev_frame) *frame_out = evdev_frame_new(nevents + 2);
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
case EVDEV_BTN_TOOL_RUBBER:
|
|
|
|
|
/* filter */
|
|
|
|
|
break;
|
|
|
|
|
case EVDEV_BTN_TOUCH:
|
|
|
|
|
if (!(filter & SKIP_BTN_TOUCH))
|
|
|
|
|
evdev_frame_append(frame_out, &event, 1);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (button == NULL || evdev_usage_cmp(event.usage, *button))
|
|
|
|
|
evdev_frame_append(frame_out, &event, 1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filter & (PEN_IN_PROX | PEN_OUT_OF_PROX)) {
|
2025-06-23 14:53:20 +10:00
|
|
|
evdev_frame_append_one(frame_out,
|
|
|
|
|
evdev_usage_from(EVDEV_BTN_TOOL_PEN),
|
|
|
|
|
(filter & PEN_IN_PROX) ? 1 : 0);
|
2025-06-02 15:11:08 +10:00
|
|
|
}
|
|
|
|
|
if (filter & (ERASER_IN_PROX | ERASER_OUT_OF_PROX)) {
|
2025-06-23 14:53:20 +10:00
|
|
|
evdev_frame_append_one(frame_out,
|
|
|
|
|
evdev_usage_from(EVDEV_BTN_TOOL_RUBBER),
|
|
|
|
|
(filter & ERASER_IN_PROX) ? 1 : 0);
|
2025-06-02 15:11:08 +10:00
|
|
|
}
|
|
|
|
|
if (filter & (BUTTON_UP | BUTTON_DOWN)) {
|
|
|
|
|
assert(button != NULL);
|
2025-06-23 14:53:20 +10:00
|
|
|
evdev_frame_append_one(frame_out,
|
|
|
|
|
*button,
|
|
|
|
|
(filter & BUTTON_DOWN) ? 1 : 0);
|
2025-06-02 15:11:08 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
evdev_frame_set_time(frame_out, evdev_frame_get_time(frame_in));
|
|
|
|
|
libinput_plugin_prepend_evdev_frame(device->parent->plugin,
|
|
|
|
|
device->device,
|
|
|
|
|
frame_out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum frame_filter_state
|
|
|
|
|
eraser_button_neutral_handle_event(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame,
|
|
|
|
|
enum eraser_button_event event,
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
switch (event) {
|
|
|
|
|
case ERASER_EVENT_PEN_ENTERING_PROX:
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_PEN_LEAVING_PROX:
|
|
|
|
|
eraser_button_set_timer(device, time);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_PEN_PENDING_ERASER);
|
|
|
|
|
return DISCARD; /* Discard this event, it has garbage data anyway */
|
|
|
|
|
case ERASER_EVENT_ERASER_ENTERING_PROX:
|
|
|
|
|
/* Change eraser prox in into pen prox in + button down */
|
|
|
|
|
eraser_button_insert_frame(device,
|
|
|
|
|
frame,
|
|
|
|
|
PEN_IN_PROX | SKIP_ERASER | BUTTON_DOWN,
|
|
|
|
|
&device->button);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_BUTTON_HELD_DOWN);
|
|
|
|
|
return DISCARD;
|
|
|
|
|
case ERASER_EVENT_ERASER_LEAVING_PROX:
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_TIMEOUT:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PROCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum frame_filter_state
|
|
|
|
|
eraser_button_pending_eraser_handle_event(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame,
|
|
|
|
|
enum eraser_button_event event,
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
switch (event) {
|
|
|
|
|
case ERASER_EVENT_PEN_ENTERING_PROX:
|
|
|
|
|
eraser_button_cancel_timer(device);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_NEUTRAL);
|
|
|
|
|
/* We just papered over a quick prox out/in here */
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_PEN_LEAVING_PROX:
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_ERASER_ENTERING_PROX:
|
|
|
|
|
eraser_button_cancel_timer(device);
|
|
|
|
|
eraser_button_insert_frame(device,
|
|
|
|
|
frame,
|
|
|
|
|
SKIP_ERASER | SKIP_PEN | BUTTON_DOWN,
|
|
|
|
|
&device->button);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_BUTTON_HELD_DOWN);
|
|
|
|
|
return DISCARD;
|
|
|
|
|
case ERASER_EVENT_ERASER_LEAVING_PROX:
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_TIMEOUT:
|
|
|
|
|
/* Pen went out of prox and we delayed expecting an eraser to
|
|
|
|
|
* come in prox. That didn't happen -> pen prox out */
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_NEUTRAL);
|
|
|
|
|
eraser_button_insert_frame(device,
|
|
|
|
|
frame,
|
|
|
|
|
SKIP_ERASER | PEN_OUT_OF_PROX,
|
|
|
|
|
NULL);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PROCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum frame_filter_state
|
|
|
|
|
eraser_button_button_held_handle_event(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame,
|
|
|
|
|
enum eraser_button_event event,
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
switch (event) {
|
|
|
|
|
case ERASER_EVENT_PEN_ENTERING_PROX:
|
|
|
|
|
case ERASER_EVENT_PEN_LEAVING_PROX:
|
|
|
|
|
/* We should've seen an eraser out-of-prox out here */
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_ERASER_ENTERING_PROX:
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_ERASER_LEAVING_PROX:
|
|
|
|
|
eraser_button_insert_frame(device,
|
|
|
|
|
device->last_frame,
|
|
|
|
|
SKIP_ERASER | SKIP_PEN | BUTTON_UP,
|
|
|
|
|
&device->button);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_BUTTON_RELEASED);
|
|
|
|
|
eraser_button_set_timer(device, time);
|
|
|
|
|
return DISCARD; /* Discard the actual frame, it has garbage data anyway
|
|
|
|
|
*/
|
|
|
|
|
case ERASER_EVENT_TIMEOUT:
|
|
|
|
|
/* Expected to be cancelled in previous state */
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PROCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum frame_filter_state
|
|
|
|
|
eraser_button_button_released_handle_event(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame,
|
|
|
|
|
enum eraser_button_event event,
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
switch (event) {
|
|
|
|
|
case ERASER_EVENT_PEN_ENTERING_PROX:
|
|
|
|
|
eraser_button_cancel_timer(device);
|
|
|
|
|
eraser_button_insert_frame(device, frame, SKIP_PEN | SKIP_ERASER, NULL);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_NEUTRAL);
|
|
|
|
|
return DISCARD;
|
|
|
|
|
case ERASER_EVENT_PEN_LEAVING_PROX:
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_ERASER_ENTERING_PROX:
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_ERASER_LEAVING_PROX:
|
|
|
|
|
eraser_button_state_bug(device, event);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_EVENT_TIMEOUT:
|
|
|
|
|
/* Eraser went out of prox, we expected the pen to come back in prox but
|
|
|
|
|
* that didn't happen. We still have the pen simulated in-prox -> pen
|
|
|
|
|
* prox out.
|
|
|
|
|
* We release the button first, then send the pen out-of-prox
|
|
|
|
|
* event sequence. This way the sequence of tip first/button first is
|
|
|
|
|
* predictable.
|
|
|
|
|
*/
|
|
|
|
|
eraser_button_insert_frame(device,
|
|
|
|
|
frame,
|
|
|
|
|
SKIP_PEN | SKIP_ERASER | BUTTON_UP,
|
|
|
|
|
&device->button);
|
|
|
|
|
eraser_button_insert_frame(device, frame, PEN_OUT_OF_PROX, NULL);
|
|
|
|
|
eraser_button_set_state(device, ERASER_BUTTON_NEUTRAL);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PROCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum frame_filter_state
|
|
|
|
|
eraser_button_handle_state(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame,
|
|
|
|
|
enum eraser_button_event event,
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
enum eraser_button_state state = device->state;
|
|
|
|
|
enum frame_filter_state ret = PROCESS;
|
|
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
|
case ERASER_BUTTON_NEUTRAL:
|
|
|
|
|
ret = eraser_button_neutral_handle_event(device, frame, event, time);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_BUTTON_PEN_PENDING_ERASER:
|
|
|
|
|
ret = eraser_button_pending_eraser_handle_event(device,
|
|
|
|
|
frame,
|
|
|
|
|
event,
|
|
|
|
|
time);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_BUTTON_BUTTON_HELD_DOWN:
|
|
|
|
|
ret = eraser_button_button_held_handle_event(device,
|
|
|
|
|
frame,
|
|
|
|
|
event,
|
|
|
|
|
time);
|
|
|
|
|
break;
|
|
|
|
|
case ERASER_BUTTON_BUTTON_RELEASED:
|
|
|
|
|
ret = eraser_button_button_released_handle_event(device,
|
|
|
|
|
frame,
|
|
|
|
|
event,
|
|
|
|
|
time);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state != device->state) {
|
|
|
|
|
plugin_log_debug(device->parent->plugin,
|
|
|
|
|
"eraser button: state %s -> %s -> %s\n",
|
|
|
|
|
eraser_button_state_str(state),
|
|
|
|
|
eraser_button_event_str(event),
|
|
|
|
|
eraser_button_state_str(device->state));
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Physical eraser button handling: if the physical eraser button is
|
|
|
|
|
* disabled paper over any pen prox out/eraser prox in events and send a button event
|
|
|
|
|
* instead.
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_handle_frame(struct plugin_device *device,
|
|
|
|
|
struct evdev_frame *frame,
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
if (device->mode == LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
size_t nevents;
|
|
|
|
|
struct evdev_event *events = evdev_frame_get_events(frame, &nevents);
|
|
|
|
|
|
|
|
|
|
bool pen_toggled = false;
|
|
|
|
|
bool eraser_toggled = 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:
|
|
|
|
|
pen_toggled = true;
|
|
|
|
|
device->pen_in_prox = !!event->value;
|
|
|
|
|
break;
|
|
|
|
|
case EVDEV_BTN_TOOL_RUBBER:
|
|
|
|
|
eraser_toggled = true;
|
|
|
|
|
device->eraser_in_prox = !!event->value;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool eraser_in_prox = device->eraser_in_prox;
|
|
|
|
|
bool pen_in_prox = device->pen_in_prox;
|
|
|
|
|
|
|
|
|
|
enum eraser_button_event eraser_event =
|
|
|
|
|
eraser_in_prox ? ERASER_EVENT_ERASER_ENTERING_PROX
|
|
|
|
|
: ERASER_EVENT_ERASER_LEAVING_PROX;
|
|
|
|
|
enum eraser_button_event pen_event = pen_in_prox
|
|
|
|
|
? ERASER_EVENT_PEN_ENTERING_PROX
|
|
|
|
|
: ERASER_EVENT_PEN_LEAVING_PROX;
|
|
|
|
|
|
|
|
|
|
enum frame_filter_state ret = PROCESS;
|
|
|
|
|
|
|
|
|
|
/* bit awkward because we definitely want whatever goes out of prox to
|
|
|
|
|
* be handled first but if one sends discard and the other one process?
|
|
|
|
|
* Unclear...
|
|
|
|
|
*/
|
|
|
|
|
if (eraser_toggled && pen_toggled) {
|
|
|
|
|
if (pen_in_prox) {
|
|
|
|
|
eraser_button_handle_state(device, frame, eraser_event, time);
|
|
|
|
|
ret = eraser_button_handle_state(device,
|
|
|
|
|
frame,
|
|
|
|
|
pen_event,
|
|
|
|
|
time);
|
|
|
|
|
} else {
|
|
|
|
|
eraser_button_handle_state(device, frame, pen_event, time);
|
|
|
|
|
ret = eraser_button_handle_state(device,
|
|
|
|
|
frame,
|
|
|
|
|
eraser_event,
|
|
|
|
|
time);
|
|
|
|
|
}
|
|
|
|
|
} else if (eraser_toggled) {
|
|
|
|
|
ret = eraser_button_handle_state(device, frame, eraser_event, time);
|
|
|
|
|
} else if (pen_toggled) {
|
|
|
|
|
ret = eraser_button_handle_state(device, frame, pen_event, time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ret == PROCESS) {
|
|
|
|
|
evdev_frame_reset(device->last_frame);
|
|
|
|
|
evdev_frame_append(device->last_frame, events, nevents);
|
|
|
|
|
} else if (ret == DISCARD) {
|
|
|
|
|
evdev_frame_reset(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_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;
|
2025-12-01 09:17:37 +10:00
|
|
|
usec_t time = evdev_frame_get_time(frame);
|
2025-06-02 15:11:08 +10:00
|
|
|
|
|
|
|
|
list_for_each(pd, &plugin->devices, link) {
|
|
|
|
|
if (pd->device == device) {
|
|
|
|
|
eraser_button_handle_frame(pd, frame, time);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2025-12-01 09:17:37 +10:00
|
|
|
eraser_button_timer_func(struct libinput_plugin *plugin, usec_t now, void *d)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
struct plugin_device *device = d;
|
|
|
|
|
|
|
|
|
|
if (!device->last_frame) {
|
|
|
|
|
plugin_log_bug(
|
|
|
|
|
device->parent->plugin,
|
|
|
|
|
"Eraser button timer fired without a frame in state %s\n",
|
|
|
|
|
eraser_button_state_str(device->state));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
eraser_button_handle_state(device,
|
|
|
|
|
device->last_frame,
|
|
|
|
|
ERASER_EVENT_TIMEOUT,
|
|
|
|
|
now);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_plugin_device_added(struct libinput_plugin *libinput_plugin,
|
2025-06-24 11:58:32 +10:00
|
|
|
struct libinput_device *device)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
if (!libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-06-17 16:01:09 +10:00
|
|
|
libinput_plugin_enable_device_event_frame(libinput_plugin, device, true);
|
2025-07-14 16:13:16 +10:00
|
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin, EVDEV_BTN_TOOL_PEN);
|
|
|
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin, EVDEV_BTN_TOOL_RUBBER);
|
|
|
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin, EVDEV_BTN_TOUCH);
|
|
|
|
|
/* These are the only ones we allow setting the eraser button to */
|
|
|
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin, EVDEV_BTN_STYLUS);
|
|
|
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin, EVDEV_BTN_STYLUS2);
|
|
|
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin, EVDEV_BTN_STYLUS3);
|
2025-06-17 16:01:09 +10:00
|
|
|
|
2025-06-02 15:11:08 +10:00
|
|
|
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
|
|
|
|
|
struct plugin_device *pd = zalloc(sizeof(*pd));
|
|
|
|
|
pd->device = libinput_device_ref(device);
|
|
|
|
|
pd->parent = plugin;
|
|
|
|
|
pd->last_frame = evdev_frame_new(64);
|
|
|
|
|
pd->timer = libinput_plugin_timer_new(libinput_plugin,
|
|
|
|
|
libinput_device_get_sysname(device),
|
|
|
|
|
eraser_button_timer_func,
|
|
|
|
|
pd);
|
|
|
|
|
|
|
|
|
|
list_take_append(&plugin->devices, pd, link);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
eraser_button_plugin_device_removed(struct libinput_plugin *libinput_plugin,
|
2025-06-24 11:58:32 +10:00
|
|
|
struct libinput_device *device)
|
2025-06-02 15:11:08 +10:00
|
|
|
{
|
|
|
|
|
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 void
|
|
|
|
|
eraser_button_plugin_tool_configured(struct libinput_plugin *libinput_plugin,
|
|
|
|
|
struct libinput_tablet_tool *tool)
|
|
|
|
|
{
|
|
|
|
|
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
|
|
|
|
|
struct plugin_device *pd;
|
|
|
|
|
list_for_each(pd, &plugin->devices, link) {
|
|
|
|
|
/* FIXME: sigh, we need a separate list of tools? */
|
|
|
|
|
pd->mode = libinput_tablet_tool_config_eraser_button_get_mode(tool);
|
|
|
|
|
uint32_t button =
|
|
|
|
|
libinput_tablet_tool_config_eraser_button_get_button(tool);
|
|
|
|
|
|
|
|
|
|
pd->button = evdev_usage_from_code(EV_KEY, button);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct libinput_plugin_interface interface = {
|
|
|
|
|
.run = NULL,
|
|
|
|
|
.destroy = plugin_destroy,
|
|
|
|
|
.device_new = NULL,
|
|
|
|
|
.device_ignored = NULL,
|
|
|
|
|
.device_added = eraser_button_plugin_device_added,
|
|
|
|
|
.device_removed = eraser_button_plugin_device_removed,
|
|
|
|
|
.evdev_frame = eraser_button_plugin_evdev_frame,
|
|
|
|
|
.tool_configured = eraser_button_plugin_tool_configured,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
libinput_tablet_plugin_eraser_button(struct libinput *libinput)
|
|
|
|
|
{
|
|
|
|
|
if (getenv("LIBINPUT_RUNNING_TEST_SUITE"))
|
2025-12-01 09:17:37 +10:00
|
|
|
ERASER_BUTTON_DELAY = usec_from_millis(150);
|
2025-06-02 15:11:08 +10:00
|
|
|
|
|
|
|
|
_destroy_(plugin_data) *plugin = zalloc(sizeof(*plugin));
|
|
|
|
|
list_init(&plugin->devices);
|
|
|
|
|
|
|
|
|
|
_unref_(libinput_plugin) *p =
|
|
|
|
|
libinput_plugin_new(libinput, "tablet-eraser-button", &interface, NULL);
|
|
|
|
|
plugin->plugin = p;
|
|
|
|
|
libinput_plugin_set_user_data(p, steal(&plugin));
|
|
|
|
|
}
|