libinput/src/libinput-plugin-lua.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1364 lines
36 KiB
C
Raw Normal View History

/*
* Copyright © 2025 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <lauxlib.h>
#include <libevdev/libevdev.h>
#include <lua.h>
#include <lualib.h>
#include "util-mem.h"
#include "util-strings.h"
#include "evdev-frame.h"
#include "libinput-log.h"
#include "libinput-plugin-lua.h"
#include "libinput-plugin.h"
#include "libinput-util.h"
#include "timer.h"
const uint32_t LIBINPUT_PLUGIN_VERSION = 1U;
#define PLUGIN_METATABLE "LibinputPlugin"
#define EVDEV_DEVICE_METATABLE "EvdevDevice"
static const char libinput_lua_plugin_key = 'p'; /* key to lua registry */
static const char libinput_key = 'l'; /* key to lua registry */
DEFINE_TRIVIAL_CLEANUP_FUNC(lua_State *, lua_close);
struct udev_property {
struct list link;
char *key;
char *value;
};
static inline struct udev_property *
udev_property_new(const char *key, const char *value)
{
struct udev_property *prop = zalloc(sizeof(*prop));
prop->key = safe_strdup(key);
prop->value = safe_strdup(value);
return prop;
}
static inline void
udev_property_destroy(struct udev_property *prop)
{
list_remove(&prop->link);
free(prop->key);
free(prop->value);
free(prop);
}
/* A thin wrapper struct that just needs to exist, all
* the actual logic is struct libinput_lua_plugin */
typedef struct {
} LibinputPlugin;
typedef struct {
struct list link;
int refid;
struct libinput_device *device;
unsigned int id;
unsigned int bustype;
unsigned int vid;
unsigned int pid;
char *name;
struct list udev_properties_list;
struct libevdev *evdev;
int device_removed_refid;
int frame_refid;
} EvdevDevice;
struct libinput_lua_plugin {
struct libinput_plugin *parent;
lua_State *L;
int sandbox_table_idx;
bool register_called;
struct list evdev_devices; /* EvdevDevice */
size_t version;
int device_new_refid;
int timer_expired_refid;
struct libinput_plugin_timer *timer;
bool in_timer_func;
struct list timer_injected_events;
};
static struct libinput_lua_plugin *
lua_get_libinput_lua_plugin(lua_State *L)
{
struct libinput_lua_plugin *plugin = NULL;
lua_pushlightuserdata(L, (void *)&libinput_lua_plugin_key);
lua_gettable(L, LUA_REGISTRYINDEX);
plugin = lua_touserdata(L, -1);
lua_pop(L, 1);
return plugin;
}
/* Prints the current stack "layout" with a message */
#define lua_show_stack(L, ...) { \
etrace(__VA_ARGS__); \
etrace("pcall: stack has: %d", lua_gettop(L)); \
for (int i = -1; i >= -lua_gettop(L); i--) \
etrace(" stack %d: %s", i, lua_typename(L, lua_type(L, i))); \
}
static struct libinput *
lua_get_libinput(lua_State *L)
{
struct libinput *libinput = NULL;
lua_pushlightuserdata(L, (void *)&libinput_key);
lua_gettable(L, LUA_REGISTRYINDEX);
libinput = lua_touserdata(L, -1);
lua_pop(L, 1);
return libinput;
}
static void
lua_push_evdev_device(lua_State *L,
struct libinput_lua_plugin *plugin,
struct libinput_device *device,
struct libevdev *evdev,
struct udev_device *udev_device)
{
EvdevDevice *lua_device = lua_newuserdata(L, sizeof(*lua_device));
memset(lua_device, 0, sizeof(*lua_device));
lua_device->device = libinput_device_ref(device);
lua_device->evdev = evdev;
lua_device->bustype = libinput_device_get_id_bustype(device);
lua_device->vid = libinput_device_get_id_vendor(device);
lua_device->pid = libinput_device_get_id_product(device);
lua_device->name = strdup(libinput_device_get_name(device));
lua_device->device_removed_refid = LUA_NOREF;
lua_device->frame_refid = LUA_NOREF;
list_init(&lua_device->udev_properties_list);
struct udev_list_entry *e = udev_device_get_properties_list_entry(udev_device);
while (e) {
const char *key = udev_list_entry_get_name(e);
if (strstartswith(key, "ID_INPUT_") &&
!streq(key, "ID_INPUT_WIDTH_MM") &&
!streq(key, "ID_INPUT_HEIGHT_MM")) {
const char *value = udev_list_entry_get_value(e);
if (!streq(value, "0")) {
struct udev_property *prop =
udev_property_new(key, value);
list_insert(&lua_device->udev_properties_list,
&prop->link);
}
}
e = udev_list_entry_get_next(e);
}
list_insert(&plugin->evdev_devices, &lua_device->link);
lua_pushvalue(L, -1); /* Copy to top */
lua_device->refid = luaL_ref(L, LUA_REGISTRYINDEX); /* ref to device */
luaL_getmetatable(L, EVDEV_DEVICE_METATABLE);
lua_setmetatable(L, -2);
}
static void
lua_push_evdev_frame(lua_State *L, struct evdev_frame *frame)
{
size_t nevents;
struct evdev_event *events = evdev_frame_get_events(frame, &nevents);
lua_newtable(L);
for (size_t i = 0; i < nevents; i++) {
struct evdev_event *e = &events[i];
if (evdev_usage_eq(e->usage, EVDEV_SYN_REPORT))
break;
lua_newtable(L);
lua_pushinteger(L, evdev_usage_as_uint32_t(e->usage));
lua_setfield(L, -2, "usage");
lua_pushinteger(L, e->value);
lua_setfield(L, -2, "value");
lua_rawseti(L, -2, i + 1);
}
}
static void
lua_pop_evdev_frame(struct libinput_lua_plugin *plugin, struct evdev_frame *frame_out)
{
lua_State *L = plugin->L;
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return;
}
if (!lua_istable(L, -1)) {
plugin_log_bug(plugin->parent,
"expected table like `{ events = { ... } }`, got %s",
lua_typename(L, lua_type(L, -1)));
return;
}
struct evdev_event events[64] = { 0 };
size_t nevents = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0 && nevents < ARRAY_LENGTH(events)) {
/* -2 is the index, -1 our { usage = ... } table */
if (!lua_istable(L, -1)) {
plugin_log_bug(
plugin->parent,
"expected table like `{ type = ..., code = ...}`, got %s",
lua_typename(L, lua_type(L, -1)));
lua_pop(L, 1);
return;
}
lua_getfield(L, -1, "usage");
uint32_t usage = luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "value");
int32_t value = luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_pop(L, 1); /* pop { usage = ..., value = ...} */
struct evdev_event *e = &events[nevents++];
e->usage = evdev_usage_from_uint32_t(usage);
e->value = value;
if (evdev_usage_eq(e->usage, EVDEV_SYN_REPORT)) {
lua_pop(L, 1); /* force-pop the nil */
break;
}
}
if (nevents == 0) {
events[0].usage = evdev_usage_from_uint32_t(EVDEV_SYN_REPORT);
events[0].value = 0;
nevents++;
}
if (evdev_frame_set(frame_out, events, nevents) == -ENOMEM) {
plugin_log_bug(plugin->parent, "too many events in frame");
}
}
static bool
libinput_lua_pcall(struct libinput_lua_plugin *plugin, int narg, int nres)
{
lua_State *L = plugin->L;
lua_pushvalue(L, -(narg + 1)); /* Copy the function */
lua_pushvalue(L, plugin->sandbox_table_idx);
/* Now set the sandbox environment for the function we're about to call */
int rc = lua_setfenv(L, -2) == 1 ? LUA_OK : LUA_ERRRUN;
if (rc == LUA_OK) {
/* Replace original function with sandboxed one */
lua_replace(L, -(narg + 2));
/* Now call the function */
rc = lua_pcall(L, narg, nres, 0);
} else {
lua_pushstring(L, "Failed to set up sandbox");
}
if (rc != LUA_OK) {
auto libinput_plugin = plugin->parent;
const char *errormsg = lua_tostring(L, -1);
if (strstr(errormsg, "@@unregistering@@") == NULL) {
plugin_log_bug(libinput_plugin,
"unloading after error: %s\n",
errormsg);
}
lua_pop(L, 1); /* pop error message */
if (plugin->timer)
libinput_plugin_timer_cancel(plugin->timer);
libinput_plugin_unregister(libinput_plugin);
/* plugin system will destroy the plugin later */
}
return rc == LUA_OK;
}
static void
libinput_lua_plugin_device_new(struct libinput_plugin *libinput_plugin,
struct libinput_device *device,
struct libevdev *evdev,
struct udev_device *udev_device)
{
struct libinput_lua_plugin *plugin =
libinput_plugin_get_user_data(libinput_plugin);
lua_rawgeti(plugin->L, LUA_REGISTRYINDEX, plugin->device_new_refid);
lua_push_evdev_device(plugin->L, plugin, device, evdev, udev_device);
libinput_lua_pcall(plugin, 1, 0);
}
static void
remove_device(struct libinput_lua_plugin *plugin, EvdevDevice *evdev)
{
/* Don't allow access to the libevdev context during remove */
evdev->evdev = NULL;
if (evdev->device_removed_refid != LUA_NOREF) {
lua_rawgeti(plugin->L, LUA_REGISTRYINDEX, evdev->device_removed_refid);
lua_rawgeti(plugin->L, LUA_REGISTRYINDEX, evdev->refid);
if (!libinput_lua_pcall(plugin, 1, 0))
return;
}
luaL_unref(plugin->L, evdev->refid, LUA_REGISTRYINDEX);
evdev->refid = LUA_NOREF;
list_remove(&evdev->link);
list_init(&evdev->link); /* so we can list_remove in _gc */
struct udev_property *prop;
list_for_each_safe(prop, &evdev->udev_properties_list, link) {
udev_property_destroy(prop);
}
free(evdev->name);
evdev->name = NULL;
evdev->device = libinput_device_unref(evdev->device);
/* This device no longer exists but our lua code may have a
* reference to it */
}
static void
libinput_lua_plugin_device_ignored(struct libinput_plugin *libinput_plugin,
struct libinput_device *device)
{
struct libinput_lua_plugin *plugin =
libinput_plugin_get_user_data(libinput_plugin);
EvdevDevice *evdev;
list_for_each_safe(evdev, &plugin->evdev_devices, link) {
if (evdev->device != device)
continue;
remove_device(plugin, evdev);
}
}
static void
libinput_lua_plugin_device_removed(struct libinput_plugin *libinput_plugin,
struct libinput_device *device)
{
struct libinput_lua_plugin *plugin =
libinput_plugin_get_user_data(libinput_plugin);
EvdevDevice *evdev;
list_for_each_safe(evdev, &plugin->evdev_devices, link) {
if (evdev->device != device)
continue;
remove_device(plugin, evdev);
}
}
static void
libinput_lua_plugin_evdev_frame(struct libinput_plugin *libinput_plugin,
struct libinput_device *device,
struct evdev_frame *frame)
{
struct libinput_lua_plugin *plugin =
libinput_plugin_get_user_data(libinput_plugin);
EvdevDevice *evdev;
list_for_each_safe(evdev, &plugin->evdev_devices, link) {
if (evdev->device != device)
continue;
if (evdev->frame_refid == LUA_NOREF)
continue;
lua_rawgeti(plugin->L, LUA_REGISTRYINDEX, evdev->frame_refid);
lua_rawgeti(plugin->L, LUA_REGISTRYINDEX, evdev->refid);
lua_push_evdev_frame(plugin->L, frame);
lua_pushinteger(plugin->L, evdev_frame_get_time(frame));
if (!libinput_lua_pcall(plugin, 3, 1))
return;
lua_pop_evdev_frame(plugin, frame);
}
}
static void
register_func(struct lua_State *L, int stack_index, int *refid)
{
if (*refid != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, *refid);
lua_pushvalue(L, stack_index); /* Copy function to top */
*refid = luaL_ref(L, LUA_REGISTRYINDEX); /* ref to function */
}
static void
unregister_func(struct lua_State *L, int *refid)
{
if (*refid != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, *refid);
*refid = LUA_NOREF;
}
}
static int
libinputplugin_connect(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
const char *name = luaL_checkstring(L, 2);
luaL_checktype(L, 3, LUA_TFUNCTION);
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
/* Version 1 signals */
if (streq(name, "new-evdev-device")) {
register_func(L, 3, &plugin->device_new_refid);
} else if (streq(name, "timer-expired")) {
register_func(L, 3, &plugin->timer_expired_refid);
} else {
return luaL_error(L, "Unknown name: %s", name);
}
return 0;
}
static int
libinputplugin_now(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
struct libinput *libinput = lua_get_libinput(L);
uint64_t now = libinput_now(libinput);
lua_pushinteger(L, now);
return 1;
}
static int
libinputplugin_version(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
lua_pushinteger(L, plugin->version);
return 1;
}
static int
libinputplugin_register(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
if (plugin->register_called) {
return luaL_error(L, "plugin already registered");
}
uint32_t versions[16] = { 0 };
size_t idx = 0;
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L);
while (idx < ARRAY_LENGTH(versions) && lua_next(L, -2) != 0) {
int version = luaL_checkinteger(L, -1);
lua_pop(L, 1);
if (version <= 0) {
return luaL_error(L, "Invalid version number");
}
versions[idx++] = version;
}
ARRAY_FOR_EACH(versions, v) {
if (*v == 0)
break;
if (*v == LIBINPUT_PLUGIN_VERSION) {
plugin->version = *v;
plugin->register_called = true;
lua_pushinteger(L, plugin->version);
return 1;
}
}
return luaL_error(L, "None of this plugin's versions are supported");
}
static int
libinputplugin_unregister(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
/* Bit of a hack: unregister should work like os.exit(1)
* but we're in a lua context here so the easiest way
* to handle this is pretend we have an error, let
* our error handler unwind and just search for this
* magic string to *not* print log message */
return luaL_error(L, "@@unregistering@@");
}
static int
libinputplugin_gc(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
if (plugin->timer)
libinput_plugin_timer_cancel(plugin->timer);
/* We're about to destroy the plugin so the timer is the only
* thing we need to stop, the rest will be cleaned up
* when we destroy the plugin */
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)
{
struct libinput_lua_plugin *plugin = data;
struct lua_State *L = plugin->L;
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
libinputplugin_timer_set(lua_State *L, uint64_t offset)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
uint64_t timeout = luaL_checkinteger(L, 2);
if (!plugin->timer) {
plugin->timer = libinput_plugin_timer_new(
plugin->parent,
libinput_plugin_get_name(plugin->parent),
plugin_timer_func,
plugin);
}
libinput_plugin_timer_set(plugin->timer, offset + timeout);
return 0;
}
static int
libinputplugin_timer_set_absolute(lua_State *L)
{
return libinputplugin_timer_set(L, 0);
}
static int
libinputplugin_timer_set_relative(lua_State *L)
{
auto libinput = lua_get_libinput(L);
return libinputplugin_timer_set(L, libinput_now(libinput));
}
static int
libinputplugin_timer_cancel(lua_State *L)
{
LibinputPlugin *p = luaL_checkudata(L, 1, PLUGIN_METATABLE);
luaL_argcheck(L, p != NULL, 1, PLUGIN_METATABLE " expected");
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
if (plugin->timer)
libinput_plugin_timer_cancel(plugin->timer);
return 0;
}
static const struct luaL_Reg libinputplugin_vtable[] = {
{ "now", libinputplugin_now },
{ "version", libinputplugin_version },
{ "connect", libinputplugin_connect },
{ "register", libinputplugin_register },
{ "unregister", libinputplugin_unregister },
{ "timer_cancel", libinputplugin_timer_cancel },
{ "timer_set_absolute", libinputplugin_timer_set_absolute },
{ "timer_set_relative", libinputplugin_timer_set_relative },
{ "__gc", libinputplugin_gc },
{ NULL, NULL }
};
static void
libinputplugin_init(lua_State *L)
{
luaL_newmetatable(L, PLUGIN_METATABLE);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* push metatable */
lua_settable(L, -3); /* metatable.__index = metatable */
luaL_setfuncs(L, libinputplugin_vtable, 0);
}
static int
evdevdevice_info(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE " expected");
lua_newtable(L); /* { bustype: ..., vid: ..., pid: ..., name: ... } */
lua_pushinteger(L, device->bustype);
lua_setfield(L, -2, "bustype");
lua_pushinteger(L, device->vid);
lua_setfield(L, -2, "vid");
lua_pushinteger(L, device->pid);
lua_setfield(L, -2, "pid");
return 1;
}
static int
evdevdevice_name(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
lua_pushstring(L, device->name);
return 1;
}
static int
evdevdevice_usages(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
lua_newtable(L); /* { evdev.REL_X: ... } */
if (device->evdev == NULL)
return 1;
for (unsigned int t = 0; t < EV_MAX; t++) {
if (!libevdev_has_event_type(device->evdev, t))
continue;
int max = libevdev_event_type_get_max(t);
for (unsigned int code = 0; (int)code < max; code++) {
if (!libevdev_has_event_code(device->evdev, t, code))
continue;
evdev_usage_t usage = evdev_usage_from_code(t, code);
lua_pushboolean(L, true);
lua_rawseti(L, -2, evdev_usage_as_uint32_t(usage));
}
}
return 1;
}
static int
evdevdevice_absinfos(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
lua_newtable(L); /* { ABS_X: { min: 1, max: 2, ... }, ... } */
if (device->evdev == NULL)
return 1;
for (unsigned int code = 0; code < ABS_MAX; code++) {
const struct input_absinfo *abs =
libevdev_get_abs_info(device->evdev, code);
if (!abs)
continue;
lua_newtable(L);
lua_pushinteger(L, abs->minimum);
lua_setfield(L, -2, "minimum");
lua_pushinteger(L, abs->maximum);
lua_setfield(L, -2, "maximum");
lua_pushinteger(L, abs->fuzz);
lua_setfield(L, -2, "fuzz");
lua_pushinteger(L, abs->flat);
lua_setfield(L, -2, "flat");
lua_pushinteger(L, abs->resolution);
lua_setfield(L, -2, "resolution");
evdev_usage_t usage = evdev_usage_from_code(EV_ABS, code);
lua_rawseti(
L,
-2,
evdev_usage_as_uint32_t(usage)); /* Assign to top-level table */
}
return 1;
}
static int
evdevdevice_udev_properties(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
lua_newtable(L); /* { ID_INPUT: { ... } , ... } */
if (device->evdev == NULL)
return 1;
struct udev_property *prop;
list_for_each(prop, &device->udev_properties_list, link) {
lua_pushstring(L, prop->value);
lua_setfield(L, -2, prop->key); /* Assign to top-level table */
}
return 1;
}
static int
evdevdevice_enable_evdev_usage(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
evdev_usage_t usage = evdev_usage_from_uint32_t(luaL_checkinteger(L, 2));
uint16_t type = evdev_usage_type(usage);
uint16_t code = evdev_usage_code(usage);
if (type > EV_MAX) {
plugin_log_bug(plugin->parent,
,
"Ignoring invalid evdev usage %#x\n",
evdev_usage_as_uint32_t(usage));
return 0;
}
if (device->evdev == NULL || type == EV_ABS)
return 0;
libevdev_enable_event_code(device->evdev, type, code, NULL);
return 0;
}
static int
evdevdevice_disable_evdev_usage(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
evdev_usage_t usage = evdev_usage_from_uint32_t(luaL_checkinteger(L, 2));
uint16_t type = evdev_usage_type(usage);
uint16_t code = evdev_usage_code(usage);
if (device->evdev == NULL || type > EV_MAX)
return 0;
libevdev_disable_event_code(device->evdev, type, code);
return 0;
}
static int
evdevdevice_set_absinfo(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
evdev_usage_t usage = evdev_usage_from_uint32_t(luaL_checkinteger(L, 2));
luaL_checktype(L, 3, LUA_TTABLE);
if (evdev_usage_type(usage) != EV_ABS)
return 0;
if (!device->evdev)
return 0;
uint16_t code = evdev_usage_code(usage);
const struct input_absinfo *absinfo =
libevdev_get_abs_info(device->evdev, code);
struct input_absinfo abs = {};
if (absinfo)
abs = *absinfo;
lua_getfield(L, 3, "minimum");
if (lua_isnumber(L, -1))
abs.minimum = luaL_checkinteger(L, -1);
lua_getfield(L, 3, "maximum");
if (lua_isnumber(L, -1))
abs.maximum = luaL_checkinteger(L, -1);
lua_getfield(L, 3, "resolution");
if (lua_isnumber(L, -1))
abs.resolution = luaL_checkinteger(L, -1);
lua_getfield(L, 3, "fuzz");
if (lua_isnumber(L, -1))
abs.fuzz = luaL_checkinteger(L, -1);
lua_getfield(L, 3, "flat");
if (lua_isnumber(L, -1))
abs.flat = luaL_checkinteger(L, -1);
libevdev_enable_event_code(device->evdev, EV_ABS, code, &abs);
return 0;
}
static int
evdevdevice_connect(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE " expected");
const char *name = luaL_checkstring(L, 2);
luaL_checktype(L, 3, LUA_TFUNCTION);
/* No refid means we got removed, so quietly
* drop any connect call */
if (device->refid == LUA_NOREF)
return 0;
if (streq(name, "device-removed")) {
register_func(L, 3, &device->device_removed_refid);
} else if (streq(name, "evdev-frame")) {
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
libinput_plugin_enable_device_event_frame(plugin->parent,
device->device,
true);
register_func(L, 3, &device->frame_refid);
} else {
return luaL_error(L, "Unknown name: %s", name);
}
return 0;
}
static int
evdevdevice_disconnect(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE " expected");
const char *name = luaL_checkstring(L, 2);
/* No refid means we got removed, so quietly
* drop any disconnect call */
if (device->refid == LUA_NOREF)
return 0;
if (streq(name, "device-removed")) {
unregister_func(L, &device->device_removed_refid);
} else if (streq(name, "evdev-frame")) {
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
libinput_plugin_enable_device_event_frame(plugin->parent,
device->device,
false);
unregister_func(L, &device->frame_refid);
} else {
return luaL_error(L, "Unknown name: %s", name);
}
return 0;
}
static struct evdev_frame *
evdevdevice_frame(lua_State *L, struct libinput_lua_plugin *plugin)
{
auto frame = evdev_frame_new(64);
lua_pop_evdev_frame(plugin, frame);
struct libinput *libinput = lua_get_libinput(L);
uint64_t now = libinput_now(libinput);
evdev_frame_set_time(frame, now);
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);
/* 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)
{
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);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin);
/* FIXME: need to really ensure that the device can never be dangling */
libinput_plugin_prepend_evdev_frame(plugin->parent, device->device, frame);
return 0;
}
static int
evdevdevice_append_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);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin);
/* FIXME: need to really ensure that the device can never be dangling */
libinput_plugin_append_evdev_frame(plugin->parent, device->device, frame);
return 0;
}
static int
evdevdevice_gc(lua_State *L)
{
EvdevDevice *device = luaL_checkudata(L, 1, EVDEV_DEVICE_METATABLE);
luaL_argcheck(L, device != NULL, 1, EVDEV_DEVICE_METATABLE "expected");
list_remove(&device->link);
struct udev_property *prop;
list_for_each_safe(prop, &device->udev_properties_list, link) {
udev_property_destroy(prop);
}
free(device->name);
return 0;
}
static const struct luaL_Reg evdevdevice_vtable[] = {
{ "info", evdevdevice_info },
{ "name", evdevdevice_name },
{ "usages", evdevdevice_usages },
{ "absinfos", evdevdevice_absinfos },
{ "udev_properties", evdevdevice_udev_properties },
{ "enable_evdev_usage", evdevdevice_enable_evdev_usage },
{ "disable_evdev_usage", evdevdevice_disable_evdev_usage },
{ "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 },
{ "__gc", evdevdevice_gc },
{ NULL, NULL }
};
static void
evdevdevice_init(lua_State *L)
{
luaL_newmetatable(L, EVDEV_DEVICE_METATABLE);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* push metatable */
lua_settable(L, -3); /* metatable.__index = metatable */
luaL_setfuncs(L, evdevdevice_vtable, 0);
}
static int
logfunc(lua_State *L, enum libinput_log_priority pri)
{
auto plugin = lua_get_libinput_lua_plugin(L);
const char *message = luaL_checkstring(L, 1);
plugin_log_msg(plugin->parent, pri, "%s\n", message);
return 0;
}
static int
log_lua_error(lua_State *L)
{
return logfunc(L, LIBINPUT_LOG_PRIORITY_ERROR);
}
static int
log_lua_info(lua_State *L)
{
return logfunc(L, LIBINPUT_LOG_PRIORITY_INFO);
}
static int
log_lua_debug(lua_State *L)
{
return logfunc(L, LIBINPUT_LOG_PRIORITY_DEBUG);
}
/* Exposes log.debug, log.info, log.error() */
static const struct luaL_Reg log_funcs[] = { { "debug", log_lua_debug },
{ "info", log_lua_info },
{ "error", log_lua_error },
{ NULL, NULL } };
static void
libinput_lua_plugin_destroy(struct libinput_lua_plugin *plugin)
{
if (plugin->timer)
libinput_plugin_timer_cancel(plugin->timer);
EvdevDevice *evdev;
list_for_each_safe(evdev, &plugin->evdev_devices, link) {
remove_device(plugin, evdev);
}
if (plugin->timer)
plugin->timer = libinput_plugin_timer_unref(plugin->timer);
if (plugin->L)
lua_close(plugin->L);
free(plugin);
}
DEFINE_DESTROY_CLEANUP_FUNC(libinput_lua_plugin);
static void
libinput_plugin_destroy(struct libinput_plugin *libinput_plugin)
{
struct libinput_lua_plugin *plugin =
libinput_plugin_get_user_data(libinput_plugin);
if (plugin)
libinput_lua_plugin_destroy(plugin);
}
static void
libinput_lua_plugin_run(struct libinput_plugin *libinput_plugin)
{
struct libinput_lua_plugin *plugin =
libinput_plugin_get_user_data(libinput_plugin);
if (libinput_lua_pcall(plugin, 0, 0) && !plugin->register_called) {
plugin_log_bug(libinput_plugin,
"plugin never registered, unloading plugin\n");
libinput_plugin_unregister(libinput_plugin);
/* plugin system will destroy the plugin later */
}
}
static void
libinput_lua_init_evdev_global(lua_State *L, int sandbox_table_idx)
{
lua_newtable(L);
for (unsigned int t = 0; t < EV_MAX; t++) {
const char *typename = libevdev_event_type_get_name(t);
if (!typename)
continue;
int max = libevdev_event_type_get_max(t);
if (max < 0)
continue;
for (int i = 0; i < max; i++) {
const char *name = libevdev_event_code_get_name(t, i);
if (!name)
continue;
evdev_usage_t usage = evdev_usage_from_code(t, i);
lua_pushinteger(L, evdev_usage_as_uint32_t(usage));
lua_setfield(L, -2, name);
}
}
#define pushbus(name, value) do { \
lua_pushinteger(L, value); \
lua_setfield(L, -2, #name); \
} while (0)
pushbus(BUS_PCI, 0x01);
pushbus(BUS_ISAPNP, 0x02);
pushbus(BUS_USB, 0x03);
pushbus(BUS_HIL, 0x04);
pushbus(BUS_BLUETOOTH, 0x05);
pushbus(BUS_VIRTUAL, 0x06);
pushbus(BUS_ISA, 0x10);
pushbus(BUS_I8042, 0x11);
pushbus(BUS_XTKBD, 0x12);
pushbus(BUS_RS232, 0x13);
pushbus(BUS_GAMEPORT, 0x14);
pushbus(BUS_PARPORT, 0x15);
pushbus(BUS_AMIGA, 0x16);
pushbus(BUS_ADB, 0x17);
pushbus(BUS_I2C, 0x18);
pushbus(BUS_HOST, 0x19);
pushbus(BUS_GSC, 0x1A);
pushbus(BUS_ATARI, 0x1B);
pushbus(BUS_SPI, 0x1C);
pushbus(BUS_RMI, 0x1D);
pushbus(BUS_CEC, 0x1E);
pushbus(BUS_INTEL_ISHTP, 0x1F);
pushbus(BUS_AMD_SFH, 0x20);
#undef pushbus
lua_setfield(L, sandbox_table_idx, "evdev");
}
static const struct libinput_plugin_interface interface = {
.run = libinput_lua_plugin_run,
.destroy = libinput_plugin_destroy,
.device_new = libinput_lua_plugin_device_new,
.device_ignored = libinput_lua_plugin_device_ignored,
.device_added = NULL,
.device_removed = libinput_lua_plugin_device_removed,
.evdev_frame = libinput_lua_plugin_evdev_frame,
};
static lua_State *
libinput_lua_plugin_init_lua(struct libinput *libinput,
struct libinput_lua_plugin *plugin)
{
lua_State *L = luaL_newstate();
if (!L)
return NULL;
/* This will be our our global env later, see libinput_lua_pcall() */
lua_newtable(L);
int sandbox_table_idx = lua_gettop(L);
plugin->sandbox_table_idx = sandbox_table_idx;
/* Load the modules we want to (partially) expose.
* An (outdated?) list of safe function is here:
* http://lua-users.org/wiki/SandBoxes
*
* Math, String and Table seem to be safe given that our plugins
* all have their own individual sandbox.
*/
luaopen_base(L);
static const char *allowed_funcs[] = {
"assert", "error", "ipairs", "next", "pcall", "pairs",
"print", "tonumber", "tostring", "type", "unpack", "xpcall",
};
ARRAY_FOR_EACH(allowed_funcs, func) {
lua_getglobal(L, *func);
lua_setfield(L, sandbox_table_idx, *func);
}
/* Math is fine as a whole */
luaopen_math(L);
lua_getglobal(L, "math");
lua_setfield(L, sandbox_table_idx, "math");
/* Table is fine as a whole */
luaopen_table(L);
lua_getglobal(L, "table");
lua_setfield(L, sandbox_table_idx, "table");
/* String is fine as a whole */
luaopen_string(L);
lua_getglobal(L, "string");
lua_setfield(L, sandbox_table_idx, "string");
/* Override metatable to prevent access to unregistered globals */
lua_newtable(L);
lua_pushstring(L, "__index");
lua_pushnil(L);
lua_settable(L, -3);
lua_setmetatable(L, sandbox_table_idx);
/* Our objects */
libinputplugin_init(L);
evdevdevice_init(L);
/* Our globals */
lua_newtable(L);
luaL_register(L, "log", log_funcs);
lua_setfield(L, sandbox_table_idx, "log");
libinput_lua_init_evdev_global(L, sandbox_table_idx);
/* The libinput global object */
lua_newuserdata(L, sizeof(LibinputPlugin));
luaL_getmetatable(L, PLUGIN_METATABLE);
lua_setmetatable(L, -2);
lua_setfield(L, sandbox_table_idx, "libinput");
/* Make struct libinput available in our callbacks */
lua_pushlightuserdata(L, (void *)&libinput_key);
lua_pushlightuserdata(L, libinput);
lua_settable(L, LUA_REGISTRYINDEX);
/* Make struct libinput_lua_plugin available in our callbacks */
lua_pushlightuserdata(L, (void *)&libinput_lua_plugin_key);
lua_pushlightuserdata(L, plugin);
lua_settable(L, LUA_REGISTRYINDEX);
return L;
}
struct libinput_plugin *
libinput_lua_plugin_new_from_path(struct libinput *libinput, const char *path)
{
_destroy_(libinput_lua_plugin) *plugin = zalloc(sizeof(*plugin));
_autofree_ char *name = safe_strdup(safe_basename(path));
/* libinput's plugin system keeps a ref, we don't need
* a separate ref here, the plugin system will outlast us.
*/
_unref_(libinput_plugin) *p =
libinput_plugin_new(libinput, name, &interface, NULL);
plugin->parent = p;
plugin->register_called = false;
plugin->version = LIBINPUT_PLUGIN_VERSION;
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);
if (!L) {
plugin_log_bug(plugin->parent,
"Failed to create lua state for %s\n",
name);
libinput_plugin_unregister(p);
return NULL;
}
int ret = luaL_loadfile(L, path);
if (ret == LUA_OK) {
plugin->L = steal(&L);
libinput_plugin_set_user_data(p, steal(&plugin));
return p;
} else {
const char *lua_error = lua_tostring(L, -1);
const char *error = lua_error;
if (!error) {
switch (ret) {
case LUA_ERRMEM:
error = "out of memory";
break;
case LUA_ERRFILE:
error = "file not found or not readable";
break;
case LUA_ERRSYNTAX:
error = "syntax error";
break;
default:
break;
}
}
if (ret == LUA_ERRSYNTAX &&
log_is_logged(libinput, LIBINPUT_LOG_PRIORITY_DEBUG)) {
luaL_traceback(L, L, NULL, 1);
for (int i = -1; i > -4; i--) {
const char *msg = lua_tostring(L, i);
if (!msg)
break;
log_debug(libinput, "%s %s\n", name, msg);
}
lua_pop(L, 1); /* traceback */
}
plugin_log_bug(plugin->parent, "Failed to load %s: %s\n", path, error);
lua_pop(L, 1); /* the lua_error message */
libinput_plugin_unregister(p);
return NULL;
}
}