Add a public plugin system to libinput

This patch adds a new API for enabling public "plugins" in libinput, in
addition to the exisitng internal ones. The API is currently limited to
specifying which paths should be loaded and whether to load them.
Public plugins are static, they are loaded before the context is initialized
and do not update after that.

If plugins are to be loaded, libinput will then run through those paths,
look up files and pass them to (future) plugins to load the actual
files.

Our debugging tools have an --enable-plugins and
--disable-plugins option that load from the default data paths
(/etc/libinput/plugins and /usr/lib{64}/libinput/plugins) and from
the $builddir/plugins directory.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1192>
This commit is contained in:
Peter Hutterer 2025-04-22 10:25:55 +10:00
parent d1dbbb7328
commit d557a649fd
13 changed files with 272 additions and 13 deletions

View file

@ -93,6 +93,9 @@ __all_seats()
+ '(natural-scrolling)' \ + '(natural-scrolling)' \
'--enable-natural-scrolling[Enable natural scrolling]' \ '--enable-natural-scrolling[Enable natural scrolling]' \
'--disable-natural-scrolling[Disable natural scrolling]' \ '--disable-natural-scrolling[Disable natural scrolling]' \
+ '(plugins)' \
'--enable-plugins[Enable plugins]' \
'--disable-plugins[Disable plugins]' \
+ '(tap-to-click)' \ + '(tap-to-click)' \
'--enable-tap[Enable tap-to-click]' \ '--enable-tap[Enable tap-to-click]' \
'--disable-tap[Disable tap-to-click]' '--disable-tap[Disable tap-to-click]'

View file

