diff --git a/CHANGELOG.md b/CHANGELOG.md index b18455a6fe..927b67c0fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ! fullscreen border_width = 0 ``` +* picom now has a rudimentary plugin system. At the moment, the only thing you can do is loading custom backends with it. ## Notable changes diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 5f995ff62b..2387ce222a 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -76,6 +76,9 @@ OPTIONS *--write-pid-path* 'PATH':: Write process ID to a file. it is recommended to use an absolute path. +*--plugins* 'PATH':: + Specify plugins to load. Plugins will first be searched in current working directory (unless specified in the config file, in which case this step is skipped), then in `$XDG_CONFIG_HOME/picom/plugins`, then in `$XDG_CONFIG_DIRS/picom/plugins`. If all of the above fail, the plugin name is passed directly to the dynamic loader. Can be specified multiple times to load more than one plugins. + *--shadow-color* 'STRING':: Color of shadow, as a hex string ('#000000') diff --git a/src/config.c b/src/config.c index 2f884ea0e4..c14257d9fe 100644 --- a/src/config.c +++ b/src/config.c @@ -3,13 +3,11 @@ // Copyright (c) 2013 Richard Grenville #include -#include +#include #include #include -#include #include #include -#include #include #include #include @@ -20,16 +18,11 @@ #include -#include "c2.h" #include "common.h" -#include "compiler.h" #include "kernel.h" #include "log.h" -#include "region.h" #include "string_utils.h" #include "types.h" -#include "utils.h" -#include "win.h" #include "config.h" @@ -632,6 +625,18 @@ void *parse_window_shader_prefix_with_cwd(const char *src, const char **end, voi return parse_window_shader_prefix(src, end, cwd); } +bool load_plugin(const char *name, const char *include_dir) { + scoped_charp path = locate_auxiliary_file("plugins", optarg, include_dir); + void *handle = NULL; + if (!path) { + handle = dlopen(name, RTLD_LAZY); + } else { + log_debug("Plugin %s resolved to %s", name, path); + handle = dlopen(path, RTLD_LAZY); + } + return handle != NULL; +} + bool parse_config(options_t *opt, const char *config_file) { // clang-format off *opt = (struct options){ diff --git a/src/config.h b/src/config.h index b86e1b3bed..729c401679 100644 --- a/src/config.h +++ b/src/config.h @@ -354,6 +354,8 @@ typedef struct options { extern const char *const BACKEND_STRS[NUM_BKEND + 1]; +bool load_plugin(const char *name, const char *include_dir); + bool must_use parse_long(const char *, long *); bool must_use parse_int(const char *, int *); struct conv **must_use parse_blur_kern_lst(const char *, int *count); diff --git a/src/config_libconfig.c b/src/config_libconfig.c index e7f37b6e88..b1bf5be14e 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -628,6 +628,37 @@ bool parse_config_libconfig(options_t *opt, const char *config_file) { // Get options from the configuration file. We don't do range checking // right now. It will be done later + // Load user specified plugins at the very beginning, because list of backends + // depends on the plugins loaded. + auto plugins = config_lookup(&cfg, "plugins"); + if (plugins != NULL) { + sval = config_setting_get_string(plugins); + if (sval) { + if (!load_plugin(sval, NULL)) { + log_fatal("Failed to load plugin \"%s\".", sval); + goto out; + } + } else if (config_setting_is_array(plugins) || config_setting_is_list(plugins)) { + for (int i = 0; i < config_setting_length(plugins); i++) { + sval = config_setting_get_string_elem(plugins, i); + if (!sval) { + log_fatal("Invalid value for \"plugins\" at line " + "%d.", + config_setting_source_line(plugins)); + goto out; + } + if (!load_plugin(sval, NULL)) { + log_fatal("Failed to load plugin \"%s\".", sval); + goto out; + } + } + } else { + log_fatal("Invalid value for \"plugins\" at line %d.", + config_setting_source_line(plugins)); + goto out; + } + } + // --dbus lcfg_lookup_bool(&cfg, "dbus", &opt->dbus); diff --git a/src/options.c b/src/options.c index 71042d7fa7..2576bd72ee 100644 --- a/src/options.c +++ b/src/options.c @@ -332,6 +332,7 @@ static const struct picom_option picom_options[] = { [314] = {"show-all-xerrors", IGNORE(no_argument)}, ['b'] = {"daemon" , IGNORE(no_argument) , "Daemonize process."}, [256] = {"config" , IGNORE(required_argument), "Path to the configuration file."}, + [307] = {"plugins" , IGNORE(required_argument), "Plugins to load. Can be specified multiple times, each time with a single plugin."}, // Simple flags ['c'] = {"shadow" , ENABLE(shadow_enable) , "Enabled client-side shadows on windows."}, @@ -691,6 +692,7 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all bool *fork, int *exit_code) { setup_longopts(); + scoped_charp current_working_dir = getcwd(NULL, 0); int o = 0, longopt_idx = -1; // Pre-parse the command line arguments to check for --config and invalid @@ -706,7 +708,6 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all } else if (o == 'h') { usage(argv[0], 0); return true; - } else if (o == 'b') { *fork = true; } else if (o == 314) { @@ -714,10 +715,17 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all } else if (o == 318) { printf("%s\n", PICOM_VERSION); return true; + } else if (o == 307) { + // --plugin + if (!load_plugin(optarg, current_working_dir)) { + log_error("Failed to load plugin %s", optarg); + goto err; + } } else if (o == '?' || o == ':') { usage(argv[0], 1); goto err; } + // TODO(yshui) maybe log-level should be handled here. } // Check for abundant positional arguments