From 0a3ecbea247a3fb075070756b73426fa630b32da Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 4 Mar 2026 11:19:09 +1000 Subject: [PATCH 1/5] tablet: fix missing linebreak after an error message Part-of: --- src/evdev-tablet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 21c3c0ab..317e35ee 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -1318,7 +1318,7 @@ eraser_button_set_button(struct libinput_tablet_tool *tool, uint32_t button) break; default: log_bug_libinput(libinput_device_get_context(tool->last_device), - "Unsupported eraser button 0x%x", + "Unsupported eraser button 0x%x\n", button); return LIBINPUT_CONFIG_STATUS_INVALID; } From cdcb8273658d2bb8f4851cbf8657d6abcf97740d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 4 Mar 2026 11:25:45 +1000 Subject: [PATCH 2/5] Fix the evdev_usage_is_button check for the BTN_STYLUS group Part-of: --- src/evdev-frame.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evdev-frame.h b/src/evdev-frame.h index 15849dd3..e544aaa4 100644 --- a/src/evdev-frame.h +++ b/src/evdev-frame.h @@ -332,9 +332,9 @@ evdev_usage_is_button(evdev_usage_t usage) case EVDEV_BTN_TOOL_FINGER: case EVDEV_BTN_TOUCH: return false; - case BTN_STYLUS: - case BTN_STYLUS2: - case BTN_STYLUS3: + case EVDEV_BTN_STYLUS: + case EVDEV_BTN_STYLUS2: + case EVDEV_BTN_STYLUS3: return true; case EVDEV_BTN_MISC ... EVDEV_BTN_DIGI - 1: case EVDEV_BTN_WHEEL ... EVDEV_BTN_GEAR_UP: From 7726350420340b607aa474f89095ad13a660a5e7 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 4 Mar 2026 11:44:14 +1000 Subject: [PATCH 3/5] tablet: allow for the eraser button to be any button The previous restriction was BTN_STYLUS* or any button the pen advertises. This is too restrictive - it works well enough for any pen with less than 3 buttons (BTN_STYLUS3 is always available on those) but otherwise it cannot work. A 3-button pen may not advertise any other buttons, leaving us with the eraser button being a duplicate button. And events cannot be distinquished between eraser button or real button. Open up the configuration to effectively any BTN_ event code. Part-of: --- src/evdev-tablet.c | 12 --- src/libinput.c | 13 +-- src/libinput.h | 9 +- test/test-tablet.c | 199 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 203 insertions(+), 30 deletions(-) diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 317e35ee..c6e6b7b6 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -1311,18 +1311,6 @@ eraser_button_get_default_mode(struct libinput_tablet_tool *tool) static enum libinput_config_status eraser_button_set_button(struct libinput_tablet_tool *tool, uint32_t button) { - 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\n", - button); - return LIBINPUT_CONFIG_STATUS_INVALID; - } - tool->eraser_button.want_button = button; eraser_button_toggle(tool); diff --git a/src/libinput.c b/src/libinput.c index 768c8e3e..630e286f 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -5216,16 +5216,9 @@ libinput_tablet_tool_config_eraser_button_set_button(struct libinput_tablet_tool if (!libinput_tablet_tool_config_eraser_button_get_modes(tool)) return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; - switch (button) { - case BTN_STYLUS: - case BTN_STYLUS2: - case BTN_STYLUS3: - break; - default: - if (!libinput_tablet_tool_has_button(tool, button)) - return LIBINPUT_CONFIG_STATUS_INVALID; - break; - } + evdev_usage_t usage = evdev_usage_from_code(EV_KEY, button); + if (!evdev_usage_is_button(usage)) + return LIBINPUT_CONFIG_STATUS_INVALID; return tool->config.eraser_button.set_button(tool, button); } diff --git a/src/libinput.h b/src/libinput.h index ff206fba..70a87e1b 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -7371,14 +7371,9 @@ libinput_tablet_tool_config_eraser_button_get_default_mode( * the eraser mode to @ref LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON via * libinput_tablet_tool_config_eraser_button_set_mode(). * - * The buttons BTN_STYLUS, BTN_STYLUS2 and BTN_STYLUS2 are always - * allowed, even if libinput_tablet_tool_has_button() returns zero - * for the button. Otherwise, the button must be one that - * libinput_tablet_tool_has_button() returns a nonzero value for. - * * @param tool The libinput tool - * @param button The button, usually one of BTN_STYLUS, BTN_STYLUS2 or - * BTN_STYLUS3 + * @param button The button code. Must be a valid button (e.g. BTN_STYLUS) + * excluding fake buttons (e.g. BTN_TOOL_*) and keys (KEY_*) * * @return A config status code * diff --git a/test/test-tablet.c b/test/test-tablet.c index d8577f04..a6584b5e 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -7883,6 +7883,189 @@ START_TEST(tablet_eraser_button_disabled) } END_TEST +START_TEST(tablet_eraser_button_different_buttons) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 }, + }; + _unref_(libinput_tablet_tool) *pen = NULL; + + uint32_t eraser_button_mapping = + litest_test_param_get_i32(test_env->params, "eraser-button-mapping"); + + if (!libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_RUBBER)) + 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); + auto tev = litest_is_proximity_event( + ev, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + pen = libinput_event_tablet_tool_get_tool(tev); + litest_assert_enum_eq(libinput_tablet_tool_get_type(pen), + LIBINPUT_TABLET_TOOL_TYPE_PEN); + pen = libinput_tablet_tool_ref(pen); + } + + if (!libinput_tablet_tool_config_eraser_button_get_modes(pen)) + return LITEST_NOT_APPLICABLE; + + auto status = libinput_tablet_tool_config_eraser_button_set_mode( + pen, + LIBINPUT_CONFIG_ERASER_BUTTON_BUTTON); + litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + status = libinput_tablet_tool_config_eraser_button_set_button( + pen, + eraser_button_mapping); + 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); + } + + litest_mark_test_start(); + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + /* Make sure the button still works as-is */ + if (libinput_tablet_tool_has_button(pen, eraser_button_mapping)) { + litest_log_group("Testing button on pen") { + litest_event(dev, EV_KEY, eraser_button_mapping, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_dispatch(li); + litest_event(dev, EV_KEY, eraser_button_mapping, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_dispatch(li); + litest_assert_tablet_button_event( + li, + eraser_button_mapping, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_tablet_button_event( + li, + eraser_button_mapping, + LIBINPUT_BUTTON_STATE_RELEASED); + } + } + + litest_dispatch(li); + + litest_log_group("Prox out for the pen ...") { + litest_with_event_frame(dev) { + litest_tablet_set_tool_type(dev, BTN_TOOL_PEN); + 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); + 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") { + _destroy_(libinput_event) *ev = libinput_get_event(li); + auto 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), + eraser_button_mapping); + litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen); + } + + litest_log_group("Prox out for the eraser...") { + litest_with_event_frame(dev) { + 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); + 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") { + _destroy_(libinput_event) *ev = libinput_get_event(li); + auto tev = + litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + litest_assert_int_eq(libinput_event_tablet_tool_get_button(tev), + eraser_button_mapping); + litest_assert_ptr_eq(libinput_event_tablet_tool_get_tool(tev), pen); + } +} +END_TEST + +START_TEST(tablet_eraser_button_invalid_buttons) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 }, + }; + + uint32_t eraser_button_mapping = + litest_test_param_get_i32(test_env->params, "eraser-button-mapping"); + + if (!libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_RUBBER)) + return LITEST_NOT_APPLICABLE; + + litest_drain_events(li); + litest_tablet_proximity_in(dev, 20, 20, axes); + litest_dispatch(li); + + _destroy_(libinput_event) *ev = libinput_get_event(li); + auto tev = + litest_is_proximity_event(ev, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + auto tool = libinput_event_tablet_tool_get_tool(tev); + + if (!libinput_tablet_tool_config_eraser_button_get_modes(tool)) + return LITEST_NOT_APPLICABLE; + + auto status = libinput_tablet_tool_config_eraser_button_set_button( + tool, + eraser_button_mapping); + litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); +} +END_TEST + START_TEST(tablet_eraser_button_config_after_device_removal) { _litest_context_destroy_ struct libinput *li = litest_create_context(); @@ -8099,7 +8282,21 @@ TEST_COLLECTION(tablet_eraser) "with-motion-events", 'b') { litest_add_parametrized(tablet_eraser_button_disabled, LITEST_TABLET, LITEST_TOTEM|LITEST_FORCED_PROXOUT, params); } - + litest_with_parameters(params, + "eraser-button-mapping", 'I', 4, + litest_named_i32(BTN_STYLUS), + litest_named_i32(BTN_STYLUS3), + litest_named_i32(BTN_LEFT), + litest_named_i32(BTN_BACK)){ + litest_add_parametrized(tablet_eraser_button_different_buttons, LITEST_TABLET, LITEST_TOTEM|LITEST_FORCED_PROXOUT, params); + } + litest_with_parameters(params, + "eraser-button-mapping", 'I', 3, + litest_named_i32(BTN_TOUCH), + litest_named_i32(BTN_TOOL_FINGER), + litest_named_i32(KEY_A)) { + litest_add_parametrized(tablet_eraser_button_invalid_buttons, LITEST_TABLET, LITEST_TOTEM|LITEST_FORCED_PROXOUT, params); + } litest_add_no_device(tablet_eraser_button_config_after_device_removal); /* clang-format on */ } From b95840d36e0457ba300271aede4d47557c6b033e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Thu, 19 Feb 2026 17:10:11 +0100 Subject: [PATCH 4/5] quirks: hp omnibook ultra flip 14 improve rule set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some initial units had 14t-fh000 instead 14-fh0xxx in their product name but they are exactly the same model. We could match both in product version SBKPF or in board product name 8CDE. Match board as seems the way hp-wmi kernel module uses to match. While at it rewrite the entire huge comment to a little less huge comment but with more really interesting info. Signed-off-by: David SantamarĂ­a Rogado Part-of: --- quirks/50-system-hp.quirks | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/quirks/50-system-hp.quirks b/quirks/50-system-hp.quirks index 31502552..d607d679 100644 --- a/quirks/50-system-hp.quirks +++ b/quirks/50-system-hp.quirks @@ -24,27 +24,23 @@ MatchName=*Intel Virtual Button* MatchDMIModalias=dmi:*:svnHP:pnHPElitex21013G3:* ModelTabletModeSwitchUnreliable=1 -# The HP OmniBook Ultra Flip Laptop 14-fh0xxx's custom Intel ISH firmware -# filters out events from its builtin keyboard and touchpad when the hinge is -# opened little more than 180 degrees but toggles tablet-mode when it's opened -# little less than 180 degrees. -# Do not suspend the keyboard and touchpad to let use the device in flat -# position and also give consistency with some keyboard keys controlled by the -# Video Bus device (brightness down/up), the HP WMI hotkeys device (mic mute and -# hp hubs launcher key) and the backlight getting on and off by the firmware at -# the same time it enables disables the input. -# This one is for the keyboard and... -[HP OmniBook Ultra Flip Laptop 14-fh0xxx Keyboard] +# The HP OmniBook Ultra Flip 14 toggles tablet mode at a little less than 180 +# degrees and hardware switches off inputs at a little more than 180 degrees. +# We don't suspend ourselves to allow using them in flat position. It is +# possible that HP fixes this in the future (i.e. so tablet mode toggles +# after 180 degrees) so check before removing these rules. +# This rule is for the keyboard and... +[HP OmniBook Ultra Flip Laptop 14-fh0xxx and 14t-fh000 Keyboard] MatchBus=ps2 MatchUdevType=keyboard -MatchDMIModalias=dmi:*:svnHP:pnHPOmniBookUltraFlipLaptop14-fh0xxx:* +MatchDMIModalias=dmi:*:svnHP:*:rn8CDE:* ModelTabletModeNoSuspend=1 -# ...this one is for the touchpad. -[HP OmniBook Ultra Flip Laptop 14-fh0xxx Touchpad] +# ...this rule is for the touchpad. +[HP OmniBook Ultra Flip Laptop 14-fh0xxx and 14t-fh000 Touchpad] MatchBus=i2c MatchUdevType=touchpad -MatchDMIModalias=dmi:*:svnHP:pnHPOmniBookUltraFlipLaptop14-fh0xxx:* +MatchDMIModalias=dmi:*:svnHP:*:rn8CDE:* ModelTabletModeNoSuspend=1 [HP Pavilion dm4] From 526130fe8d9545b9f38d0caf9d7c906775c48a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Tue, 3 Mar 2026 01:12:12 +0100 Subject: [PATCH 5/5] quirks: hp omnibook ultra flip 14 touch pressure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add pressure attributes to make libinput measure touchpad-pressure behave right. Signed-off-by: David SantamarĂ­a Rogado Part-of: --- quirks/50-system-hp.quirks | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quirks/50-system-hp.quirks b/quirks/50-system-hp.quirks index d607d679..e4d1e4cc 100644 --- a/quirks/50-system-hp.quirks +++ b/quirks/50-system-hp.quirks @@ -42,6 +42,9 @@ MatchBus=i2c MatchUdevType=touchpad MatchDMIModalias=dmi:*:svnHP:*:rn8CDE:* ModelTabletModeNoSuspend=1 +AttrPressureRange=15:5 +AttrThumbPressureThreshold=80 +AttrPalmPressureThreshold=125 [HP Pavilion dm4] MatchName=*SynPS/2 Synaptics TouchPad