From d557a649fd3a9fdad053ef7164b0a6f4717b20c2 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 22 Apr 2025 10:25:55 +1000 Subject: [PATCH] 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: --- completion/zsh/_libinput | 3 ++ meson.build | 5 ++ src/libinput-plugin.c | 50 +++++++++++++++++++ src/libinput.h | 83 +++++++++++++++++++++++++++++++ src/libinput.sym | 6 +++ test/litest.c | 2 + tools/libinput-debug-events.c | 8 ++- tools/libinput-debug-gui.c | 8 ++- tools/libinput-debug-tablet-pad.c | 9 +++- tools/libinput-debug-tablet.c | 8 ++- tools/libinput-list-devices.c | 9 +++- tools/shared.c | 79 +++++++++++++++++++++++++++-- tools/shared.h | 15 +++++- 13 files changed, 272 insertions(+), 13 deletions(-) diff --git a/completion/zsh/_libinput b/completion/zsh/_libinput index 003e2fba..05323bb4 100644 --- a/completion/zsh/_libinput +++ b/completion/zsh/_libinput @@ -93,6 +93,9 @@ __all_seats() + '(natural-scrolling)' \ '--enable-natural-scrolling[Enable natural scrolling]' \ '--disable-natural-scrolling[Disable natural scrolling]' \ + + '(plugins)' \ + '--enable-plugins[Enable plugins]' \ + '--disable-plugins[Disable plugins]' \ + '(tap-to-click)' \ '--enable-tap[Enable tap-to-click]' \ '--disable-tap[Disable tap-to-click]' diff --git a/meson.build b/meson.build index 44a6a7c1..3da428cb 100644 --- a/meson.build +++ b/meson.build @@ -168,6 +168,8 @@ dep_libevdev = dependency('libevdev', version: '>= 1.10.0') dep_lm = cc.find_library('m', required : false) dep_rt = cc.find_library('rt', required : false) +config_h.set10('HAVE_PLUGINS', true) + # Include directories includes_include = include_directories('include') includes_src = include_directories('src') @@ -381,6 +383,9 @@ if get_option('internal-event-debugging') config_h.set('EVENT_DEBUGGING', 1) 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') src_libinput = src_libfilter + [ 'src/libinput.c', diff --git a/src/libinput-plugin.c b/src/libinput-plugin.c index 107f8cc7..181b8cf7 100644 --- a/src/libinput-plugin.c +++ b/src/libinput-plugin.c @@ -327,6 +327,55 @@ libinput_plugin_notify_device_removed(struct libinput_plugin *plugin, 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 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 */ libinput_evdev_dispatch_plugin(libinput); } + void libinput_plugin_system_destroy(struct libinput_plugin_system *system) { diff --git a/src/libinput.h b/src/libinput.h index d99d55ed..70a14372 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -3690,6 +3690,89 @@ libinput_path_add_device(struct libinput *libinput, const char *path); void 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 * diff --git a/src/libinput.sym b/src/libinput.sym index 74ebad83..74955ecd 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -374,3 +374,9 @@ LIBINPUT_1.29 { libinput_tablet_tool_config_eraser_button_set_button; libinput_tablet_tool_config_eraser_button_set_mode; } 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; diff --git a/test/litest.c b/test/litest.c index d94a2eaf..fc92cdc3 100644 --- a/test/litest.c +++ b/test/litest.c @@ -2144,6 +2144,8 @@ litest_create_context(void) if (verbose) libinput_log_set_priority(libinput, LIBINPUT_LOG_PRIORITY_DEBUG); + libinput_plugin_system_load_plugins(libinput, LIBINPUT_PLUGIN_FLAG_NONE); + return libinput; } diff --git a/tools/libinput-debug-events.c b/tools/libinput-debug-events.c index 8a633657..7bb8166b 100644 --- a/tools/libinput-debug-events.c +++ b/tools/libinput-debug-events.c @@ -319,7 +319,13 @@ main(int argc, char **argv) if (verbose) 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) return EXIT_FAILURE; diff --git a/tools/libinput-debug-gui.c b/tools/libinput-debug-gui.c index 0c2fcfc5..36aa9dd6 100644 --- a/tools/libinput-debug-gui.c +++ b/tools/libinput-debug-gui.c @@ -2007,7 +2007,13 @@ main(int argc, char **argv) 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) return EXIT_FAILURE; diff --git a/tools/libinput-debug-tablet-pad.c b/tools/libinput-debug-tablet-pad.c index a389b9e2..51b678f6 100644 --- a/tools/libinput-debug-tablet-pad.c +++ b/tools/libinput-debug-tablet-pad.c @@ -528,7 +528,14 @@ main(int argc, char **argv) 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) return EXIT_FAILURE; diff --git a/tools/libinput-debug-tablet.c b/tools/libinput-debug-tablet.c index 0d478751..08845ae1 100644 --- a/tools/libinput-debug-tablet.c +++ b/tools/libinput-debug-tablet.c @@ -602,7 +602,13 @@ main(int argc, char **argv) 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) return EXIT_FAILURE; diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c index 5eb0b4d0..5df5ea91 100644 --- a/tools/libinput-list-devices.c +++ b/tools/libinput-list-devices.c @@ -606,10 +606,15 @@ main(int argc, char **argv) } devices[ndevices++] = argv[optind]; } while (++optind < argc); - li = tools_open_backend(BACKEND_DEVICE, devices, false, &grab); + li = tools_open_backend(BACKEND_DEVICE, + devices, + false, + &grab, + false, + NULL); } else { 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) return 1; diff --git a/tools/shared.c b/tools/shared.c index 9553aa62..8928ef95 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -107,6 +107,8 @@ void tools_init_options(struct tools_options *options) { memset(options, 0, sizeof(*options)); + options->plugins = -1; + options->plugin_paths = NULL; options->tapping = -1; options->tap_map = -1; options->drag = -1; @@ -146,6 +148,15 @@ int tools_parse_option(int option, const char *optarg, struct tools_options *options) { 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: options->tapping = 1; break; @@ -494,8 +505,46 @@ static const struct libinput_interface interface = { .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 * -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(); if (!udev) { @@ -513,6 +562,9 @@ tools_open_udev(const char *seat, bool verbose, bool *grab) if (verbose) 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)) { fprintf(stderr, "Failed to set seat\n"); return NULL; @@ -522,7 +574,11 @@ tools_open_udev(const char *seat, bool verbose, bool *grab) } 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); 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); } + if (with_plugins) + tools_load_plugins(li, plugin_paths); + const char **p = paths; while (*p) { struct libinput_device *device = libinput_path_add_device(li, *p); @@ -559,7 +618,9 @@ struct libinput * tools_open_backend(enum tools_backend which, const char **seat_or_device, bool verbose, - bool *grab) + bool *grab, + bool with_plugins, + char **plugin_paths) { struct libinput *li; @@ -567,10 +628,18 @@ tools_open_backend(enum tools_backend which, switch (which) { 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; 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; default: abort(); diff --git a/tools/shared.h b/tools/shared.h index eb4195ec..c9c3bd67 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -77,10 +77,15 @@ enum configuration_options { OPT_SENDEVENTS, OPT_ERASER_BUTTON_MODE, OPT_ERASER_BUTTON_BUTTON, + OPT_PLUGINS_DISABLE, + OPT_PLUGINS_ENABLE, + OPT_PLUGIN_PATH, }; #define CONFIGURATION_OPTIONS \ { "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 }, \ { "disable-tap", no_argument, 0, OPT_TAP_DISABLE }, \ { "enable-drag", no_argument, 0, OPT_DRAG_ENABLE }, \ @@ -117,7 +122,8 @@ enum configuration_options { { "set-calibration", required_argument, 0, OPT_CALIBRATION }, \ { "set-area", required_argument, 0, OPT_AREA }, \ { "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 */ @@ -144,6 +150,9 @@ enum tools_backend { BACKEND_NONE, BACKEND_DEVICE, BACKEND_UDEV }; struct tools_options { char match[256]; + int plugins; + char **plugin_paths; + int tapping; int drag; int drag_lock; @@ -183,7 +192,9 @@ struct libinput * tools_open_backend(enum tools_backend which, const char **seat_or_devices, bool verbose, - bool *grab); + bool *grab, + bool with_plugins, + char **plugin_paths); void tools_device_apply_config(struct libinput_device *device, struct tools_options *options);