[geany/geany] 280163: Merge pull request #469 from kugel-/new_hooks

Colomban Wendling git-noreply at xxxxx
Sun Aug 23 21:50:44 UTC 2015


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Sun, 23 Aug 2015 21:50:44 UTC
Commit:      280163a24411f84a01b107c524d4a6c4f87a6710
             https://github.com/geany/geany/commit/280163a24411f84a01b107c524d4a6c4f87a6710

Log Message:
-----------
Merge pull request #469 from kugel-/new_hooks

Plugin loader redesign


Modified Paths:
--------------
    doc/plugins.dox
    doc/pluginsymbols.c
    plugins/Makefile.am
    plugins/demoplugin.c
    src/plugindata.h
    src/pluginprivate.h
    src/plugins.c
    src/pluginutils.c

Modified: doc/plugins.dox
555 lines changed, 405 insertions(+), 150 deletions(-)
===================================================================
@@ -5,6 +5,7 @@
  * Copyright 2008-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
  * Copyright 2009-2012 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
  * Copyright 2014 Matthew Brush <matt(at)geany(dot)org>
+ * Copyright 2015 Thomas Martitz <kugel(at)rockbox(dot)org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,10 +37,13 @@ This is the Geany API documentation. It should be considered work in progress.
 We will try to document as many functions and structs as possible.
 
 @warning Do not use any symbol not in the documentation - it may change.
+ at warning Except for exceptions stated in the documentation for geany_load_module(), no API function
+may be called if the plugin is not enabled (between the calls to their GeanyFuncs::init and
+GeanyFuncs::cleanup functions).
 
 @section pluginsupport Plugin Support
 - @link howto Plugin HowTo @endlink - get started
-- @link pluginsymbols.c Plugin Symbols @endlink
+- @ref legacy
 - @link plugindata.h Plugin Datatypes and Macros @endlink
 - @link pluginsignals.c Plugin Signals @endlink
 - @link pluginutils.h Plugin Utility Functions @endlink
@@ -170,75 +174,176 @@ Geany's website at http://www.geany.org/Support/BuildingOnWin32.
 
 @section helloworld "Hello World"
 
-When writing a plugin, you will find a couple of functions or macros which are mandatory
-and some which are free to use for implementing some useful feature once your plugin
-becomes more powerful like including a configuration or help dialog.
+ at note This section describes the new entry points for plugins introduced with Geany 1.26. A short
+summary of the legacy entry points is given at page @ref legacy but they are deprecated should not
+be used any more.
 
-You should start your plugin with including some of the needed C header files and defining
-some basic global variables which will help you to access all needed functions of the plugin
-API in a more comfortable way.
+When writing a plugin you will find a couple of functions which are mandatory and some which can be
+implemented optionally for implementing some useful features once your plugin becomes more
+powerful. For example to provide a configuration or help dialog.
 
-Let's start with the very basic headers and add more later if necessary.
- at code
-#include <geanyplugin.h>
- at endcode
+ at subsection beginning First steps for any Plugin
+
+You should start your plugin with including <geanyplugin.h> and exporting a function named @a
+geany_load_module(). In this function you must fill in basic information that Geany uses to learn
+more about your plugin and present it to the user. You also must define some hooks that enable
+Geany to actually execute your code.
 
- at a geanyplugin.h includes all of the Geany API and also the necessary GTK header files,
-so there is no need to include @a gtk/gtk.h yourself.
+Please also do not forget about license headers which are by convention at the start of source
+files. You can use templates provided by Geany to get started. Without a proper license it will be
+difficult for packagers to pick up and distribute your plugin.
 
- at note
- at a plugindata.h contains the biggest part of the plugin API and provides some basic macros.
+As mentioned above, start with the very fundamental header that gets you all goodies of Geany's
+plugin API. @a geanyplugin.h includes all of the Geany API and also the necessary GTK header
+files so there is no need to include @a gtk/gtk.h yourself. In fact it includes a utility header
+that helps supporting GTK+2 and GTK+3 in the same source.
 
-Then you should define two basic variables which will give access to data fields
-provided by the plugin API.
 @code
-GeanyPlugin			*geany_plugin;
-GeanyData			*geany_data;
+#include <geanyplugin.h>
 @endcode
 
