[geany/geany] 6e5ca6: plugins: add geany_plugin_register_proxy() to the plugin API
Thomas Martitz
git-noreply at xxxxx
Mon Oct 5 20:11:12 UTC 2015
Branch: refs/heads/master
Author: Thomas Martitz <kugel at rockbox.org>
Committer: Thomas Martitz <kugel at rockbox.org>
Date: Mon, 05 Oct 2015 20:11:12 UTC
Commit: 6e5ca69e2e9ddbfc2cc44527a1aa5fca8db09167
https://github.com/geany/geany/commit/6e5ca69e2e9ddbfc2cc44527a1aa5fca8db09167
Log Message:
-----------
plugins: add geany_plugin_register_proxy() to the plugin API
This function finally allows plugins to register themselves as a proxy
for one or more file extensions.
Lots of documentation is added to doc/plugins.dox, please refer to that for more
details.
Modified Paths:
--------------
doc/plugins.dox
src/plugindata.h
src/plugins.c
Modified: doc/plugins.dox
397 lines changed, 397 insertions(+), 0 deletions(-)
===================================================================
@@ -43,6 +43,7 @@ GeanyFuncs::cleanup functions).
@section pluginsupport Plugin Support
- @link howto Plugin HowTo @endlink - get started
+- @ref proxy
- @ref legacy
- @link plugindata.h Plugin Datatypes and Macros @endlink
- @link pluginsignals.c Plugin Signals @endlink
@@ -725,4 +726,400 @@ void geany_load_module(GeanyPlugin *plugin)
@endcode
+ at page proxy Proxy Plugin HowTo
+
+ at section proxy_intro Introduction
+
+Geany has built-in support for plugins. These plugins can alter the way Geany operates in many
+imaginable ways which leaves little to be desired.
+
+However, there is one significant short-coming. Due to the infrastructure, Geany's built-in support
+only covers plugins written in C, perhaps C++ and Vala. Basically all languages which can be
+compiled into native shared libraries and can link GTK libraries. This excludes dynamic languages
+such as Python.
+
+Geany provides a mechanism to enable support for those languages. Native plugins can register as
+proxy plugins by being a normal plugin to the Geany-side and by providing a bridge to write plugins
+in another language on the other side.
+
+These plugins are also called sub-plugins. This refers to the relation to their proxy.
+To Geany they are first-class citizens.
+
+ at section proxy_protocol Writing a Proxy Plugin
+
+The basic idea is that a proxy plugin provides methods to match, load and unload one or more
+sub-plugin plugins in an abstract manner:
+
+ - Matching consists of providing a list of supported file extensions for the sub-plugins and
+ a mechanism to resolve file extension uncertainty or ambiguity. The matching makes the plugin
+ visible to the user within the Plugin Manager.
+ - Loading consists of loading the sub-plugin's file, passing the file to some form of interpreter
+ and calling GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() on behalf of the sub-plugin
+ at some point.
+ - Unloading simply reverses the effect of loading.
+
+For providing these methods, GeanyPlugin has a field GeanyProxyFuncs which contains three function
+pointers which must be initialized proir to calling geany_plugin_register_proxy(). This should be
+done in the GeanyPluginFuncs::init function of the proxy plugin.
+
+ - In the call to geany_plugin_register_proxy() the proxy plugin passes a list of file extensions.
+ When Geany scans through its plugin directories as usual it will also look for files with
+ that extensions and consider found files as plugin candidate.
+ - GeanyProxyFuncs::probe may be implemented to probe if a plugin candidate (that has one of the
+ provided file extensions) is actually a plugin. This may depend on the plugin file itself in
+ case of ambiguity or availability of runtime dependencies or even configuration.
+ @ref PROXY_IGNORED or @ref PROXY_MATCHED should be returned, possibly in combination
+ with the @ref PROXY_NOLOAD flag. Not implementing GeanyProxyFuncs::probe at all is eqivalent to
+ always returning @ref PROXY_MATCHED.
+ - GeanyProxyFuncs::load must be implemented to actually load the plugin. It is called by Geany
+ when the user enables the sub-plugin. What "loading" means is entirely up to the proxy plugin and
+ probably depends on the interpreter of the dynamic language that shall be supported. After
+ setting everything up as necessary GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() must
+ be called to register the sub-plugin.
+ - GeanyProxyFuncs::unload must be implemented and is called when the user unchecks the sub-plugin
+ or when Geany exits. Here, the proxy should release any references or memory associated to the
+ sub-plugin. Note that if GeanyProxyFuncs::load didn't succeed, i.e. didn't successfully register
+ the sub-plugin, then this function won't be called.
+
+GeanyProxyFuncs::load and GeanyProxyFuncs::unload receive two GeanyPlugin pointers: One that
+corresponds to the proxy itself and another that corresponds to the sub-plugin. The sub-plugin's
+one may be used to call various API functions on behalf of the sub-plugin, including
+GEANY_PLUGIN_REGISTER() and GEANY_PLUGIN_REGISTER_FULL().
+
+GeanyProxyFuncs::load may return a pointer that is passed back to GeanyProxyFuncs::unload. This can
+be used to store proxy-defined but sub-plugin-specific data required for unloading. However, this
+pointer is not passed to the sub-plugin's GeanyPluginFuncs. To arrange for that, you want to call
+GEANY_PLUGIN_REGISTER_FULL(). This method is the key to enable proxy plugins to wrap the
+GeanyPluginFuncs of all sub-plugins and yet multiplex between multiple sub-plugin, for example by
+storing a per-sub-plugin interpreter context.
+
+ at note If the pointer returned from GeanyProxyFuncs::load is the same that is passed to
+GEANY_PLUGIN_REGISTER_FULL() then you must pass NULL as free_func, because that would be invoked
+prior to unloading. Insert the corresponding code into GeanyProxyFuncs::unload.
+
+ at section proxy_compat_guideline Guideline for Checking Compatiblity
+
+Determining if a plugin candidate is compatible is not a single test. There are multiple levels and
+each should be handled differently in order to give the user a consistent feedback.
+
+Consider the 5 basic cases:
+
+1) A candidate comes with a suitable file extension but is not a workable plugin file at all. For
+example, your proxy supports plugins written in a shell script (.sh) but the shebang of that script
+points to an incompatible shell (or even lacks a shebang). You should check for this in
+GeanyProxyFuncs::probe() and return @ref PROXY_IGNORED which hides that script from the Plugin
+Manager and allows other enabled proxy plugins to pick it up. GeanyProxyFuncs::probe() returning
+ at ref PROXY_IGNORED is an indication that the candidate is meant for another proxy, or the user
+placed the file by accident in one of Geany's plugin directories. In other words the candidate
+simply doesn't correspond to your proxy. Thus any noise by debug messages for this case is
+undesirable.
+
+2) A proxy plugin provides its own, versioned API to sub-plugin. The API version of the sub-plugin
+is not compatible with the API exposed by the proxy. GeanyProxyFuncs::probe() should never perform
+a version check because its sole purpose is to indicate a proxy's correspondence to a given
+candidate. It should return @ref PROXY_MATCHED instead. Later, Geany will invoke the
+GeanyProxyFuncs::load(), and this function is the right place for a version check. If it fails then
+you simply do not call GEANY_PLUGIN_REGISTER(), but rather print a debug message. The result is
+that the sub-plugin is not shown in the Plugin Manager at all. This is consistent with the
+treatment of native plugins by Geany.
+
+3) The sub-plugin is also depending on Geany's API version (whether it is or not depends on the
+design of the proxy). In this case do not do anything special but forward the API version the
+sub-plugin is written/compiled against to GEANY_PLUGIN_REGISTER(). Here, Geany will perform its own
+compatiblity check, allowing for a consistent user feedback. The result is again that the
+sub-plugin is hidden from the Plugin Manager, like in case 2. But Geany will print a debug message
+so you can skip that.
+
+
+If you have even more cases try to fit it into case 1 or 2, depending on whether other proxy
+plugins should get a chance to load the candidate or not.
+
+ at section proxy_dep_guideline Guideline for Runtime Errors
+
+A sub-plugin might not be able to run even if it's perfectly compatible with its proxy. This
+includes the case when it lacks certain runtime dependencies such as programs or modules but also
+syntactic problems or other errors.
+
+There are two basic classes:
+
+1) Runtime errors that can be determined at load time. For example, the shebang of a script
+indicates a specific interpeter version but that version is not installed on the system. Your proxy
+should respond the same way as for version-incompatible plugins: don't register the plugin at
+all, but leave a message the user suggesting what has to be installed in order to work. Handle
+syntax errors in the scripts of sub-plugins the same way if possible.
+
+2) Runtime errors that cannot be determined without actually running the plugin. An example would
+be missing modules in Python scripts. If your proxy has no way of foreseeing the problem the plugin
+will be registered normally. However, you can catch runtime errors by implementing
+GeanyPluginFuncs::init() on the plugin's behalf. This is called after user activation and allows to
+indicate errors by returning @c FALSE. However, allowing the user to enable a plugin and then
+disabling anyway is a poor user experience.
+
+Therefore, if possible, try to fail fast and disallow registration.
+
+ at section Proxy Plugin Example
+
+In this section a dumb example proxy plugin is shown in order to give a practical starting point.
+The sub-plugin are not actually code but rather a ini-style description of one or more menu items
+that are added to Geany's tools menu and a help dialog. Real world sub-plugins would contain actual
+code, usually written in a scripting language.
+
+A sub-plugin file looks like this:
+
+ at code{.ini}
+#!!PROXY_MAGIC!!
+
+[Init]
+item0 = Bam
+item1 = Foo
+item2 = Bar
+
+[Help]
+text = I'm a simple test. Nothing to see!
+
+[Info]
+name = Demo Proxy Tester
+description = I'm a simple test. Nothing to see!
+version = 0.1
+author = The Geany developer team
+ at endcode
+
+The first line acts as a verification that this file is truly a sub-plugin. Within the [Init] section
+there is the menu items for Geany's tools menu. The [Help] section declares the sub-plugins help
+text which is shown in its help dialog (via GeanyPluginFuncs::help). The [Info] section is
+used as-is for filling the sub-plugins PluginInfo fields.
+
+That's it, this dumb format is purely declarative and contains no logic. Yet we will create plugins
+from it.
+
+We start by registering the proxy plugin to Geany. There is nothing special to it compared to
+normal plugins. A proxy plugin must also fill its own @ref PluginInfo and @ref GeanyPluginFuncs,
+followed by registering through GEANY_PLUGIN_REGISTER().
+
+
+ at code{.c}
+
+/* Called by Geany to initialize the plugin. */
+static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+ // ...
+}
+
+
+/* Called by Geany before unloading the plugin. */
+static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data)
+{
+ // ...
+}
+
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+ plugin->info->name = _("Demo Proxy");
+ plugin->info->description = _("Example Proxy.");
+ plugin->info->version = "0.1";
+ plugin->info->author = _("The Geany developer team");
+
+ plugin->funcs->init = demoproxy_init;
+ plugin->funcs->cleanup = demoproxy_cleanup;
+
+ GEANY_PLUGIN_REGISTER(plugin, 225);
+}
+
+ at endcode
+
+The next step is to actually register as a proxy plugin. This is done in demoproxy_init().
+As previously mentioned, it needs a list of accepted file extensions and a set of callback
+functions.
+
+ at code{.c}
+static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+ const gchar *extensions[] = { "ini", "px", NULL };
+
+ plugin->proxy_funcs->probe = demoproxy_probe;
+ plugin->proxy_funcs->load = demoproxy_load;
+ plugin->proxy_funcs->unload = demoproxy_unload;
+
+ return geany_plugin_register_proxy(plugin, extensions);
+}
+
+ at endcode
+
+The callback functions deserve a closer look.
+
+As already mentioned the file format includes a magic first line which must be present.
+GeanyProxyFuncs::probe() verifies that it's present and avoids showing the sub-plugin in the
+Plugin Manager if not.
+
+ at code{.c}
+static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata)
+{
+ /* We know the extension is right (Geany checks that). For demo purposes we perform an
+ * additional check. This is not necessary when the extension is unique enough. */
+ gboolean match = FALSE;
+ gchar linebuf[128];
+ FILE *f = fopen(filename, "r");
+ if (f != NULL)
+ {
+ if (fgets(linebuf, sizeof(linebuf), f) != NULL)
+ match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n");
+ fclose(f);
+ }
+ return match ? PROXY_MATCHED : PROXY_IGNORED;
+}
+ at endcode
+
+GeanyProxyFuncs::load is a bit more complex. It reads the file, fills the sub-plugin's PluginInfo
+fields and calls GEANY_PLUGIN_REGISTER_FULL(). Additionally, it creates a per-plugin context that
+holds GKeyFile instance (a poor man's interpeter context). You can also see that it does not call
+GEANY_PLUGIN_REGISTER_FULL() if g_key_file_load_from_file() found an error (probably a syntax
+problem) which means the sub-plugin cannot be enabled.
+
+It also installs wrapper functions for the sub-plugin's GeanyPluginFuncs as ini files aren't code.
+It's very likely that your proxy needs something similar because you can only install function
+pointers to native code.
+
+ at code{.c}
+typedef struct {
+ GKeyFile *file;
+ gchar *help_text;
+ GSList *menu_items;
+}
+PluginContext;
+
+
+static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata);
+static void proxy_help(GeanyPlugin *plugin, gpointer pdata);
+static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata);
+
+
+static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin,
+ const gchar *filename, gpointer pdata)
+{
+ GKeyFile *file;
+ gboolean result;
+
+ file = g_key_file_new();
+ result = g_key_file_load_from_file(file, filename, 0, NULL);
+
+ if (result)
+ {
+ PluginContext *data = g_new0(PluginContext, 1);
+ data->file = file;
+
+ plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL);
+ plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL);
+ plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL);
+ plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL);
+
+ plugin->funcs->init = proxy_init;
+ plugin->funcs->help = proxy_help;
+ plugin->funcs->cleanup = proxy_cleanup;
+
+ /* Cannot pass g_free as free_func be Geany calls it before unloading, and since
+ * demoproxy_unload() accesses the data this would be catastrophic */
+ GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL);
+ return data;
+ }
+
+ g_key_file_free(file);
+ return NULL;
+}
+ at endcode
+
+demoproxy_unload() simply releases all resources aquired in demoproxy_load(). It does not have to
+do anything else in for unloading.
+
+ at code{.c}
+static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata)
+{
+ PluginContext *data = load_data;
+
+ g_free((gchar *)plugin->info->name);
+ g_free((gchar *)plugin->info->description);
+ g_free((gchar *)plugin->info->version);
+ g_free((gchar *)plugin->info->author);
+
+ g_key_file_free(data->file);
+ g_free(data);
+}
+ at endcode
+
+Finally the demo_proxy's wrapper GeanyPluginFuncs. They are called for each possible sub-plugin and
+therefore have to multiplex between each using the plugin-defined data pointer. Each is called by
+Geany as if it were an ordinary, native plugin.
+
+proxy_init() actually reads the sub-plugin's file using GKeyFile APIs. It prepares for the help
+dialog and installs the menu items. proxy_help() is called when the user clicks the help button in
+the Plugin Manager. Consequently, this fires up a suitable dialog, although with a dummy message.
+proxy_cleanup() frees all memory allocated in proxy_init().
+
+ at code{.c}
+static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+ PluginContext *data;
+ gint i = 0;
+ gchar *text;
+
+ data = (PluginContext *) pdata;
+
+ /* Normally, you would instruct the VM/interpreter to call into the actual plugin. The
+ * plugin would be identified by pdata. Because there is no interpreter for
+ * .ini files we do it inline, as this is just a demo */
+ data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL);
+ while (TRUE)
+ {
+ GtkWidget *item;
+ gchar *key = g_strdup_printf("item%d", i++);
+ text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL);
+ g_free(key);
+
+ if (!text)
+ break;
+
+ item = gtk_menu_item_new_with_label(text);
+ gtk_widget_show(item);
+ gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item);
+ gtk_widget_set_sensitive(item, FALSE);
+ data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item);
+ g_free(text);
+ }
+
+ return TRUE;
+}
+
+
+static void proxy_help(GeanyPlugin *plugin, gpointer pdata)
+{
+ PluginContext *data;
+ GtkWidget *dialog;
+
+ data = (PluginContext *) pdata;
+
+ dialog = gtk_message_dialog_new(
+ GTK_WINDOW(plugin->geany_data->main_widgets->window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ "%s", data->help_text);
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ _("(From the %s plugin)"), plugin->info->name);
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+
+static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+ PluginContext *data = (PluginContext *) pdata;
+
+ g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy);
+ g_free(data->help_text);
+}
+ at endcode
+
+
*/
Modified: src/plugindata.h
43 lines changed, 38 insertions(+), 5 deletions(-)
===================================================================
@@ -240,6 +240,7 @@ GeanyData;
#define geany geany_data /**< Simple macro for @c geany_data that reduces typing. */
typedef struct GeanyPluginFuncs GeanyPluginFuncs;
+typedef struct GeanyProxyFuncs GeanyProxyFuncs;
/** Basic information for the plugin and identification.
* @see geany_plugin. */
@@ -248,7 +249,8 @@ typedef struct GeanyPlugin
PluginInfo *info; /**< Fields set in plugin_set_info(). */
GeanyData *geany_data; /**< Pointer to global GeanyData intance */
GeanyPluginFuncs *funcs; /**< Functions implemented by the plugin, set in geany_load_module() */
-
+ GeanyProxyFuncs *proxy_funcs; /**< Hooks implemented by the plugin if it wants to act as a proxy
+ Must be set prior to calling geany_plugin_register_proxy() */
struct GeanyPluginPrivate *priv; /* private */
}
GeanyPlugin;
@@ -347,24 +349,55 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr
geany_plugin_register_full((plugin), GEANY_API_VERSION, \
(min_api_version), GEANY_ABI_VERSION, (pdata), (free_func))
+/** Return values for GeanyProxyHooks::probe()
+ *
+ * Only @c PROXY_IGNORED, @c PROXY_MATCHED or @c PROXY_MATCHED|PROXY_NOLOAD
+ * are valid return values.
+ *
+ * @see geany_plugin_register_proxy() for a full description of the proxy plugin mechanisms.
+ *
+ * @since 1.26 (API 226)
+ */
typedef enum
{
+ /** The proxy is not responsible at all, and Geany or other plugins are free
+ * to probe it.
+ **/
PROXY_IGNORED,
+ /** The proxy is responsible for this file, and creates a plugin for it */
PROXY_MATCHED,
+ /** The proxy is does not directly load it, but it's still tied to the proxy
+ *
+ * This is for plugins that come in multiple files where only one of these
+ * files is relevant for the plugin creation (for the PM dialog). The other
+ * files should be ignored by Geany and other proxies. Example: libpeas has
+ * a .plugin and a .so per plugin. Geany should not process the .so file
+ * if there is a corresponding .plugin.
+ */
PROXY_NOLOAD = 0x100,
}
GeanyProxyProbeResults;
-/* Hooks that need to be implemented for every proxy */
-typedef struct _GeanyProxyFuncs
+
+/** Hooks that need to be implemented by every proxy
+ *
+ * @see geany_plugin_register_proxy() for a full description of the proxy mechanism.
+ *
+ * @since 1.26 (API 226)
+ **/
+struct GeanyProxyFuncs
{
+ /** Called to determine whether the proxy is truly responsible for the requested plugin.
+ * A NULL pointer assumes the probe() function would always return @ref PROXY_MATCHED */
gint (*probe) (GeanyPlugin *proxy, const gchar *filename, gpointer pdata);
+ /** Called after probe(), to perform the actual job of loading the plugin */
gpointer (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata);
+ /** Called when the user initiates unloading of a plugin, e.g. on Geany exit */
void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata);
-}
-GeanyProxyFuncs;
+};
+gint geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions);
/* Deprecated aliases */
#ifndef GEANY_DISABLE_DEPRECATED
Modified: src/plugins.c
52 lines changed, 52 insertions(+), 0 deletions(-)
===================================================================
@@ -653,6 +653,7 @@ plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add
/* Fields of plugin->info/funcs must to be initialized by the plugin */
plugin->public.info = &plugin->info;
plugin->public.funcs = &plugin->cbs;
+ plugin->public.proxy_funcs = &plugin->proxy_cbs;
if (plugin_loaded(plugin))
{
@@ -1758,4 +1759,55 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
}
+/** Register the plugin as a proxy for other plugins
+ *
+ * Proxy plugins register a list of file extensions and a set of callbacks that are called
+ * appropriately. A plugin can be a proxy for multiple types of sub-plugins by handling
+ * separate file extensions, however they must share the same set of hooks, because this
+ * function can only be called at most once per plugin.
+ *
+ * Each callback receives the plugin-defined data as parameter (see geany_plugin_register()). The
+ * callbacks must be set prior to calling this, by assigning to @a plugin->proxy_funcs.
+ * GeanyProxyFuncs::load and GeanyProxyFuncs::unload must be implemented.
+ *
+ * Nested proxies are unsupported at this point (TODO).
+ *
+ * @note It is entirely up to the proxy to provide access to Geany's plugin API. Native code
+ * can naturally call Geany's API directly, for interpreted languages the proxy has to
+ * implement some kind of bindings that the plugin can use.
+ *
+ * @see proxy for detailed documentation and an example.
+ *
+ * @param plugin The pointer to the plugin's GeanyPlugin instance
+ * @param extensions A @c NULL-terminated string array of file extensions, excluding the dot.
+ * @return @c TRUE if the proxy was successfully registered, otherwise @c FALSE
+ *
+ * @since 1.26 (API 226)
+ */
+GEANY_API_SYMBOL
+gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions)
+{
+ Plugin *p;
+ const gchar **ext;
+
+ g_return_val_if_fail(plugin != NULL, FALSE);
+ g_return_val_if_fail(extensions != NULL, FALSE);
+ g_return_val_if_fail(*extensions != NULL, FALSE);
+ g_return_val_if_fail(plugin->proxy_funcs->load != NULL, FALSE);
+ g_return_val_if_fail(plugin->proxy_funcs->unload != NULL, FALSE);
+
+ p = plugin->priv;
+
+ foreach_strv(ext, extensions)
+ {
+ PluginProxy *proxy = g_new(PluginProxy, 1);
+ g_strlcpy(proxy->extension, *ext, sizeof(proxy->extension));
+ proxy->plugin = p;
+ /* prepend, so that plugins automatically override core providers for a given extension */
+ g_ptr_array_insert(active_proxies, 0, proxy);
+ }
+
+ return TRUE;
+}
+
#endif
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
More information about the Commits
mailing list