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 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + Eraser + Pen + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + Eraser + Pen + + 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);