diff --git a/ChangeLog b/ChangeLog index 46b15de7a..5d7a6b28c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2007-06-27 Nick Treleaven + + * plugins/demoplugin.c, src/plugindata.h, src/plugins.c: + Move plugin name and description into a separate struct, which is set + by calling the PLUGIN_INFO() macro - this can be read before the + plugin is initialized. + Added more comments for plugin authors. + + 2007-06-26 Nick Treleaven * plugins/demoplugin.c, plugins/Makefile.am, configure.in, diff --git a/plugins/demoplugin.c b/plugins/demoplugin.c index 34a596321..84cf8a3ab 100644 --- a/plugins/demoplugin.c +++ b/plugins/demoplugin.c @@ -38,12 +38,15 @@ static struct local_data; -/* This performs runtime checks that try to ensure: - * 1. Geany ABI data types are compatible with this plugin. - * 2. Geany sources provide the required API for this plugin. */ -VERSION_CHECK(1) +/* Check that Geany supports plugin API version 2 or later, and check + * for binary compatibility. */ +VERSION_CHECK(2) + +/* All plugins must set name and description */ +PLUGIN_INFO(_("Demo"), _("Example plugin.")) +/* Callback when the menu item is clicked */ static void item_activate(GtkMenuItem *menuitem, gpointer gdata) { @@ -56,28 +59,35 @@ item_activate(GtkMenuItem *menuitem, gpointer gdata) GTK_BUTTONS_OK, _("Hello World!")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), - _("(From the %s plugin)"), my_data->name); + _("(From the %s plugin)"), info()->name); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } +/* Called by Geany to initialize the plugin */ void init(PluginData *data) { - my_data = data; - my_data->name = g_strdup("Demo"); + GtkWidget *demo_item; - local_data.menu_item = gtk_menu_item_new_with_mnemonic(_("_Demo Plugin")); - gtk_widget_show(local_data.menu_item); - gtk_container_add(GTK_CONTAINER(my_data->tools_menu), local_data.menu_item); - g_signal_connect(G_OBJECT(local_data.menu_item), "activate", G_CALLBACK(item_activate), NULL); + my_data = data; // keep a pointer to the main application fields & functions + + // 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(my_data->tools_menu), demo_item); + g_signal_connect(G_OBJECT(demo_item), "activate", G_CALLBACK(item_activate), NULL); + + // keep a pointer to the menu item, so we can remove it when the plugin is unloaded + local_data.menu_item = demo_item; } +/* Called by Geany before unloading the plugin. + * Here any UI changes should be removed, memory freed and any other finalization done */ void cleanup() { + // remove the menu item added in init() gtk_widget_destroy(local_data.menu_item); - - g_free(my_data->name); } diff --git a/src/plugindata.h b/src/plugindata.h index 054113e9d..59dc5a95f 100644 --- a/src/plugindata.h +++ b/src/plugindata.h @@ -27,14 +27,16 @@ /* The API version should be incremented whenever any plugin data types below are * modified. */ -static const gint api_version = 1; +static const gint api_version = 2; /* The ABI version should be incremented whenever existing fields in the plugin * data types below have to be changed or reordered. It should stay the same if fields * are only appended, as this doesn't affect existing fields. */ -static const gint abi_version = 1; - +static const gint abi_version = 2; +/* This performs runtime checks that try to ensure: + * 1. Geany ABI data types are compatible with this plugin. + * 2. Geany sources provide the required API for this plugin. */ /* TODO: if possible, the API version should be checked at compile time, not runtime. */ #define VERSION_CHECK(api_required) \ gint version_check(gint abi_ver) \ @@ -47,18 +49,37 @@ static const gint abi_version = 1; } -typedef struct PluginData PluginData; - -struct PluginData +typedef struct PluginInfo { gchar *name; // name of plugin gchar *description; // description of plugin +} +PluginInfo; +/* Sets the plugin name and a brief description of what it is. */ +#define PLUGIN_INFO(p_name, p_description) \ + PluginInfo *info() \ + { \ + static PluginInfo p_info; \ + \ + p_info.name = (p_name); \ + p_info.description = (p_description); \ + return &p_info; \ + } + + +/* These are fields and functions owned by Geany. + * Fields will be appended when needed by plugin authors. + * Note: Remember to increment api_version (and abi_version if necessary) when + * making changes. */ +typedef struct PluginData +{ MyApp *app; // Geany application data fields /* Almost all plugins should add menu items to the Tools menu only */ GtkWidget *tools_menu; -}; +} +PluginData; #endif diff --git a/src/plugins.c b/src/plugins.c index 3b45f42cc..973dce212 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -42,6 +42,7 @@ struct Plugin gchar *filename; // plugin filename (/path/libname.so) PluginData data; + PluginInfo* (*info) (); /* Returns plugin name, description */ void (*init) (PluginData *data); /* Called when the plugin is enabled */ void (*cleanup) (); /* Called when the plugin is disabled or when Geany exits */ }; @@ -102,6 +103,7 @@ plugin_new(const gchar *fname) { Plugin *plugin; GModule *module; + PluginInfo* (*info)(); g_return_val_if_fail(fname, NULL); g_return_val_if_fail(g_module_supported(), NULL); @@ -129,7 +131,20 @@ plugin_new(const gchar *fname) return NULL; } + g_module_symbol(module, "info", (void *) &info); + if (info == NULL) + { + geany_debug("Unknown plugin info for \"%s\"!", fname); + + if (! g_module_close(module)) + g_warning("%s: %s", fname, g_module_error()); + return NULL; + } + geany_debug("Initializing plugin '%s' (%s)", + info()->name, info()->description); + plugin = g_new0(Plugin, 1); + plugin->info = info; plugin->filename = g_strdup(fname); plugin->module = module; @@ -143,7 +158,7 @@ plugin_new(const gchar *fname) plugin->init(&plugin->data); geany_debug("Loaded: %s (%s)", fname, - NVL(plugin->data.name, "")); + NVL(plugin->info()->name, "")); return plugin; }