lua: implement support for disabling of features

Because our lua hooks don't expose the device-added callback we need to
cache any calls to disable a feature and then apply it quietly when the
device is actually added. Any other call can go straight through.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1249>
This commit is contained in:
Peter Hutterer 2025-06-25 15:10:03 +10:00
parent 5b2a723a02
commit 347ff90871
5 changed files with 200 additions and 0 deletions

View file

@ -631,3 +631,22 @@ a device's capabilities before the device is processed by libinput.
function is identical to ``prepend_frame()``.
See ``prepend_frame()`` for more details.
.. function:: EvdevDevice:disable_feature(feature_name)
Disable the given libinput-internal feature for this device. This should be used
by plugins that replace that feature with a custom implementation for this device.
libinput may have multiple internal implementations for any given feature, disabling
it via this API disables any and all of those implementations, causing the feature to
no longer work at all. It is up to the plugin implementation to re-implement that
feature to match the user's expectation.
Version 1 of the plugin API supports the following features:
- ``button-debouncing``: see :ref:`button_debouncing`
- ``touchpad-hysteresis``: see :ref:`touchpad_jitter`
- ``touchpad-jump-detection``: see :ref:`touchpad_jumping_cursor`
- ``touchpad-palm-detection``: see :ref:`palm_detection`
- ``wheel-debouncing``: some high-resolution mouse wheel movements inside libinput
are delayed and/or modified

View file

@ -0,0 +1,16 @@
-- SPDX-License-Identifier: MIT
--
-- An example plugin to show how to disable an internal feature.
--
-- Typically one would expect the plugin to re-implement the feature
-- in a more device-specific manner but that's not done here.
-- UNCOMMENT THIS LINE TO ACTIVATE THE PLUGIN
-- libinput:register({1})
libinput:connect("new-evdev-device", function(device)
local udev_info = device:udev_properties()
if udev_info["ID_INPUT_TOUCHPAD"] then
log.info("Disabling palm detection on " .. device:name())
device:disable_feature("touchpad-palm-detection")
end
end)

View file

@ -5,6 +5,7 @@ plugins = [
'10-pointer-go-faster.lua',
'10-pointer-go-slower.lua',
'10-delay-motion.lua',
'10-disable-feature.lua',
]
fs = import('fs')

View file