-Now you can go on and write your first lines for your new plugin. As mentioned before,
-you will need to implement and fill out a couple of functions/macros to make the plugin work.
-So let's start with PLUGIN_VERSION_CHECK().
+ at note If you use autoconf then config.h must be included even before that as usual.
+
+Now you can go on and write your first lines for your new plugin. As mentioned before, you will
+need to implement a couple of functions. The first mandatory one is @a geany_load_module(). Geany
+uses the presence fo this function to identify a library as a plugin. When Geany scans the
+pre-defined and user-configured plugin directories, it will take a look at each shared library (or
+DLL on Windows) to see if it exports a @a geany_load_module() symbol. Files lacking these will be
+ignored. The second mandatory one is an initialization function that is only called when the plugin
+becomes actually enabled (by the user or at startup).
+
+ at subsection register Registering a Plugin
+
+Geany will always invoke this geany_load_module(), regardless of whether the user activates your
+plugin. In fact its purpose to probe if the plugin should even be presented to the user. Therefore
+you must use this function to register your plugin. Geany will pass a pointer to a GeanyPlugin
+instance which acts as a unique handle to your plugin. Use this pointer for registering and later
+API calls. It won't change for the life time of the plugin. Registering the plugin consists of a
+number of steps:
+
+  1. Filling in GeanyPlugin::info with metadata that is shown to the user.
+    - @ref PluginInfo::name : The name of your plugin
+    - @ref PluginInfo::description : A brief description.
+    - @ref PluginInfo::version : The plugin's version.
+    - @ref PluginInfo::author : Your contact information, preferably in the form "Name <email>".
+    .
+    Filling in all of them is recommended to provide the best user experience, but only the name is
+    truly mandatory. Since all of the strings are shown to the user they should be human readable.
+  2. Filling in GeanyPlugin::funcs with function pointers that are called by Geany.
+    - @ref GeanyPluginFuncs::init : an initialization function
+    - @ref GeanyPluginFuncs::cleanup : a finalization function
+    - @ref GeanyPluginFuncs::configure : a function that provides configuration (optional)
+    - @ref GeanyPluginFuncs::help : a function that provides help (optional)
+    - @ref GeanyPluginFuncs::callbacks : a pointer to an array of PluginCallback (optional).
+    .
+    @a init and @a cleanup are mandatory, the other ones depend on how advanced your plugin is.
+    Furthermore, @a init is called on startup and when the user activates your plugin in the Plugin
+    Manager, and @a cleanup is called on exit and when the user deactivates it. So use these to do
+    advanced initialization and teardown as to not waste resources when the plugin is not even
+    enabled.
+  3. Actually registering by calling GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL().
+   - Usually you should use GEANY_PLUGIN_REGISTER() to register your plugin, passing the
+    GeanyPlugin pointer that you received and filled out as above. GEANY_PLUGIN_REGISTER() also
+    takes the minimum API version number you want to support (see @ref versions for details). Please
+    also <b>check the return value</b>. Geany may refuse to load your plugin due to
+    incompatibilities, you should then abort any extra setup. GEANY_PLUGIN_REGISTER() is a macro
+    wrapping geany_plugin_register() which takes additional the API and ABI that you should not pass
+    manually.
+   - If you require a plugin-specific context or state to be passed to your GeanyPlugin::funcs then
+    use GEANY_PLUGIN_REGISTER_FULL() to register. This one takes additional parameters for adding
+    user data to your plugin. That user data pointer is subsequently passed back to your functions.
+    It allows, for example, to set instance pointer to objects in case your plugin isn't written in
+    pure C, enabling you to use member functions as plugin functions. You may also set such data
+    later on, for example in your @ref GeanyPluginFuncs::init routine to defer costly allocations
+    to when the plugin is actually activated by the user. However, you then have to call
+    geany_plugin_set_data().
+
+
+ at subsection versions On API and ABI Versions
+As previously mentioned @a geany_plugin_register() takes a number of versions as arguments:
+  1. api_version
+  2. min_api_version
+  3. abi_version
+
+These refer to Geany's versioning scheme to manage plugin compatibility. The following rules apply:
+  - Plugins are compiled against a specific Geany version on the build machine. This version of
+    Geany has specific ABI and API versions, which will be compiled into the plugin. Both are
+    managed automatically, by calling GEANY_PLUGIN_REGISTER().
+  - The Geany version that loads the plugin may be different, possibly even have different API and
+    ABI versions.
+  - The ABI version is the primary plugin compatibility criteria. The ABI version of the running
+    Geany and the one that's compiled into the plugin must match exactly (==). In case of mismatch,
+    the affected plugins need to be recompiled (generally without source code changes) against the
+    running Geany. The ABI is usually stable even across multiple releases of Geany.
+  - The API version is secondary. It doesn't have to match exactly, however a plugin can report
+    a minimum API version that it requires to run. Geany will check if its own API is larger than
+    that (>=) and will otherwise refuse to load the plugin. The API version is incremented when
+    functions or variables are added to the API which often happens more than once within a release
+    cycle.
+  - The API version the plugin is compiled against is still relevant for enabling compatibility
+    code inside Geany (for cases where incrementing the ABI version could be avoided).
+
+Instead of calling geany_plugin_register() directly it is very highly recommended to use
+GEANY_PLUGIN_REGISTER(). This is a convenient way to pass Geany's current API and ABI versions
+without requiring future code changes whenever either one changes. In fact, the promise that
+plugins need to be just recompiled on ABI change can hold if the plugins use this macro. You still
+want to pass the API version needed at minimum to run your plugin. The value is defined in
+plugindata.h by @ref GEANY_API_VERSION. In most cases this should be your minimum. Nevertheless when
+setting this value, you should choose the lowest possible version here to make the plugin
+compatible with a bigger number of versions of Geany. The absolute minimum is 225 which introduced
+the new plugin entry points.
+
+To increase your flexibility the API version of the running Geany is passed to geany_load_module().
+You can use this information to toggle API-specific code. This comes handy, for example to enable
+optional code that requires a recent API version without raising your minimum required API version.
+This enables running the plugin against more Geany versions, although perhaps at reduced
+functionality.
+
+ at subsection example Example
+
+Going back to our "Hello World" plugin here is example code that properly adds the HelloWorld
+plugin to Geany.
 
-PLUGIN_VERSION_CHECK() is a convenient way to tell Geany which version of Geany's plugin API
-is needed at minimum to run your plugin. The value is defined in
- at a plugindata.h by @a GEANY_API_VERSION. In most cases this should be your minimum.
-Nevertheless when setting this value, you should choose the lowest possible version here to
-make the plugin compatible with a bigger number of versions of Geany.
+ at code
+/* License blob */
 
-For the next step, you will need to tell Geany some basic information about your plugin
-which will be shown in the plugin manager dialog.
+#include <geanyplugin.h>
 
-To do this you should use the PLUGIN_SET_INFO() macro, which expects 4 parameters:
-- Plugin name
-- Short description
-- Version
-- Author
 
-Based on this, the line could look like:
- at code
-PLUGIN_SET_INFO("HelloWorld", "Just another tool to say hello world",
-				"1.0", "John Doe <john.doe at example.org>");
- at endcode
+static gboolean hello_init(GeanyPlugin *plugin, gpointer pdata)
+{
+	printf("Hello World from plugin!\n");
 
-Once this is done, you will need to implement the function which will be executed when the
-plugin is loaded. Part of that function could be adding and removing of an item to
-Geany's Tools menu, setting up keybindings or registering some callbacks. Also you will
-need to implement the function that is called when your plugin is unloaded.
-These functions are called plugin_init() and plugin_cleanup(). Let's see what this
-looks like:
- at code
-PLUGIN_VERSION_CHECK(211)
+	/* Perform advanced set up here */
+
+	return TRUE;
+}
 
-PLUGIN_SET_INFO("HelloWorld", "Just another tool to say hello world",
-				"1.0", "Joe Doe <joe.doe at example.org>");
 
-void plugin_init(GeanyData *data)
+static void hello_cleanup(GeanyPlugin *plugin, gpointer pdata)
 {
+	printf("Bye World :-(\n");
 }
 
-void plugin_cleanup(void)
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
 {
+	/* Step 1: Set metadata */
+	plugin->info->name = "HelloWorld";
+	plugin->info->description = "Just another tool to say hello world";
+	plugin->info->version = "1.0";
+	plugin->info->author = "John Doe <john.doe at example.org>";
+
+	/* Step 2: Set functions */
+	plugin->funcs->init = hello_init;
+	plugin->funcs->cleanup = hello_cleanup;
+
+	/* Step 3: Register! */
+	if (GEANY_PLUGIN_REGISTER(plugin, 225))
+		return;
+	/* alternatively:
+	GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, free_func); */
 }
 @endcode
 
@@ -246,22 +351,21 @@ If you think this plugin seems not to implement any functionality right now and
 some memory, you are right. But it should compile and load/unload in Geany nicely.
 Now you have the very basic layout of a new plugin. Great, isn't it?
 
- at note
-
-If you would rather write the plugin in C++, you can do that by marking the
-plugin functions that it implements as @c extern @c "C", for example:
+If you would rather write the plugin in C++, you can do that by marking @a geany_load_module()
+as <c> extern "C" </c>, for example:
 
 @code
 
-extern "C" void plugin_init(GeanyData *data)
+extern "C" void geany_load_module(GeanyPlugin *plugin)
 {
 }
 
-extern "C" void plugin_cleanup(void)
-{
-}
 @endcode
 
+You can also create an instance of a class and set that as data pointer (with
+GEANY_PLUGIN_REGISTER_FULL()). With small wrappers that shuffle the parameters you can even use
+member functions for @ref GeanyPlugin::funcs etc.
+
 @section building Building
 
 First make plugin.o:
@@ -275,8 +379,6 @@ Then make the plugin library plugin.so (or plugin.dll on Windows):
 If all went OK, put the library into one of the paths Geany looks for plugins,
 e.g. $prefix/lib/geany. See @ref paths "Installation paths" for details.
 
