Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy plugins #629

Merged
merged 12 commits into from Oct 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
397 changes: 397 additions & 0 deletions doc/plugins.dox

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions plugins/Makefile.am
@@ -1,7 +1,8 @@
# Adapted from Pidgin's plugins/Makefile.am, thanks

EXTRA_DIST = \
makefile.win32
makefile.win32 \
demoproxytest.px

plugindir = $(libdir)/geany

Expand All @@ -11,6 +12,7 @@ plugins_include_HEADERS = \
geanyplugin.h

demoplugin_la_LDFLAGS = -module -avoid-version -no-undefined
demoproxy_la_LDFLAGS = -module -avoid-version -no-undefined
classbuilder_la_LDFLAGS = -module -avoid-version -no-undefined
htmlchars_la_LDFLAGS = -module -avoid-version -no-undefined
export_la_LDFLAGS = -module -avoid-version -no-undefined
Expand All @@ -30,9 +32,11 @@ plugin_LTLIBRARIES = \

# Plugins not to be installed
noinst_LTLIBRARIES = \
demoplugin.la
demoplugin.la \
demoproxy.la

demoplugin_la_SOURCES = demoplugin.c
demoproxy_la_SOURCES = demoproxy.c
classbuilder_la_SOURCES = classbuilder.c
htmlchars_la_SOURCES = htmlchars.c
export_la_SOURCES = export.c
Expand All @@ -41,6 +45,7 @@ filebrowser_la_SOURCES = filebrowser.c
splitwindow_la_SOURCES = splitwindow.c

demoplugin_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoplugin"\" -DLOCALEDIR=\""$(LOCALEDIR)"\"
demoproxy_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoproxy"\"
classbuilder_la_CFLAGS = -DG_LOG_DOMAIN=\""Classbuilder"\"
htmlchars_la_CFLAGS = -DG_LOG_DOMAIN=\""HTMLChars"\"
export_la_CFLAGS = -DG_LOG_DOMAIN=\""Export"\"
Expand All @@ -49,6 +54,7 @@ filebrowser_la_CFLAGS = -DG_LOG_DOMAIN=\""FileBrowser"\"
splitwindow_la_CFLAGS = -DG_LOG_DOMAIN=\""SplitWindow"\"

demoplugin_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
demoproxy_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
classbuilder_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
htmlchars_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS)
export_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) -lm
Expand Down
202 changes: 202 additions & 0 deletions plugins/demoproxy.c
@@ -0,0 +1,202 @@
/*
* demoproxy.c - this file is part of Geany, a fast and lightweight IDE
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

/**
* Demo proxy - example of a basic proxy plugin for Geany. Sub-plugins add menu items to the
* Tools menu and have a help dialog.
*
* Note: This is compiled but not installed by default. On Unix, you can install it by compiling
* Geany and then copying (or symlinking) to the plugins/demoproxy.so and
* plugins/demoproxytest.px files to ~/.config/geany/plugins
* - it will be loaded at next startup.
*/

/* plugin API, always comes first */
#include "geanyplugin.h"

typedef struct {
GKeyFile *file;
gchar *help_text;
GSList *menu_items;
}
PluginContext;


static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata)
{
PluginContext *data;
gint i = 0;
gchar *text;

data = (PluginContext *) pdata;

/* Normally, you would instruct the VM/interpreter to call into the actual plugin. The
* plugin would be identified by pdata. Because there is no interpreter for
* .ini files we do it inline, as this is just a demo */
data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL);
while (TRUE)
{
GtkWidget *item;
gchar *key = g_strdup_printf("item%d", i++);
text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL);
g_free(key);

if (!text)
break;

item = gtk_menu_item_new_with_label(text);
gtk_widget_show(item);
gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item);
gtk_widget_set_sensitive(item, FALSE);
data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item);
g_free(text);
}

return TRUE;
}


