Branch: refs/heads/master
Author: Thomas Martitz <kugel(a)rockbox.org>
Committer: Thomas Martitz <kugel(a)rockbox.org>
Date: Sun, 23 Aug 2015 13:23:24 UTC
Commit: f2579141bb4188d4fc97eee494ba36e809661ddf
https://github.com/geany/geany/commit/f2579141bb4188d4fc97eee494ba36e809661…
Log Message:
-----------
plugins: Replace geany_plugin_register() pdata with a separate API function
The API function adds a free_func parameter, and can also be called
after geany_plugin_register(), i.e. in the plugin's init() callback. This
fixes a by-design memory leak and gives greater flexibility.
Modified Paths:
--------------
src/plugindata.h
src/pluginprivate.h
src/plugins.c
src/pluginutils.c
Modified: src/plugindata.h
7 lines changed, 4 insertions(+), 3 deletions(-)
===================================================================
@@ -305,7 +305,8 @@ struct GeanyPluginFuncs
};
gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_api_version,
- gint abi_version, gpointer pdata);
+ gint abi_version);
+void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify destroy_notify);
/** Convinience macro to register a plugin.
*
@@ -313,9 +314,9 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a
*
* @since 1.26 (API 225)
**/
-#define GEANY_PLUGIN_REGISTER(plugin, min_api_version, pdata) \
+#define GEANY_PLUGIN_REGISTER(plugin, min_api_version) \
geany_plugin_register((plugin), GEANY_API_VERSION, \
- (min_api_version), GEANY_ABI_VERSION, pdata)
+ (min_api_version), GEANY_ABI_VERSION)
/* Deprecated aliases */
#ifndef GEANY_DISABLE_DEPRECATED
Modified: src/pluginprivate.h
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -81,6 +81,7 @@ typedef struct GeanyPluginPrivate
GList *sources; /* GSources to destroy when unloading */
gpointer cb_data; /* user data passed back to functions in GeanyPluginFuncs */
+ GDestroyNotify cb_data_destroy; /* called when the plugin is unloaded, for cb_data */
LoadedFlags flags; /* bit-or of LoadedFlags */
}
GeanyPluginPrivate;
Modified: src/plugins.c
12 lines changed, 8 insertions(+), 4 deletions(-)
===================================================================
@@ -280,7 +280,6 @@ static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
* @param api_version The API version the plugin is compiled against (pass GEANY_API_VERSION)
* @param min_api_version The minimum API version required by the plugin
* @param abi_version The exact ABI version the plugin is compiled against (pass GEANY_ABI_VERSION)
- * @param pdata A data pointer to store plugin-specific data, will be passed to the plugin's callbacks
*
* @return TRUE if the plugin was successfully registered. Otherwise FALSE.
*
@@ -289,7 +288,7 @@ static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
**/
GEANY_API_SYMBOL
gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_api_version,
- gint abi_version, gpointer pdata)
+ gint abi_version)
{
Plugin *p;
GeanyPluginFuncs *cbs = plugin->funcs;
@@ -531,6 +530,8 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list)
return plugin;
err:
+ if (plugin->cb_data_destroy)
+ plugin->cb_data_destroy(plugin->cb_data);
if (! g_module_close(module))
g_warning("%s: %s", fname, g_module_error());
g_free(plugin->filename);
@@ -646,12 +647,15 @@ plugin_free(Plugin *plugin)
plugin_cleanup(plugin);
active_plugin_list = g_list_remove(active_plugin_list, plugin);
+ plugin_list = g_list_remove(plugin_list, plugin);
+
+ /* cb_data_destroy might be plugin code and must be called before unloading the module */
+ if (plugin->cb_data_destroy)
+ plugin->cb_data_destroy(plugin->cb_data);
if (! g_module_close(plugin->module))
g_warning("%s: %s", plugin->filename, g_module_error());
- plugin_list = g_list_remove(plugin_list, plugin);
-
g_free(plugin->filename);
g_free(plugin);
plugin = NULL;
Modified: src/pluginutils.c
47 lines changed, 47 insertions(+), 0 deletions(-)
===================================================================
@@ -520,4 +520,51 @@ void plugin_builder_connect_signals(GeanyPlugin *plugin,
}
+/** Add additional data that corresponds to the plugin.
+ *
+ * This is the data pointer passed to the individual plugin callbacks. It may be
+ * called as soon as geany_plugin_register() was called with success. When the
+ * plugin is unloaded, @a free_func is invoked for the data, which connects the
+ * data to the plugin's own life time.
+ *
+ * One intended use case is to set GObjects as data and have them destroyed automatically
+ * by passing g_object_unref() as @a free_func, so that member functions can be used
+ * for the @ref GeanyPluginFuncs (via wrappers) but you can set completely custom data.
+ *
+ * Be aware that this can only be called once.
+ *
+ * @param plugin The plugin provided by Geany
+ * @param pdata The plugin's data to associate, must not be @c NULL
+ * @param free_func The destroy notify
+ *
+ * @since 1.26 (API 225)
+ */
+GEANY_API_SYMBOL
+void geany_plugin_set_data(GeanyPlugin *plugin, gpointer pdata, GDestroyNotify free_func)
+{
+ Plugin *p = plugin->priv;
+
+ g_return_if_fail(PLUGIN_LOADED_OK(p));
+ /* Do not allow calling this only to set a notify. */
+ g_return_if_fail(pdata != NULL);
+ /* The rationale to allow only setting the data once is the following:
+ * In the future we want to support proxy plugins (which bind non-C plugins to
+ * Geany's plugin api). These proxy plugins might need to own the data pointer
+ * on behalf of the proxied plugin. However, if not, then the plugin should be
+ * free to use it. This way we can make sure the plugin doesn't accidently trash
+ * its proxy.
+ *
+ * Better a more limited API now that can be opened up later than a potentially
+ * wrong one that can only be replaced by another one. */
+ if (p->cb_data != NULL || p->cb_data_destroy != NULL)
+ {
+ g_warning("Double call to %s(), ignored!", G_STRFUNC);
+ return;
+ }
+
+ p->cb_data = pdata;
+ p->cb_data_destroy = free_func;
+}
+
+
#endif
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
Branch: refs/heads/master
Author: Thomas Martitz <kugel(a)rockbox.org>
Committer: Thomas Martitz <kugel(a)rockbox.org>
Date: Sun, 23 Aug 2015 13:23:22 UTC
Commit: babf0083353b1d3501fee439b6228c2fbe84cc82
https://github.com/geany/geany/commit/babf0083353b1d3501fee439b6228c2fbe84c…
Log Message:
-----------
plugins: Let plugins fill GeanyPlugin::callbacks instead of passing their own pointer
This is easier to handle if we decide to add callbacks. Since we can
zero-initialize callbacks before passing it to the plugin we can be certain as
to which callbacks the plugin knew about when it was compiled. This is exactly
the same method used for GeanyPlugin::info already and easier than inspecting
the API version.
Modified Paths:
--------------
src/plugindata.h
src/plugins.c
Modified: src/plugindata.h
14 lines changed, 8 insertions(+), 6 deletions(-)
===================================================================
@@ -237,12 +237,15 @@ GeanyData;
#define geany geany_data /**< Simple macro for @c geany_data that reduces typing. */
+typedef struct GeanyPluginFuncs GeanyPluginFuncs;
+
/** Basic information for the plugin and identification.
* @see geany_plugin. */
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() */
struct GeanyPluginPrivate *priv; /* private */
}
@@ -287,7 +290,7 @@ gboolean geany_load_module(GeanyPlugin *plugin);
*
* @since 1.26 (API 225)
**/
-typedef struct GeanyPluginFuncs
+struct GeanyPluginFuncs
{
/** Array of plugin-provided signal handlers @see PluginCallback */
PluginCallback *callbacks;
@@ -299,11 +302,10 @@ typedef struct GeanyPluginFuncs
void (*help) (GeanyPlugin *plugin, gpointer pdata);
/** Called when the plugin is disabled or when Geany exits (must not be @c NULL) */
void (*cleanup) (GeanyPlugin *plugin, gpointer pdata);
-}
-GeanyPluginFuncs;
+};
gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_api_version,
- gint abi_version, GeanyPluginFuncs *cbs, gpointer pdata);
+ gint abi_version, gpointer pdata);
/** Convinience macro to register a plugin.
*
@@ -311,9 +313,9 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a
*
* @since 1.26 (API 225)
**/
-#define GEANY_PLUGIN_REGISTER(plugin, min_api_version) \
+#define GEANY_PLUGIN_REGISTER(plugin, min_api_version, pdata) \
geany_plugin_register((plugin), GEANY_API_VERSION, \
- (min_api_version), GEANY_ABI_VERSION)
+ (min_api_version), GEANY_ABI_VERSION, pdata)
/* Deprecated aliases */
#ifndef GEANY_DISABLE_DEPRECATED
Modified: src/plugins.c
27 lines changed, 15 insertions(+), 12 deletions(-)
===================================================================
@@ -261,7 +261,12 @@ static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
/** Register a plugin to Geany.
*
* The plugin will show up in the plugin manager. The user can interact with
- * it based on the callbacks it provides and installed GUI elements.
+ * it based on the functions it provides and installed GUI elements.
+ *
+ * You must initialize the info and funcs fields of @ref GeanyPlugin
+ * appropriately prior to calling this, otherwise registration will fail. For
+ * info at least a valid name must be set (possibly localized). For funcs,
+ * at least init() and cleanup() functions must be implemented and set.
*
* The return value must be checked. It may be FALSE if the plugin failed to register which can
* mainly happen for two reasons (future Geany versions may add new failure conditions):
@@ -275,7 +280,6 @@ static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
* @param api_version The API version the plugin is compiled against (pass GEANY_API_VERSION)
* @param min_api_version The minimum API version required by the plugin
* @param abi_version The exact ABI version the plugin is compiled against (pass GEANY_ABI_VERSION)
- * @param cbs A statically allocated @ref GeanyPluginFuncs structure filled with callbacks
* @param pdata A data pointer to store plugin-specific data, will be passed to the plugin's callbacks
*
* @return TRUE if the plugin was successfully registered. Otherwise FALSE.
@@ -285,9 +289,10 @@ static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
**/
GEANY_API_SYMBOL
gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_api_version,
- gint abi_version, GeanyPluginFuncs *cbs, gpointer pdata)
+ gint abi_version, gpointer pdata)
{
Plugin *p;
+ GeanyPluginFuncs *cbs = plugin->funcs;
g_return_val_if_fail(plugin != NULL, FALSE);
@@ -295,19 +300,12 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a
/* Prevent registering incompatible plugins. */
if (! plugin_check_version(p, PLUGIN_VERSION_CODE(api_version, abi_version)))
return FALSE;
- /* If it ever becomes necessary we can save the api version in Plugin
- * and apply compat code on a per-plugin basis, because we learn about
- * the requested API version here. Also if we add to GeanyPluginFuncs then
- * we have to inspect the plugin's api so that we don't misinterpret
- * function pointers the plugin doesn't know anything about. */
- p->cbs.n = *cbs;
- p->cb_data = pdata;
/* Only init and cleanup callbacks are truly mandatory. */
if (! cbs->init || ! cbs->cleanup)
{
geany_debug("Plugin '%s' has no %s function - ignoring plugin!",
- cbs->init ? "cleanup" : "init", g_module_name(p->module));
+ g_module_name(p->module), cbs->init ? "cleanup" : "init");
}
else
{
@@ -317,6 +315,10 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a
p->flags = LOADED_OK;
}
+ /* If it ever becomes necessary we can save the api version in Plugin
+ * and apply compat code on a per-plugin basis, because we learn about
+ * the requested API version here. For now it's not necessary. */
+
return PLUGIN_LOADED_OK(p);
}
@@ -487,8 +489,9 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list)
plugin->filename = g_strdup(fname);
plugin->public.geany_data = &geany_data;
plugin->public.priv = plugin;
- /* Fields of plugin->info must to be initialized by the plugin */
+ /* Fields of plugin->info/funcs must to be initialized by the plugin */
plugin->public.info = &plugin->info;
+ plugin->public.funcs = &plugin->cbs.n;
g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module);
if (p_geany_load_module)
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
Branch: refs/heads/master
Author: Thomas Martitz <kugel(a)rockbox.org>
Committer: Thomas Martitz <kugel(a)rockbox.org>
Date: Sun, 23 Aug 2015 13:23:20 UTC
Commit: 721009e262e9bc279122566a60e14af0048d9094
https://github.com/geany/geany/commit/721009e262e9bc279122566a60e14af0048d9…
Log Message:
-----------
plugins: plugin loader redesign
The old plugin loader has a number of deficiencies:
- plugins need to export a couple of callback functions into the global namespace
- plugins need to export data pointers, that are written by Geany
- the exported functions have no user_data param, so there is no way to
pass context/state back to the plugin (it needs global storage for that)
- plugin registration is implicit, plugins have no way to not register themselves
(it may want that due to missing runtime dependencies)
- plugins perform the ABI/API verification, and even though we provide a
convinience wrapper, it may get that wrong
As a result, I designed a new loader with the following design principles
- semantics of callbacks should not change, but they they shouldn't be mess
with the global namespace
- each callback receives a self-identifying param (the GeanyPlugin instance) and
a plugin-defined data pointer for their own use
- explicit registration through a new API function
- in-core API/ABI checks
The following principles shall be left unchanged:
- The scan is done on startup and when the PM dialog is opened
- Geany allocates GeanyPluginPrivate for each plugin, and GeanyPlugin is
a member of it
- Geany initially probes for the validity of the plugin, including file type
and API/ABI check, thus Geany has the last word in determining what a
plugin is
- the PM dialog is updated with the proper, translated plugin information
- the PM dialog GUI and user interaction in general is unchanged
With the redesign, plugins export a single function: geany_load_module().
This is called when the GModule is loaded. The main purpose of this function
is to call geany_plugin_register() (new API function) to register the plugin.
This is the only function that is learned about through g_module_symbol().
Within this call the plugin should
a) set the localized info fields of GeanyPlugin::info
b) pass compiled-against and minimum API version as well as compiled-against
ABI version, to allow Geany to verify compatibility
c) pass a pointer to an instance of GeanyPluginFuncs
which holds pointers to enhanced versions of the known callbacks (except
configure_single which is dropped).
d) optionally pass a plugin-private data pointer for later callbacks
Enhanced means that all callbacks receive the GeanyPlugin pointer as the first
and a pdata pointer as the last. pdata is private to the plugin and is set
by geany_plugin_register().
The callbacks need (should) not be globally defined anymore, and the global
GeanyData, GeanyPlugin and GeanyFunctions pointers are ignored and not set
anymore. GeanyData is available through GeanyPlugin::geany_data.
Modified Paths:
--------------
src/plugindata.h
src/pluginprivate.h
src/plugins.c
src/pluginutils.c
Modified: src/plugindata.h
72 lines changed, 61 insertions(+), 11 deletions(-)
===================================================================
@@ -108,17 +108,6 @@ typedef struct PluginInfo
PluginInfo;
-/** Basic information for the plugin and identification.
- * @see geany_plugin. */
-typedef struct GeanyPlugin
-{
- PluginInfo *info; /**< Fields set in plugin_set_info(). */
-
- struct GeanyPluginPrivate *priv; /* private */
-}
-GeanyPlugin;
-
-
/** Sets the plugin name and some other basic information about a plugin.
*
* @note If you want some of the arguments to be translated, see @ref PLUGIN_SET_TRANSLATABLE_INFO()
@@ -248,6 +237,16 @@ GeanyData;
#define geany geany_data /**< Simple macro for @c geany_data that reduces typing. */
+/** Basic information for the plugin and identification.
+ * @see geany_plugin. */
+typedef struct GeanyPlugin
+{
+ PluginInfo *info; /**< Fields set in plugin_set_info(). */
+ GeanyData *geany_data; /**< Pointer to global GeanyData intance */
+
+ struct GeanyPluginPrivate *priv; /* private */
+}
+GeanyPlugin;
#ifndef GEANY_PRIVATE
@@ -263,8 +262,59 @@ void plugin_configure_single(GtkWidget *parent);
void plugin_help(void);
void plugin_cleanup(void);
+/** Called by Geany when a plugin library is loaded.
+ *
+ * This is the original entry point. Implement and export this function to be loadable at all.
+ * Then fill in GeanyPlugin::info and GeanyPlugin::funcs of the passed @p plugin. Finally
+ * GEANY_PLUGIN_REGISTER() and specify a minimum supported API version.
+ *
+ * @param plugin The unique plugin handle to your plugin. You must set some fields here.
+ *
+ * @since 1.26 (API 225)
+ */
+gboolean geany_load_module(GeanyPlugin *plugin);
+
#endif
+/** Callback functions that need to be implemented for every plugin.
+ *
+ * These callbacks should be registered by the plugin within Geany's call to
+ * geany_load_module() by calling geany_plugin_register() with an instance of this type.
+ *
+ * Geany will then call the callbacks at appropriate times. Each gets passed the
+ * plugin-defined data pointer as well as the corresponding GeanyPlugin instance
+ * pointer.
+ *
+ * @since 1.26 (API 225)
+ **/
+typedef struct GeanyPluginFuncs
+{
+ /** Array of plugin-provided signal handlers @see PluginCallback */
+ PluginCallback *callbacks;
+ /** Called to initialize the plugin, when the user activates it (must not be @c NULL) */
+ void (*init) (GeanyPlugin *plugin, gpointer pdata);
+ /** plugins configure dialog, optional (can be @c NULL) */
+ GtkWidget* (*configure) (GeanyPlugin *plugin, GtkDialog *dialog, gpointer pdata);
+ /** Called when the plugin should show some help, optional (can be @c NULL) */
+ void (*help) (GeanyPlugin *plugin, gpointer pdata);
+ /** Called when the plugin is disabled or when Geany exits (must not be @c NULL) */
+ void (*cleanup) (GeanyPlugin *plugin, gpointer pdata);
+}
+GeanyPluginFuncs;
+
+gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_api_version,
+ gint abi_version, GeanyPluginFuncs *cbs, gpointer pdata);
+
+/** Convinience macro to register a plugin.
+ *
+ * It simply calls geany_plugin_register() with GEANY_API_VERSION and GEANY_ABI_VERSION.
+ *
+ * @since 1.26 (API 225)
+ **/
+#define GEANY_PLUGIN_REGISTER(plugin, min_api_version) \
+ geany_plugin_register((plugin), GEANY_API_VERSION, \
+ (min_api_version), GEANY_ABI_VERSION)
+
/* Deprecated aliases */
#ifndef GEANY_DISABLE_DEPRECATED
Modified: src/pluginprivate.h
36 lines changed, 31 insertions(+), 5 deletions(-)
===================================================================
@@ -32,6 +32,8 @@
G_BEGIN_DECLS
+typedef struct GeanyData GeanyData;
+
typedef struct SignalConnection
{
GObject *object;
@@ -39,6 +41,22 @@ typedef struct SignalConnection
}
SignalConnection;
+typedef struct _GeanyPluginFuncsLegacy
+{
+ void (*init) (GeanyData *data); /* Called when the plugin is enabled */
+ GtkWidget* (*configure) (GtkDialog *dialog); /* plugins configure dialog, optional */
+ void (*configure_single) (GtkWidget *parent); /* plugin configure dialog, optional */
+ void (*help) (void); /* Called when the plugin should show some help, optional */
+ void (*cleanup) (void); /* Called when the plugin is disabled or when Geany exits */
+ void (*set_info) (PluginInfo *info); /* Called to let the plugin provide metadata for the PM dialog */
+}
+GeanyPluginFuncsLegacy;
+
+typedef enum _LoadedFlags {
+ LOADED_OK = 0x01,
+ IS_LEGACY = 0x02,
+}
+LoadedFlags;
typedef struct GeanyPluginPrivate
{
@@ -47,11 +65,13 @@ typedef struct GeanyPluginPrivate
PluginInfo info; /* plugin name, description, etc */
GeanyPlugin public; /* fields the plugin can read */
- void (*init) (GeanyData *data); /* Called when the plugin is enabled */
- GtkWidget* (*configure) (GtkDialog *dialog); /* plugins configure dialog, optional */
- void (*configure_single) (GtkWidget *parent); /* plugin configure dialog, optional */
- void (*help) (void); /* Called when the plugin should show some help, optional */
- void (*cleanup) (void); /* Called when the plugin is disabled or when Geany exits */
+ union {
+ GeanyPluginFuncs n; /* new-style callbacks, set by geany_plugin_register()
+ * NULL for legacy plugins (they do not call
+ * geany_plugin_register()) */
+ GeanyPluginFuncsLegacy l; /* old callbacks, complete with set_info(), version_check()
+ * and configure_single. Deprecated */
+ } cbs;
/* extra stuff */
PluginFields fields;
@@ -59,9 +79,15 @@ typedef struct GeanyPluginPrivate
GeanyAutoSeparator toolbar_separator;
GArray *signal_ids; /* SignalConnection's to disconnect when unloading */
GList *sources; /* GSources to destroy when unloading */
+
+ gpointer cb_data; /* user data passed back to functions in GeanyPluginFuncs */
+ LoadedFlags flags; /* bit-or of LoadedFlags */
}
GeanyPluginPrivate;
+#define PLUGIN_LOADED_OK(p) (((p)->flags & LOADED_OK) != 0)
+#define PLUGIN_IS_LEGACY(p) (((p)->flags & IS_LEGACY) != 0)
+
typedef GeanyPluginPrivate Plugin; /* shorter alias */
Modified: src/plugins.c
349 lines changed, 226 insertions(+), 123 deletions(-)
===================================================================
@@ -162,37 +162,26 @@ static Plugin *find_active_plugin_by_name(const gchar *filename)
}
+/* Mimics plugin_version_check() of legacy plugins for use with plugin_check_version() below */
+#define PLUGIN_VERSION_CODE(api, abi) ((abi) != GEANY_ABI_VERSION ? -1 : (api))
+
static gboolean
-plugin_check_version(GModule *module)
+plugin_check_version(Plugin *plugin, int plugin_version_code)
{
- gint (*version_check)(gint) = NULL;
-
- g_module_symbol(module, "plugin_version_check", (void *) &version_check);
-
- if (G_UNLIKELY(! version_check))
+ GModule *module = plugin->module;
+ if (plugin_version_code < 0)
{
- geany_debug("Plugin \"%s\" has no plugin_version_check() function - ignoring plugin!",
- g_module_name(module));
+ msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this "
+ "release of Geany - please recompile it."), g_module_name(module));
+ geany_debug("Plugin \"%s\" is not binary compatible with this "
+ "release of Geany - recompile it.", g_module_name(module));
return FALSE;
}
- else
+ if (plugin_version_code > GEANY_API_VERSION)
{
- gint result = version_check(GEANY_ABI_VERSION);
-
- if (result < 0)
- {
- msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this "
- "release of Geany - please recompile it."), g_module_name(module));
- geany_debug("Plugin \"%s\" is not binary compatible with this "
- "release of Geany - recompile it.", g_module_name(module));
- return FALSE;
- }
- if (result > GEANY_API_VERSION)
- {
- geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).",
- g_module_name(module), result);
- return FALSE;
- }
+ geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).",
+ g_module_name(module), plugin_version_code);
+ return FALSE;
}
return TRUE;
}
@@ -269,56 +258,164 @@ static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
}
-static void
-plugin_load(Plugin *plugin)
+/** Register a plugin to Geany.
+ *
+ * The plugin will show up in the plugin manager. The user can interact with
+ * it based on the callbacks it provides and installed GUI elements.
+ *
+ * The return value must be checked. It may be FALSE if the plugin failed to register which can
+ * mainly happen for two reasons (future Geany versions may add new failure conditions):
+ * - Not all mandatory fields of GeanyPlugin have been set.
+ * - The ABI or API versions reported by the plugin are incompatible with the running Geany.
+ *
+ * Do not call this directly. Use GEANY_PLUGIN_REGISTER() instead which automatically
+ * handles @a api_version and @a abi_version.
+ *
+ * @param plugin The plugin provided by Geany
+ * @param api_version The API version the plugin is compiled against (pass GEANY_API_VERSION)
+ * @param min_api_version The minimum API version required by the plugin
+ * @param abi_version The exact ABI version the plugin is compiled against (pass GEANY_ABI_VERSION)
+ * @param cbs A statically allocated @ref GeanyPluginFuncs structure filled with callbacks
+ * @param pdata A data pointer to store plugin-specific data, will be passed to the plugin's callbacks
+ *
+ * @return TRUE if the plugin was successfully registered. Otherwise FALSE.
+ *
+ * @since 1.26 (API 225)
+ * @see GEANY_PLUGIN_REGISTER()
+ **/
+GEANY_API_SYMBOL
+gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_api_version,
+ gint abi_version, GeanyPluginFuncs *cbs, gpointer pdata)
{
- GeanyPlugin **p_geany_plugin;
- PluginCallback *callbacks;
- PluginInfo **p_info;
- PluginFields **plugin_fields;
-
- /* set these symbols before plugin_init() is called
- * we don't set geany_data since it is set directly by plugin_new() */
- g_module_symbol(plugin->module, "geany_plugin", (void *) &p_geany_plugin);
- if (p_geany_plugin)
- *p_geany_plugin = &plugin->public;
- g_module_symbol(plugin->module, "plugin_info", (void *) &p_info);
- if (p_info)
- *p_info = &plugin->info;
- g_module_symbol(plugin->module, "plugin_fields", (void *) &plugin_fields);
- if (plugin_fields)
- *plugin_fields = &plugin->fields;
- read_key_group(plugin);
-
- /* start the plugin */
- g_return_if_fail(plugin->init);
- plugin->init(&geany_data);
-
- /* store some function pointers for later use */
- g_module_symbol(plugin->module, "plugin_configure", (void *) &plugin->configure);
- g_module_symbol(plugin->module, "plugin_configure_single", (void *) &plugin->configure_single);
- if (app->debug_mode && plugin->configure && plugin->configure_single)
- g_warning("Plugin '%s' implements plugin_configure_single() unnecessarily - "
- "only plugin_configure() will be used!",
- plugin->info.name);
+ Plugin *p;
- g_module_symbol(plugin->module, "plugin_help", (void *) &plugin->help);
- g_module_symbol(plugin->module, "plugin_cleanup", (void *) &plugin->cleanup);
- if (plugin->cleanup == NULL)
+ g_return_val_if_fail(plugin != NULL, FALSE);
+
+ p = plugin->priv;
+ /* Prevent registering incompatible plugins. */
+ if (! plugin_check_version(p, PLUGIN_VERSION_CODE(api_version, abi_version)))
+ return FALSE;
+ /* If it ever becomes necessary we can save the api version in Plugin
+ * and apply compat code on a per-plugin basis, because we learn about
+ * the requested API version here. Also if we add to GeanyPluginFuncs then
+ * we have to inspect the plugin's api so that we don't misinterpret
+ * function pointers the plugin doesn't know anything about. */
+ p->cbs.n = *cbs;
+ p->cb_data = pdata;
+
+ /* Only init and cleanup callbacks are truly mandatory. */
+ if (! cbs->init || ! cbs->cleanup)
+ {
+ geany_debug("Plugin '%s' has no %s function - ignoring plugin!",
+ cbs->init ? "cleanup" : "init", g_module_name(p->module));
+ }
+ else
{
- if (app->debug_mode)
+ /* Yes, name is checked again later on, however we want return FALSE here
+ * to signal the error back to the plugin (but we don't print the message twice) */
+ if (! EMPTY(p->info.name))
+ p->flags = LOADED_OK;
+ }
+
+ return PLUGIN_LOADED_OK(p);
+}
+
+
+/* This function is the equivalent of geany_plugin_register() for legacy-style
+ * plugins which we continue to load for the time being. */
+static void register_legacy_plugin(Plugin *plugin, GModule *module)
+{
+ gint (*p_version_check) (gint abi_version);
+ void (*p_set_info) (PluginInfo *info);
+ GeanyData **p_geany_data;
+
+#define CHECK_FUNC(__x, p) \
+ if (! g_module_symbol(module, "plugin_" #__x, (void *) (p))) \
+ { \
+ geany_debug("Plugin \"%s\" has no plugin_" #__x "() function - ignoring plugin!", \
+ g_module_name(plugin->module)); \
+ return; \
+ }
+ CHECK_FUNC(version_check, &p_version_check);
+ CHECK_FUNC(set_info, &p_set_info);
+ CHECK_FUNC(init, &plugin->cbs.l.init);
+#undef CHECK_FUNC
+
+ /* We must verify the version first. If the plugin has become incompatible any
+ * further actions should be considered invalid and therefore skipped. */
+ if (! plugin_check_version(plugin, p_version_check(GEANY_ABI_VERSION)))
+ return;
+
+ /* Since the version check passed we can proceed with setting basic fields and
+ * calling its set_info() (which might want to call Geany functions already). */
+ g_module_symbol(module, "geany_data", (void *) &p_geany_data);
+ if (p_geany_data)
+ *p_geany_data = &geany_data;
+ /* Read plugin name, etc. name is mandatory but that's enforced in the common code. */
+ p_set_info(&plugin->info);
+
+ /* If all went well we can set the remaining callbacks and let it go for good. */
+ g_module_symbol(module, "plugin_configure", (void *) &plugin->cbs.l.configure);
+ g_module_symbol(module, "plugin_configure_single", (void *) &plugin->cbs.l.configure_single);
+ g_module_symbol(module, "plugin_help", (void *) &plugin->cbs.l.help);
+ g_module_symbol(module, "plugin_cleanup", (void *) &plugin->cbs.l.cleanup);
+
+ if (app->debug_mode)
+ {
+ if (plugin->cbs.l.configure && plugin->cbs.l.configure_single)
+ g_warning("Plugin '%s' implements plugin_configure_single() unnecessarily - "
+ "only plugin_configure() will be used!",
+ plugin->info.name);
+ if (plugin->cbs.l.cleanup == NULL)
g_warning("Plugin '%s' has no plugin_cleanup() function - there may be memory leaks!",
plugin->info.name);
}
- /* now read any plugin-owned data that might have been set in plugin_init() */
+ plugin->flags = LOADED_OK | IS_LEGACY;
+}
+
+
+static void
+plugin_load(Plugin *plugin)
+{
+ PluginCallback *callbacks;
+
+ if (PLUGIN_IS_LEGACY(plugin))
+ {
+ GeanyPlugin **p_geany_plugin;
+ PluginInfo **p_info;
+ PluginFields **plugin_fields;
+ /* set these symbols before plugin_init() is called
+ * we don't set geany_data since it is set directly by plugin_new() */
+ g_module_symbol(plugin->module, "geany_plugin", (void *) &p_geany_plugin);
+ if (p_geany_plugin)
+ *p_geany_plugin = &plugin->public;
+ g_module_symbol(plugin->module, "plugin_info", (void *) &p_info);
+ if (p_info)
+ *p_info = &plugin->info;
+ g_module_symbol(plugin->module, "plugin_fields", (void *) &plugin_fields);
+ if (plugin_fields)
+ *plugin_fields = &plugin->fields;
+ read_key_group(plugin);
+
+ /* start the plugin */
+ plugin->cbs.l.init(&geany_data);
+
+ /* now read any plugin-owned data that might have been set in plugin_init() */
+ if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
+ {
+ ui_add_document_sensitive(plugin->fields.menu_item);
+ }
- if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
+ g_module_symbol(plugin->module, "plugin_callbacks", (void *) &callbacks);
+ }
+ else
{
- ui_add_document_sensitive(plugin->fields.menu_item);
+ plugin->cbs.n.init(&plugin->public, plugin->cb_data);
+ callbacks = plugin->cbs.n.callbacks;
}
- g_module_symbol(plugin->module, "plugin_callbacks", (void *) &callbacks);
+ /* new-style plugins set their callbacks in geany_load_module() */
if (callbacks)
add_callbacks(plugin, callbacks);
@@ -327,8 +424,7 @@ plugin_load(Plugin *plugin)
* sorted by plugin name */
active_plugin_list = g_list_insert_sorted(active_plugin_list, plugin, cmp_plugin_names);
- geany_debug("Loaded: %s (%s)", plugin->filename,
- FALLBACK(plugin->info.name, "<Unknown>"));
+ geany_debug("Loaded: %s (%s)", plugin->filename, plugin->info.name);
}
@@ -342,8 +438,7 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list)
{
Plugin *plugin;
GModule *module;
- GeanyData **p_geany_data;
- void (*plugin_set_info)(PluginInfo*);
+ gboolean (*p_geany_load_module)(GeanyPlugin *);
g_return_val_if_fail(fname, NULL);
g_return_val_if_fail(g_module_supported(), NULL);
@@ -387,60 +482,42 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list)
return NULL;
}
- if (! plugin_check_version(module))
+ plugin = g_new0(Plugin, 1);
+ plugin->module = module;
+ plugin->filename = g_strdup(fname);
+ plugin->public.geany_data = &geany_data;
+ plugin->public.priv = plugin;
+ /* Fields of plugin->info must to be initialized by the plugin */
+ plugin->public.info = &plugin->info;
+
+ g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module);
+ if (p_geany_load_module)
{
- if (! g_module_close(module))
- g_warning("%s: %s", fname, g_module_error());
- return NULL;
+ /* This is a new style plugin. It should fill in plugin->info and then call
+ * geany_plugin_register() in its geany_load_module() to successfully load.
+ * The ABI and API checks are performed by geany_plugin_register() (i.e. by us).
+ * We check the LOADED_OK flag separately to protect us against buggy plugins
+ * who ignore the result of geany_plugin_register() and register anyway */
+ p_geany_load_module(&plugin->public);
}
-
- g_module_symbol(module, "plugin_set_info", (void *) &plugin_set_info);
- if (plugin_set_info == NULL)
+ else
{
- geany_debug("No plugin_set_info() defined for \"%s\" - ignoring plugin!", fname);
-
- if (! g_module_close(module))
- g_warning("%s: %s", fname, g_module_error());
- return NULL;
+ /* This is the legacy / deprecated code path. It does roughly the same as
+ * geany_load_module() and geany_plugin_register() together for the new ones */
+ register_legacy_plugin(plugin, module);
}
- plugin = g_new0(Plugin, 1);
-
- /* set basic fields here to allow plugins to call Geany functions in set_info() */
- g_module_symbol(module, "geany_data", (void *) &p_geany_data);
- if (p_geany_data)
- *p_geany_data = &geany_data;
-
- /* read plugin name, etc. */
- plugin_set_info(&plugin->info);
- if (G_UNLIKELY(EMPTY(plugin->info.name)))
+ if (! PLUGIN_LOADED_OK(plugin))
{
- geany_debug("No plugin name set in plugin_set_info() for \"%s\" - ignoring plugin!",
- fname);
-
- if (! g_module_close(module))
- g_warning("%s: %s", fname, g_module_error());
- g_free(plugin);
- return NULL;
+ geany_debug("Failed to load \"%s\" - ignoring plugin!", fname);
+ goto err;
}
- g_module_symbol(module, "plugin_init", (void *) &plugin->init);
- if (plugin->init == NULL)
+ if (EMPTY(plugin->info.name))
{
- geany_debug("Plugin '%s' has no plugin_init() function - ignoring plugin!",
- plugin->info.name);
-
- if (! g_module_close(module))
- g_warning("%s: %s", fname, g_module_error());
- g_free(plugin);
- return NULL;
+ geany_debug("No plugin name set for \"%s\" - ignoring plugin!", fname);
+ goto err;
}
- /*geany_debug("Initializing plugin '%s'", plugin->info.name);*/
-
- plugin->filename = g_strdup(fname);
- plugin->module = module;
- plugin->public.info = &plugin->info;
- plugin->public.priv = plugin;
if (load_plugin)
plugin_load(plugin);
@@ -449,6 +526,13 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list)
plugin_list = g_list_prepend(plugin_list, plugin);
return plugin;
+
+err:
+ if (! g_module_close(module))
+ g_warning("%s: %s", fname, g_module_error());
+ g_free(plugin->filename);
+ g_free(plugin);
+ return NULL;
}
@@ -529,8 +613,11 @@ plugin_cleanup(Plugin *plugin)
{
GtkWidget *widget;
- if (plugin->cleanup)
- plugin->cleanup();
+ /* With geany_plugin_register() cleanup is mandatory */
+ if (! PLUGIN_IS_LEGACY(plugin))
+ plugin->cbs.n.cleanup(&plugin->public, plugin->cb_data);
+ else if (plugin->cbs.l.cleanup)
+ plugin->cbs.l.cleanup();
remove_callbacks(plugin);
remove_sources(plugin);
@@ -851,7 +938,12 @@ gboolean plugins_have_preferences(void)
foreach_list(item, active_plugin_list)
{
Plugin *plugin = item->data;
- if (plugin->configure != NULL || plugin->configure_single != NULL)
+ gboolean result;
+ if (! PLUGIN_IS_LEGACY(plugin))
+ result = plugin->cbs.n.configure != NULL;
+ else
+ result = plugin->cbs.l.configure != NULL || plugin->cbs.l.configure_single != NULL;
+ if (result)
return TRUE;
}
@@ -892,17 +984,23 @@ static PluginManagerWidgets pm_widgets;
static void pm_update_buttons(Plugin *p)
{
- gboolean is_active = FALSE;
gboolean has_configure = FALSE;
gboolean has_help = FALSE;
gboolean has_keybindings = FALSE;
- if (p != NULL)
+ if (p != NULL && is_active_plugin(p))
{
- is_active = is_active_plugin(p);
- has_configure = (p->configure || p->configure_single) && is_active;
- has_help = p->help != NULL && is_active;
- has_keybindings = p->key_group && p->key_group->plugin_key_count > 0 && is_active;
+ if (PLUGIN_IS_LEGACY(p))
+ {
+ has_configure = p->cbs.l.configure || p->cbs.l.configure_single;
+ has_help = p->cbs.l.help != NULL;
+ }
+ else
+ {
+ has_configure = p->cbs.n.configure != NULL;
+ has_help = p->cbs.n.help != NULL;
+ }
+ has_keybindings = p->key_group && p->key_group->plugin_key_count;
}
gtk_widget_set_sensitive(pm_widgets.configure_button, has_configure);
@@ -1239,8 +1337,13 @@ static void pm_on_plugin_button_clicked(G_GNUC_UNUSED GtkButton *button, gpointe
{
if (GPOINTER_TO_INT(user_data) == PM_BUTTON_CONFIGURE)
plugin_show_configure(&p->public);
- else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_HELP && p->help != NULL)
- p->help();
+ else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_HELP)
+ {
+ if (PLUGIN_IS_LEGACY(p))
+ p->cbs.l.help();
+ else
+ p->cbs.n.help(&p->public, p->cb_data);
+ }
else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_KEYBINDINGS && p->key_group && p->key_group->plugin_key_count > 0)
keybindings_dialog_show_prefs_scroll(p->info.name);
}
Modified: src/pluginutils.c
20 lines changed, 13 insertions(+), 7 deletions(-)
===================================================================
@@ -310,7 +310,7 @@ GeanyKeyGroup *plugin_set_key_group(GeanyPlugin *plugin,
static void on_pref_btn_clicked(gpointer btn, Plugin *p)
{
- p->configure_single(main_widgets.window);
+ p->cbs.l.configure_single(main_widgets.window);
}
@@ -318,10 +318,16 @@ static GtkWidget *create_pref_page(Plugin *p, GtkWidget *dialog)
{
GtkWidget *page = NULL; /* some plugins don't have prefs */
- if (p->configure)
+ if (!PLUGIN_IS_LEGACY(p))
{
- page = p->configure(GTK_DIALOG(dialog));
+ if (p->cbs.n.configure)
+ page = p->cbs.n.configure(&p->public, GTK_DIALOG(dialog), p->cb_data);
+ }
+ else if (p->cbs.l.configure)
+ page = p->cbs.l.configure(GTK_DIALOG(dialog));
+ if (page)
+ {
if (! GTK_IS_WIDGET(page))
{
geany_debug("Invalid widget returned from plugin_configure() in plugin \"%s\"!",
@@ -338,7 +344,7 @@ static GtkWidget *create_pref_page(Plugin *p, GtkWidget *dialog)
gtk_box_pack_start(GTK_BOX(page), align, TRUE, TRUE, 0);
}
}
- else if (p->configure_single)
+ else if (PLUGIN_IS_LEGACY(p) && p->cbs.l.configure_single)
{
GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0);
GtkWidget *btn;
@@ -421,12 +427,12 @@ void plugin_show_configure(GeanyPlugin *plugin)
}
p = plugin->priv;
- if (p->configure)
+ if (!PLUGIN_IS_LEGACY(p) || p->cbs.l.configure)
configure_plugins(p);
else
{
- g_return_if_fail(p->configure_single);
- p->configure_single(main_widgets.window);
+ g_return_if_fail(p->cbs.l.configure_single);
+ p->cbs.l.configure_single(main_widgets.window);
}
}
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).