- at note
-
 If you are writing the plugin in C++, then you will need to use your C++
 compiler here, for example @c g++.
 
@@ -284,11 +386,12 @@ compiler here, for example @c g++.
 
 Let's go on and implement some real functionality.
 
-As mentioned before, plugin_init() will be called when the plugin is loaded in Geany.
-So it should implement everything that needs to be done during startup. In this case,
-we'd like to add a menu item to Geany's Tools menu which runs a dialog printing "Hello World".
+As mentioned before, GeanyPluginFuncs::init() will be called when the plugin is activated by
+Geany. So it should implement everything that needs to be done during startup. In this case, we'd
+like to add a menu item to Geany's Tools menu which runs a dialog printing "Hello World".
+
 @code
-void plugin_init(GeanyData *data)
+static gboolean hello_init(GeanyPlugin *plugin, gpointer pdata)
 {
 	GtkWidget *main_menu_item;
 
@@ -297,26 +400,27 @@ void plugin_init(GeanyData *data)
 	gtk_widget_show(main_menu_item);
 
 	// Attach the new menu item to the Tools menu
-	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),
+	gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu),
 		main_menu_item);
 
 	// Connect the menu item with a callback function
 	// which is called when the item is clicked
 	g_signal_connect(main_menu_item, "activate",
 		G_CALLBACK(item_activate_cb), NULL);
+
+	return TRUE;
 }
 @endcode
 
-This will add an item to the Tools menu and connect this item to a function which implements
-what should be done when the menu item is activated by the user.
-This is done by g_signal_connect(). The Tools menu can be accessed with
-geany->main_widgets->tools_menu. The structure @a main_widgets contains pointers to the
-main GUI elements in Geany.
+This will add an item to the Tools menu and connect this item to a function which implements what
+should be done when the menu item is activated by the user. This is done by g_signal_connect(). The
+Tools menu can be accessed with plugin->geany_data->main_widgets->tools_menu. The structure
+GeanyMainWidgets contains pointers to all main GUI elements in Geany.
 
 Geany has a simple API for showing message dialogs. So our function contains
 only a few lines:
 @code
-void item_activate_cb(GtkMenuItem *menuitem, gpointer user_data)
+static void item_activate_cb(GtkMenuItem *menuitem, gpointer user_data)
 {
 	dialogs_show_msgbox(GTK_MESSAGE_INFO, "Hello World");
 }
@@ -326,27 +430,36 @@ For the moment you don't need to worry about the parameters of that function.
 
 Now we need to clean up properly when the plugin is unloaded.
 
-To remove the menu item from the Tools menu, you can use gtk_widget_destroy().
-gtk_widget_destroy() expects a pointer to a GtkWidget object.
+To remove the menu item from the Tools menu you can use gtk_widget_destroy().
 
-First you should add gtk_widget_destroy() to your plugin_cleanup() function.
-The argument for gtk_widget_destroy() is the widget object you created earlier in
-plugin_init(). To be able to access this pointer in plugin_cleanup(), you need to move
-its definition from plugin_init() into the global context so its visibility will increase
-and it can be accessed in all functions.
- at code
-static GtkWidget *main_menu_item = NULL;
+First you should add gtk_widget_destroy() to your GeanyPluginFuncs::cleanup() function. The
+argument for gtk_widget_destroy() is the widget object you created earlier in
+GeanyPluginFuncs::init(). To be able to access this pointer in GeanyPluginFuncs::cleanup() you can
+use geany_plugin_set_data() to set plugin-defined data pointer to the widget. Alternatively, you
+can store the pointer in some global variable so its visibility will increase and it can be
+accessed in all functions.
 
+ at code
+/* alternative: global variable:
+static GtkWidget *main_menu_item;
+*/
 // ...
-void plugin_init(GeanyData *data)
+static gboolean hello_init(GeanyPlugin *plugin, gpointer pdata)
 {
+	GtkWidget *main_menu_item;
+
+	// Create a new menu item and show it
 	main_menu_item = gtk_menu_item_new_with_mnemonic("Hello World");
 	gtk_widget_show(main_menu_item);
 // ...
+	geany_plugin_set_data(plugin, main_menu_item, NULL);
+	return TRUE;
 }
 
-void plugin_cleanup(void)
+static void hello_cleanup(GeanyPlugin *plugin, gpointer pdata)
 {
+	GtkWidget *main_menu_item = (GtkWidget *) pdata;
+// ...
 	gtk_widget_destroy(main_menu_item);
 }
 @endcode
@@ -358,81 +471,82 @@ Once this is done, your first plugin is ready. Congratulations!
 @section listing Complete listing (without comments)
 
 @code
-#include <geanyplugin.h>
-
-GeanyPlugin		*geany_plugin;
-GeanyData		*geany_data;
-
-PLUGIN_VERSION_CHECK(211)
 
-PLUGIN_SET_INFO("HelloWorld", "Just another tool to say hello world",
-				"1.0", "John Doe <john.doe at example.org>");
-
-
-static GtkWidget *main_menu_item = NULL;
+#include <geanyplugin.h>
 
-static void item_activate_cb(GtkMenuItem *menuitem, gpointer gdata)
+static void item_activate_cb(GtkMenuItem *menuitem, gpointer user_data)
 {
 	dialogs_show_msgbox(GTK_MESSAGE_INFO, "Hello World");
 }
 
-void plugin_init(GeanyData *data)
+
+static gboolean hello_init(GeanyPlugin *plugin, gpointer pdata)
 {
+	GtkWidget *main_menu_item;
+
+	// Create a new menu item and show it
 	main_menu_item = gtk_menu_item_new_with_mnemonic("Hello World");
 	gtk_widget_show(main_menu_item);
-	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),
-		main_menu_item);
+	gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu),
+			main_menu_item);
 	g_signal_connect(main_menu_item, "activate",
-		G_CALLBACK(item_activate_cb), NULL);
+			G_CALLBACK(item_activate_cb), NULL);
+
+	geany_plugin_set_data(plugin, main_menu_item, NULL);
+	return TRUE;
 }
 
-void plugin_cleanup(void)
+
+static void hello_cleanup(GeanyPlugin *plugin, gpointer pdata)
 {
+	GtkWidget *main_menu_item = (GtkWidget *) pdata;
+
 	gtk_widget_destroy(main_menu_item);
 }
- @endcode
+
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+	plugin->info->name = "HelloWorld";
+	plugin->info->description = "Just another tool to say hello world";
+	plugin->info->version = "1.0";
+	plugin->info->author = "John Doe <john.doe at example.org>";
+
+	plugin->funcs->init = hello_init;
+	plugin->funcs->cleanup = hello_cleanup;
+
+	GEANY_PLUGIN_REGISTER(plugin, 225);
+}
+ at endcode
 
 
 Now you might like to look at Geany's source code for core plugins such as
 @a plugins/demoplugin.c.
 
- at section furtherimprovements Furter Improvements and next steps
+ at section furtherimprovements Further Improvements and next steps
 @subsection translatable_plugin_information Translatable plugin information
 
 After having written our first plugin, there is still room for improvement.
 
-By default, PLUGIN_SET_INFO() does not allow translation of the basic plugin
-information for plugins which are not shipped with Geany's core distribution.
-Since most plugins are not shipped with Geany's core, it makes sense to
-enable translation when the plugin is loaded so that it gets translated
-inside Geany's Plugin Manager.  As of Geany 0.19, the plugin API contains
-the PLUGIN_SET_TRANSLATABLE_INFO() macro which enables translation of the
-basic plugin details passed to PLUGIN_SET_INFO() when the plugin is loaded.
-
-PLUGIN_SET_TRANSLATABLE_INFO() takes two more parameters than PLUGIN_SET_INFO(),
-for a total of six parameters.
-
- - Localedir
- - Gettextpackage
- - Plugin name
- - Short description
- - Version
- - Author
-
-The @a Localdir and the @a Gettextpackage parameters are usually set inside
-the build system.  If this has been done, the call for example HelloWorld
-plugin could look like:
+By default, @ref geany_load_module() is not prepared to allow translation of the basic plugin
+information, except plugins which are shipped with Geany's core distribution, because custom
+gettext catalogs are not setup. Since most plugins are not shipped with Geany's core, it makes
+sense to setup gettext when the plugin is loaded so that it gets translated inside Geany's Plugin
+Manager. The solution is to call the API function main_locale_init() inside @ref
+geany_load_module() and then use gettext's _() as usual.
 
+The invocation will most probably look similar to this:
 @code
-PLUGIN_SET_TRANSLATABLE_INFO(
-	LOCALEDIR, GETTEXT_PACKAGE, _("Hello World"),
-	_("Just another tool to say hello world"),
-	"1.0", "John Doe <john.doe at example.org>");
+// ...
+	main_locale_init(LOCALEDIR, GETTEXT_PACKAGE);
+	plugin->info->name = _("HelloWorld");
+	plugin->info->description = _("Just another tool to say hello world");
+	plugin->info->version = "1.0";
+	plugin->info->author = "John Doe <john.doe at example.org>";
 @endcode
 
-When using this macro, you should use the gettext macro @a _() to mark
-the strings like name and the short description as translatable as well. You
-can see how this is done in the above example.
+The @a LOCALEDIR and the @a GETTEXT_PACKAGE parameters are usually set inside the build system.
 
 As you can see the author's information is not marked as translatable in
 this example.  The community has agreed that the best practice here is to
@@ -442,32 +556,173 @@ native spelling inside parenthesis, where applicable.
 @subsection plugin_i18n Using i18n/l10n inside Plugin
 
 
-You can (and should) also mark other strings beside the plugin's meta
-information as translatable.  Strings used in menu entries, information
-boxes or configuration dialogs should also be translatable as well.  Geany
-offers a way to enable this in the plugin's code using the main_locale_init()
-function provided by the plugin API. This function takes the same two
-parameters discussed in the previous section; @a GETTEXT_PACKAGE and
- at a LOCALEDIR.
+You can (and should) also mark other strings beside the plugin's meta information as translatable.
+Strings used in menu entries, information boxes or configuration dialogs should be translatable as
+well.
 
-The main_locale_init() function is best called during initialization in the
-plugin's plugin_init() function.  Adding this to the HelloWorld example could
-look like:
 @code
-void plugin_init(GeanyData *data)
+static gboolean hello_init(GeanyPlugin *plugin, gpointer pdata)
 {
 	main_locale_init(LOCALEDIR, GETTEXT_PACKAGE);
-	main_menu_item = gtk_menu_item_new_with_mnemonic("Hello World");
-	gtk_widget_show(main_menu_item);
-	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu),
-		main_menu_item);
-	g_signal_connect(main_menu_item, "activate",
-		G_CALLBACK(item_activate_cb), NULL);
+	main_menu_item = gtk_menu_item_new_with_mnemonic(_("Hello World"));
+// ...
+}
+ at endcode
+
+ at page legacy Porting guide from legacy entry points to the current ones
+
+ at section intro_legacy Introduction
+
+This page briefly describes the deprecated, legacy plugin entry points. These have been in place
+prior to Geany 1.26 and are still loadable and working for the time being. However, do not create
+new plugins against these. For this reason, the actual description here is rather minimalistic and
+concentrates on porting legacy plugins to the new interface. Basically it's main purpose
+is to give newcomers an idea of what they are looking at if they come across a legacy plugin.
+
+ at section overview Overview
+
+The legacy entry points consist of a number of pre-defined symbols (functions and variables)
+exported by plugins. There is no active registration procedure. It is implicit simply by exporting
+the mandatory symbols. The entirety of the symbols is described at the page @link pluginsymbols.c
+Plugin Symbols @endlink.
+
+At the very least plugins must define the functions @a plugin_init(GeanyData *) and @a
+plugin_version_check(gint). Additionally, an instance of the struct PluginInfo named plugin_info
+must be exported as well, this contains the same metadata already known from GeanyPlugin::info. The
+functions plugin_cleanup(), plugin_help(), plugin_configure(GtkDialog *) and
+plugin_configure_single(GtkWidget *) are optional, however Geany prints a warning if
+plugin_cleanup() is missing and only one of plugin_configure(GtkDialog *) and
+plugin_configure_single(GtkWidget *) is used for any single plugin.
+
+By convention, plugin_version_check() is implicitely defined through the use of PLUGIN_VERSION_CHECK(),
+and similarly plugin_info is defined through PLUGIN_SET_INFO() or PLUGIN_SET_TRANSLATABLE_INFO().
+
+The functions should generally perform the same tasks as their eqivalents in GeanyPlugin::funcs.
+
+Geany also recognized numerous variable fields if the plugin exported them globally, and actually
+set a few of them inside the plugins data section.
+
+ at section porting Porting a Legacy Plugin
+
+Given a legacy plugin it can be modified to use the new entry points without much effort. This
+section gives a basic recipe that should work for most existing plugins. The transition should
+be easy and painless so it is recommended that you adapt your plugin as soon as possible.
+
+ at note This guide is intentionally minimalistic (in terms of suggested code changes) in order to
+allow adaption to the current entry points as quickly as possible and without a lot effort. It
+should also work even for rather complex plugins comprising multiple source files. On the other hand
+it does not make use of new features such as geany_plugin_set_data().
+
+ at subsection functions Functions
+
+Probably the biggest hurdle is the dropped support of the long-deprecated
+plugin_configure_single(). This means you first have to port the configuration dialog (if any) to
+the combined plugin dialog. While you previously created a custom dialog you now attach the main
+widget of that dialog to the combined plugin dialog simply by returning it from
+GeanyPluginFuncs::configure. You don't actually add it, Geany will do that for you. The pointer to
+the dialog is passed to @a configure simply to allow you to connect to its "response" or "close"
+signals.
+
+The following lists the function mapping of previous @a plugin_* functions to the new @a
+GeanyPlugin::funcs. They are semantically the same, however the new functions receive more
+parameters which you may use or not.
+
+  - plugin_init() => GeanyPlugin->funcs->init
+  - plugin_cleanup() => GeanyPlugin->funcs->cleanup
+  - plugin_help() => GeanyPlugin->funcs->help
+  - plugin_configure() => GeanyPlugin->funcs->configure
+
+ at note @ref GeanyPluginFuncs::init() should return a boolean value: whether or not the plugin loaded
+succesfully. Since legacy plugins couldn't fail in plugin_init() you should return @c TRUE
+unconditionally.
+
+ at note Again, plugin_configure_single() is not supported anymore.
+
+ at subsection Variables
+
+Exported global variables are not recognized anymore. They are replaced in the following ways:
+
+ at ref plugin_info is simply removed. Instead, you have to assign the values to GeanyPlugin::info
+yourself, and it must be done inside your @a geany_load_module().
+
+Example:
+
+ at code
+PLUGIN_SET_INFO(
+	"HelloWorld",
+	"Just another tool to say hello world",
+	"1.0", "John Doe <john.doe at example.org>");
+ at endcode
+
+becomes
+
+ at code
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+// ...
+	plugin->info->name = "HelloWorld";
+	plugin->info->description = "Just another tool to say hello world";
+	plugin->info->version = "1.0";
+	plugin->info->author = "John Doe <john.doe at example.org>";
+// ...
+}
+ at endcode
+ at note Refer to @ref translatable_plugin_information for i18n support for the metadata.
+
+
+The @ref plugin_callbacks array is supported by assigning the GeanyPluginFuncs::callbacks to
+the array.
+
+ at ref plugin_fields is not supported anymore. Use ui_add_document_sensitive() instead.
+ at ref PLUGIN_KEY_GROUP and @ref plugin_key_group are also not supported anymore. Use
+plugin_set_key_group() and keybindings_set_item() respectively.
+
+Additionally, Geany traditionally set a few variables. This is not the case anymore. @ref
+geany_functions has been removed in 1.25 and since then existed only for compatibility and has been
+empty. You can simply remove its declaration from your source code. @ref geany_plugin is passed to
+each @ref GeanyPluginFuncs function. You need to store it yourself somewhere if you need it
+elsewhere. @ref geany_data is now available as a member of GeanyPlugin.
+
+ at code
+GeanyPlugin *geany_plugin;
+GeanyData *geany_data;
+
+static gboolean my_init(GeanyPlugin *plugin, gpointer pdata)
+{
+// ...
+	geany_plugin = plugin;
+	geany_data = plugin->geany_data;
+	return TRUE;
+}
+ at endcode
+
+ at ref geany_plugin is now also passed by default to the PluginCallback signal handlers as data
+pointer if it's set to NULL.
+
+ at code
+static PluginCallback plugin_callbacks[] = {
+	{ "editor-notify", (GCallback) &on_editor_notify_cb, FALSE, NULL },
+// ...
+};
+
+static gboolean on_editor_notify_cb(GObject *object, GeanyEditor *editor,
+									SCNotification *nt, gpointer data)
+{
+	GeanyPlugin *plugin = data;
+//...
+}
+
+
+G_MODULE_EXPORT
+void geany_load_module(GeanyPlugin *plugin)
+{
+// ...
+	plugin->funcs->callbacks = plugin_callbacks;
+// ...
+}
 }
 @endcode
 
