commit
280163a244
561
doc/plugins.dox
561
doc/plugins.dox
@ -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.
|
||||
@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.
|
||||
@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() 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().
|
||||
|
||||
|
||||
@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;
|
||||
/* 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?
|
||||
|
||||
@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.
|
||||
|
||||
@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 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 +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@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 +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
|
||||
@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.
|
||||
|
||||
*/
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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"\"
|
||||
|
@ -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);
|
||||
}
|
||||
|
106
src/plugindata.h
106
src/plugindata.h
@ -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
|
||||
|
||||
|
@ -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 */
|
||||
|
||||
|
||||
|
474
src/plugins.c
474
src/plugins.c
@ -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;
|
||||
Plugin *p;
|
||||
GeanyPluginFuncs *cbs = plugin->funcs;
|
||||
|
||||
/* 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);
|
||||
g_return_val_if_fail(plugin != NULL, FALSE);
|
||||
|
||||
/* start the plugin */
|
||||
g_return_if_fail(plugin->init);
|
||||
plugin->init(&geany_data);
|
||||
p = plugin->priv;
|
||||
/* already registered successfully */
|
||||
g_return_val_if_fail(!PLUGIN_LOADED_OK(p), FALSE);
|
||||
|
||||
/* 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);
|
||||
/* Prevent registering incompatible plugins. */
|
||||
if (! plugin_check_version(p, PLUGIN_VERSION_CODE(api_version, abi_version)))
|
||||
return 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)
|
||||
/* Only init and cleanup callbacks are truly mandatory. */
|
||||
if (! cbs->init || ! cbs->cleanup)
|
||||
{
|
||||
if (app->debug_mode)
|
||||
geany_debug("Plugin '%s' has no %s function - ignoring plugin!",
|
||||
g_module_name(p->module), cbs->init ? "cleanup" : "init");
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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;
|
||||
|
||||
if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
|
||||
plugin->flags = LOADED_OK | IS_LEGACY;
|
||||
geany_plugin_set_data(&plugin->public, h, free_legacy_cbs);
|
||||
}
|
||||
|
||||
|
||||
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))
|
||||
{
|
||||
if (! g_module_close(module))
|
||||
g_warning("%s: %s", fname, g_module_error());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_module_symbol(module, "plugin_set_info", (void *) &plugin_set_info);
|
||||
if (plugin_set_info == NULL)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
g_module_symbol(module, "plugin_init", (void *) &plugin->init);
|
||||
if (plugin->init == NULL)
|
||||
{
|
||||
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("Initializing plugin '%s'", plugin->info.name);*/
|
||||
|
||||
plugin->filename = g_strdup(fname);
|
||||
plugin->module = module;
|
||||
plugin->public.info = &plugin->info;
|
||||
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;
|
||||
|
||||
if (load_plugin)
|
||||
plugin_load(plugin);
|
||||
g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module);
|
||||
if (p_geany_load_module)
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
|
||||
if (! PLUGIN_LOADED_OK(plugin))
|
||||
{
|
||||
geany_debug("Failed to load \"%s\" - ignoring plugin!", fname);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (EMPTY(plugin->info.name))
|
||||
{
|
||||
geany_debug("No plugin name set for \"%s\" - ignoring plugin!", fname);
|
||||
goto err;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user