mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-28 16:40:07 +01:00
tablet: implement eraser button disabling
This adds a new (internal) plugin that is responsible for eraser button disabling. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net> Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1218>
This commit is contained in:
parent
42c0bff29b
commit
c48aff86d7
9 changed files with 1112 additions and 0 deletions
|
|
@ -376,6 +376,7 @@ src_libinput = src_libfilter + [
|
|||
'src/libinput.c',
|
||||
'src/libinput-plugin.c',
|
||||
'src/libinput-plugin-tablet-double-tool.c',
|
||||
'src/libinput-plugin-tablet-eraser-button.c',
|
||||
'src/libinput-plugin-tablet-forced-tool.c',
|
||||
'src/libinput-plugin-tablet-proximity-timer.c',
|
||||
'src/libinput-private-config.c',
|
||||
|
|
|
|||
|
|
@ -1264,6 +1264,161 @@ pressure_range_get_default(struct libinput_tablet_tool *tool, double *min, doubl
|
|||
*max = 1.0;
|
||||
}
|
||||
|
||||
static void
|
||||
tablet_tool_apply_eraser_button(struct tablet_dispatch *tablet,
|
||||
struct libinput_tablet_tool *tool)
|
||||
{
|
||||
if (bitmask_is_empty(tool->eraser_button.available_modes))
|
||||
return;
|
||||
|
||||
if (tool->eraser_button.mode == tool->eraser_button.want_mode &&
|
||||
tool->eraser_button.button == tool->eraser_button.want_button)
|
||||
return;
|
||||
|
||||
if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
|
||||
return;
|
||||
|
||||
tool->eraser_button.mode = tool->eraser_button.want_mode;
|
||||
tool->eraser_button.button = tool->eraser_button.want_button;
|
||||
|
||||
struct libinput *libinput = tablet_libinput_context(tablet);
|
||||
libinput_plugin_system_notify_tablet_tool_configured(&libinput->plugin_system,
|
||||
tool);
|
||||
}
|
||||
|
||||
static bitmask_t
|
||||
eraser_button_get_modes(struct libinput_tablet_tool *tool)
|
||||
{
|
||||
return tool->eraser_button.available_modes;
|
||||
}
|
||||
|
||||
static void
|
||||
eraser_button_toggle(struct libinput_tablet_tool *tool)
|
||||
{
|
||||
struct libinput_device *libinput_device = tool->last_device;
|
||||
struct evdev_device *device = evdev_device(libinput_device);
|
||||
struct tablet_dispatch *tablet = tablet_dispatch(device->dispatch);
|
||||
|
||||
tablet_tool_apply_eraser_button(tablet, tool);
|
||||
}
|
||||
|
||||
static enum libinput_config_status
|
||||
eraser_button_set_mode(struct libinput_tablet_tool *tool,
|
||||
enum libinput_config_eraser_button_mode mode)
|
||||
{
|
||||
if (mode != LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT &&
|
||||
!bitmask_all(tool->eraser_button.available_modes, bitmask_from_u32(mode)))
|
||||
return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
|
||||
|
||||
tool->eraser_button.want_mode = mode;
|
||||
|
||||
eraser_button_toggle(tool);
|
||||
|
||||
return LIBINPUT_CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static enum libinput_config_eraser_button_mode
|
||||
eraser_button_get_mode(struct libinput_tablet_tool *tool)
|
||||
{
|
||||
return tool->eraser_button.mode;
|
||||
}
|
||||
|
||||
static enum libinput_config_eraser_button_mode
|
||||
eraser_button_get_default_mode(struct libinput_tablet_tool *tool)
|
||||
{
|
||||
return LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
|
||||
}
|
||||
|
||||
static enum libinput_config_status
|
||||
eraser_button_set_button(struct libinput_tablet_tool *tool, unsigned int button)
|
||||
{
|
||||
if (!libinput_tablet_tool_has_button(tool, button))
|
||||
return LIBINPUT_CONFIG_STATUS_INVALID;
|
||||
|
||||
switch (button) {
|
||||
case BTN_STYLUS:
|
||||
case BTN_STYLUS2:
|
||||
case BTN_STYLUS3:
|
||||
break;
|
||||
default:
|
||||
log_bug_libinput(libinput_device_get_context(tool->last_device),
|
||||
"Unsupported eraser button 0x%x",
|
||||
button);
|
||||
return LIBINPUT_CONFIG_STATUS_INVALID;
|
||||
}
|
||||
|
||||
tool->eraser_button.want_button = button;
|
||||
|
||||
eraser_button_toggle(tool);
|
||||
|
||||
return LIBINPUT_CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
eraser_button_get_button(struct libinput_tablet_tool *tool)
|
||||
{
|
||||
return tool->eraser_button.button;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
eraser_button_get_default_button(struct libinput_tablet_tool *tool)
|
||||
{
|
||||
/* Which button we want is complicated. Other than Wacom no-one supports
|
||||
* tool ids so we cannot know if an individual tool supports any of the
|
||||
* BTN_STYLUS. e.g. any Huion tablet that supports the Huion PW600
|
||||
* will have BTN_STYLUS3 - regardless if that tool is actually present.
|
||||
* So we default to BTN_STYLUS3 because there's no placeholder BTN_STYLUS4.
|
||||
* in the kernel.
|
||||
*/
|
||||
if (!libinput_tablet_tool_has_button(tool, BTN_STYLUS))
|
||||
return BTN_STYLUS;
|
||||
if (!libinput_tablet_tool_has_button(tool, BTN_STYLUS2))
|
||||
return BTN_STYLUS2;
|
||||
|
||||
return BTN_STYLUS3;
|
||||
}
|
||||
|
||||
static void
|
||||
tool_init_eraser_button(struct tablet_dispatch *tablet,
|
||||
struct libinput_tablet_tool *tool,
|
||||
const WacomStylus *s)
|
||||
{
|
||||
/* We provide an eraser button config if:
|
||||
* - the tool is a pen
|
||||
* - we don't know about the stylus (that's a good indication the
|
||||
* stylus doesn't have tool ids which means it'll follow the windows
|
||||
* pen protocol)
|
||||
* - the tool does *not* have an eraser on the back end
|
||||
*
|
||||
* Because those are the only tools where the eraser button may
|
||||
* get changed to a real button (by udev-hid-bpf).
|
||||
*/
|
||||
if (libinput_tablet_tool_get_type(tool) != LIBINPUT_TABLET_TOOL_TYPE_PEN)
|
||||
return;
|
||||
|
||||
#if HAVE_LIBWACOM
|
||||
/* libwacom's API is a bit terrible here:
|
||||
* - has_eraser is true on styli that have a separate eraser, all
|
||||
* those are INVERT so we can exclude them
|
||||
* - get_eraser_type() returns something on actual eraser tools
|
||||
* but we don't have any separate erasers with buttons so
|
||||
* we only need to exclude INVERT
|
||||
*/
|
||||
if (s &&
|
||||
libwacom_stylus_has_eraser(s) &&
|
||||
libwacom_stylus_get_eraser_type(s) == WACOM_ERASER_INVERT) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
/* All other pens need eraser button handling because most of the time
|
||||
* we don't know if they have one (Huion, XP-Pen, ...) */
|
||||
bitmask_t available_modes = bitmask_from_masks(LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON);
|
||||
|
||||
tool->eraser_button.available_modes = available_modes;
|
||||
tool->eraser_button.want_button = eraser_button_get_default_button(tool);
|
||||
tool->eraser_button.button = tool->eraser_button.want_button;
|
||||
}
|
||||
|
||||
static struct libinput_tablet_tool *
|
||||
tablet_new_tool(struct tablet_dispatch *tablet,
|
||||
enum libinput_tablet_tool_type type,
|
||||
|
|
@ -1295,14 +1450,29 @@ tablet_new_tool(struct tablet_dispatch *tablet,
|
|||
.pressure.wanted_range.min = 0.0,
|
||||
.pressure.wanted_range.max = 1.0,
|
||||
|
||||
.eraser_button.available_modes = bitmask_new(),
|
||||
.eraser_button.mode = LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT,
|
||||
.eraser_button.want_mode = LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT,
|
||||
.eraser_button.button = BTN_STYLUS2,
|
||||
.eraser_button.want_button = BTN_STYLUS2,
|
||||
|
||||
.config.pressure_range.is_available = pressure_range_is_available,
|
||||
.config.pressure_range.set = pressure_range_set,
|
||||
.config.pressure_range.get = pressure_range_get,
|
||||
.config.pressure_range.get_default = pressure_range_get_default,
|
||||
|
||||
.config.eraser_button.get_modes = eraser_button_get_modes,
|
||||
.config.eraser_button.set_mode = eraser_button_set_mode,
|
||||
.config.eraser_button.get_mode = eraser_button_get_mode,
|
||||
.config.eraser_button.get_default_mode = eraser_button_get_default_mode,
|
||||
.config.eraser_button.set_button = eraser_button_set_button,
|
||||
.config.eraser_button.get_button = eraser_button_get_button,
|
||||
.config.eraser_button.get_default_button = eraser_button_get_default_button,
|
||||
};
|
||||
|
||||
tool_init_pressure_thresholds(tablet, tool, &tool->pressure.threshold);
|
||||
tool_set_bits(tablet, tool, s);
|
||||
tool_init_eraser_button(tablet, tool, s);
|
||||
|
||||
return tool;
|
||||
}
|
||||
|
|
@ -1367,6 +1537,8 @@ tablet_get_tool(struct tablet_dispatch *tablet,
|
|||
if (last)
|
||||
libinput_device_unref(last);
|
||||
|
||||
tool->last_tablet_id = tablet->tablet_id;
|
||||
|
||||
return tool;
|
||||
}
|
||||
|
||||
|
|
@ -2147,6 +2319,7 @@ tablet_flush(struct tablet_dispatch *tablet,
|
|||
tablet_apply_rotation(device);
|
||||
tablet_change_area(device);
|
||||
tablet_history_reset(tablet);
|
||||
tablet_tool_apply_eraser_button(tablet, tool);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
604
src/libinput-plugin-tablet-eraser-button.c
Normal file
604
src/libinput-plugin-tablet-eraser-button.c
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* 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-util.h"
|
||||
#include "libinput-plugin.h"
|
||||
#include "libinput-plugin-tablet-eraser-button.h"
|
||||
|
||||
static int ERASER_BUTTON_DELAY = 30 * 1000; /* µs */
|
||||
|
||||
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
|
||||
eraser_button_set_timer(struct plugin_device *device, uint64_t time)
|
||||
{
|
||||
libinput_plugin_timer_set(device->timer, time + ERASER_BUTTON_DELAY);
|
||||
}
|
||||
|
||||
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)) {
|
||||
struct evdev_event event = {
|
||||
.usage = evdev_usage_from(EVDEV_BTN_TOOL_PEN),
|
||||
.value = (filter & PEN_IN_PROX) ? 1 : 0,
|
||||
};
|
||||
evdev_frame_append(frame_out, &event, 1);
|
||||
}
|
||||
if (filter & (ERASER_IN_PROX|ERASER_OUT_OF_PROX)) {
|
||||
struct evdev_event event = {
|
||||
.usage = evdev_usage_from(EVDEV_BTN_TOOL_RUBBER),
|
||||
.value = (filter & ERASER_IN_PROX) ? 1 : 0,
|
||||
};
|
||||
evdev_frame_append(frame_out, &event, 1);
|
||||
}
|
||||
if (filter & (BUTTON_UP|BUTTON_DOWN)) {
|
||||
assert (button != NULL);
|
||||
struct evdev_event event = {
|
||||
.usage = *button,
|
||||
.value = (filter & BUTTON_DOWN) ? 1 : 0,
|
||||
};
|
||||
evdev_frame_append(frame_out, &event, 1);
|
||||
}
|
||||
|
||||
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,
|
||||
uint64_t time)
|
||||
{
|
||||
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,
|
||||
uint64_t time)
|
||||
{
|
||||
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,
|
||||
uint64_t time)
|
||||
{
|
||||
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,
|
||||
uint64_t time)
|
||||
{
|
||||
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,
|
||||
uint64_t time)
|
||||
{
|
||||
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,
|
||||
uint64_t time)
|
||||
{
|
||||
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;
|
||||
uint64_t time = evdev_frame_get_time(frame);
|
||||
|
||||
list_for_each(pd, &plugin->devices, link) {
|
||||
if (pd->device == device) {
|
||||
eraser_button_handle_frame(pd, frame, time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
eraser_button_timer_func(struct libinput_plugin *plugin, uint64_t now, void *d)
|
||||
{
|
||||
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,
|
||||
struct libinput_device *device)
|
||||
{
|
||||
if (!libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL))
|
||||
return;
|
||||
|
||||
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,
|
||||
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 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"))
|
||||
ERASER_BUTTON_DELAY = ms2us(150);
|
||||
|
||||
_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));
|
||||
}
|
||||
30
src/libinput-plugin-tablet-eraser-button.h
Normal file
30
src/libinput-plugin-tablet-eraser-button.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 "libinput.h"
|
||||
#include "libinput-plugin.h"
|
||||
|
||||
void
|
||||
libinput_tablet_plugin_eraser_button(struct libinput *libinput);
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "libinput-util.h"
|
||||
#include "libinput-private.h"
|
||||
#include "libinput-plugin-tablet-double-tool.h"
|
||||
#include "libinput-plugin-tablet-eraser-button.h"
|
||||
#include "libinput-plugin-tablet-forced-tool.h"
|
||||
#include "libinput-plugin-tablet-proximity-timer.h"
|
||||
|
||||
|
|
@ -367,6 +368,7 @@ libinput_plugin_system_load_internal_plugins(struct libinput *libinput,
|
|||
libinput_tablet_plugin_forced_tool(libinput);
|
||||
libinput_tablet_plugin_double_tool(libinput);
|
||||
libinput_tablet_plugin_proximity_timer(libinput);
|
||||
libinput_tablet_plugin_eraser_button(libinput);
|
||||
|
||||
/* Our own event dispatch is implemented as mini-plugin,
|
||||
* guarantee this one to always be last (and after any
|
||||
|
|
|
|||
|
|
@ -590,10 +590,20 @@ struct libinput_tablet_tool {
|
|||
struct libinput_tablet_tool_pressure_threshold threshold;
|
||||
} pressure;
|
||||
|
||||
struct {
|
||||
bitmask_t available_modes;
|
||||
enum libinput_config_eraser_button_mode mode;
|
||||
enum libinput_config_eraser_button_mode want_mode;
|
||||
unsigned int button;
|
||||
unsigned int want_button;
|
||||
} eraser_button;
|
||||
|
||||
struct {
|
||||
struct libinput_tablet_tool_config_pressure_range pressure_range;
|
||||
struct libinput_tablet_tool_config_eraser_button eraser_button;
|
||||
} config;
|
||||
|
||||
unsigned int last_tablet_id; /* tablet_dispatch->tablet_id */
|
||||
};
|
||||
|
||||
struct libinput_tablet_pad_mode_group {
|
||||
|
|
|
|||
|
|
@ -1349,6 +1349,7 @@ _litest_timeout(struct libinput *li, const char *func, int lineno, int millis);
|
|||
#define litest_timeout_touch_arbitration(li_) litest_timeout(li_, 100)
|
||||
#define litest_timeout_hysteresis(li_) litest_timeout(li_, 90)
|
||||
#define litest_timeout_3fg_drag(li_) litest_timeout(li_, 800)
|
||||
#define litest_timeout_eraser_button(li_) litest_timeout(li_, 50)
|
||||
|
||||
struct litest_logcapture {
|
||||
char **errors;
|
||||
|
|
|
|||
|
|
@ -7202,6 +7202,282 @@ START_TEST(tablet_smoothing)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(tablet_eraser_button_disabled)
|
||||
{
|
||||
struct litest_device *dev = litest_current_device();
|
||||
struct libinput *li = dev->libinput;
|
||||
struct axis_replacement axes[] = {
|
||||
{ ABS_DISTANCE, 10 },
|
||||
{ ABS_PRESSURE, 0 },
|
||||
{ -1, -1 }
|
||||
};
|
||||
struct axis_replacement tip_down_axes[] = {
|
||||
{ ABS_DISTANCE, 0 },
|
||||
{ ABS_PRESSURE, 30 },
|
||||
{ -1, -1 }
|
||||
};
|
||||
struct libinput_event *ev;
|
||||
struct libinput_event_tablet_tool *tev;
|
||||
struct libinput_tablet_tool *tool, *pen;
|
||||
bool with_tip_down = litest_test_param_get_bool(test_env->params, "with-tip-down");
|
||||
bool configure_while_out_of_prox = litest_test_param_get_bool(test_env->params, "configure-while-out-of-prox");
|
||||
bool down_when_in_prox = litest_test_param_get_bool(test_env->params, "down-when-in-prox");
|
||||
bool down_when_out_of_prox = litest_test_param_get_bool(test_env->params, "down-when-out-of-prox");
|
||||
bool with_motion_events = litest_test_param_get_bool(test_env->params, "with-motion-events");
|
||||
|
||||
/* Device forces BTN_TOOL_PEN on tip */
|
||||
if (with_tip_down && dev->which == LITEST_WACOM_ISDV4_524C_PEN)
|
||||
return LITEST_NOT_APPLICABLE;
|
||||
|
||||
litest_log_group("Prox in/out to disable proximity timer") {
|
||||
litest_tablet_proximity_in(dev, 25, 25, axes);
|
||||
litest_tablet_proximity_out(dev);
|
||||
litest_timeout_tablet_proxout(li);
|
||||
|
||||
litest_checkpoint("Eraser prox in/out to force-disable config on broken tablets");
|
||||
litest_tablet_set_tool_type(dev, BTN_TOOL_RUBBER);
|
||||
litest_tablet_proximity_in(dev, 25, 25, axes);
|
||||
litest_tablet_proximity_out(dev);
|
||||
litest_timeout_tablet_proxout(li);
|
||||
}
|
||||
|
||||
litest_drain_events(li);
|
||||
|
||||
litest_log_group("Proximity in for pen") {
|
||||
litest_tablet_set_tool_type(dev, BTN_TOOL_PEN);
|
||||
litest_tablet_proximity_in(dev, 20, 20, axes);
|
||||
litest_dispatch(li);
|
||||
_destroy_(libinput_event) *ev = libinput_get_event(li);
|
||||
tev = litest_is_proximity_event(ev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
|
||||
tool = libinput_event_tablet_tool_get_tool(tev);
|
||||
litest_assert_enum_eq(libinput_tablet_tool_get_type(tool), LIBINPUT_TABLET_TOOL_TYPE_PEN);
|
||||
pen = libinput_tablet_tool_ref(tool);
|
||||
}
|
||||
|
||||
if (!libinput_tablet_tool_config_eraser_button_get_modes(tool)) {
|
||||
libinput_tablet_tool_unref(tool);
|
||||
return LITEST_NOT_APPLICABLE;
|
||||
}
|
||||
|
||||
unsigned int expected_button = BTN_STYLUS3;
|
||||
if (!libinput_tablet_tool_has_button(pen, BTN_STYLUS))
|
||||
expected_button = BTN_STYLUS;
|
||||
else if (!libinput_tablet_tool_has_button(pen, BTN_STYLUS2))
|
||||
expected_button = BTN_STYLUS2;
|
||||
else if (!libinput_tablet_tool_has_button(pen, BTN_STYLUS3))
|
||||
expected_button = BTN_STYLUS3;
|
||||
litest_checkpoint("expected button from now on: %s (%d)",
|
||||
libevdev_event_code_get_name(EV_KEY, expected_button),
|
||||
expected_button);
|
||||
|
||||
if (!configure_while_out_of_prox) {
|
||||
litest_log_group("Configuring eraser button while in of proximity") {
|
||||
auto status = libinput_tablet_tool_config_eraser_button_set_mode(tool,
|
||||
LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON);
|
||||
if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
|
||||
return LITEST_NOT_APPLICABLE;
|
||||
litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
litest_log_group("Prox out to apply changed settings") {
|
||||
litest_tablet_proximity_out(dev);
|
||||
litest_timeout_tablet_proxout(li);
|
||||
litest_drain_events(li);
|
||||
}
|
||||
|
||||
if (configure_while_out_of_prox) {
|
||||
litest_log_group("Configuring eraser button while out of proximity") {
|
||||
auto status = libinput_tablet_tool_config_eraser_button_set_mode(tool,
|
||||
LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON);
|
||||
if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
|
||||
return LITEST_NOT_APPLICABLE;
|
||||
litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
litest_mark_test_start();
|
||||
|
||||
if (down_when_in_prox) {
|
||||
litest_log_group("Prox in with eraser, expecting eraser button event") {
|
||||
litest_tablet_set_tool_type(dev, BTN_TOOL_RUBBER);
|
||||
litest_tablet_proximity_in(dev, 10, 10, axes);
|
||||
litest_wait_for_event(li);
|
||||
ev = libinput_get_event(li);
|
||||
tev = litest_is_proximity_event(ev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
|
||||
ev = libinput_get_event(li);
|
||||
tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_BUTTON);
|
||||
litest_assert_enum_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_PRESSED);
|
||||
litest_assert_int_eq(libinput_event_tablet_tool_get_button(tev), expected_button);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
}
|
||||
} else {
|
||||
litest_tablet_proximity_in(dev, 10, 10, axes);
|
||||
}
|
||||
|
||||
litest_drain_events(li);
|
||||
|
||||
if (with_motion_events) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
litest_tablet_motion(dev, 11 + i, 11 + i, axes);
|
||||
litest_dispatch(li);
|
||||
}
|
||||
ev = libinput_get_event(li);
|
||||
do {
|
||||
tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
ev = libinput_get_event(li);
|
||||
} while (ev);
|
||||
}
|
||||
|
||||
if (with_tip_down) {
|
||||
litest_tablet_tip_down(dev, 11, 11, tip_down_axes);
|
||||
litest_dispatch(li);
|
||||
litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_DOWN);
|
||||
|
||||
if (with_motion_events) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
litest_tablet_motion(dev, 11 + i, 11 + i, tip_down_axes);
|
||||
litest_dispatch(li);
|
||||
}
|
||||
ev = libinput_get_event(li);
|
||||
do {
|
||||
tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
ev = libinput_get_event(li);
|
||||
} while (ev);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure the button still works as-is */
|
||||
if (libinput_tablet_tool_has_button(pen, BTN_STYLUS)) {
|
||||
litest_log_group("Testing BTN_STYLUS") {
|
||||
litest_event(dev, EV_KEY, BTN_STYLUS, 1);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
litest_dispatch(li);
|
||||
litest_event(dev, EV_KEY, BTN_STYLUS, 0);
|
||||
litest_event(dev, EV_SYN, SYN_REPORT, 0);
|
||||
litest_dispatch(li);
|
||||
litest_assert_tablet_button_event(li, BTN_STYLUS, LIBINPUT_BUTTON_STATE_PRESSED);
|
||||
litest_assert_tablet_button_event(li, BTN_STYLUS, LIBINPUT_BUTTON_STATE_RELEASED);
|
||||
}
|
||||
}
|
||||
|
||||
litest_dispatch(li);
|
||||
|
||||
if (!down_when_in_prox) {
|
||||
litest_log_group("Prox out for the pen ...") {
|
||||
litest_with_event_frame(dev) {
|
||||
litest_tablet_set_tool_type(dev, BTN_TOOL_PEN);
|
||||
if (with_tip_down)
|
||||
litest_tablet_tip_up(dev, 11, 11, axes);
|
||||
litest_tablet_proximity_out(dev);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
}
|
||||
|
||||
litest_log_group("...and prox in for the eraser") {
|
||||
litest_with_event_frame(dev) {
|
||||
litest_tablet_set_tool_type(dev, BTN_TOOL_RUBBER);
|
||||
if (with_tip_down) {
|
||||
litest_tablet_tip_down(dev, 12, 12, tip_down_axes);
|
||||
litest_tablet_proximity_in(dev, 12, 12, tip_down_axes);
|
||||
} else {
|
||||
litest_tablet_proximity_in(dev, 12, 12, axes);
|
||||
}
|
||||
}
|
||||
litest_dispatch(li);
|
||||
}
|
||||
|
||||
litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
|
||||
|
||||
litest_log_group("Expect button event") {
|
||||
ev = libinput_get_event(li);
|
||||
tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_BUTTON);
|
||||
litest_assert_enum_eq(libinput_event_tablet_tool_get_button_state(tev), LIBINPUT_BUTTON_STATE_PRESSED);
|
||||
litest_assert_int_eq(libinput_event_tablet_tool_get_button(tev), expected_button);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
}
|
||||
}
|
||||
|
||||
if (!down_when_out_of_prox) {
|
||||
litest_log_group("Prox out for the eraser...") {
|
||||
litest_with_event_frame(dev) {
|
||||
if (with_tip_down)
|
||||
litest_tablet_tip_up(dev, 11, 11, axes);
|
||||
litest_tablet_proximity_out(dev);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
}
|
||||
|
||||
litest_log_group("...and prox in for the pen") {
|
||||
litest_with_event_frame(dev) {
|
||||
litest_tablet_set_tool_type(dev, BTN_TOOL_PEN);
|
||||
if (with_tip_down) {
|
||||
litest_tablet_tip_down(dev, 12, 12, tip_down_axes);
|
||||
litest_tablet_proximity_in(dev, 12, 12, tip_down_axes);
|
||||
} else {
|
||||
litest_tablet_proximity_in(dev, 12, 12, axes);
|
||||
}
|
||||
}
|
||||
litest_dispatch(li);
|
||||
}
|
||||
|
||||
litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
|
||||
|
||||
litest_log_group("Expect button event") {
|
||||
ev = libinput_get_event(li);
|
||||
tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_BUTTON);
|
||||
litest_assert_int_eq(libinput_event_tablet_tool_get_button(tev), expected_button);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
}
|
||||
}
|
||||
|
||||
litest_log_group("Real prox out for the %s", down_when_out_of_prox ? "eraser" : "pen") {
|
||||
litest_with_event_frame(dev) {
|
||||
if (with_tip_down)
|
||||
litest_tablet_tip_up(dev, 12, 12, axes);
|
||||
litest_tablet_proximity_out(dev);
|
||||
}
|
||||
litest_dispatch(li);
|
||||
litest_timeout_tablet_proxout(li);
|
||||
}
|
||||
|
||||
litest_drain_events_of_type(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
|
||||
|
||||
if (down_when_out_of_prox) {
|
||||
litest_log_group("Expect button release") {
|
||||
ev = libinput_get_event(li);
|
||||
tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_BUTTON);
|
||||
litest_assert_int_eq(libinput_event_tablet_tool_get_button(tev), expected_button);
|
||||
litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen);
|
||||
libinput_event_destroy(ev);
|
||||
}
|
||||
}
|
||||
|
||||
if (with_tip_down)
|
||||
litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_UP);
|
||||
|
||||
litest_log_group("Expect final prox out for the pen") {
|
||||
ev = libinput_get_event(li);
|
||||
tev = litest_is_proximity_event(ev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
|
||||
tool = libinput_event_tablet_tool_get_tool(tev);
|
||||
litest_assert_ptr_eq(pen, tool);
|
||||
libinput_event_destroy(ev);
|
||||
}
|
||||
|
||||
libinput_tablet_tool_unref(pen);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
TEST_COLLECTION(tablet)
|
||||
{
|
||||
litest_add(tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
|
||||
|
|
@ -7355,6 +7631,15 @@ TEST_COLLECTION(tablet)
|
|||
}
|
||||
|
||||
litest_add_for_device(tablet_smoothing, LITEST_WACOM_HID4800_PEN);
|
||||
|
||||
litest_with_parameters(params,
|
||||
"with-tip-down", 'b',
|
||||
"configure-while-out-of-prox", 'b',
|
||||
"down-when-in-prox", 'b',
|
||||
"down-when-out-of-prox", 'b',
|
||||
"with-motion-events", 'b') {
|
||||
litest_add_parametrized(tablet_eraser_button_disabled, LITEST_TABLET, LITEST_TOTEM|LITEST_FORCED_PROXOUT, params);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_COLLECTION(tablet_left_handed)
|
||||
|
|
|
|||
|
|
@ -139,6 +139,9 @@ tools_init_options(struct tools_options *options)
|
|||
options->area.x2 = 1.0;
|
||||
options->area.y2 = 1.0;
|
||||
options->sendevents = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
|
||||
options->eraser_button_mode = LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
|
||||
options->eraser_button_button = BTN_STYLUS;
|
||||
options->eraser_button_button = 0;
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -680,6 +683,9 @@ tools_tablet_tool_apply_config(struct libinput_tablet_tool *tool,
|
|||
libinput_tablet_tool_config_pressure_range_set(tool,
|
||||
options->pressure_range[0],
|
||||
options->pressure_range[1]);
|
||||
if (options->eraser_button_button)
|
||||
libinput_tablet_tool_config_eraser_button_set_button(tool, options->eraser_button_button);
|
||||
libinput_tablet_tool_config_eraser_button_set_mode(tool, options->eraser_button_mode);
|
||||
}
|
||||
|
||||
static char*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue