[geany/geany] 721009: plugins: plugin loader redesign

Thomas Martitz git-noreply at xxxxx
Sun Aug 23 13:23:20 UTC 2015


Branch:      refs/heads/master
Author:      Thomas Martitz <kugel at rockbox.org>
Committer:   Thomas Martitz <kugel at rockbox.org>
Date:        Sun, 23 Aug 2015 13:23:20 UTC
Commit:      721009e262e9bc279122566a60e14af0048d9094
             https://github.com/geany/geany/commit/721009e262e9bc279122566a60e14af0048d9094

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).


More information about the Commits mailing list