@ -168,6 +168,8 @@ dep_libevdev = dependency('libevdev', version: '>= 1.10.0')
dep_lm = cc.find_library('m', required : false) dep_lm = cc.find_library('m', required : false)
dep_rt = cc.find_library('rt', required : false) dep_rt = cc.find_library('rt', required : false)
config_h.set10('HAVE_PLUGINS', true)
# Include directories # Include directories
includes_include = include_directories('include') includes_include = include_directories('include')
includes_src = include_directories('src') includes_src = include_directories('src')
@ -381,6 +383,9 @@ if get_option('internal-event-debugging')
config_h.set('EVENT_DEBUGGING', 1) config_h.set('EVENT_DEBUGGING', 1)
endif endif
config_h.set_quoted('LIBINPUT_PLUGIN_LIBDIR', dir_lib / 'libinput' / 'plugins')
config_h.set_quoted('LIBINPUT_PLUGIN_ETCDIR', dir_etc / 'libinput' / 'plugins')
install_headers('src/libinput.h') install_headers('src/libinput.h')
src_libinput = src_libfilter + [ src_libinput = src_libfilter + [
'src/libinput.c', 'src/libinput.c',

View file

@ -327,6 +327,55 @@ libinput_plugin_notify_device_removed(struct libinput_plugin *plugin,
plugin->interface->device_removed(plugin, device); plugin->interface->device_removed(plugin, device);
} }
LIBINPUT_EXPORT void
libinput_plugin_system_append_path(struct libinput *libinput, const char *path)
{
if (libinput->plugin_system.loaded) {
log_bug_client(libinput, "plugin system already initialized\n");
return;
}
if (strv_find(libinput->plugin_system.directories, path, NULL))
return;
libinput->plugin_system.directories =
strv_append_strdup(libinput->plugin_system.directories, path);
}
LIBINPUT_EXPORT void
libinput_plugin_system_append_default_paths(struct libinput *libinput)
{
if (libinput->plugin_system.loaded) {
log_bug_client(libinput, "plugin system already initialized\n");
return;
}
libinput_plugin_system_append_path(libinput, LIBINPUT_PLUGIN_ETCDIR);
libinput_plugin_system_append_path(libinput, LIBINPUT_PLUGIN_LIBDIR);
}
LIBINPUT_EXPORT int
libinput_plugin_system_load_plugins(struct libinput *libinput,
enum libinput_plugins_flags flags)
{
if (libinput->plugin_system.loaded) {
log_bug_client(libinput, "%s() called twice\n", __func__);
return 0;
}
libinput_plugin_system_load_internal_plugins(libinput,
&libinput->plugin_system);
libinput->plugin_system.loaded = true;
libinput_plugin_system_run(&libinput->plugin_system);
#if !HAVE_PLUGINS
return -ENOSYS;
#else
return 0;
#endif
}
void void
libinput_plugin_system_run(struct libinput_plugin_system *system) libinput_plugin_system_run(struct libinput_plugin_system *system)
{ {
@ -403,6 +452,7 @@ libinput_plugin_system_load_internal_plugins(struct libinput *libinput,
* actually connected to anything yet */ * actually connected to anything yet */
libinput_evdev_dispatch_plugin(libinput); libinput_evdev_dispatch_plugin(libinput);
} }
void void
libinput_plugin_system_destroy(struct libinput_plugin_system *system) libinput_plugin_system_destroy(struct libinput_plugin_system *system)
{ {

View file

@ -3690,6 +3690,89 @@ libinput_path_add_device(struct libinput *libinput, const char *path);
void void
libinput_path_remove_device(struct libinput_device *device); libinput_path_remove_device(struct libinput_device *device);
/**
* @ingroup base
*
* Appends the given directory path to the libinput plugin lookup path.
* If the path is already in the lookup paths, it is ignored.
*
* A path's priority is determined by its position in the list; the first
* path in the list has the highest priority.
*
* Plugin lookup is performed across all paths in lexical order. If
* a plugin exists in multiple paths, the one in the highest priority
* path (i.e. front of the list) is used.
*
* Paths are not traversed recursively.
*
* Plugins that have a 0 byte size shadow any plugins with the same name
* but do not provide any fuctionality. This allows disabling a plugin
* by simply dropping an empty file in a higher-priority directory.
*
* This function must be called before i
* libinput_plugin_system_load_plugins().
*
* @see libinput_plugin_system_append_default_paths
*
* @since 1.29
*/
void
libinput_plugin_system_append_path(struct libinput *libinput, const char *path);
/**
* @ingroup base
*
* Add the default plugin lookup paths, typically:
* - /etc/libinput/plugins/
* - /usr/lib{64}/libinput/plugins/
*
* These paths are inserted at the current priority - to add
* paths with a higher priority than these, call
* libinput_plugin_system_append_path() prior to this function.
*
* See libinput_plugin_system_append_path() for more details.
*
* This function must be called before
* libinput_plugin_system_load_plugins().
*
* @see libinput_plugin_system_append_paths
*
* @since 1.29
*/
void
libinput_plugin_system_append_default_paths(struct libinput *libinput);
enum libinput_plugins_flags {
LIBINPUT_PLUGIN_FLAG_NONE = 0,
};
/**
* @ingroup base
*
* Load the plugins from the set of lookup paths. This function does nothing
* if no plugin paths have been configured, see
* libinput_plugin_system_append_default_paths() and
* libinput_plugin_system_append_path().
*
* The typical use of this function is:
* ```
* struct libinput *li = libinput_udev_create_context(...);
* libinput_plugin_system_append_default_paths(li);
* libinput_plugin_system_load(li, flags);
* ```
*
* This function must be called before libinput iterates through the
* devices, i.e. before libinput_udev_assign_seat() or libinput_path_add_device().
*
* @return 0 or a negative errno on failure
* @retval -ENOSYS libinput was compiled without plugin support
*
* @since 1.29
*/
int
libinput_plugin_system_load_plugins(struct libinput *libinput,
enum libinput_plugins_flags flags);
/** /**
* @ingroup base * @ingroup base
* *

View file

@ -374,3 +374,9 @@ LIBINPUT_1.29 {
libinput_tablet_tool_config_eraser_button_set_button; libinput_tablet_tool_config_eraser_button_set_button;
libinput_tablet_tool_config_eraser_button_set_mode; libinput_tablet_tool_config_eraser_button_set_mode;
} LIBINPUT_1.28; } LIBINPUT_1.28;
LIBINPUT_1.30 {
libinput_plugin_system_append_default_paths;
libinput_plugin_system_append_path;
libinput_plugin_system_load_plugins;
} LIBINPUT_1.29;

View file

@ -2144,6 +2144,8 @@ litest_create_context(void)
if (verbose) if (verbose)
libinput_log_set_priority(libinput, LIBINPUT_LOG_PRIORITY_DEBUG); libinput_log_set_priority(libinput, LIBINPUT_LOG_PRIORITY_DEBUG);
libinput_plugin_system_load_plugins(libinput, LIBINPUT_PLUGIN_FLAG_NONE);
return libinput; return libinput;
} }

View file

@ -319,7 +319,13 @@ main(int argc, char **argv)
if (verbose) if (verbose)
printf("libinput version: %s\n", LIBINPUT_VERSION); printf("libinput version: %s\n", LIBINPUT_VERSION);
li = tools_open_backend(backend, seat_or_devices, verbose, &grab); bool with_plugins = (options.plugins == 1);
li = tools_open_backend(backend,
seat_or_devices,
verbose,
&grab,
with_plugins,
steal(&options.plugin_paths));
if (!li) if (!li)
return EXIT_FAILURE; return EXIT_FAILURE;

View file

@ -2007,7 +2007,13 @@ main(int argc, char **argv)
backend = BACKEND_UDEV; backend = BACKEND_UDEV;
} }
li = tools_open_backend(backend, seat_or_device, verbose, &w.grab); bool with_plugins = (options.plugins == 1);
li = tools_open_backend(backend,
seat_or_device,
verbose,
&w.grab,
with_plugins,
steal(&options.plugin_paths));
if (!li) if (!li)
return EXIT_FAILURE; return EXIT_FAILURE;

View file

@ -528,7 +528,14 @@ main(int argc, char **argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
li = tools_open_backend(backend, seat_or_device, false, &grab); bool with_plugins = (options.plugins == 1);
li = tools_open_backend(backend,
seat_or_device,
false,
&grab,
with_plugins,
steal(&options.plugin_paths));
if (!li) if (!li)
return EXIT_FAILURE; return EXIT_FAILURE;

View file

@ -602,7 +602,13 @@ main(int argc, char **argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
li = tools_open_backend(backend, seat_or_device, false, &grab); bool with_plugins = (options.plugins == 1);
li = tools_open_backend(backend,
seat_or_device,
false,
&grab,
with_plugins,
steal(&options.plugin_paths));
if (!li) if (!li)
return EXIT_FAILURE; return EXIT_FAILURE;

View file

@ -606,10 +606,15 @@ main(int argc, char **argv)
} }
devices[ndevices++] = argv[optind]; devices[ndevices++] = argv[optind];
} while (++optind < argc); } while (++optind < argc);
li = tools_open_backend(BACKEND_DEVICE, devices, false, &grab); li = tools_open_backend(BACKEND_DEVICE,
devices,
false,
&grab,
false,
NULL);
} else { } else {
const char *seat[2] = { "seat0", NULL }; const char *seat[2] = { "seat0", NULL };
li = tools_open_backend(BACKEND_UDEV, seat, false, &grab); li = tools_open_backend(BACKEND_UDEV, seat, false, &grab, false, NULL);
} }
if (!li) if (!li)
return 1; return 1;

View file

@ -107,6 +107,8 @@ void
tools_init_options(struct tools_options *options) tools_init_options(struct tools_options *options)
{ {
memset(options, 0, sizeof(*options)); memset(options, 0, sizeof(*options));
options->plugins = -1;
options->plugin_paths = NULL;
options->tapping = -1; options->tapping = -1;
options->tap_map = -1; options->tap_map = -1;
options->drag = -1; options->drag = -1;
@ -146,6 +148,15 @@ int
tools_parse_option(int option, const char *optarg, struct tools_options *options) tools_parse_option(int option, const char *optarg, struct tools_options *options)
{ {
switch (option) { switch (option) {
case OPT_PLUGINS_ENABLE:
options->plugins = 1;
break;
case OPT_PLUGINS_DISABLE:
options->plugins = 0;
break;
case OPT_PLUGIN_PATH:
options->plugin_paths = strv_from_string(optarg, ":", NULL);
break;
case OPT_TAP_ENABLE: case OPT_TAP_ENABLE:
options->tapping = 1; options->tapping = 1;
break; break;
@ -494,8 +505,46 @@ static const struct libinput_interface interface = {
.close_restricted = close_restricted, .close_restricted = close_restricted,
}; };
static int
add_path(const char *str, size_t index, void *data)
{
struct libinput *libinput = data;
libinput_plugin_system_append_default_paths(libinput);
return 0;
}
static void
tools_load_plugins(struct libinput *libinput, char **plugin_paths)
{
_autofree_ char *builddir = NULL;
if (plugin_paths) {
strv_for_each((const char **)plugin_paths, add_path, libinput);
strv_free(plugin_paths);
} else {
if (builddir_lookup(&builddir)) {
_autofree_ char *plugindir =
strdup_printf("%s/plugins", builddir);
libinput_plugin_system_append_path(libinput, plugindir);
}
libinput_plugin_system_append_default_paths(libinput);
}
switch (libinput_plugin_system_load_plugins(libinput,
LIBINPUT_PLUGIN_FLAG_NONE)) {
case -ENOSYS:
fprintf(stderr, "Warning: plugins were disabled at compile time");
break;
case 0:
break;
}
}
static struct libinput * static struct libinput *
tools_open_udev(const char *seat, bool verbose, bool *grab) tools_open_udev(const char *seat,
bool verbose,
bool *grab,
bool with_plugins,
char **plugin_paths)
{ {
_unref_(udev) *udev = udev_new(); _unref_(udev) *udev = udev_new();
if (!udev) { if (!udev) {
@ -513,6 +562,9 @@ tools_open_udev(const char *seat, bool verbose, bool *grab)
if (verbose) if (verbose)
libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG); libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);
if (with_plugins)
tools_load_plugins(li, plugin_paths);
if (libinput_udev_assign_seat(li, seat)) { if (libinput_udev_assign_seat(li, seat)) {
fprintf(stderr, "Failed to set seat\n"); fprintf(stderr, "Failed to set seat\n");
return NULL; return NULL;
@ -522,7 +574,11 @@ tools_open_udev(const char *seat, bool verbose, bool *grab)
} }
static struct libinput * static struct libinput *
tools_open_device(const char **paths, bool verbose, bool *grab) tools_open_device(const char **paths,
bool verbose,
bool *grab,
bool with_plugins,
char **plugin_paths)
{ {
_unref_(libinput) *li = libinput_path_create_context(&interface, grab); _unref_(libinput) *li = libinput_path_create_context(&interface, grab);
if (!li) { if (!li) {
@ -535,6 +591,9 @@ tools_open_device(const char **paths, bool verbose, bool *grab)
libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG); libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);
} }
if (with_plugins)
tools_load_plugins(li, plugin_paths);
const char **p = paths; const char **p = paths;
while (*p) { while (*p) {
struct libinput_device *device = libinput_path_add_device(li, *p); struct libinput_device *device = libinput_path_add_device(li, *p);
@ -559,7 +618,9 @@ struct libinput *
tools_open_backend(enum tools_backend which, tools_open_backend(enum tools_backend which,
const char **seat_or_device, const char **seat_or_device,
bool verbose, bool verbose,
bool *grab) bool *grab,
bool with_plugins,
char **plugin_paths)
{ {
struct libinput *li; struct libinput *li;
@ -567,10 +628,18 @@ tools_open_backend(enum tools_backend which,
switch (which) { switch (which) {
case BACKEND_UDEV: case BACKEND_UDEV:
li = tools_open_udev(seat_or_device[0], verbose, grab); li = tools_open_udev(seat_or_device[0],
verbose,
grab,
with_plugins,
plugin_paths);
break; break;
case BACKEND_DEVICE: case BACKEND_DEVICE:
li = tools_open_device(seat_or_device, verbose, grab); li = tools_open_device(seat_or_device,
verbose,
grab,
with_plugins,
plugin_paths);
break; break;
default: default:
abort(); abort();

View file

@ -77,10 +77,15 @@ enum configuration_options {
OPT_SENDEVENTS, OPT_SENDEVENTS,
OPT_ERASER_BUTTON_MODE, OPT_ERASER_BUTTON_MODE,
OPT_ERASER_BUTTON_BUTTON, OPT_ERASER_BUTTON_BUTTON,
OPT_PLUGINS_DISABLE,
OPT_PLUGINS_ENABLE,
OPT_PLUGIN_PATH,
}; };
#define CONFIGURATION_OPTIONS \ #define CONFIGURATION_OPTIONS \
{ "disable-sendevents", required_argument, 0, OPT_DISABLE_SENDEVENTS }, \ { "disable-sendevents", required_argument, 0, OPT_DISABLE_SENDEVENTS }, \
{ "enable-plugins", no_argument, 0, OPT_PLUGINS_ENABLE }, \
{ "disable-plugins", no_argument, 0, OPT_PLUGINS_DISABLE }, \
{ "enable-tap", no_argument, 0, OPT_TAP_ENABLE }, \ { "enable-tap", no_argument, 0, OPT_TAP_ENABLE }, \
{ "disable-tap", no_argument, 0, OPT_TAP_DISABLE }, \ { "disable-tap", no_argument, 0, OPT_TAP_DISABLE }, \
{ "enable-drag", no_argument, 0, OPT_DRAG_ENABLE }, \ { "enable-drag", no_argument, 0, OPT_DRAG_ENABLE }, \
@ -117,7 +122,8 @@ enum configuration_options {
{ "set-calibration", required_argument, 0, OPT_CALIBRATION }, \ { "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-mode", required_argument, 0, OPT_ERASER_BUTTON_MODE }, \
{ "set-eraser-button-button", required_argument, 0, OPT_ERASER_BUTTON_BUTTON } { "set-eraser-button-button", required_argument, 0, OPT_ERASER_BUTTON_BUTTON },\
{ "set-plugin-path", required_argument, 0, OPT_PLUGIN_PATH }
/* Note: New arguments should be added to shell completions */ /* Note: New arguments should be added to shell completions */
@ -144,6 +150,9 @@ enum tools_backend { BACKEND_NONE, BACKEND_DEVICE, BACKEND_UDEV };
struct tools_options { struct tools_options {
char match[256]; char match[256];
int plugins;
char **plugin_paths;
int tapping; int tapping;
int drag; int drag;
int drag_lock; int drag_lock;
@ -183,7 +192,9 @@ struct libinput *
tools_open_backend(enum tools_backend which, tools_open_backend(enum tools_backend which,
const char **seat_or_devices, const char **seat_or_devices,
bool verbose, bool verbose,
bool *grab); bool *grab,
bool with_plugins,
char **plugin_paths);
void void
tools_device_apply_config(struct libinput_device *device, tools_device_apply_config(struct libinput_device *device,
struct tools_options *options); struct tools_options *options);