- at note If you've previously called the PLUGIN_SET_TRANSLATABLE_INFO() you do not
-need to call main_locale_init() yourself, as this has been already been
-done for you.
 
 */


Modified: doc/pluginsymbols.c
8 lines changed, 6 insertions(+), 2 deletions(-)
===================================================================
@@ -23,7 +23,12 @@
 
 /**
  * @file pluginsymbols.c
- * Symbols declared from within plugins.
+ * Symbols declared from within plugins, all of this is <b>deprecated</b>.
+ *
+ * @deprecated This is the legacy way of making plugins for Geany. Refer to @ref howto for the
+ * reworked process and @ref legacy to learn how to port your plugin to that new world.
+ * Meanwhile Geany will still load plugins programmed against this interface (even the items that
+ * are marked deprecated individually such as @ref plugin_fields).
  *
  * Geany looks for these symbols (arrays, pointers and functions) when initializing
  * plugins. Some of them are optional, i.e. they can be omitted; others are required
@@ -106,4 +111,3 @@ void plugin_cleanup();
  * or something else.
  * Can be omitted when not needed. */
 void plugin_help();
-


Modified: plugins/Makefile.am
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -40,7 +40,7 @@ saveactions_la_SOURCES   = saveactions.c
 filebrowser_la_SOURCES   = filebrowser.c
 splitwindow_la_SOURCES   = splitwindow.c
 
-demoplugin_la_CFLAGS    = -DG_LOG_DOMAIN=\""Demoplugin"\"
+demoplugin_la_CFLAGS    = -DG_LOG_DOMAIN=\""Demoplugin"\" -DLOCALEDIR=\""$(LOCALEDIR)"\"
 classbuilder_la_CFLAGS  = -DG_LOG_DOMAIN=\""Classbuilder"\"
 htmlchars_la_CFLAGS     = -DG_LOG_DOMAIN=\""HTMLChars"\"
 export_la_CFLAGS        = -DG_LOG_DOMAIN=\""Export"\"


Modified: plugins/demoplugin.c
82 lines changed, 51 insertions(+), 31 deletions(-)
===================================================================
@@ -35,29 +35,20 @@
 #include "geanyplugin.h"	/* plugin API, always comes first */
 #include "Scintilla.h"	/* for the SCNotification struct */
 
-
-/* These items are set by Geany before plugin_init() is called. */
-GeanyPlugin		*geany_plugin;
-GeanyData		*geany_data;
-
-
-/* Check that the running Geany supports the plugin API version used below, and check
- * for binary compatibility. */
-PLUGIN_VERSION_CHECK(147)
-
-/* All plugins must set name, description, version and author. */
-PLUGIN_SET_INFO(_("Demo"), _("Example plugin."), "0.1" , _("The Geany developer team"))
-
-
 static GtkWidget *main_menu_item = NULL;
 /* text to be shown in the plugin dialog */
 static gchar *welcome_text = NULL;
 
 
