plugins: Updated doxygen for the new plugin loader

The documentation provides a quite detailed description of the new loader
In addition it adds a "how to transition" that briefly describes the old
loader (for curious newcomers) and lots of hints for porting legacy
plugins to the new loader.
This commit is contained in:
Thomas Martitz 2015-08-23 15:23:35 +02:00
parent 58c8144afc
commit d54b65b9ac
3 changed files with 415 additions and 155 deletions

View File

@ -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
@ -39,7 +40,7 @@ We will try to document as many functions and structs as possible.
@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 +171,175 @@ 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.
@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.
@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.
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.
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.
Let's start with the very basic headers and add more later if necessary.
@code
#include <geanyplugin.h>
@endcode
@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.
@note If you use autoconf then config.h must be included even before that as usual.
@note
@a plugindata.h contains the biggest part of the plugin API and provides some basic macros.
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).
@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(), 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.
4. Optionally setting user data that is passed to GeanyPlugin::funcs with geany_plugin_set_data().
Here you can set a data pointer that is passed back to your functions called by Geany. This
allows to avoid global variables which may be useful. It also allows 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 call this function later on, for example in your @ref GeanyPluginFuncs::init
routine to defer costly allocations to when the plugin is actually activated by the user.
@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.
@subsection example Example
Going back to our "Hello World" plugin here is example code that properly adds the HelloWorld
plugin to Geany.
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;
@endcode
/* License blob */
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().
#include <geanyplugin.h>
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
@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.
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.
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:
@code
PLUGIN_SET_INFO("HelloWorld", "Just another tool to say hello world",
"1.0", "John Doe <john.doe@example.org>");
@endcode
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:
@code
PLUGIN_VERSION_CHECK(211)
PLUGIN_SET_INFO("HelloWorld", "Just another tool to say hello world",
"1.0", "Joe Doe <joe.doe@example.org>");
void plugin_init(GeanyData *data)
static gboolean hello_init(GeanyPlugin *plugin, gpointer pdata)
{
printf("Hello World from plugin!\n");
/* Perform advanced set up here */
return TRUE;
}
void plugin_cleanup(void)
static void hello_cleanup(GeanyPlugin *plugin, gpointer pdata)
{
printf("Bye World :-(\n");
}
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@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;
/* Step 4: call geany_plugin_set_data(), not done here
geany_plugin_set_data(plugin, data, free_func); */
}
@endcode
@ -246,22 +347,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?
@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_set_data()). 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 +375,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.
@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 +382,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 +396,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 +426,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 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.
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.
@code
static GtkWidget *main_menu_item = NULL;
/* 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 +467,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@example.org>");
static GtkWidget *main_menu_item = NULL;
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@example.org>";
plugin->funcs->init = hello_init;
plugin->funcs->cleanup = hello_cleanup;
GEANY_PLUGIN_REGISTER(plugin, 225);
}
@endcode
Now you might like to look at Geany's source code for core plugins such as
@a plugins/demoplugin.c.
@section furtherimprovements Furter Improvements and next steps
@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@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@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 +552,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
@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"));
// ...
}
@endcode
@page legacy Porting guide from legacy entry points to the current ones
@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.
@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.
@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.
@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().
@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
@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.
@note Again, plugin_configure_single() is not supported anymore.
@subsection Variables
Exported global variables are not recognized anymore. They are replaced in the following ways:
@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:
@code
PLUGIN_SET_INFO(
"HelloWorld",
"Just another tool to say hello world",
"1.0", "John Doe <john.doe@example.org>");
@endcode
becomes
@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@example.org>";
// ...
}
@endcode
@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.
@ref plugin_fields is not supported anymore. Use ui_add_document_sensitive() instead.
@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.
@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;
}
@endcode
@ref geany_plugin is now also passed by default to the PluginCallback signal handlers as data
pointer if it's set to NULL.
@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
@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.
*/

View File

@ -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();

View File

@ -273,9 +273,12 @@ void plugin_cleanup(void);
* 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.
*
* @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);
@ -291,6 +294,7 @@ void geany_load_module(GeanyPlugin *plugin);
* pointer.
*
* @since 1.26 (API 225)
* @see @ref howto
**/
struct GeanyPluginFuncs
{
@ -315,6 +319,7 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify de
* 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, \