lua: remove the inject_frame API

Injecting frame was the first implementation of adding event frames but
it has since effectively been replaced by append/prepend_frame which are
more predictable and easier to support.

In the Lua API injecting frames was only possible within the timer and
the only real use-case for this is to inject events that are then also
seen by other plugins. But that can be achieved by simply ordering the
plugin before the other plugins and using the append/prepend approach.

Until we have a real use-case for injecting events let's remove the API
so we don't lock ourselves into an API that may not do what it needs to
but needs to be supported for a long time.

Closes: #1210
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1351>
This commit is contained in:
Peter Hutterer 2025-10-27 15:39:18 +10:00 committed by Marge Bot
parent 2784973b4d
commit 6b9dbc2a25
3 changed files with 0 additions and 240 deletions

View file

@ -604,24 +604,6 @@ methods will be noops.
Disconnect the existing callback (if any) for the given event name. See
``EvdevDevice:connect()`` for a list of supported names.
.. function:: EvdevDevice:inject_frame(frame)
.. warning:: This function is only available from inside a timer callback.
Inject an :ref:`evdev frame <plugins_api_evdev_frame>` into the event stream
for this device. This emulates that same event frame being sent by the kernel
immediately with the current time.
Assuming three plugins P1, P2 and P3, if P2 injects a frame the frame is
seen by P1, P2 and P3.
This is rarely the right API to use. Injecting frames at the lowest level
may make other plugins behave unexpectedly. Use ``prepend_frame`` or
``append_frame`` instead.
.. warning:: The injected frame will be seen by all plugins, including the
injecting frame. Ensure a guard is in place to prevent recursion.
.. function:: EvdevDevice:prepend_frame(frame)
Prepend an :ref:`evdev frame <plugins_api_evdev_frame>` for this device

View file