-
 static gboolean on_editor_notify(GObject *object, GeanyEditor *editor,
 								 SCNotification *nt, gpointer data)
 {
+	/* data == GeanyPlugin because the data member of PluginCallback was set to NULL
+	 * and this plugin has called geany_plugin_set_data() with the GeanyPlugin pointer as
+	 * data */
+	GeanyPlugin *plugin = data;
+	GeanyData *geany_data = plugin->geany_data;
+
 	/* For detailed documentation about the SCNotification struct, please see
 	 * http://www.scintilla.org/ScintillaDoc.html#Notifications. */
 	switch (nt->nmhdr.code)
@@ -78,7 +69,7 @@ static gboolean on_editor_notify(GObject *object, GeanyEditor *editor,
 				GtkWidget *dialog;
 
 				dialog = gtk_message_dialog_new(
-					GTK_WINDOW(geany->main_widgets->window),
+					GTK_WINDOW(geany_data->main_widgets->window),
 					GTK_DIALOG_DESTROY_WITH_PARENT,
 					GTK_MESSAGE_INFO,
 					GTK_BUTTONS_OK,
@@ -99,7 +90,7 @@ static gboolean on_editor_notify(GObject *object, GeanyEditor *editor,
 }
 
 
-PluginCallback plugin_callbacks[] =
+static PluginCallback demo_callbacks[] =
 {
 	/* Set 'after' (third field) to TRUE to run the callback @a after the default handler.
 	 * If 'after' is FALSE, the callback is run @a before the default handler, so the plugin
@@ -114,32 +105,34 @@ static void
 item_activate(GtkMenuItem *menuitem, gpointer gdata)
 {
 	GtkWidget *dialog;
+	GeanyPlugin *plugin = gdata;
+	GeanyData *geany_data = plugin->geany_data;
 
 	dialog = gtk_message_dialog_new(
-		GTK_WINDOW(geany->main_widgets->window),
+		GTK_WINDOW(geany_data->main_widgets->window),
 		GTK_DIALOG_DESTROY_WITH_PARENT,
 		GTK_MESSAGE_INFO,
 		GTK_BUTTONS_OK,
 		"%s", welcome_text);
 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
-		_("(From the %s plugin)"), geany_plugin->info->name);
+		_("(From the %s plugin)"), plugin->info->name);
 
 	gtk_dialog_run(GTK_DIALOG(dialog));
 	gtk_widget_destroy(dialog);
 }
 
 
-/* Called by Geany to initialize the plugin.
- * Note: data is the same as geany_data. */
-void plugin_init(GeanyData *data)
+/* Called by Geany to initialize the plugin */
+static gboolean demo_init(GeanyPlugin *plugin, gpointer data)
 {
 	GtkWidget *demo_item;
+	GeanyData *geany_data = plugin->geany_data;
 
 	/* Add an item to the Tools menu */
 	demo_item = gtk_menu_item_new_with_mnemonic(_("_Demo Plugin"));
 	gtk_widget_show(demo_item);
-	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), demo_item);
-	g_signal_connect(demo_item, "activate", G_CALLBACK(item_activate), NULL);
+	gtk_container_add(GTK_CONTAINER(geany_data->main_widgets->tools_menu), demo_item);
+	g_signal_connect(demo_item, "activate", G_CALLBACK(item_activate), plugin);
 
 	/* make the menu item sensitive only when documents are open */
 	ui_add_document_sensitive(demo_item);
@@ -147,10 +140,20 @@ void plugin_init(GeanyData *data)
 	main_menu_item = demo_item;
 
 	welcome_text = g_strdup(_("Hello World!"));
+
+	/* This might seem strange but is a method to get the GeanyPlugin pointer passed to
+	 * on_editor_notify(). PluginCallback functions get the same data that was set via
+	 * GEANY_PLUING_REGISTER_FULL() or geany_plugin_set_data() by default (unless the data pointer
+	 * was set to non-NULL at compile time).
+	 * This is really only done for demoing PluginCallback. Actual plugins will use real custom
+	 * data and perhaps embed the GeanyPlugin or GeanyData pointer their if they also use
+	 * PluginCallback. */
+	geany_plugin_set_data(plugin, plugin, NULL);
+	return TRUE;
 }
 
 
-/* Callback connected in plugin_configure(). */
+/* Callback connected in demo_configure(). */
 static void
 on_configure_response(GtkDialog *dialog, gint response, gpointer user_data)
 {
@@ -170,13 +173,12 @@ on_configure_response(GtkDialog *dialog, gint response, gpointer user_data)
 	}
 }
 
-
 /* Called by Geany to show the plugin's configure dialog. This function is always called after
- * plugin_init() was called.
+ * demo_init() was called.
  * You can omit this function if the plugin doesn't need to be configured.
  * Note: parent is the parent window which can be used as the transient window for the created
  *       dialog. */
-GtkWidget *plugin_configure(GtkDialog *dialog)
+static GtkWidget *demo_configure(GeanyPlugin *plugin, GtkDialog *dialog, gpointer data)
 {
 	GtkWidget *label, *entry, *vbox;
 
@@ -203,11 +205,29 @@ GtkWidget *plugin_configure(GtkDialog *dialog)
 
 /* Called by Geany before unloading the plugin.
  * Here any UI changes should be removed, memory freed and any other finalization done.
- * Be sure to leave Geany as it was before plugin_init(). */
-void plugin_cleanup(void)
+ * Be sure to leave Geany as it was before demo_init(). */
+static void demo_cleanup(GeanyPlugin *plugin, gpointer data)
 {
-	/* remove the menu item added in plugin_init() */
+	/* remove the menu item added in demo_init() */
 	gtk_widget_destroy(main_menu_item);
 	/* release other allocated strings and objects */
 	g_free(welcome_text);
 }
+
+void geany_load_module(GeanyPlugin *plugin)
+{
+	/* main_locale_init() must be called for your package before any localization can be done */
+	main_locale_init(LOCALEDIR, GETTEXT_PACKAGE);
+	plugin->info->name = _("Demo");
+	plugin->info->description = _("Example plugin.");
+	plugin->info->version = "0.4";
+	plugin->info->author =  _("The Geany developer team");
+
+	plugin->funcs->init = demo_init;
+	plugin->funcs->configure = demo_configure;
+	plugin->funcs->help = NULL; /* This demo has no help but it is an option */
+	plugin->funcs->cleanup = demo_cleanup;
+	plugin->funcs->callbacks = demo_callbacks;
+
+	GEANY_PLUGIN_REGISTER(plugin, 225);
+}


Modified: src/plugindata.h
106 lines changed, 94 insertions(+), 12 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()
@@ -196,7 +185,9 @@ typedef struct PluginCallback
 	GCallback	callback;
 	/** Set to TRUE to connect your handler with g_signal_connect_after(). */
 	gboolean	after;
-	/** The user data passed to the signal handler. */
+	/** The user data passed to the signal handler. If set to NULL then the signal
+	 * handler will receive the data set with geany_plugin_register_full() or
+	 * geany_plugin_set_data() */
 	gpointer	user_data;
 }
 PluginCallback;
@@ -248,6 +239,19 @@ 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 */
+}
+GeanyPlugin;
 
 #ifndef GEANY_PRIVATE
 
@@ -263,8 +267,86 @@ 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.
+ *
+ * For all glory details please read @ref howto.
+ *
+ * Because the plugin is not yet enabled by the user you may not call plugin API functions inside
+ * this function, except for the API functions below which are required for proper registration.
+ *
+ * API functions which are allowed to be called within this function:
+ *  - main_locale_init()
+ *  - geany_plugin_register() (and GEANY_PLUGIN_REGISTER())
+ *  - geany_plugin_register_full() (and GEANY_PLUGIN_REGISTER_FULL())
+ *
+ * @param plugin The unique plugin handle to your plugin. You must set some fields here.
+ *
+ * @since 1.26 (API 225)
+ * @see @ref howto
+ */
+void 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)
+ * @see @ref howto
+ **/
+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) */
+	gboolean    (*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);
+};
+
+gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version,
+                               gint min_api_version, gint abi_version);
+gboolean geany_plugin_register_full(GeanyPlugin *plugin, gint api_version,
+                                    gint min_api_version, gint abi_version,
+                                    gpointer data, GDestroyNotify free_func);
+void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify free_func);
+
+/** 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)
+ * @see @ref howto
+ **/
+#define GEANY_PLUGIN_REGISTER(plugin, min_api_version) \
+	geany_plugin_register((plugin), GEANY_API_VERSION, \
+	                      (min_api_version), GEANY_ABI_VERSION)
+
+/** Convinience macro to register a plugin with data.
+ *
+ * It simply calls geany_plugin_register_full() with GEANY_API_VERSION and GEANY_ABI_VERSION.
+ *
+ * @since 1.26 (API 225)
+ * @see @ref howto
+ **/
+#define GEANY_PLUGIN_REGISTER_FULL(plugin, min_api_version, pdata, free_func) \
+	geany_plugin_register_full((plugin), GEANY_API_VERSION, \
+	                           (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func))
+
 /* Deprecated aliases */
 #ifndef GEANY_DISABLE_DEPRECATED
 


