diff --git a/geanypy/geany/Makefile.am b/geanypy/geany/Makefile.am
index 880c5f662..62bac20df 100644
--- a/geanypy/geany/Makefile.am
+++ b/geanypy/geany/Makefile.am
@@ -1,7 +1,5 @@
geanypy_sources = __init__.py \
console.py \
- manager.py \
- loader.py \
plugin.py \
signalmanager.py
geanypy_objects = $(geanypy_sources:.py=.pyc)
diff --git a/geanypy/geany/__init__.py b/geanypy/geany/__init__.py
index 493dfa4d8..264d6eee1 100644
--- a/geanypy/geany/__init__.py
+++ b/geanypy/geany/__init__.py
@@ -15,9 +15,7 @@
import encoding
import filetypes
import highlighting
-import loader
import main
-import manager
import msgwindow
import navqueue
import prefs
diff --git a/geanypy/geany/loader.py b/geanypy/geany/loader.py
deleted file mode 100644
index 14b9b73d6..000000000
--- a/geanypy/geany/loader.py
+++ /dev/null
@@ -1,172 +0,0 @@
-import os
-import imp
-from collections import namedtuple
-import geany
-
-PluginInfo = namedtuple('PluginInfo', 'filename, name, version, description, author, cls')
-
-
-class PluginLoader(object):
-
- plugins = {}
-
- def __init__(self, plugin_dirs):
-
- self.plugin_dirs = plugin_dirs
-
- self.available_plugins = []
- for plugin in self.iter_plugin_info():
- self.available_plugins.append(plugin)
-
- self.restore_loaded_plugins()
-
-
- def update_loaded_plugins_file(self):
- for path in self.plugin_dirs:
- if os.path.isdir(path):
- try:
- state_file = os.path.join(path, '.loaded_plugins')
- with open(state_file, 'w') as f:
- for plugfn in self.plugins:
- f.write("%s\n" % plugfn)
- except IOError as err:
- if err.errno == 13: #perms
- pass
- else:
- raise
-
-
- def restore_loaded_plugins(self):
- loaded_plugins = []
- for path in reversed(self.plugin_dirs):
- state_file = os.path.join(path, ".loaded_plugins")
- if os.path.exists(state_file):
- for line in open(state_file):
- line = line.strip()
- if line not in loaded_plugins:
- loaded_plugins.append(line)
- for filename in loaded_plugins:
- self.load_plugin(filename)
-
-
- def load_all_plugins(self):
-
- for plugin_info in self.iter_plugin_info():
- if plugin_filename.endswith('test.py'): # hack for testing
- continue
- plug = self.load_plugin(plugin_info.filename)
- if plug:
- print("Loaded plugin: %s" % plugin_info.filename)
- print(" Name: %s v%s" % (plug.name, plug.version))
- print(" Desc: %s" % plug.description)
- print(" Author: %s" % plug.author)
-
-
- def unload_all_plugins(self):
-
- for plugin in self.plugins:
- self.unload_plugin(plugin)
-
-
- def reload_all_plugins(self):
-
- self.unload_all_plugins()
- self.load_all_plugins()
-
-
- def iter_plugin_info(self):
-
- for d in self.plugin_dirs:
- if os.path.isdir(d):
- for current_file in os.listdir(d):
- #check inside folders inside the plugins dir so we can load .py files here as plugins
- current_path=os.path.abspath(os.path.join(d, current_file))
- if os.path.isdir(current_path):
- for plugin_folder_file in os.listdir(current_path):
- if plugin_folder_file.endswith('.py'):
- #loop around results if its fails to load will never reach yield
- for p in self.load_plugin_info(current_path,plugin_folder_file):
- yield p
-
- #not a sub directory so if it ends with .py lets just attempt to load it as a plugin
- if current_file.endswith('.py'):
- #loop around results if its fails to load will never reach yield
- for p in self.load_plugin_info(d,current_file):
- yield p
-
- def load_plugin_info(self,d,f):
- filename = os.path.abspath(os.path.join(d, f))
- if filename.endswith("test.py"):
- pass
- text = open(filename).read()
- module_name = os.path.basename(filename)[:-3]
- try:
- module = imp.load_source(module_name, filename)
- except ImportError as exc:
- print "Error: failed to import settings module ({})".format(exc)
- module=None
- if module:
- for k, v in module.__dict__.iteritems():
- if k == geany.Plugin.__name__:
- continue
- try:
- if issubclass(v, geany.Plugin):
- inf = PluginInfo(
- filename,
- getattr(v, '__plugin_name__'),
- getattr(v, '__plugin_version__', ''),
- getattr(v, '__plugin_description__', ''),
- getattr(v, '__plugin_author__', ''),
- v)
- yield inf
-
- except TypeError:
- continue
-
-
- def load_plugin(self, filename):
-
- for avail in self.available_plugins:
- if avail.filename == filename:
- inst = avail.cls()
- self.plugins[filename] = inst
- self.update_loaded_plugins_file()
- geany.ui_utils.set_statusbar('GeanyPy: plugin activated: %s' %
- inst.name, True)
- return inst
-
-
- def unload_plugin(self, filename):
-
- try:
- plugin = self.plugins[filename]
- name = plugin.name
- plugin.cleanup()
- del self.plugins[filename]
- self.update_loaded_plugins_file()
- geany.ui_utils.set_statusbar('GeanyPy: plugin deactivated: %s' %
- name, True)
- except KeyError:
- print("Unable to unload plugin '%s': it's not loaded" % filename)
-
-
- def reload_plugin(self, filename):
-
- if filename in self.plugins:
- self.unload_plugin(filename)
- self.load_plugin(filename)
-
-
- def plugin_has_help(self, filename):
-
- for plugin_info in self.iter_plugin_info():
- if plugin_info.filename == filename:
- return hasattr(plugin_info.cls, 'show_help')
-
-
- def plugin_has_configure(self, filename):
-
- try:
- return hasattr(self.plugins[filename], 'show_configure')
- except KeyError:
- return None
diff --git a/geanypy/geany/manager.py b/geanypy/geany/manager.py
deleted file mode 100644
index 9c7e9a478..000000000
--- a/geanypy/geany/manager.py
+++ /dev/null
@@ -1,179 +0,0 @@
-import gtk
-import gobject
-import glib
-from htmlentitydefs import name2codepoint
-from loader import PluginLoader
-
-
-class PluginManager(gtk.Dialog):
-
- def __init__(self, plugin_dirs=[]):
- gtk.Dialog.__init__(self, title="Plugin Manager")
- self.loader = PluginLoader(plugin_dirs)
-
- self.set_default_size(400, 450)
- self.set_has_separator(True)
- icon = self.render_icon(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
- self.set_icon(icon)
-
- self.connect("response", lambda w,d: self.hide())
-
- vbox = gtk.VBox(False, 12)
- vbox.set_border_width(12)
-
- lbl = gtk.Label("Choose plugins to load or unload:")
- lbl.set_alignment(0.0, 0.5)
- vbox.pack_start(lbl, False, False, 0)
-
- sw = gtk.ScrolledWindow()
- sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
- vbox.pack_start(sw, True, True, 0)
-
- self.treeview = gtk.TreeView()
- sw.add(self.treeview)
-
- vbox.show_all()
-
- self.get_content_area().add(vbox)
-
- action_area = self.get_action_area()
- action_area.set_spacing(0)
- action_area.set_homogeneous(False)
-
- btn = gtk.Button(stock=gtk.STOCK_CLOSE)
- btn.set_border_width(6)
- btn.connect("clicked", lambda x: self.response(gtk.RESPONSE_CLOSE))
- action_area.pack_start(btn, False, True, 0)
- btn.show()
-
- self.btn_help = gtk.Button(stock=gtk.STOCK_HELP)
- self.btn_help.set_border_width(6)
- self.btn_help.set_no_show_all(True)
- action_area.pack_start(self.btn_help, False, True, 0)
- action_area.set_child_secondary(self.btn_help, True)
-
- self.btn_prefs = gtk.Button(stock=gtk.STOCK_PREFERENCES)
- self.btn_prefs.set_border_width(6)
- self.btn_prefs.set_no_show_all(True)
- action_area.pack_start(self.btn_prefs, False, True, 0)
- action_area.set_child_secondary(self.btn_prefs, True)
-
- action_area.show()
-
- self.load_plugins_list()
-
-
- def on_help_button_clicked(self, button, treeview, model):
- path = treeview.get_cursor()[0]
- iter = model.get_iter(path)
- filename = model.get_value(iter, 2)
- for plugin in self.loader.available_plugins:
- if plugin.filename == filename:
- plugin.cls.show_help()
- break
- else:
- print("Plugin does not support help function")
-
-
- def on_preferences_button_clicked(self, button, treeview, model):
- path = treeview.get_cursor()[0]
- iter = model.get_iter(path)
- filename = model.get_value(iter, 2)
- try:
- self.loader.plugins[filename].show_configure()
- except KeyError:
- print("Plugin is not loaded, can't run configure function")
-
-
- def activate_plugin(self, filename):
- self.loader.load_plugin(filename)
-
-
- def deactivate_plugin(self, filename):
- self.loader.unload_plugin(filename)
-
-
- def load_plugins_list(self):
- liststore = gtk.ListStore(gobject.TYPE_BOOLEAN, str, str)
-
- self.btn_help.connect("clicked",
- self.on_help_button_clicked, self.treeview, liststore)
-
- self.btn_prefs.connect("clicked",
- self.on_preferences_button_clicked, self.treeview, liststore)
-
- self.treeview.set_model(liststore)
- self.treeview.set_headers_visible(False)
- self.treeview.set_grid_lines(True)
-
- check_renderer = gtk.CellRendererToggle()
- check_renderer.set_radio(False)
- check_renderer.connect('toggled', self.on_plugin_load_toggled, liststore)
- text_renderer = gtk.CellRendererText()
-
- check_column = gtk.TreeViewColumn(None, check_renderer, active=0)
- text_column = gtk.TreeViewColumn(None, text_renderer, markup=1)
-
- self.treeview.append_column(check_column)
- self.treeview.append_column(text_column)
-
- self.treeview.connect('row-activated',
- self.on_row_activated, check_renderer, liststore)
- self.treeview.connect('cursor-changed',
- self.on_selected_plugin_changed, liststore)
-
- self.load_sorted_plugins_info(liststore)
-
-
- def load_sorted_plugins_info(self, list_store):
-
- plugin_info_list = list(self.loader.iter_plugin_info())
- #plugin_info_list.sort(key=lambda pi: pi[1])
-
- for plugin_info in plugin_info_list:
-
- lbl = str('%s %s\n%s\n' +
- 'Author: %s\n' +
- 'Filename: %s') % (
- glib.markup_escape_text(plugin_info.name),
- glib.markup_escape_text(plugin_info.version),
- glib.markup_escape_text(plugin_info.description),
- glib.markup_escape_text(plugin_info.author),
- glib.markup_escape_text(plugin_info.filename))
-
- loaded = plugin_info.filename in self.loader.plugins
-
- list_store.append([loaded, lbl, plugin_info.filename])
-
-
- def on_selected_plugin_changed(self, treeview, model):
-
- path = treeview.get_cursor()[0]
- iter = model.get_iter(path)
- filename = model.get_value(iter, 2)
- active = model.get_value(iter, 0)
-
- if self.loader.plugin_has_configure(filename):
- self.btn_prefs.set_visible(True)
- else:
- self.btn_prefs.set_visible(False)
-
- if self.loader.plugin_has_help(filename):
- self.btn_help.set_visible(True)
- else:
- self.btn_help.set_visible(False)
-
-
- def on_plugin_load_toggled(self, cell, path, model):
- active = not cell.get_active()
- iter = model.get_iter(path)
- model.set_value(iter, 0, active)
- if active:
- self.activate_plugin(model.get_value(iter, 2))
- else:
- self.deactivate_plugin(model.get_value(iter, 2))
-
-
- def on_row_activated(self, tvw, path, view_col, cell, model):
- self.on_plugin_load_toggled(cell, path, model)
diff --git a/geanypy/geany/plugin.py b/geanypy/geany/plugin.py
index f8adef6be..212fa7ca7 100644
--- a/geanypy/geany/plugin.py
+++ b/geanypy/geany/plugin.py
@@ -36,8 +36,9 @@ def cleanup(self):
files with a `.py` extension will be loaded.
"""
-
-class Plugin(object):
+from geany.pluginbase import PluginBase
+
+class Plugin(PluginBase):
"""
Base class for all plugins. All plugins must inherit from this in order
to be properly detected.
@@ -49,14 +50,14 @@ class Plugin(object):
#__plugin_version__ = None
#__plugin_author__ = None
-
_events = {
"document-open": [],
# TODO: add more events here
}
- def __init__(self):
+ def __init__(self, ctx=None):
+ PluginBase.__init__(self, ctx)
"""
When the plugin is loaded its __init__() function will be called
so that's a good place to put plugin initialization code.
diff --git a/geanypy/plugins/Makefile.am b/geanypy/plugins/Makefile.am
index 6aa07ff22..3cb662de8 100644
--- a/geanypy/plugins/Makefile.am
+++ b/geanypy/plugins/Makefile.am
@@ -1,4 +1,4 @@
geanypy_plugins = demo.py hello.py console.py
-geanypydir = $(datadir)/geany/geanypy/plugins
+geanypydir = $(libdir)/geany
geanypy_DATA = $(geanypy_plugins)
EXTRA_DIST = $(geanypy_plugins)
diff --git a/geanypy/plugins/console.py b/geanypy/plugins/console.py
index ee26ae35b..df5180e7e 100644
--- a/geanypy/plugins/console.py
+++ b/geanypy/plugins/console.py
@@ -191,16 +191,7 @@ def on_bg_color_changed(self, clr_btn, data=None):
self.bg = clr_btn.get_color().to_string()
- def show_configure(self):
- dialog = gtk.Dialog("Configure Python Console",
- geany.main_widgets.window,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT))
-
- dialog.set_has_separator(True)
-
- content_area = dialog.get_content_area()
- content_area.set_border_width(6)
+ def configure(self, dialog):
vbox = gtk.VBox(spacing=6)
vbox.set_border_width(6)
@@ -306,8 +297,5 @@ def show_configure(self):
vbox.pack_start(fra_general, True, True, 0)
vbox.pack_start(fra_appearances, False, True, 0)
- content_area.pack_start(vbox, True, True, 0)
- content_area.show_all()
- dialog.run()
- dialog.destroy()
+ return vbox
diff --git a/geanypy/src/Makefile.am b/geanypy/src/Makefile.am
index 303b07a81..c81b0f51e 100644
--- a/geanypy/src/Makefile.am
+++ b/geanypy/src/Makefile.am
@@ -6,9 +6,9 @@ geanyplugindir = $(libdir)/geany
geanypy_la_LDFLAGS = -module -avoid-version -Wl,--export-dynamic
geanypy_la_CPPFLAGS = @GEANY_CFLAGS@ @PYGTK_CFLAGS@ @PYTHON_CPPFLAGS@ \
-DGEANYPY_PYTHON_DIR="\"$(libdir)/geany/geanypy\"" \
- -DGEANYPY_PLUGIN_DIR="\"$(datadir)/geany/geanypy/plugins\""
+ -DGEANYPY_PLUGIN_DIR="\"$(libdir)/geany\""
geanypy_la_CFLAGS = @GEANYPY_CFLAGS@ @GMODULE_CFLAGS@
-geanypy_la_LIBADD = @GEANY_LIBS@ @PYGTK_LIBS@ @PYTHON_LDFLAGS@ \
+geanypy_la_LIBADD = @GEANY_LIBS@ @PYGTK_LIBS@ @PYTHON_LIBS@ \
@PYTHON_EXTRA_LIBS@ @PYTHON_EXTRA_LDFLAGS@ \
@GMODULE_LIBS@
geanypy_la_SOURCES = geanypy-app.c \
diff --git a/geanypy/src/geanypy-plugin.c b/geanypy/src/geanypy-plugin.c
index 338067c6a..ef3df16aa 100644
--- a/geanypy/src/geanypy-plugin.c
+++ b/geanypy/src/geanypy-plugin.c
@@ -27,25 +27,10 @@
#include "geanypy.h"
-G_MODULE_EXPORT GeanyPlugin *geany_plugin;
-G_MODULE_EXPORT GeanyData *geany_data;
-G_MODULE_EXPORT GeanyFunctions *geany_functions;
-
-
-G_MODULE_EXPORT PLUGIN_VERSION_CHECK(211)
-
-G_MODULE_EXPORT PLUGIN_SET_INFO(
- _("GeanyPy"),
- _("Python plugins support"),
- "1.0",
- "Matthew Brush ")
-
-
-static GtkWidget *loader_item = NULL;
-static PyObject *manager = NULL;
-static gchar *plugin_dir = NULL;
-static SignalManager *signal_manager = NULL;
+#include
+#include
+GeanyData *geany_data;
/* Forward declarations to prevent compiler warnings. */
PyMODINIT_FUNC initapp(void);
@@ -64,6 +49,7 @@ PyMODINIT_FUNC initscintilla(void);
PyMODINIT_FUNC initsearch(void);
PyMODINIT_FUNC inittemplates(void);
PyMODINIT_FUNC initui_utils(void);
+PyMODINIT_FUNC initpluginbase(void);
static void
@@ -104,6 +90,7 @@ GeanyPy_start_interpreter(void)
initsearch();
inittemplates();
initui_utils();
+ initpluginbase();
#ifdef GEANYPY_WINDOWS
{ /* On windows, get path at runtime since we don't really know where
@@ -131,7 +118,9 @@ GeanyPy_start_interpreter(void)
"import os, sys\n"
"path = '%s'.replace('~', os.path.expanduser('~'))\n"
"sys.path.append(path)\n"
- "import geany\n", py_dir);
+ "path = '%s'.replace('~', os.path.expanduser('~'))\n"
+ "sys.path.append(path)\n"
+ "import geany\n", py_dir, GEANYPY_PLUGIN_DIR);
g_free(py_dir);
PyRun_SimpleString(init_code);
@@ -146,141 +135,410 @@ GeanyPy_stop_interpreter(void)
Py_Finalize();
}
+typedef struct
+{
+ PyObject *base;
+ SignalManager *signal_manager;
+}
+GeanyPyData;
-static void
-GeanyPy_init_manager(const gchar *dir)
+typedef struct
{
- PyObject *module, *man, *args;
- gchar *sys_plugin_dir = NULL;
+ PyObject *class;
+ PyObject *module;
+ PyObject *instance;
+}
+GeanyPyPluginData;
+
+static gboolean has_error(void)
+{
+ if (PyErr_Occurred())
+ {
+ PyErr_Print();
+ return TRUE;
+ }
+ return FALSE;
+}
- g_return_if_fail(dir != NULL);
+static PyTypeObject PluginBaseType;
- module = PyImport_ImportModule("geany.manager");
- if (module == NULL)
- {
- g_warning(_("Failed to import manager module"));
- return;
- }
+static gboolean geanypy_proxy_init(GeanyPlugin *plugin, gpointer pdata)
+{
+ GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
+ GeanyPyPluginBase *base;
+ PyObject *args;
+
+ base = PyObject_New(GeanyPyPluginBase, &PluginBaseType);
+ base->plugin = plugin;
+
+ /* The new-style constructor gets a context parameter, and the class must pass
+ * it to the geany.Plugin constructor. The new-style constructor is required
+ * to have certain APIs work in it (those that need the GeanyPlugin pointer) */
+ args = Py_BuildValue("(O)", base);
+ data->instance = PyObject_CallObject(data->class, args);
+ Py_DECREF(args);
+ Py_DECREF(base);
+
+ if (PyErr_Occurred()) {
+ PyErr_Clear();
+ /* If the plugin still implements the old constructor we can catch this and try again */
+ data->instance = PyObject_CallObject(data->class, NULL);
+ }
- man = PyObject_GetAttrString(module, "PluginManager");
- Py_DECREF(module);
+ if (has_error())
+ return FALSE;
- if (man == NULL)
- {
- g_warning(_("Failed to retrieve PluginManager from manager module"));
- return;
- }
+ ((GeanyPyPluginBase *)data->instance)->plugin = plugin;
-#ifdef GEANYPY_WINDOWS
- { /* Detect the system plugin's dir at runtime on Windows since we
- * don't really know where Geany is installed. */
- gchar *geany_base_dir;
- geany_base_dir = g_win32_get_package_installation_directory_of_module(NULL);
- if (geany_base_dir)
- {
- sys_plugin_dir = g_build_filename(geany_base_dir, "lib", "geanypy", "plugins", NULL);
- g_free(geany_base_dir);
- }
- if (!g_file_test(sys_plugin_dir, G_FILE_TEST_EXISTS))
- {
- g_warning(_("System plugin directory not found."));
- g_free(sys_plugin_dir);
- sys_plugin_dir = NULL;
- }
+ return TRUE;
+}
+
+
+static void geanypy_proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+ GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
+
+ PyObject_CallMethod(data->instance, "cleanup", NULL);
+ if (has_error())
+ return;
+}
+
+
+static GtkWidget *geanypy_proxy_configure(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata)
+{
+ GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
+ PyObject *o, *oparent;
+ GObject *widget;
+
+ oparent = pygobject_new(G_OBJECT(parent));
+ o = PyObject_CallMethod(data->instance, "configure", "O", oparent, NULL);
+ Py_DECREF(oparent);
+
+ if (!has_error() && o != NULL)
+ {
+ /* Geany wants only the underlying GtkWidget, we must only ref that
+ * and free the pygobject wrapper */
+ widget = g_object_ref(pygobject_get(o));
+ Py_DECREF(o);
+ return GTK_WIDGET(widget);
}
-#else
- sys_plugin_dir = g_strdup(GEANYPY_PLUGIN_DIR);
-#endif
+ return NULL;
+}
+
+static void geanypy_proxy_help(GeanyPlugin *plugin, gpointer pdata)
+{
+ GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
+ PyObject_CallMethod(data->instance, "help", NULL);
+ if (has_error())
+ return;
+}
+
+static gint
+geanypy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata)
+{
+ gchar *file_plugin = g_strdup_printf("%.*s.plugin",
+ (int)(strrchr(filename, '.') - filename), filename);
+ gint ret = PROXY_IGNORED;
+
+ /* avoid clash with libpeas py plugins, those come with a corresponding .plugin file */
+ if (!g_file_test(file_plugin, G_FILE_TEST_EXISTS))
+ ret = PROXY_MATCHED;
- g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "User plugins: %s", dir);
+ g_free(file_plugin);
+ return ret;
+}
+
+
+static const gchar *string_from_attr(PyObject *o, const gchar *attr)
+{
+ PyObject *string = PyObject_GetAttrString(o, "__plugin_name__");
+ const gchar *ret = PyString_AsString(string);
+ Py_DECREF(string);
- if (sys_plugin_dir)
+ return ret;
+}
+
+
+static gpointer
+geanypy_load(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata)
+{
+ GeanyPyData *data = pdata;
+ PyObject *fromlist, *module, *dict, *key, *val, *found = NULL;
+ Py_ssize_t pos = 0;
+ gchar *modulename, *dot;
+ gpointer ret = NULL;
+
+ modulename = g_path_get_basename(filename);
+ /* We are guaranteed that filename has a .py extension
+ * because we did geany_plugin_register_proxy() for it */
+ dot = strrchr(modulename, '.');
+ *dot = '\0';
+ /* we need a fromlist to be able to import modules with a '.' in the
+ * name. -- libpeas */
+ fromlist = PyTuple_New (0);
+
+ module = PyImport_ImportModuleEx(modulename, NULL, NULL, fromlist);
+ if (has_error() || !module)
+ goto err;
+
+ dict = PyModule_GetDict(module);
+
+ while (PyDict_Next (dict, &pos, &key, &val) && found == NULL)
{
- g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "System plugins: %s", sys_plugin_dir);
- args = Py_BuildValue("([s, s])", sys_plugin_dir, dir);
- g_free(sys_plugin_dir);
+ if (PyType_Check(val) && PyObject_IsSubclass(val, data->base))
+ found = val;
}
- else
- args = Py_BuildValue("([s])", dir);
- manager = PyObject_CallObject(man, args);
- if (PyErr_Occurred())
- PyErr_Print();
- Py_DECREF(man);
- Py_DECREF(args);
-
- if (manager == NULL)
- {
- g_warning(_("Unable to instantiate new PluginManager"));
- return;
- }
+ if (found)
+ {
+ GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData);
+ PluginInfo *info = subplugin->info;
+ GeanyPluginFuncs *funcs = subplugin->funcs;
+ Py_INCREF(found);
+ pdata->module = module;
+ pdata->class = found;
+ pdata->instance = NULL;
+ info->name = string_from_attr(pdata->class, "__plugin_name__");
+ info->description = string_from_attr(pdata->class, "__plugin_description__");
+ info->version = string_from_attr(pdata->class, "__plugin_version__");
+ info->author = string_from_attr(pdata->class, "__plugin_author__");
+ funcs->init = geanypy_proxy_init;
+ funcs->cleanup = geanypy_proxy_cleanup;
+ if (PyObject_HasAttrString(found, "configure"))
+ funcs->configure = geanypy_proxy_configure;
+ if (PyObject_HasAttrString(found, "help"))
+ funcs->help = geanypy_proxy_help;
+ if (GEANY_PLUGIN_REGISTER_FULL(subplugin, 224, pdata, NULL))
+ ret = pdata;
+ }
+
+err:
+ g_free(modulename);
+ Py_DECREF(fromlist);
+ return ret;
}
static void
-GeanyPy_show_manager(void)
+geanypy_unload(GeanyPlugin *plugin, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata_)
{
- PyObject *show_method;
-
- g_return_if_fail(manager != NULL);
-
- show_method = PyObject_GetAttrString(manager, "show_all");
- if (show_method == NULL)
- {
- g_warning(_("Unable to get show_all() method on plugin manager"));
- return;
- }
- PyObject_CallObject(show_method, NULL);
- Py_DECREF(show_method);
+ GeanyPyPluginData *pdata = load_data;
+
+ Py_XDECREF(pdata->instance);
+ Py_DECREF(pdata->class);
+ Py_DECREF(pdata->module);
+ while (PyGC_Collect());
+ g_slice_free(GeanyPyPluginData, pdata);
}
-static void
-on_python_plugin_loader_activate(GtkMenuItem *item, gpointer user_data)
+static gboolean geanypy_init(GeanyPlugin *plugin_, gpointer pdata)
{
- GeanyPy_show_manager();
+ const gchar *exts[] = { "py", NULL };
+ GeanyPyData *state = pdata;
+ PyObject *module;
+
+ plugin_->proxy_funcs->probe = geanypy_probe;
+ plugin_->proxy_funcs->load = geanypy_load;
+ plugin_->proxy_funcs->unload = geanypy_unload;
+
+ geany_data = plugin_->geany_data;
+
+ GeanyPy_start_interpreter();
+ state->signal_manager = signal_manager_new(plugin_);
+
+ module = PyImport_ImportModule("geany.plugin");
+ if (has_error() || !module)
+ goto err;
+
+ state->base = PyObject_GetAttrString(module, "Plugin");
+ Py_DECREF(module);
+ if (has_error() || !state->base)
+ goto err;
+
+ if (!geany_plugin_register_proxy(plugin_, exts)) {
+ Py_DECREF(state->base);
+ goto err;
+ }
+
+ return TRUE;
+
+err:
+ signal_manager_free(state->signal_manager);
+ GeanyPy_stop_interpreter();
+ return FALSE;
}
+static void geanypy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+ GeanyPyData *state = pdata;
+ signal_manager_free(state->signal_manager);
+ Py_DECREF(state->base);
+ GeanyPy_stop_interpreter();
+}
+
G_MODULE_EXPORT void
-plugin_init(GeanyData *data)
+geany_load_module(GeanyPlugin *plugin)
{
- GeanyPy_start_interpreter();
- signal_manager = signal_manager_new(geany_plugin);
-
- plugin_dir = g_build_filename(geany->app->configdir,
- "plugins", "geanypy", "plugins", NULL);
-
- if (!g_file_test(plugin_dir, G_FILE_TEST_IS_DIR))
- {
- if (g_mkdir_with_parents(plugin_dir, 0755) == -1)
- {
- g_warning(_("Unable to create Python plugins directory: %s: %s"),
- plugin_dir,
- strerror(errno));
- g_free(plugin_dir);
- plugin_dir = NULL;
- }
- }
-
- if (plugin_dir != NULL)
- GeanyPy_init_manager(plugin_dir);
-
- loader_item = gtk_menu_item_new_with_label(_("Python Plugin Manager"));
- gtk_widget_set_sensitive(loader_item, plugin_dir != NULL);
- gtk_menu_append(GTK_MENU(geany->main_widgets->tools_menu), loader_item);
- gtk_widget_show(loader_item);
- g_signal_connect(loader_item, "activate",
- G_CALLBACK(on_python_plugin_loader_activate), NULL);
+ GeanyPyData *state = g_new0(GeanyPyData, 1);
+
+ plugin->info->name = _("GeanyPy");
+ plugin->info->description = _("Python plugins support");
+ plugin->info->version = "1.0";
+ plugin->info->author = "Matthew Brush ";
+ plugin->funcs->init = geanypy_init;
+ plugin->funcs->cleanup = geanypy_cleanup;
+
+ GEANY_PLUGIN_REGISTER_FULL(plugin, 224, state, g_free);
}
-G_MODULE_EXPORT void plugin_cleanup(void)
+static void PluginBase_dealloc(GeanyPyPluginBase *self) { }
+
+
+static gboolean call_key(gpointer *unused, guint key_id, gpointer data)
{
- signal_manager_free(signal_manager);
- Py_XDECREF(manager);
- GeanyPy_stop_interpreter();
- gtk_widget_destroy(loader_item);
- g_free(plugin_dir);
+ PyObject *callback = data;
+ PyObject *args;
+
+ args = Py_BuildValue("(i)", key_id);
+ PyObject_CallObject(callback, args);
+ Py_DECREF(args);
+}
+
+
+static PyObject *
+PluginBase_set_kb_group(GeanyPyPluginBase *self, PyObject *args, PyObject *kwargs)
+{
+ static gchar *kwlist[] = { "section_name", "count", "callback", NULL };
+ int count = 0;
+ const gchar *section_name = NULL;
+ GeanyKeyGroup *group = NULL;
+ PyObject *py_callback = NULL;
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "si|O", kwlist, §ion_name, &count, &py_callback))
+ {
+ if (PyCallable_Check(py_callback))
+ {
+ Py_INCREF(py_callback);
+ group = plugin_set_key_group_full(self->plugin, section_name, count,
+ (GeanyKeyGroupFunc) call_key, py_callback, Py_DecRef);
+ }
+ else
+ group = plugin_set_key_group(self->plugin, section_name, count, NULL);
+ }
+
+ if (group)
+ {
+ GObject *wrapper;
+ PyObject *ret;
+ wrapper = g_object_new(G_TYPE_OBJECT, NULL);
+ g_object_set_data(wrapper, "pointer", group);
+ ret = pygobject_new(wrapper);
+ g_object_unref(wrapper);
+ return ret;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+PluginBase_set_kb_item(GeanyPyPluginBase *self, PyObject *args, PyObject *kwargs)
+{
+ static gchar *kwlist[] = { "key_group", "key_id", "key", "mod", "name", "label", "menu_item", "callback", NULL };
+ int id = -1;
+ int key;
+ int mod;
+ PyObject *py_group;
+ const gchar *name = NULL, *label = NULL;
+ PyObject *py_menu_item = NULL;
+ PyObject *py_callback = NULL;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "Oiiiss|OO", kwlist,
+ &py_group, &id, &key, &mod, &name, &label, &py_menu_item, &py_callback) && id >= 0)
+ {
+ GObject *group_wrapper = G_OBJECT(pygobject_get(py_group));
+ GtkWidget *menu_item = (py_menu_item == NULL || py_menu_item == Py_None)
+ ? NULL : GTK_WIDGET(pygobject_get(py_menu_item));
+ GeanyKeyGroup *group = g_object_get_data(group_wrapper, "pointer");
+ if (PyCallable_Check(py_callback))
+ {
+ Py_INCREF(py_callback);
+ keybindings_set_item_full(group, id, (guint) key, (GdkModifierType) mod, name, label,
+ menu_item, (GeanyKeyBindingFunc) call_key, py_callback, Py_DecRef);
+ }
+ else
+ keybindings_set_item(group, id, NULL, (guint) key, (GdkModifierType) mod, name, label,
+ menu_item);
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef
+PluginBase_methods[] = {
+ { "set_key_group", (PyCFunction)PluginBase_set_kb_group, METH_KEYWORDS,
+ "Sets up a GeanyKeybindingGroup for this plugin." },
+ { "set_key_item", (PyCFunction)PluginBase_set_kb_item, METH_KEYWORDS,
+ "Adds an action to one of the plugin's key groups" },
+ { NULL }
+};
+
+static PyMethodDef
+PluginModule_methods[] = {
+ { NULL }
+};
+
+
+static PyGetSetDef
+PluginBase_getseters[] = {
+ { NULL },
+};
+
+
+static int
+PluginBase_init(GeanyPyPluginBase *self, PyObject *args, PyObject *kwargs)
+{
+ GeanyPyPluginBase *py_context;
+
+ if (PyArg_ParseTuple(args, "O", (PyObject **) &py_context) && (PyObject *)py_context != Py_None)
+ self->plugin = py_context->plugin;
+
+ return 0;
+}
+
+
+static PyTypeObject PluginBaseType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "geany.pluginbase,PluginBase", /* tp_name */
+ sizeof(GeanyPyPluginBase), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor) PluginBase_dealloc, /* tp_dealloc */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_print - tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "Wrapper around a GeanyPlugin structure." ,/* tp_doc */
+ 0, 0, 0, 0, 0, 0, /* tp_traverse - tp_iternext */
+ PluginBase_methods, /* tp_methods */
+ 0, /* tp_members */
+ PluginBase_getseters, /* tp_getset */
+ 0, 0, 0, 0, 0, /* tp_base - tp_dictoffset */
+ (initproc) PluginBase_init, /* tp_init */
+ 0, 0, /* tp_alloc - tp_new */
+};
+
+
+PyMODINIT_FUNC initpluginbase(void)
+{
+ PyObject *m;
+
+ PluginBaseType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&PluginBaseType) < 0)
+ return;
+
+ m = Py_InitModule3("geany.pluginbase", PluginModule_methods,
+ "Plugin management.");
+
+ Py_INCREF(&PluginBaseType);
+ PyModule_AddObject(m, "PluginBase", (PyObject *)&PluginBaseType);
}
diff --git a/geanypy/src/geanypy-plugin.h b/geanypy/src/geanypy-plugin.h
index 5457404d8..dea777c45 100644
--- a/geanypy/src/geanypy-plugin.h
+++ b/geanypy/src/geanypy-plugin.h
@@ -26,16 +26,18 @@
extern "C" {
#endif
-
-extern GeanyPlugin *geany_plugin;
-extern GeanyData *geany_data;
-extern GeanyFunctions *geany_functions;
+extern GeanyData *geany_data;
#ifndef PyMODINIT_FUNC
#define PyMODINIT_FUNC void
#endif
+typedef struct
+{
+ PyObject_HEAD
+ GeanyPlugin *plugin;
+} GeanyPyPluginBase;
#ifdef __cplusplus
} /* extern "C" */
diff --git a/geanypy/src/geanypy-signalmanager.c b/geanypy/src/geanypy-signalmanager.c
index 0f7fd5ee7..8682da816 100644
--- a/geanypy/src/geanypy-signalmanager.c
+++ b/geanypy/src/geanypy-signalmanager.c
@@ -88,6 +88,7 @@ GObject *signal_manager_get_gobject(SignalManager *signal_manager)
static void signal_manager_connect_signals(SignalManager *man)
{
+ GeanyPlugin *geany_plugin = man->geany_plugin;
plugin_signal_connect(geany_plugin, NULL, "build-start", TRUE, G_CALLBACK(on_build_start), man);
plugin_signal_connect(geany_plugin, NULL, "document-activate", TRUE, G_CALLBACK(on_document_activate), man);
plugin_signal_connect(geany_plugin, NULL, "document-before-save", TRUE, G_CALLBACK(on_document_before_save), man);