diff options
Diffstat (limited to 'degesch.c')
-rw-r--r-- | degesch.c | 192 |
1 files changed, 192 insertions, 0 deletions
@@ -1352,6 +1352,30 @@ REF_COUNTABLE_METHODS (server) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +struct plugin +{ + LIST_HEADER (struct plugin) + + char *name; ///< Name of the plugin + struct plugin_vtable *vtable; ///< Methods +}; + +struct plugin_vtable +{ + /// Unregister and free the plugin including all relevant resources + void (*free) (struct plugin *self); +}; + +static void +plugin_destroy (struct plugin *self) +{ + self->vtable->free (self); + free (self->name); + free (self); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + struct app_context { bool no_colors; ///< Disable attribute printing @@ -1411,6 +1435,8 @@ struct app_context bool running_backlog_helper; ///< Running a backlog helper int terminal_suspended; ///< Terminal suspension level + + struct plugin *plugins; ///< Loaded plugins } *g_ctx; @@ -1482,6 +1508,10 @@ app_context_init (struct app_context *self) static void app_context_free (struct app_context *self) { + // Plugins can try to use of the other fields when destroyed + LIST_FOR_EACH (struct plugin, iter, self->plugins) + plugin_destroy (iter); + config_free (&self->config); for (size_t i = 0; i < ATTR_COUNT; i++) { @@ -1749,6 +1779,11 @@ static struct config_schema g_config_behaviour[] = .type = CONFIG_ITEM_INTEGER, .validate = config_validate_nonnegative, .default_ = "600" }, + + { .name = "plugin_autoload", + .comment = "Plugins to automatically load on start", + .type = CONFIG_ITEM_STRING_ARRAY, + .validate = config_validate_nonjunk_string }, {} }; @@ -7094,6 +7129,125 @@ server_rename (struct app_context *ctx, struct server *s, const char *new_name) } } +// --- Plugins ----------------------------------------------------------------- + +typedef struct plugin *(*plugin_load_fn) + (struct app_context *ctx, const char *filename, struct error **e); + +// We can potentially add support for other scripting languages if so desired, +// however this possibility is just a byproduct of abstraction +static plugin_load_fn g_plugin_loaders[] = +{ +}; + +static struct plugin * +plugin_load_from_filename (struct app_context *ctx, const char *filename, + struct error **e) +{ + struct plugin *plugin = NULL; + struct error *error = NULL; + for (size_t i = 0; i < N_ELEMENTS (g_plugin_loaders); i++) + if ((plugin = g_plugin_loaders[i](ctx, filename, &error)) || error) + break; + + if (error) + error_propagate (e, error); + else if (!plugin) + FAIL ("no plugin handler for \"%s\"", filename); + return plugin; +} + +static struct plugin * +plugin_find (struct app_context *ctx, const char *name) +{ + LIST_FOR_EACH (struct plugin, iter, ctx->plugins) + if (!strcmp (name, iter->name)) + return iter; + return NULL; +} + +static char * +plugin_resolve_relative_filename (const char *filename) +{ + struct str_vector paths; + str_vector_init (&paths); + get_xdg_data_dirs (&paths); + char *result = resolve_relative_filename_generic + (&paths, PROGRAM_NAME "/plugins/", filename); + str_vector_free (&paths); + return result; +} + +static struct plugin * +plugin_load_by_name (struct app_context *ctx, const char *name, + struct error **e) +{ + struct plugin *plugin = plugin_find (ctx, name); + if (plugin) + FAIL ("plugin already loaded"); + + // As a side effect, a plugin can be loaded multiple times by giving + // various relative or non-relative paths to the function; this is not + // supposed to be fool-proof though, that requires other mechanisms + char *filename = resolve_filename (name, plugin_resolve_relative_filename); + if (!filename) + FAIL ("file not found"); + + plugin = plugin_load_from_filename (ctx, filename, e); + free (filename); + return plugin; +} + +static void +plugin_load (struct app_context *ctx, const char *name) +{ + struct error *e = NULL; + struct plugin *plugin = plugin_load_by_name (ctx, name, &e); + if (plugin) + { + log_global_status (ctx, "Plugin \"#s\" loaded", name); + LIST_PREPEND (ctx->plugins, plugin); + plugin->name = xstrdup (name); + } + else + { + log_global_error (ctx, "Can't load plugin \"#s\": #s", + name, e->message); + error_free (e); + } +} + +static void +plugin_unload (struct app_context *ctx, const char *name) +{ + struct plugin *plugin = plugin_find (ctx, name); + if (!plugin) + log_global_error (ctx, "Can't unload plugin \"#s\": #s", + name, "plugin not loaded"); + else + { + log_global_status (ctx, "Plugin \"#s\" unloaded", name); + LIST_UNLINK (ctx->plugins, plugin); + plugin_destroy (plugin); + } +} + +static void +load_plugins (struct app_context *ctx) +{ + const char *plugins = get_config_string + (ctx->config.root, "behaviour.plugin_autoload"); + if (plugins) + { + struct str_vector v; + str_vector_init (&v); + cstr_split_ignore_empty (plugins, ',', &v); + for (size_t i = 0; i < v.len; i++) + plugin_load (ctx, v.vector[i]); + str_vector_free (&v); + } +} + // --- User input handling ----------------------------------------------------- // HANDLER_NEEDS_REG is primarily for message sending commands, @@ -7502,6 +7656,40 @@ handle_command_save (struct handler_args *a) return true; } +static void +show_plugin_list (struct app_context *ctx) +{ + log_global_indent (ctx, ""); + log_global_indent (ctx, "Plugins:"); + LIST_FOR_EACH (struct plugin, iter, ctx->plugins) + log_global_indent (ctx, " #s", iter->name); +} + +static bool +handle_command_plugin (struct handler_args *a) +{ + char *action = cut_word (&a->arguments); + if (!*action || !strcasecmp_ascii (action, "list")) + show_plugin_list (a->ctx); + else if (!strcasecmp_ascii (action, "load")) + { + if (!*a->arguments) + return false; + + plugin_load (a->ctx, cut_word (&a->arguments)); + } + else if (!strcasecmp_ascii (action, "unload")) + { + if (!*a->arguments) + return false; + + plugin_unload (a->ctx, cut_word (&a->arguments)); + } + else + return false; + return true; +} + static bool show_aliases_list (struct app_context *ctx) { @@ -8219,6 +8407,9 @@ g_command_handlers[] = { "save", "Save configuration", NULL, handle_command_save, 0 }, + { "plugin", "Manage plugins", + "list | load <name> | unload <name>", + handle_command_plugin, 0 }, { "alias", "List or set aliases", "[<name> <definition>]", @@ -10189,6 +10380,7 @@ main (int argc, char *argv[]) toggle_bracketed_paste (true); // Finally, we juice the configuration for some servers to create + load_plugins (&ctx); load_servers (&ctx); ctx.polling = true; |