Modified: src/pluginprivate.h
21 lines changed, 16 insertions(+), 5 deletions(-)
===================================================================
@@ -39,6 +39,12 @@ typedef struct SignalConnection
 }
 SignalConnection;
 
+typedef enum _LoadedFlags {
+	LOADED_OK = 0x01,
+	IS_LEGACY = 0x02,
+	LOAD_DATA = 0x04,
+}
+LoadedFlags;
 
 typedef struct GeanyPluginPrivate
 {
@@ -47,11 +53,8 @@ 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 */
+	GeanyPluginFuncs cbs;					/* Callbacks set by geany_plugin_register() */
+	void		(*configure_single) (GtkWidget *parent); /* plugin configure dialog, optional and deprecated */
 
 	/* extra stuff */
 	PluginFields	fields;
@@ -59,9 +62,17 @@ 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 */
+	GDestroyNotify	cb_data_destroy;		/* called when the plugin is unloaded, for cb_data */
+	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)
+#define PLUGIN_HAS_LOAD_DATA(p) (((p)->flags & LOAD_DATA) != 0)
+
 typedef GeanyPluginPrivate Plugin;	/* shorter alias */
 
 


Modified: src/plugins.c
466 lines changed, 336 insertions(+), 130 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;
 }
@@ -217,8 +206,9 @@ static void add_callbacks(Plugin *plugin, PluginCallback *callbacks)
 	{
 		cb = &callbacks[i];
 
+		/* Pass the callback data as default user_data if none was set by the plugin itself */
 		plugin_signal_connect(&plugin->public, NULL, cb->signal_name, cb->after,
-			cb->callback, cb->user_data);
+			cb->callback, cb->user_data ? cb->user_data : plugin->cb_data);
 	}
 }
 
@@ -269,66 +259,275 @@ 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 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):
+ *  - 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)
+ *
+ * @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)
 {
-	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;
+	GeanyPluginFuncs *cbs = plugin->funcs;
+
+	g_return_val_if_fail(plugin != NULL, FALSE);
+
+	p = plugin->priv;
+	/* already registered successfully */
+	g_return_val_if_fail(!PLUGIN_LOADED_OK(p), FALSE);
 
-	g_module_symbol(plugin->module, "plugin_help", (void *) &plugin->help);
-	g_module_symbol(plugin->module, "plugin_cleanup", (void *) &plugin->cleanup);
-	if (plugin->cleanup == NULL)
+	/* Prevent registering incompatible plugins. */
+	if (! plugin_check_version(p, PLUGIN_VERSION_CODE(api_version, abi_version)))
+		return FALSE;
+
+	/* Only init and cleanup callbacks are truly mandatory. */
+	if (! cbs->init || ! cbs->cleanup)
+	{
+		geany_debug("Plugin '%s' has no %s function - ignoring plugin!",
+				 g_module_name(p->module), cbs->init ? "cleanup" : "init");
+	}
+	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;
+	}
+
+	/* 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);
+}
+
+
+/** Register a plugin to Geany, with plugin-defined data.
+ *
+ * This is a variant of geany_plugin_register() that also allows to set the plugin-defined data.
+ * Refer to that function for more details on registering in general.
+ *
+ * @p pdata is the pointer going to be passed to the individual plugin callbacks
+ * of GeanyPlugin::funcs. When the plugin module is unloaded, @p free_func is invoked on
+ * @p pdata, which connects the data to the plugin's module life time.
+ *
+ * You cannot use geany_plugin_set_data() after registering with this function. Use
+ * geany_plugin_register() if you need to.
+ *
+ * Do not call this directly. Use GEANY_PLUGIN_REGISTER_FULL() instead which automatically
+ * handles @p api_version and @p 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 pdata Pointer to the plugin-defined data. Must not be @c NULL.
+ * @param free_func Function used to deallocate @a pdata, may be @c NULL.
+ *
+ * @return TRUE if the plugin was successfully registered. Otherwise FALSE.
+ *
+ * @since 1.26 (API 225)
+ * @see GEANY_PLUGIN_REGISTER_FULL()
+ * @see geany_plugin_register()
+ **/
+GEANY_API_SYMBOL
+gboolean geany_plugin_register_full(GeanyPlugin *plugin, gint api_version, gint min_api_version,
+									gint abi_version, gpointer pdata, GDestroyNotify free_func)
+{
+	if (geany_plugin_register(plugin, api_version, min_api_version, abi_version))
+	{
+		geany_plugin_set_data(plugin, pdata, free_func);
+		/* We use LOAD_DATA to indicate that pdata cb_data was set during loading/registration
+		 * as opposed to during GeanyPluginFuncs::init(). In the latter case we call free_func
+		 * after GeanyPluginFuncs::cleanup() */
+		plugin->priv->flags |= LOAD_DATA;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+struct LegacyRealFuncs
+{
+	void       (*init) (GeanyData *data);
+	GtkWidget* (*configure) (GtkDialog *dialog);
+	void       (*help) (void);
+	void       (*cleanup) (void);
+};
+
+/* Wrappers to support legacy plugins are below */
+static gboolean legacy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+	struct LegacyRealFuncs *h = pdata;
+	h->init(plugin->geany_data);
+	return TRUE;
+}
+
+static void legacy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+	struct LegacyRealFuncs *h = pdata;
+	/* Can be NULL because it's optional for legacy plugins */
+	if (h->cleanup)
+		h->cleanup();
+}
+
+static void legacy_help(GeanyPlugin *plugin, gpointer pdata)
+{
+	struct LegacyRealFuncs *h = pdata;
+	h->help();
+}
+
+static GtkWidget *legacy_configure(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata)
+{
+	struct LegacyRealFuncs *h = pdata;
+	return h->configure(parent);
+}
+
+static void free_legacy_cbs(gpointer data)
+{
+	g_slice_free(struct LegacyRealFuncs, data);
+}
+
+/* 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);
+	void (*p_init) (GeanyData *geany_data);
+	GeanyData **p_geany_data;
+	struct LegacyRealFuncs *h;
+
+#define CHECK_FUNC(__x)                                                                   \
+	if (! g_module_symbol(module, "plugin_" #__x, (void *) (&p_##__x)))                   \
+	{                                                                                     \
+		geany_debug("Plugin \"%s\" has no plugin_" #__x "() function - ignoring plugin!", \
+				g_module_name(plugin->module));                                           \
+		return;                                                                           \
+	}
+	CHECK_FUNC(version_check);
+	CHECK_FUNC(set_info);
+	CHECK_FUNC(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;
+
+	h = g_slice_new(struct LegacyRealFuncs);
+
+	/* 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. */
+	h->init = p_init;
+	g_module_symbol(module, "plugin_configure", (void *) &h->configure);
+	g_module_symbol(module, "plugin_configure_single", (void *) &plugin->configure_single);
+	g_module_symbol(module, "plugin_help", (void *) &h->help);
+	g_module_symbol(module, "plugin_cleanup", (void *) &h->cleanup);
+	/* pointer to callbacks struct can be stored directly, no wrapper necessary */
+	g_module_symbol(module, "plugin_callbacks", (void *) &plugin->cbs.callbacks);
+	if (app->debug_mode)
+	{
+		if (h->configure && plugin->configure_single)
+			g_warning("Plugin '%s' implements plugin_configure_single() unnecessarily - "
+				"only plugin_configure() will be used!",
+				plugin->info.name);
+		if (h->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->cbs.init = legacy_init;
+	plugin->cbs.cleanup = legacy_cleanup;
+	plugin->cbs.configure = h->configure ? legacy_configure : NULL;
+	plugin->cbs.help = h->help ? legacy_help : NULL;
+
+	plugin->flags = LOADED_OK | IS_LEGACY;
+	geany_plugin_set_data(&plugin->public, h, free_legacy_cbs);
+}
+
 
-	if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
+static gboolean
+plugin_load(Plugin *plugin)
+{
+	gboolean init_ok = TRUE;
+	/* Start the plugin. Legacy plugins require additional cruft. */
+	if (PLUGIN_IS_LEGACY(plugin))
 	{
-		ui_add_document_sensitive(plugin->fields.menu_item);
+		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);
+
+		/* Legacy plugin_init() cannot fail. */
+		plugin->cbs.init(&plugin->public, plugin->cb_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);
+		}
+	}
+	else
+	{
+		init_ok = plugin->cbs.init(&plugin->public, plugin->cb_data);
 	}
 
