diff --git a/doc/user/meson.build b/doc/user/meson.build
index bad2503a..53d8fb3b 100644
--- a/doc/user/meson.build
+++ b/doc/user/meson.build
@@ -123,6 +123,8 @@ src_rst = files(
'svg/tablet-area.svg',
'svg/tablet-axes.svg',
'svg/tablet-cintiq24hd-modes.svg',
+ 'svg/tablet-eraser-invert.svg',
+ 'svg/tablet-eraser-button.svg',
'svg/tablet-interfaces.svg',
'svg/tablet-intuos-modes.svg',
'svg/tablet-left-handed.svg',
diff --git a/doc/user/svg/tablet-eraser-button.svg b/doc/user/svg/tablet-eraser-button.svg
new file mode 100644
index 00000000..4999601f
--- /dev/null
+++ b/doc/user/svg/tablet-eraser-button.svg
@@ -0,0 +1,198 @@
+
+
+
+
diff --git a/doc/user/svg/tablet-eraser-invert.svg b/doc/user/svg/tablet-eraser-invert.svg
new file mode 100644
index 00000000..d81d90e6
--- /dev/null
+++ b/doc/user/svg/tablet-eraser-invert.svg
@@ -0,0 +1,179 @@
+
+
+
+
diff --git a/doc/user/tablet-support.rst b/doc/user/tablet-support.rst
index 5182a496..76a9e0f7 100644
--- a/doc/user/tablet-support.rst
+++ b/doc/user/tablet-support.rst
@@ -495,3 +495,50 @@ maximum provided in this call.
The size of the tablet reported by **libinput_device_get_size()** always reflects
the physical area, not the logical area.
+
+.. _tablet-eraser-button:
+
+------------------------------------------------------------------------------
+Tablet eraser buttons
+------------------------------------------------------------------------------
+
+Tablet tools come in a variety of forms but the most common one is a
+pen-like tool. Some of these pen-like tools have a virtual eraser at the
+tip of the tool - inverting the tool brings the eraser into proximity.
+
+.. figure:: tablet-eraser-invert.svg
+ :align: center
+
+ An pen-like tool used as pen and as eraser by inverting it
+
+Having an eraser as a separate tool is beneficial in many applications as the
+eraser tool can be assigned different functionality (colors, paint tools, etc.)
+that is easily available.
+
+However, a large proportion of tablet pens have an "eraser button". By
+pressing the button the pen switches to be an eraser tool.
+On the data level this is not done via a button event, instead the firmware
+will pretend the pen tool going out of proximity and the eraser coming
+into proximity immediately after - as if the tool was physically inverted.
+
+.. figure:: tablet-eraser-button.svg
+ :align: center
+
+ An pen-like tool used as pen and as eraser by pressing the eraser button
+
+Microsoft mandates this behavior (see
+`Windows Pen States `_
+for details) and thus the overwhelming majority of devices will have
+an eraser button that virtually inverts the pen.
+
+Enforcing an eraser button means that users have one button less on the
+stylus that they would have otherwise. For some users the eraser button
+is in an inconvenient location, others don't want an eraser button at all.
+
+libinput provides an eraser button configuration that allows disabling the
+eraser button and turning it into a normal button event. If the eraser button
+is disabled, pressing that button will generate a normal tablet tool button
+event.
+
+This configuration is only available on pens with an eraser button, not on
+with an invert-type eraser.
diff --git a/src/libinput-private.h b/src/libinput-private.h
index 200d1117..ab73163b 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -542,6 +542,17 @@ struct libinput_tablet_tool_config_pressure_range {
void (*get_default)(struct libinput_tablet_tool *tool, double *min, double *max);
};
+struct libinput_tablet_tool_config_eraser_button {
+ bitmask_t (*get_modes)(struct libinput_tablet_tool *tool);
+ enum libinput_config_status (*set_mode)(struct libinput_tablet_tool *tool, enum libinput_config_eraser_button_mode mode);
+ enum libinput_config_eraser_button_mode (*get_mode)(struct libinput_tablet_tool *tool);
+ enum libinput_config_eraser_button_mode (*get_default_mode)(struct libinput_tablet_tool *tool);
+
+ enum libinput_config_status (*set_button)(struct libinput_tablet_tool *tool, unsigned int button);
+ unsigned int (*get_button)(struct libinput_tablet_tool *tool);
+ unsigned int (*get_default_button)(struct libinput_tablet_tool *tool);
+};
+
struct libinput_tablet_tool_pressure_threshold {
unsigned int tablet_id;
@@ -581,6 +592,7 @@ struct libinput_tablet_tool {
struct {
struct libinput_tablet_tool_config_pressure_range pressure_range;
+ struct libinput_tablet_tool_config_eraser_button eraser_button;
} config;
};
diff --git a/src/libinput.c b/src/libinput.c
index c303185b..4d4620b4 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -5056,6 +5056,83 @@ libinput_tablet_tool_config_pressure_range_get_default_maximum(struct libinput_t
return max;
}
+LIBINPUT_EXPORT uint32_t
+libinput_tablet_tool_config_eraser_button_get_modes(struct libinput_tablet_tool *tool)
+{
+ if (!tool->config.eraser_button.get_modes)
+ return 0;
+
+ return bitmask_as_u32(tool->config.eraser_button.get_modes(tool));
+ }
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_tablet_tool_config_eraser_button_set_mode(struct libinput_tablet_tool *tool,
+ enum libinput_config_eraser_button_mode mode)
+{
+ uint32_t modes = libinput_tablet_tool_config_eraser_button_get_modes(tool);
+ if (mode && (modes & mode) == 0)
+ return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+
+ switch (mode) {
+ case LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT:
+ case LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON:
+ break;
+ default:
+ return LIBINPUT_CONFIG_STATUS_INVALID;
+ }
+
+ return tool->config.eraser_button.set_mode(tool, mode);
+}
+
+LIBINPUT_EXPORT enum libinput_config_eraser_button_mode
+libinput_tablet_tool_config_eraser_button_get_mode(struct libinput_tablet_tool *tool)
+{
+ if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
+ return LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
+
+ return tool->config.eraser_button.get_mode(tool);
+}
+
+LIBINPUT_EXPORT enum libinput_config_eraser_button_mode
+libinput_tablet_tool_config_eraser_button_get_default_mode(struct libinput_tablet_tool *tool)
+{
+ if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
+ return LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
+
+ return tool->config.eraser_button.get_mode(tool);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_tablet_tool_config_eraser_button_set_button(struct libinput_tablet_tool *tool,
+ unsigned int button)
+{
+ if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
+ return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+
+ if (!libinput_tablet_tool_has_button(tool, button))
+ return LIBINPUT_CONFIG_STATUS_INVALID;
+
+ return tool->config.eraser_button.set_button(tool, button);
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_tablet_tool_config_eraser_button_get_button(struct libinput_tablet_tool *tool)
+{
+ if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
+ return 0;
+
+ return tool->config.eraser_button.get_button(tool);
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_tablet_tool_config_eraser_button_get_default_button(struct libinput_tablet_tool *tool)
+{
+ if (!libinput_tablet_tool_config_eraser_button_get_modes(tool))
+ return 0;
+
+ return tool->config.eraser_button.get_button(tool);
+}
+
#if HAVE_LIBWACOM
WacomDeviceDatabase *
libinput_libwacom_ref(struct libinput *li)
diff --git a/src/libinput.h b/src/libinput.h
index 8d58b5af..09368f06 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -6973,6 +6973,203 @@ libinput_tablet_tool_config_pressure_range_get_default_minimum(struct libinput_t
double
libinput_tablet_tool_config_pressure_range_get_default_maximum(struct libinput_tablet_tool *tool);
+/**
+ * @ingroup config
+ */
+enum libinput_config_eraser_button_mode {
+ /**
+ * Use the default hardware behavior of the tool. libinput
+ * does not modify the behavior of the eraser button (if any).
+ */
+ LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT = 0,
+ /**
+ * The eraser button on the tool sends a button event
+ * instead. If this tool comes into proximity as an eraser,
+ * a button event on the pen is emulated instead.
+ *
+ * See libinput_tablet_tool_config_eraser_button_set_mode() for details.
+ */
+ LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON = (1 << 0),
+};
+
+/**
+ * @ingroup config
+ *
+ * Check if a tool can change the behavior of or to a firmware eraser button.
+ *
+ * A firmware eraser button is a button on the tool that, when pressed,
+ * virtually toggles the pen going out of proximity followed by the
+ * eraser tool coming in proximity. When released, the eraser goes
+ * out of proximity followed by the pen coming back into proximity.
+ *
+ * This is the default behavior for many contemporary pens who implement
+ * this in firmware. See also the [Windows Pen
+ * States](https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states).
+ *
+ * See the libinput documentation for more details.
+ *
+ * @param tool The libinput tool
+ * @return Non-zero if the device can be set to change to an eraser on button
+ * press.
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ *
+ * @since 1.29
+ */
+uint32_t
+libinput_tablet_tool_config_eraser_button_get_modes(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Change the eraser button behavior on a tool.
+ *
+ * If set to @ref LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON, pressing the
+ * firmware eraser button on the tool instead triggers an event
+ * of type @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON.
+ * This event's libinput_event_tablet_tool_get_button() returns the
+ * button set with
+ * libinput_tablet_tool_config_eraser_button_set_button()
+ * Releasing the firmware eraser button releases that button again.
+ *
+ * @param tool The libinput tool
+ * @param mode The eraser button mode to switch to
+ *
+ * @return A config status code
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ * @see libinput_tablet_tool_config_eraser_button_set_button
+ * @see libinput_tablet_tool_config_eraser_button_get_button
+ * @see libinput_tablet_tool_config_eraser_button_get_default_button
+ *
+ * @since 1.29
+ */
+enum libinput_config_status
+libinput_tablet_tool_config_eraser_button_set_mode(struct libinput_tablet_tool *tool,
+ enum libinput_config_eraser_button_mode mode);
+
+/**
+ * @ingroup config
+ *
+ * Get the mode for the eraser button.
+ *
+ * @param tool The libinput tool
+ *
+ * @return The eraser mode
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ * @see libinput_tablet_tool_config_eraser_button_set_button
+ * @see libinput_tablet_tool_config_eraser_button_get_button
+ * @see libinput_tablet_tool_config_eraser_button_get_default_button
+ *
+ * @since 1.29
+ */
+enum libinput_config_eraser_button_mode
+libinput_tablet_tool_config_eraser_button_get_mode(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Get the default mode for the eraser button.
+ *
+ * @param tool The libinput tool
+ *
+ * @return The eraser button, if any, or zero otherwise
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ * @see libinput_tablet_tool_config_eraser_button_set_button
+ * @see libinput_tablet_tool_config_eraser_button_get_button
+ * @see libinput_tablet_tool_config_eraser_button_get_default_button
+ *
+ * @since 1.29
+ */
+enum libinput_config_eraser_button_mode
+libinput_tablet_tool_config_eraser_button_get_default_mode(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Set a button to be the eraser button for this tool.
+ * This configuration has no effect unless the caller also sets
+ * the eraser mode to @ref LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON via
+ * libinput_tablet_tool_config_eraser_button_set_mode().
+ *
+ * @param tool The libinput tool
+ * @param button The button, usually one of BTN_STYLUS, BTN_STYLUS2 or
+ * BTN_STYLUS3
+ *
+ * @return A config status code
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ * @see libinput_tablet_tool_config_eraser_button_set_button
+ * @see libinput_tablet_tool_config_eraser_button_get_button
+ * @see libinput_tablet_tool_config_eraser_button_get_default_button
+ *
+ * @since 1.29
+ */
+enum libinput_config_status
+libinput_tablet_tool_config_eraser_button_set_button(struct libinput_tablet_tool *tool,
+ unsigned int button);
+
+/**
+ * @ingroup config
+ *
+ * Get the button configured to emulate an eraser for this tool.
+ *
+ * @param tool The libinput tool
+ *
+ * @return The eraser button, if any, or zero otherwise
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ * @see libinput_tablet_tool_config_eraser_button_set_button
+ * @see libinput_tablet_tool_config_eraser_button_get_button
+ * @see libinput_tablet_tool_config_eraser_button_get_default_button
+ *
+ * @since 1.29
+ */
+unsigned int
+libinput_tablet_tool_config_eraser_button_get_button(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Get the default button configured to emulate an eraser for this tool.
+ *
+ * @param tool The libinput tool
+ *
+ * @return The eraser button, if any, or zero otherwise
+ *
+ * @see libinput_tablet_tool_config_eraser_button_get_modes
+ * @see libinput_tablet_tool_config_eraser_button_set_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_mode
+ * @see libinput_tablet_tool_config_eraser_button_get_default_mode
+ * @see libinput_tablet_tool_config_eraser_button_set_button
+ * @see libinput_tablet_tool_config_eraser_button_get_button
+ * @see libinput_tablet_tool_config_eraser_button_get_default_button
+ *
+ * @since 1.29
+ */
+unsigned int
+libinput_tablet_tool_config_eraser_button_get_default_button(struct libinput_tablet_tool *tool);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/libinput.sym b/src/libinput.sym
index 68c8651f..74ebad83 100644
--- a/src/libinput.sym
+++ b/src/libinput.sym
@@ -364,3 +364,13 @@ LIBINPUT_1.28 {
libinput_device_config_3fg_drag_get_enabled;
libinput_device_config_3fg_drag_get_default_enabled;
} LIBINPUT_1.27;
+
+LIBINPUT_1.29 {
+ libinput_tablet_tool_config_eraser_button_get_button;
+ libinput_tablet_tool_config_eraser_button_get_default_button;
+ libinput_tablet_tool_config_eraser_button_get_default_mode;
+ libinput_tablet_tool_config_eraser_button_get_mode;
+ libinput_tablet_tool_config_eraser_button_get_modes;
+ libinput_tablet_tool_config_eraser_button_set_button;
+ libinput_tablet_tool_config_eraser_button_set_mode;
+} LIBINPUT_1.28;
diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man
index 19b77a61..b782cd0d 100644
--- a/tools/libinput-debug-events.man
+++ b/tools/libinput-debug-events.man
@@ -117,6 +117,14 @@ Sets the type of the custom acceleration function.
Defaults to fallback.
This only applies to the custom profile.
.TP 8
+.B \-\-set\-eraser\-button\-button=[BTN_STYLUS|BTN_STYLUS2|BTN_STYLUS3]
+Sets the eraser button button to the given tablet tool button. Only
+takes effect if combined with
+.B \-\-set\-eraser\-button\-mode=on\-button\-down.
+.TP 8
+.B \-\-set\-eraser\-button\-mode=[default|on-button-down]
+Sets the eraser button mode to the given mode.
+.TP 8
.B \-\-set\-pressure\-range=:
Set the tablet tool pressure range to min:max. min and max must be in range [0.0, 1.0].
.TP 8
diff --git a/tools/shared.c b/tools/shared.c
index b0d382cb..920e5024 100644
--- a/tools/shared.c
+++ b/tools/shared.c
@@ -436,6 +436,33 @@ tools_parse_option(int option,
return 1;
}
break;
+ case OPT_ERASER_BUTTON_MODE:
+ if (!optarg)
+ return 1;
+ if (streq(optarg, "default"))
+ options->eraser_button_mode = LIBINPUT_CONFIG_ERASER_BUTTON_DEFAULT;
+ else if (streq(optarg, "button"))
+ options->eraser_button_mode = LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON;
+ else {
+ fprintf(stderr, "Invalid --set-eraser-button-mode\n"
+ "Valid options: default|button\n");
+ return 1;
+ }
+ break;
+ case OPT_ERASER_BUTTON_BUTTON:
+ if (!optarg)
+ return 1;
+ if (streq(optarg, "BTN_STYLUS"))
+ options->eraser_button_button = BTN_STYLUS;
+ else if (streq(optarg, "BTN_STYLUS2"))
+ options->eraser_button_button = BTN_STYLUS2;
+ else if (streq(optarg, "BTN_STYLUS3"))
+ options->eraser_button_button = BTN_STYLUS3;
+ else {
+ fprintf(stderr, "Unsupported eraser button %s\n", optarg);
+ return 1;
+ }
+ break;
}
return 0;
}
diff --git a/tools/shared.h b/tools/shared.h
index 2cf14f95..64ce325b 100644
--- a/tools/shared.h
+++ b/tools/shared.h
@@ -76,6 +76,8 @@ enum configuration_options {
OPT_AREA,
OPT_3FG_DRAG,
OPT_SENDEVENTS,
+ OPT_ERASER_BUTTON_MODE,
+ OPT_ERASER_BUTTON_BUTTON,
};
#define CONFIGURATION_OPTIONS \
@@ -114,7 +116,9 @@ enum configuration_options {
{ "set-rotation-angle", required_argument, 0, OPT_ROTATION_ANGLE }, \
{ "set-pressure-range", required_argument, 0, OPT_PRESSURE_RANGE }, \
{ "set-calibration", required_argument, 0, OPT_CALIBRATION }, \
- { "set-area", required_argument, 0, OPT_AREA }
+ { "set-area", required_argument, 0, OPT_AREA }, \
+ { "set-eraser-button-mode", required_argument, 0, OPT_ERASER_BUTTON_MODE }, \
+ { "set-eraser-button-button", required_argument, 0, OPT_ERASER_BUTTON_BUTTON }
static inline void
tools_print_usage_option_list(struct option *opts)
@@ -170,6 +174,8 @@ struct tools_options {
struct libinput_config_area_rectangle area;
enum libinput_config_3fg_drag_state drag_3fg;
enum libinput_config_send_events_mode sendevents;
+ enum libinput_config_eraser_button_mode eraser_button_mode;
+ unsigned int eraser_button_button;
};
void tools_init_options(struct tools_options *options);