@ -33,6 +33,7 @@
#include "util-strings.h"
#include "evdev-frame.h"
#include "libinput-feature.h"
#include "libinput-log.h"
#include "libinput-plugin-lua.h"
#include "libinput-plugin.h"
@ -95,6 +96,10 @@ typedef struct {
int device_removed_refid;
int frame_refid;
/* Caches any disable_feature calls during device_new */
bool was_added;
bitmask_t disabled_features;
} EvdevDevice;
struct libinput_lua_plugin {
@ -1011,6 +1016,44 @@ evdevdevice_append_frame(lua_State *L)
return 0;
}
static int
evdevdevice_disable_feature(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE " expected");
const char *feature = luaL_checkstring(L, 2);
/* No refid means we got removed, so quietly
* drop any call */
if (device->refid == LUA_NOREF)
return 0;
const struct {
const char *name;
enum libinput_feature feature;
} map[] = {
{ "button-debouncing", LIBINPUT_FEATURE_BUTTON_DEBOUNCING },
{ "wheel-debouncing", LIBINPUT_FEATURE_WHEEL_DEBOUNCING },
{ "touchpad-jump-detection", LIBINPUT_FEATURE_TOUCHPAD_JUMP_DETECTION },
{ "touchpad-palm-detection", LIBINPUT_FEATURE_TOUCHPAD_PALM_DETECTION },
{ "touchpad-hysteresis", LIBINPUT_FEATURE_TOUCHPAD_HYSTERESIS },
};
ARRAY_FOR_EACH(map, m) {
if (streq(feature, m->name)) {
struct libinput_lua_plugin *plugin =
lua_get_libinput_lua_plugin(L);
libinput_plugin_disable_device_feature(plugin->parent,
device->device,
m->feature);
return 0;
}
}
return luaL_error(L, "Unknown feature: %s", feature);
}
static int
evdevdevice_gc(lua_State *L)
{
@ -1041,6 +1084,7 @@ static const struct luaL_Reg evdevdevice_vtable[] = {
{ "inject_frame", evdevdevice_inject_frame },
{ "prepend_frame", evdevdevice_prepend_frame },
{ "append_frame", evdevdevice_append_frame },
{ "disable_feature", evdevdevice_disable_feature },
{ "__gc", evdevdevice_gc },
{ NULL, NULL }
};

View file

@ -1026,6 +1026,119 @@ START_TEST(lua_inject_frame)
}
END_TEST
enum when {
DEVICE_NEW,
FIRST_FRAME,
};
START_TEST(lua_disable_button_debounce)
{
enum when when = litest_test_param_get_i32(test_env->params, "when");
_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
_autofree_ char *lua = strdup_printf(
"libinput:register({1})\n"
"function frame_handler(device, _, _)\n"
" device:disable_feature(\"button-debouncing\")\n"
"end\n"
"function new_device(device)\n"
" %s device:disable_feature(\"button-debouncing\")\n"
" %s device:connect(\"evdev-frame\", frame_handler)\n"
"end\n"
"libinput:connect(\"new-evdev-device\", new_device)\n",
when == DEVICE_NEW ? "" : "--",
when == FIRST_FRAME ? "" : "--");
_autofree_ char *path = litest_write_plugin(tmpdir->path, lua);
etrace("%s", lua);
_litest_context_destroy_ struct libinput *li =
litest_create_context_with_plugindir(tmpdir->path);
if (libinput_log_get_priority(li) > LIBINPUT_LOG_PRIORITY_DEBUG)
libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);
litest_with_logcapture(li, capture) {
libinput_plugin_system_load_plugins(li, LIBINPUT_PLUGIN_FLAG_NONE);
litest_drain_events(li);
_destroy_(litest_device) *dev = litest_add_device(li, LITEST_MOUSE);
litest_drain_events(li);
litest_disable_middleemu(dev);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_timeout_debounce(li);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
litest_assert_strv_substring(capture->debugs,
"disabled button debouncing on request");
}
}
END_TEST
START_TEST(lua_disable_touchpad_jump_detection)
{
enum when when = litest_test_param_get_i32(test_env->params, "when");
_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
_autofree_ char *lua = strdup_printf(
"libinput:register({1})\n"
"function frame_handler(dev, f, ts)\n"
" dev:disable_feature(\"touchpad-jump-detection\")\n"
"end\n"
"function new_device(device)\n"
" %sdevice:disable_feature(\"touchpad-jump-detection\")\n"
" %sdevice:connect(\"evdev-frame\", frame_handler)\n"
"end\n"
"libinput:connect(\"new-evdev-device\", new_device)\n",
when == DEVICE_NEW ? "" : "-- ",
when == FIRST_FRAME ? "" : "-- ");
etrace("plugin data:\n%s", lua);
_autofree_ char *path = litest_write_plugin(tmpdir->path, lua);
_litest_context_destroy_ struct libinput *li =
litest_create_context_with_plugindir(tmpdir->path);
if (libinput_log_get_priority(li) > LIBINPUT_LOG_PRIORITY_DEBUG)
libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);
litest_with_logcapture(li, capture) {
libinput_plugin_system_load_plugins(li, LIBINPUT_PLUGIN_FLAG_NONE);
litest_drain_events(li);
_destroy_(litest_device) *dev =
litest_add_device(li, LITEST_SYNAPTICS_RMI4);
litest_drain_events(li);
litest_touch_down(dev, 0, 40, 50);
litest_touch_move(dev, 0, 80, 80);
litest_touch_move(dev, 0, 90, 90);
litest_touch_up(dev, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_assert_empty_queue(li);
litest_assert(!strv_find_substring(capture->infos,
"Touch jump detected and discarded",
NULL));
}
}
END_TEST
TEST_COLLECTION(lua)
{
/* clang-format off */
@ -1094,5 +1207,12 @@ TEST_COLLECTION(lua)
litest_with_parameters(params, "in_timer", 'b') {
litest_add_parametrized_no_device(lua_inject_frame, params);
}
litest_with_parameters(params, "when", 'I', 2,
litest_named_i32(DEVICE_NEW),
litest_named_i32(FIRST_FRAME)) {
litest_add_parametrized_no_device(lua_disable_button_debounce, params);
litest_add_parametrized_no_device(lua_disable_touchpad_jump_detection, params);
}
/* clang-format on */
}