-	g_module_symbol(plugin->module, "plugin_callbacks", (void *) &callbacks);
-	if (callbacks)
-		add_callbacks(plugin, callbacks);
+	if (! init_ok)
+		return FALSE;
+
+	/* new-style plugins set their callbacks in geany_load_module() */
+	if (plugin->cbs.callbacks)
+		add_callbacks(plugin, plugin->cbs.callbacks);
 
 	/* remember which plugins are active.
 	 * keep list sorted so tools menu items and plugin preference tabs are
 	 * 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);
+	return TRUE;
 }
 
 
@@ -342,8 +541,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*);
+	void (*p_geany_load_module)(GeanyPlugin *);
 
 	g_return_val_if_fail(fname, NULL);
 	g_return_val_if_fail(g_module_supported(), NULL);
@@ -387,68 +585,65 @@ 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/funcs must to be initialized by the plugin */
+	plugin->public.info = &plugin->info;
+	plugin->public.funcs = &plugin->cbs;
+
+	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);
+	if (load_plugin && !plugin_load(plugin))
+	{
+		/* Handle failing init same as failing to load for now. In future we
+		 * could present a informational UI or something */
+		geany_debug("Plugin failed to initialize \"%s\" - ignoring plugin!", fname);
+		goto err;
+	}
 
 	if (add_to_list)
 		plugin_list = g_list_prepend(plugin_list, plugin);
 
 	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);
+	g_free(plugin);
+	return NULL;
 }
 
 
@@ -529,8 +724,8 @@ plugin_cleanup(Plugin *plugin)
 {
 	GtkWidget *widget;
 
-	if (plugin->cleanup)
-		plugin->cleanup();
+	/* With geany_register_plugin cleanup is mandatory */
+	plugin->cbs.cleanup(&plugin->public, plugin->cb_data);
 
 	remove_callbacks(plugin);
 	remove_sources(plugin);
@@ -542,6 +737,16 @@ plugin_cleanup(Plugin *plugin)
 	if (widget)
 		gtk_widget_destroy(widget);
 
+	if (!PLUGIN_HAS_LOAD_DATA(plugin) && plugin->cb_data_destroy)
+	{
+		/* If the plugin has used geany_plugin_set_data(), destroy the data here. But don't
+		 * if it was already set through geany_plugin_register_full() because we couldn't call
+		 * its init() anymore (not without completely reloading it anyway). */
+		plugin->cb_data_destroy(plugin->cb_data);
+		plugin->cb_data = NULL;
+		plugin->cb_data_destroy = NULL;
+	}
+
 	geany_debug("Unloaded: %s", plugin->filename);
 }
 
@@ -556,12 +761,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;
@@ -851,7 +1059,7 @@ gboolean plugins_have_preferences(void)
 	foreach_list(item, active_plugin_list)
 	{
 		Plugin *plugin = item->data;
-		if (plugin->configure != NULL || plugin->configure_single != NULL)
+		if (plugin->configure_single != NULL || plugin->cbs.configure != NULL)
 			return TRUE;
 	}
 
@@ -892,17 +1100,15 @@ 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;
+		has_configure = p->cbs.configure || p->configure_single;
+		has_help = p->cbs.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 +1445,8 @@ 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)
+				p->cbs.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
60 lines changed, 56 insertions(+), 4 deletions(-)
===================================================================
@@ -318,10 +318,9 @@ static GtkWidget *create_pref_page(Plugin *p, GtkWidget *dialog)
 {
 	GtkWidget *page = NULL;	/* some plugins don't have prefs */
 
-	if (p->configure)
+	if (p->cbs.configure)
 	{
-		page = p->configure(GTK_DIALOG(dialog));
-
+		page = p->cbs.configure(&p->public, GTK_DIALOG(dialog), p->cb_data);
 		if (! GTK_IS_WIDGET(page))
 		{
 			geany_debug("Invalid widget returned from plugin_configure() in plugin \"%s\"!",
@@ -421,7 +420,7 @@ void plugin_show_configure(GeanyPlugin *plugin)
 	}
 	p = plugin->priv;
 
-	if (p->configure)
+	if (p->cbs.configure)
 		configure_plugins(p);
 	else
 	{
@@ -514,4 +513,57 @@ void plugin_builder_connect_signals(GeanyPlugin *plugin,
 }
 
 
+/** Add additional data that corresponds to the plugin.
+ *
+ * @p pdata is the pointer going to be passed to the individual plugin callbacks
+ * of GeanyPlugin::funcs. When the  plugin is cleaned up, @p free_func is invoked for the data,
+ * which connects the data to the time the plugin is enabled.
+ *
+ * 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 and only by plugins registered via
+ * @ref geany_plugin_register(). So-called legacy plugins cannot use this function.
+ *
+ * @note This function must not be called if the plugin was registered with
+ * geany_plugin_register_full().
+ *
+ * @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)
+	{
+		if (PLUGIN_HAS_LOAD_DATA(p))
+			g_warning("Invalid call to %s(), geany_plugin_register_full() was used. Ignored!\n", G_STRFUNC);
+		else
+			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).


More information about the Commits mailing list