@ -116,7 +116,6 @@ struct libinput_lua_plugin {
struct libinput_plugin_timer *timer;
bool in_timer_func;
struct list timer_injected_events;
};
static struct libinput_lua_plugin *
@ -566,12 +565,6 @@ libinputplugin_gc(lua_State *L)
return 0;
}
struct timer_injected_event {
struct list link;
struct evdev_frame *frame;
int device_refid;
};
static void
plugin_timer_func(struct libinput_plugin *libinput_plugin, uint64_t now, void *data)
{
@ -581,28 +574,7 @@ plugin_timer_func(struct libinput_plugin *libinput_plugin, uint64_t now, void *d
lua_rawgeti(L, LUA_REGISTRYINDEX, plugin->timer_expired_refid);
lua_pushinteger(L, now);
/* To allow for injecting events */
plugin->in_timer_func = true;
libinput_lua_pcall(plugin, 1, 0);
plugin->in_timer_func = false;
struct timer_injected_event *injected_event;
list_for_each_safe(injected_event, &plugin->timer_injected_events, link) {
EvdevDevice *device;
list_for_each(device, &plugin->evdev_devices, link) {
if (device->refid == injected_event->device_refid) {
libinput_plugin_inject_evdev_frame(
plugin->parent,
device->device,
injected_event->frame);
break;
}
}
list_remove(&injected_event->link);
evdev_frame_unref(injected_event->frame);
free(injected_event);
}
}
static int
@ -951,36 +923,6 @@ evdevdevice_frame(lua_State *L, struct libinput_lua_plugin *plugin, EvdevDevice
return frame;
}
static int
evdevdevice_inject_frame(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE " expected");
luaL_checktype(L, 2, LUA_TTABLE);
/* No refid means we got removed, so quietly
* drop any disconnect call */
if (device->refid == LUA_NOREF)
return 0;
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
if (!plugin->in_timer_func) {
return luaL_error(L, "Injecting events only possible in a timer func");
}
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin, device);
/* Lua is unhappy if we inject an event which calls into our lua state
* immediately so we need to queue this for later when we're out of the timer
* func */
struct timer_injected_event *event = zalloc(sizeof(*event));
event->device_refid = device->refid;
event->frame = steal(&frame);
list_insert(&plugin->timer_injected_events, &event->link);
return 0;
}
static int
evdevdevice_prepend_frame(lua_State *L)
{
@ -1089,7 +1031,6 @@ static const struct luaL_Reg evdevdevice_vtable[] = {
{ "set_absinfo", evdevdevice_set_absinfo },
{ "connect", evdevdevice_connect },
{ "disconnect", evdevdevice_disconnect },
{ "inject_frame", evdevdevice_inject_frame },
{ "prepend_frame", evdevdevice_prepend_frame },
{ "append_frame", evdevdevice_append_frame },
{ "disable_feature", evdevdevice_disable_feature },
@ -1355,7 +1296,6 @@ libinput_lua_plugin_new_from_path(struct libinput *libinput, const char *path)
plugin->device_new_refid = LUA_NOREF;
plugin->timer_expired_refid = LUA_NOREF;
list_init(&plugin->evdev_devices);
list_init(&plugin->timer_injected_events);
_cleanup_(lua_closep) lua_State *L =
libinput_lua_plugin_init_lua(libinput, plugin);

View file

@ -929,164 +929,6 @@ START_TEST(lua_ignore_unsupported_codes)
}
END_TEST
START_TEST(lua_inject_frame)
{
bool in_timer = litest_test_param_get_bool(test_env->params, "in_timer");
_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
/* Plugin 1 swaps left to right */
const char *lua1 =
"libinput:register({1})\n"
"function frame_handler(device, frame, timestamp)\n"
" for _, e in ipairs(frame) do\n"
" if e.usage == evdev.BTN_LEFT then\n"
" e.usage = evdev.BTN_RIGHT\n"
" end\n"
" end\n"
" return frame\n"
"end\n"
"libinput:connect(\"new-evdev-device\", function(device)\n"
" device:connect(\"evdev-frame\", frame_handler)\n"
"end)\n";
/* Plugin 2 injects a left button if a middle button is pressed */
_autofree_ char *lua2 = strdup_printf(
"libinput:register({1})\n"
"mydev = nil\n"
"function frame_handler(device, frame, timestamp)\n"
" for _, e in ipairs(frame) do\n"
" if e.usage == evdev.BTN_SIDE then\n"
" e.usage = evdev.BTN_EXTRA\n"
" end\n"
" if e.usage == evdev.BTN_MIDDLE then\n"
" log.debug(\"Injecting frame BTN_LEFT value \" .. e.value)\n"
" %sdevice:inject_frame({{ usage = evdev.BTN_LEFT, value = e.value }})\n"
" %slibinput:timer_set_relative(200000)\n" /* commented out
if !in_timer */
" end\n"
" end\n"
" return frame\n"
"end\n"
"function timer_expired(t)\n"
" log.debug(\"Injecting timer BTN_LEFT\")\n"
" mydev:inject_frame({{ usage = evdev.BTN_LEFT, value = 1 }})\n"
"end\n"
"libinput:connect(\"new-evdev-device\", function(device)\n"
" mydev = device\n"
" device:connect(\"evdev-frame\", frame_handler)\n"
"end)\n"
"libinput:connect(\"timer-expired\", timer_expired)\n",
in_timer ? "-- " : "",
in_timer ? "" : "-- ");
_autofree_ char *p1 = litest_write_plugin(tmpdir->path, lua1);
_autofree_ char *p2 = litest_write_plugin(tmpdir->path, lua2);
_litest_context_destroy_ struct libinput *li =
litest_create_context_with_plugindir(tmpdir->path);
libinput_plugin_system_load_plugins(li, LIBINPUT_PLUGIN_SYSTEM_FLAG_NONE);
litest_drain_events(li);
_destroy_(litest_device) *device = litest_add_device(li, LITEST_CYBORG_RAT);
litest_drain_events(li);
litest_dispatch(li);
litest_log_group("P1 should swap left to right") {
litest_button_click_debounced(device, li, BTN_LEFT, 1);
litest_button_click_debounced(device, li, BTN_LEFT, 0);
litest_dispatch(li);
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_drain_events(li);
}
litest_log_group("P1/P2 should leave BTN_EXTRA untouched") {
litest_button_click_debounced(device, li, BTN_EXTRA, 1);
litest_button_click_debounced(device, li, BTN_EXTRA, 0);
litest_dispatch(li);
/* This might be a false positive */
litest_assert_button_event(li,
BTN_EXTRA,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_EXTRA,
LIBINPUT_BUTTON_STATE_RELEASED);
}
litest_log_group("P2 should map BTN_SIDE to BTN_EXTRA") {
litest_button_click_debounced(device, li, BTN_SIDE, 1);
litest_button_click_debounced(device, li, BTN_SIDE, 0);
litest_dispatch(li);
litest_timeout_debounce(li);
litest_dispatch(li);
litest_assert_button_event(li,
BTN_EXTRA,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_EXTRA,
LIBINPUT_BUTTON_STATE_RELEASED);
}
if (in_timer) {
litest_log_group(
"P2 should inject left on middle via timer, P1 changes that left to right in timer") {
litest_button_click_debounced(device, li, BTN_MIDDLE, 1);
litest_dispatch(li);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_button_click_debounced(device, li, BTN_MIDDLE, 0);
litest_dispatch(li);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED);
/* We only inject BTN_RIGHT down, so the second inject should
* get filtered */
litest_assert_empty_queue(li);
}
} else {
litest_log_group("P2 tries to inject during frame, gets unloaded") {
litest_set_log_handler_bug(li);
litest_button_click_debounced(device, li, BTN_MIDDLE, 1);
litest_restore_log_handler(li);
litest_button_click_debounced(device, li, BTN_MIDDLE, 0);
litest_dispatch(li);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED);
}
litest_log_group("P2 is unloaded, expect BTN_SIDE") {
litest_button_click_debounced(device, li, BTN_SIDE, 1);
litest_button_click_debounced(device, li, BTN_SIDE, 0);
litest_dispatch(li);
litest_assert_button_event(li,
BTN_SIDE,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_SIDE,
LIBINPUT_BUTTON_STATE_RELEASED);
}
}
}
END_TEST
enum when {
DEVICE_NEW,
FIRST_FRAME,
@ -1268,10 +1110,6 @@ TEST_COLLECTION(lua)
litest_add_parametrized_no_device(lua_append_prepend_frame, params);
}
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)) {