static void proxy_help(GeanyPlugin *plugin, gpointer pdata)
{
PluginContext *data;
GtkWidget *dialog;

data = (PluginContext *) pdata;

dialog = gtk_message_dialog_new(
GTK_WINDOW(plugin->geany_data->main_widgets->window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO,
GTK_BUTTONS_OK,
"%s", data->help_text);
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
_("(From the %s plugin)"), plugin->info->name);

gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}


static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
{
PluginContext *data = (PluginContext *) pdata;

g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy);
g_free(data->help_text);
}


static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata)
{
/* We know the extension is right (Geany checks that). For demo purposes we perform an
* additional check. This is not necessary when the extension is unique enough. */
gboolean match = FALSE;
gchar linebuf[128];
FILE *f = fopen(filename, "r");
if (f != NULL)
{
if (fgets(linebuf, sizeof(linebuf), f) != NULL)
match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n");
fclose(f);
}
return match ? PROXY_MATCHED : PROXY_IGNORED;
}


static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin,
const gchar *filename, gpointer pdata)
{
GKeyFile *file;
gboolean result;

file = g_key_file_new();
result = g_key_file_load_from_file(file, filename, 0, NULL);

if (result)
{
PluginContext *data = g_new0(PluginContext, 1);
data->file = file;

plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL);
plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL);
plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL);
plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL);

plugin->funcs->init = proxy_init;
plugin->funcs->help = proxy_help;
plugin->funcs->cleanup = proxy_cleanup;

/* Cannot pass g_free as free_func be Geany calls it before unloading, and since
* demoproxy_unload() accesses the data this would be catastrophic */
GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL);
return data;
}

g_key_file_free(file);
return NULL;
}


static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata)
{
PluginContext *data = load_data;

g_free((gchar *)plugin->info->name);
g_free((gchar *)plugin->info->description);
g_free((gchar *)plugin->info->version);
g_free((gchar *)plugin->info->author);

g_key_file_free(data->file);
g_free(data);
}


/* Called by Geany to initialize the plugin. */
static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata)
{
const gchar *extensions[] = { "ini", "px", NULL };

plugin->proxy_funcs->probe = demoproxy_probe;
plugin->proxy_funcs->load = demoproxy_load;
plugin->proxy_funcs->unload = demoproxy_unload;

return geany_plugin_register_proxy(plugin, extensions);
}


/* Called by Geany before unloading the plugin. */
static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data)
{
}


G_MODULE_EXPORT
void geany_load_module(GeanyPlugin *plugin)
{
plugin->info->name = _("Demo Proxy");
plugin->info->description = _("Example Proxy.");
plugin->info->version = "0.1";
plugin->info->author = _("The Geany developer team");

plugin->funcs->init = demoproxy_init;
plugin->funcs->cleanup = demoproxy_cleanup;

GEANY_PLUGIN_REGISTER(plugin, 225);
}
15 changes: 15 additions & 0 deletions plugins/demoproxytest.px
@@ -0,0 +1,15 @@
#!!PLUXY_MAGIC!!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't match the one in the docs, nor the one actually checked by the demoproxy


[Init]
item0 = Bam
item1 = Foo
item2 = Bar

[Help]
text = I'm a simple test. Nothing to see!

[Info]
name = Demo Pluxy Tester
description = I'm a simple test. Nothing to see!
version = 0.1
author = The Geany developer team
1 change: 1 addition & 0 deletions po/POTFILES.skip
Expand Up @@ -6,5 +6,6 @@ geany.desktop.in
geany.glade
# no need to translate these files
plugins/demoplugin.c
plugins/demoproxy.c
doc/stash-example.c
doc/stash-gui-example.c
56 changes: 54 additions & 2 deletions src/plugindata.h
Expand Up @@ -58,7 +58,7 @@ G_BEGIN_DECLS
* @warning You should not test for values below 200 as previously
* @c GEANY_API_VERSION was defined as an enum value, not a macro.
*/
#define GEANY_API_VERSION 225
#define GEANY_API_VERSION 226

/* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins
* with GTK3-linked Geany leads to crash */
Expand Down Expand Up @@ -240,6 +240,7 @@ GeanyData;
#define geany geany_data /**< Simple macro for @c geany_data that reduces typing. */

typedef struct GeanyPluginFuncs GeanyPluginFuncs;
typedef struct GeanyProxyFuncs GeanyProxyFuncs;

/** Basic information for the plugin and identification.
* @see geany_plugin. */
Expand All @@ -248,7 +249,8 @@ 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() */

GeanyProxyFuncs *proxy_funcs; /**< Hooks implemented by the plugin if it wants to act as a proxy
Must be set prior to calling geany_plugin_register_proxy() */
struct GeanyPluginPrivate *priv; /* private */
}
GeanyPlugin;
Expand Down Expand Up @@ -347,6 +349,56 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr
geany_plugin_register_full((plugin), GEANY_API_VERSION, \
(min_api_version), GEANY_ABI_VERSION, (pdata), (free_func))

/** Return values for GeanyProxyHooks::probe()
*
* Only @c PROXY_IGNORED, @c PROXY_MATCHED or @c PROXY_MATCHED|PROXY_NOLOAD
* are valid return values.
*
* @see geany_plugin_register_proxy() for a full description of the proxy plugin mechanisms.
*
* @since 1.26 (API 226)
*/
typedef enum
{
/** The proxy is not responsible at all, and Geany or other plugins are free
* to probe it.
**/
PROXY_IGNORED,
/** The proxy is responsible for this file, and creates a plugin for it */
PROXY_MATCHED,

/** The proxy is does not directly load it, but it's still tied to the proxy
*
* This is for plugins that come in multiple files where only one of these
* files is relevant for the plugin creation (for the PM dialog). The other
* files should be ignored by Geany and other proxies. Example: libpeas has
* a .plugin and a .so per plugin. Geany should not process the .so file
* if there is a corresponding .plugin.
*/
PROXY_NOLOAD = 0x100,
}
GeanyProxyProbeResults;


/** Hooks that need to be implemented by every proxy
*
* @see geany_plugin_register_proxy() for a full description of the proxy mechanism.
*
* @since 1.26 (API 226)
**/
struct GeanyProxyFuncs
{
/** Called to determine whether the proxy is truly responsible for the requested plugin.
* A NULL pointer assumes the probe() function would always return @ref PROXY_MATCHED */
gint (*probe) (GeanyPlugin *proxy, const gchar *filename, gpointer pdata);
/** Called after probe(), to perform the actual job of loading the plugin */
gpointer (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata);
/** Called when the user initiates unloading of a plugin, e.g. on Geany exit */
void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata);
};

gint geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions);

/* Deprecated aliases */
#ifndef GEANY_DISABLE_DEPRECATED

Expand Down
16 changes: 12 additions & 4 deletions src/pluginprivate.h
Expand Up @@ -46,9 +46,10 @@ typedef enum _LoadedFlags {
}
LoadedFlags;

typedef struct GeanyPluginPrivate Plugin; /* shorter alias */

typedef struct GeanyPluginPrivate
{
GModule *module;
gchar *filename; /* plugin filename (/path/libname.so) */
PluginInfo info; /* plugin name, description, etc */
GeanyPlugin public; /* fields the plugin can read */
Expand All @@ -66,17 +67,24 @@ typedef struct GeanyPluginPrivate
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 */

/* proxy plugin support */
GeanyProxyFuncs proxy_cbs;
Plugin *proxy; /* The proxy that handles this plugin */
gpointer proxy_data; /* Data passed to the proxy hooks of above proxy, so
* this gives the proxy a pointer to each plugin */
gint proxied_count; /* count of active plugins this provides a proxy for
* (a count because of possibly nested proxies) */
}
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 */


void plugin_watch_object(Plugin *plugin, gpointer object);
void plugin_make_resident(Plugin *plugin);
gpointer plugin_get_module_symbol(Plugin *plugin, const gchar *sym);

G_END_DECLS

Expand Down