[geany/geany-plugins] 3ef891: Merge pull request #384 from kugel-/geanypy-proxy

Frank Lanitz git-noreply at xxxxx
Fri Mar 11 06:15:18 UTC 2016


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   Frank Lanitz <frank at frank.uvena.de>
Date:        Fri, 11 Mar 2016 06:15:18 UTC
Commit:      3ef8910f756bb6e6bc01693541f602266db43b43
             https://github.com/geany/geany-plugins/commit/3ef8910f756bb6e6bc01693541f602266db43b43

Log Message:
-----------
Merge pull request #384 from kugel-/geanypy-proxy

 Geanypy proxy and keybindings


Modified Paths:
--------------
    geanypy/README
    geanypy/doc/source/starting.rst
    geanypy/geany/Makefile.am
    geanypy/geany/__init__.py
    geanypy/geany/loader.py
    geanypy/geany/manager.py
    geanypy/geany/plugin.py
    geanypy/plugins/Makefile.am
    geanypy/plugins/console.py
    geanypy/src/Makefile.am
    geanypy/src/geanypy-keybindings.c
    geanypy/src/geanypy-keybindings.h
    geanypy/src/geanypy-plugin.c
    geanypy/src/geanypy-plugin.h
    geanypy/src/geanypy-signalmanager.c

Modified: geanypy/README
11 lines changed, 5 insertions(+), 6 deletions(-)
===================================================================
@@ -33,14 +33,13 @@ module more "Pythonic".
 To write a plugin, inherit from the ``geany.Plugin`` class and implmenent the
 required members (see ``geany/plugin.py`` documentation comments).  Then put the
 plugin in a searched plugin path.  Currently two locations are search for
-plugins.  The first is ``PREFIX/share/geany/geanypy/plugins`` and the recommended
+plugins.  The first is ``PREFIX/lib/geany`` and the recommended
 location is under your personal Geany directory (usually
-``~/.config/geany/plugins/geanypy/plugins``).  To load or unload plugins, click
-the Python Plugin Manager item under the Tools menu which will appear when you
-activate GeanyPy through Geany's regular plugin manager.
+``~/.config/geany/plugins``). To load or unload plugins, use Geany's regular Plugin
++Manager. Python plugins appear there once GeanyPy is activated.
 
-When the GeanyPy plugin is loaded, it has an option to add a new tab to 
-the notebook in the message window area that contains an interactive 
+When the Python Console plugin is enabled, it will add a new tab to the notebook in
+the message window area that contains an interactive Python shell with the `geany`
 Python shell with the ``geany`` module pre-imported.  You can tinker 
 around with API with this console, for example::
 


Modified: geanypy/doc/source/starting.rst
40 lines changed, 18 insertions(+), 22 deletions(-)
===================================================================
@@ -7,34 +7,30 @@ GeanyPy, it's important to note how it works and some features it provides.
 What the heck is GeanyPy, really?
 =================================
 
-GeanyPy is "just another Geany plugin", really.  Geany sees GeanyPy as any
-other `plugin <http://www.geany.org/manual/current/index.html#plugins>`_, so
-to activate GeanyPy, use Geany's
+GeanyPy is a proxy plugin. Geany initially sees GeanyPy as any other
+`plugin <http://www.geany.org/manual/current/index.html#plugins>`_, but
+GeanyPy registers some additional stuff that enables Geany to load python plugins
+through GeanyPy. So to activate, use Geany's
 `Plugin Manager <http://www.geany.org/manual/current/index.html#plugin-manager>`_
 under the Tools menu as you would for any other plugin.
 
-Once the GeanyPy plugin has been activated, a few elements are added to Geany's
-user interface as described below.
+Once the GeanyPy plugin has been activated, Geany should rescan the plugin
+directories and pick those up that are supported through GeanyPy. It'll integrate
+the python plugins into the Plugin Manager in an additional hierarchy level below
+GeanyPy.
 
-Python Plugin Manager
-=====================
+* [ ] Geany plugin 1
+* [x] GeanyPy
+ * [ ] Python plugin 1
+ * [x] Python plugin 2
+ * [ ] Python plugin 3
+* [ ] Geany plugin 3
 
-Under the Tools menu, you will find the Python Plugin Manager, which is meant
-to be similar to Geany's own Plugin Manager.  This is where you will activate
-any plugins written in Python.
+Remember that Geany looks in three places for plugins:
 
-The Python Plugin Manager looks in exactly two places for plugins:
-
-1. For system-wide plugins, it will search in PREFIX/share/geany/geanypy/plugins.
-2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins/geanypy/plugins.
-
-Where `PREFIX` is the prefix used at configure time with Geany/GeanyPy (see
-the previous section, Installation).  Both of these paths may vary depending on
-your platform, but for most \*nix systems, the above paths should hold true.
-
-Any plugins which follow the proper interface found in either of those two
-directories will be listed in the Python Plugin Manager and you will be able
-to activate and deactivate them there.
+1. For system-wide plugins, it will search in (usually) /usr/share/geany or /usr/local/share/geany.
+2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins.
+3. A user-configurable plugin directory (useful during plugin development).
 
 Python Console
 ==============


Modified: geanypy/geany/Makefile.am
2 lines changed, 0 insertions(+), 2 deletions(-)
===================================================================
@@ -1,7 +1,5 @@
 geanypy_sources				=	__init__.py \
 								console.py \
-								manager.py \
-								loader.py \
 								plugin.py \
 								signalmanager.py
 geanypy_objects				=	$(geanypy_sources:.py=.pyc)


Modified: geanypy/geany/__init__.py
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -15,9 +15,7 @@
 import encoding
 import filetypes
 import highlighting
-import loader
 import main
-import manager
 import msgwindow
 import navqueue
 import prefs
@@ -26,6 +24,7 @@
 import search
 import templates
 import ui_utils
+import keybindings
 
 from app import App
 from prefs import Prefs, ToolPrefs
@@ -43,6 +42,7 @@
             "main_widgets",
             "interface_prefs",
             "app",
+            "keybindings",
             "general_prefs",
             "search_prefs",
             "template_prefs",


Modified: geanypy/geany/loader.py
172 lines changed, 0 insertions(+), 172 deletions(-)
===================================================================
@@ -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


Modified: geanypy/geany/manager.py
179 lines changed, 0 insertions(+), 179 deletions(-)
===================================================================
@@ -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('<big><b>%s</b></big> <small>%s</small>\n%s\n' +
-					'<small><b>Author:</b> %s\n' +
-					'<b>Filename:</b> %s</small>') % (
-					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)


Modified: geanypy/geany/plugin.py
16 lines changed, 11 insertions(+), 5 deletions(-)
===================================================================
@@ -30,12 +30,12 @@ def cleanup(self):
 The guts of the API are exposed to plugins through the `geany` package and
 its modules.
 
-Plugins should be placed in either the system plugin directory (something
-like /usr/local/share/geany/geanypy/plugins) or in their personal plugin
-directory (something like ~/.config/geany/plugins/geanypy/plugins).  Only
-files with a `.py` extension will be loaded.
+Plugins should be placed in either the system plugin directory (something like
+/usr/local/lib/geany) or in the user plugin directory (something like
+~/.config/geany/plugins).  Only files with a `.py` extension will be loaded.
 """
 
+import keybindings
 
 class Plugin(object):
 	"""
@@ -49,7 +49,6 @@ class Plugin(object):
 	#__plugin_version__ = None
 	#__plugin_author__ = None
 
-
 	_events = {
 		"document-open": [],
 		# TODO: add more events here
@@ -121,3 +120,10 @@ def author(self):
 			return self.__plugin_author__
 		else:
 			return ""
+
+	def set_key_group(self, section_name, count, callback = None):
+		"""
+		Sets up a GeanyKeyGroup for this plugin. You can use that group to add keybindings
+		with group.add_key_item().
+		"""
+		return keybindings.set_key_group(self, section_name, count, callback)


Modified: geanypy/plugins/Makefile.am
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -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)


Modified: geanypy/plugins/console.py
16 lines changed, 2 insertions(+), 14 deletions(-)
===================================================================
@@ -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


Modified: geanypy/src/Makefile.am
3 lines changed, 2 insertions(+), 1 deletions(-)
===================================================================
@@ -6,7 +6,7 @@ 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\"" \
 								-DG_LOG_DOMAIN=\"GeanyPy\"
 geanypy_la_CFLAGS			=	@GEANYPY_CFLAGS@ @GMODULE_CFLAGS@
 geanypy_la_LIBADD			=	@GEANY_LIBS@ @PYGTK_LIBS@ \
@@ -23,6 +23,7 @@ geanypy_la_SOURCES			=	geanypy-app.c \
 								geanypy-highlighting.c \
 								geanypy-indentprefs.c \
 								geanypy-interfaceprefs.c \
+								geanypy-keybindings.c \
 								geanypy-main.c \
 								geanypy-mainwidgets.c \
 								geanypy-msgwindow.c \


Modified: geanypy/src/geanypy-keybindings.c
213 lines changed, 213 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,213 @@
+/*
+ * plugin.c
+ *
+ * Copyright 2015 Thomas Martitz <kugel at rockbox.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.
+ */
+
+
+#include "geanypy.h"
+#include "geanypy-keybindings.h"
+
+#include <glib.h>
+
+static gboolean call_key(gpointer *unused, guint key_id, gpointer data)
+{
+	PyObject *callback = data;
+	PyObject *args;
+
+	args = Py_BuildValue("(i)", key_id);
+	PyObject_CallObject(callback, args);
+	Py_DECREF(args);
+}
+
+
+/* plugin.py provides an OOP-style wrapper around this so call it like:
+ * class Foo(geany.Plugin):
+ *   def __init__(self):
+ *     self.set_key_group(...)
+ */
+static PyObject *
+Keybindings_set_key_group(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+	static gchar *kwlist[] = { "plugin", "section_name", "count", "callback", NULL };
+	int count = 0;
+	const gchar *section_name = NULL;
+	GeanyKeyGroup *group = NULL;
+	PyObject *py_callback = NULL;
+	PyObject *py_ret = Py_None;
+	PyObject *py_plugin;
+	gboolean has_cb = FALSE;
+
+	Py_INCREF(Py_None);
+
+	if (PyArg_ParseTupleAndKeywords(args, kwargs, "Osi|O", kwlist,
+		&py_plugin, &section_name, &count, &py_callback))
+	{
+		GeanyPlugin *plugin = plugin_get(py_plugin);
+		g_return_val_if_fail(plugin != NULL, Py_None);
+
+		has_cb = PyCallable_Check(py_callback);
+		if (has_cb)
+		{
+			Py_INCREF(py_callback);
+			group = plugin_set_key_group_full(plugin, section_name, count,
+			                                  (GeanyKeyGroupFunc) call_key, py_callback,
+			                                  (GDestroyNotify) Py_DecRef);
+		}
+		else
+			group = plugin_set_key_group(plugin, section_name, count, NULL);
+	}
+
+	if (group)
+	{
+		Py_DECREF(py_ret);
+		py_ret = KeyGroup_new_with_geany_key_group(group, has_cb);
+	}
+
+	return py_ret;
+}
+
+
+static PyObject *
+KeyGroup_add_key_item(KeyGroup *self, PyObject *args, PyObject *kwargs)
+{
+	static gchar *kwlist[] = { "name", "label", "callback", "key_id", "key", "mod" , "menu_item", NULL };
+	int id = -1;
+	int key = 0, mod = 0;
+	const gchar *name = NULL, *label = NULL;
+	PyObject *py_menu_item = NULL;
+	PyObject *py_callback  = NULL;
+	GeanyKeyBinding *item = NULL;
+
+	if (PyArg_ParseTupleAndKeywords(args, kwargs, "ss|OiiiO", kwlist,
+		&name, &label, &py_callback, &id, &key, &mod, &py_menu_item))
+	{
+		if (id == -1)
+			id = self->item_index;
+
+		GtkWidget *menu_item = (py_menu_item == NULL || py_menu_item == Py_None)
+									? NULL : GTK_WIDGET(pygobject_get(py_menu_item));
+		if (PyCallable_Check(py_callback))
+		{
+			Py_INCREF(py_callback);
+			item = keybindings_set_item_full(self->kb_group, id, (guint) key,
+			                                 (GdkModifierType) mod, name, label, menu_item,
+			                                 (GeanyKeyBindingFunc) call_key, py_callback,
+			                                 (GDestroyNotify) Py_DecRef);
+		}
+		else
+		{
+			if (!self->has_cb)
+				g_warning("Either KeyGroup or the Keybinding must have a callback\n");
+			else
+				item = keybindings_set_item(self->kb_group, id, NULL, (guint) key,
+				                            (GdkModifierType) mod, name, label, menu_item);
+		}
+		Py_XDECREF(py_menu_item);
+
+		self->item_index = id + 1;
+	}
+
+	if (item)
+	{
+		/* Return a tuple containing the key group and the opaque GeanyKeyBinding pointer.
+		 * This is in preparation of allowing chained calls like
+		 * set_kb_group(X, 3).add_key_item().add_key_item().add_key_item()
+		 * without losing access to the keybinding pointer (might become necessary for newer
+		 * Geany APIs).
+		 * Note that the plain tuple doesn't support the above yet, we've got to subclass it,
+		 * but we are prepared without breaking sub-plugins */
+		PyObject *ret = PyTuple_Pack(2, self, PyCapsule_New(item, "GeanyKeyBinding", NULL));
+		return ret;
+	}
+	Py_RETURN_NONE;
+}
+
+
+static PyMethodDef
+KeyGroup_methods[] = {
+	{ "add_key_item",				(PyCFunction)KeyGroup_add_key_item,	METH_KEYWORDS,
+		"Adds an action to the plugin's key group" },
+	{ NULL }
+};
+
+static PyMethodDef
+Keybindings_methods[] = {
+	{ "set_key_group",				(PyCFunction)Keybindings_set_key_group,	METH_KEYWORDS,
+		"Sets up a GeanyKeybindingGroup for this plugin." },
+	{ NULL }
+};
+
+
+static PyGetSetDef
+KeyGroup_getseters[] = {
+	{ NULL },
+};
+
+
+static void
+KeyGroup_dealloc(KeyGroup *self)
+{
+	g_return_if_fail(self != NULL);
+	self->ob_type->tp_free((PyObject *) self);
+}
+
+
+static PyTypeObject KeyGroupType = {
+	PyObject_HEAD_INIT(NULL)
+	0,											/* ob_size */
+	"geany.keybindings.KeyGroup",					/* tp_name */
+	sizeof(KeyGroup),								/* tp_basicsize */
+	0,											/* tp_itemsize */
+	(destructor) KeyGroup_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 GeanyKeyGroup structure."	,/* tp_doc */
+	0, 0, 0, 0, 0, 0,							/* tp_traverse - tp_iternext */
+	KeyGroup_methods,							/* tp_methods */
+	0,											/* tp_members */
+	KeyGroup_getseters,							/* tp_getset */
+	0, 0, 0, 0, 0,								/* tp_base - tp_dictoffset */
+	0, 0, (newfunc) PyType_GenericNew,			/* tp_init - tp_alloc, tp_new */
+};
+
+
+PyMODINIT_FUNC initkeybindings(void)
+{
+	PyObject *m;
+
+	if (PyType_Ready(&KeyGroupType) < 0)
+		return;
+
+	m = Py_InitModule3("keybindings", Keybindings_methods, "Keybindings support.");
+
+	Py_INCREF(&KeyGroupType);
+	PyModule_AddObject(m, "KeyGroup", (PyObject *)&KeyGroupType);
+}
+
+PyObject *KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb)
+{
+	KeyGroup *ret = PyObject_New(KeyGroup, &KeyGroupType);
+
+	ret->kb_group = group;
+	ret->has_cb = has_cb;
+	ret->item_index = 0;
+
+	return (PyObject *) ret;
+}
+


Modified: geanypy/src/geanypy-keybindings.h
39 lines changed, 39 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,39 @@
+/*
+ * geanypy-keybindings.h
+ *
+ * Copyright 2015 Thomas Martitz <kugel at rockbox.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.
+ */
+
+
+#ifndef GEANYPY_KEYBINDINGS_H
+#define GEANYPY_KEYBINDINGS_H
+
+#include <glib.h>
+
+typedef struct
+{
+	PyObject_HEAD
+	GeanyKeyGroup *kb_group;
+	gboolean has_cb;
+	gint item_index;
+} KeyGroup;
+
+extern PyObject *
+KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb);
+
+#endif /* GEANYPY_KEYBINDINGS_H */


Modified: geanypy/src/geanypy-plugin.c
383 lines changed, 258 insertions(+), 125 deletions(-)
===================================================================
@@ -26,26 +26,12 @@
 #define INCLUDE_PYGOBJECT_ONCE_FULL
 
 #include "geanypy.h"
+#include "geanypy-keybindings.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 <mbrush at codebrainz.ca>")
-
-
-static GtkWidget *loader_item = NULL;
-static PyObject *manager = NULL;
-static gchar *plugin_dir = NULL;
-static SignalManager *signal_manager = NULL;
+#include <glib.h>
+#include <glib/gstdio.h>
 
+GeanyData *geany_data;
 
 /* Forward declarations to prevent compiler warnings. */
 PyMODINIT_FUNC initapp(void);
@@ -64,6 +50,7 @@ PyMODINIT_FUNC initscintilla(void);
 PyMODINIT_FUNC initsearch(void);
 PyMODINIT_FUNC inittemplates(void);
 PyMODINIT_FUNC initui_utils(void);
+PyMODINIT_FUNC initkeybindings(void);
 
 
 static void
@@ -104,6 +91,7 @@ GeanyPy_start_interpreter(void)
     initsearch();
     inittemplates();
     initui_utils();
+    initkeybindings();
 
 #ifdef GEANYPY_WINDOWS
 	{ /* On windows, get path at runtime since we don't really know where
@@ -131,7 +119,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 +136,284 @@ 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;
 
-    g_return_if_fail(dir != NULL);
+static gboolean has_error(void)
+{
+	if (PyErr_Occurred())
+	{
+		PyErr_Print();
+		return TRUE;
+	}
+	return FALSE;
+}
 
-    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;
 
-    man = PyObject_GetAttrString(module, "PluginManager");
-    Py_DECREF(module);
+	data->instance = PyObject_CallObject(data->class, NULL);
+	if (has_error())
+		return FALSE;
 
-    if (man == NULL)
-    {
-        g_warning(_("Failed to retrieve PluginManager from manager module"));
-        return;
-    }
+	return TRUE;
+}
 
-#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;
-		}
-	}
-#else
-	sys_plugin_dir = g_strdup(GEANYPY_PLUGIN_DIR);
-#endif
 
+static void geanypy_proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+	GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
 
-	g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "User plugins: %s", dir);
+	PyObject_CallMethod(data->instance, "cleanup", NULL);
+	if (has_error())
+		return;
+}
 
-	if (sys_plugin_dir)
+
+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 != Py_None)
 	{
-		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);
+		/* 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
-		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;
-    }
+	Py_DECREF(o); /* Must unref even if it's Py_None */
+	return NULL;
 }
 
 
-static void
-GeanyPy_show_manager(void)
+static void do_show_configure(GtkWidget *button, gpointer pdata)
+{
+	GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
+	PyObject_CallMethod(data->instance, "show_configure", NULL);
+}
+
+
+static GtkWidget *geanypy_proxy_configure_legacy(GeanyPlugin *plugin, GtkDialog *parent, 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 *data = (GeanyPyPluginData *) pdata;
+	PyObject *o, *oparent;
+	GtkWidget *box, *label, *button, *align;
+	gchar *text;
+
+	/* This creates a simple page that has only one button to show the plugin's legacy configure
+	 * dialog. It is for older plugins that implement show_configure(). It's not pretty but
+	 * it provides basic backwards compatibility. */
+	box = gtk_vbox_new(FALSE, 2);
+
+	text = g_strdup_printf("The plugin \"%s\" is older and hasn't been updated\nto provide a configuration UI. However, it provides a dialog to\nallow you to change the plugin's preferences.", plugin->info->name);
+	label = gtk_label_new(text);
+
+	align = gtk_alignment_new(0, 0, 1, 1);
+	gtk_container_add(GTK_CONTAINER(align), label);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 6, 2, 2);
+	gtk_box_pack_start(GTK_BOX(box), align, FALSE, FALSE, 0);
+
+	button = gtk_button_new_with_label("Open dialog");
+	align = gtk_alignment_new(0.5, 0, 0.3f, 1);
+	gtk_container_add(GTK_CONTAINER(align), button);
+	g_signal_connect(button, "clicked", (GCallback) do_show_configure, pdata);
+	gtk_box_pack_start(GTK_BOX(box), align, FALSE, TRUE, 0);
+
+	gtk_widget_show_all(box);
+	g_free(text);
+	return box;
+}
+
+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>.plugin file */
+	if (!g_file_test(file_plugin, G_FILE_TEST_EXISTS))
+		ret = PROXY_MATCHED;
+
+	g_free(file_plugin);
+	return ret;
+}
+
+
+static const gchar *string_from_attr(PyObject *o, const gchar *attr)
+{
+	PyObject *string = PyObject_GetAttrString(o, attr);
+	const gchar *ret = PyString_AsString(string);
+	Py_DECREF(string);
+
+	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)
+	{
+		if (PyType_Check(val) && PyObject_IsSubclass(val, data->base))
+			found = val;
+	}
+
+	if (found)
+	{
+		GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData);
+		PluginInfo *info     = subplugin->info;
+		GeanyPluginFuncs *funcs = subplugin->funcs;
+		PyObject *caps = PyCapsule_New(subplugin, "GeanyPlugin", NULL);
+		Py_INCREF(found);
+		pdata->module        = module;
+		pdata->class         = found;
+		PyObject_SetAttrString(pdata->class, "__geany_plugin__", caps);
+		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;
+		else if (PyObject_HasAttrString(found, "show_configure"))
+			funcs->configure = geanypy_proxy_configure_legacy;
+		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
-on_python_plugin_loader_activate(GtkMenuItem *item, gpointer user_data)
+geanypy_unload(GeanyPlugin *plugin, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata_)
 {
-    GeanyPy_show_manager();
+	GeanyPyPluginData *pdata = load_data;
+
+	Py_XDECREF(pdata->instance);
+	Py_DECREF(pdata->class);
+	Py_DECREF(pdata->module);
+	while (PyGC_Collect());
+	g_slice_free(GeanyPyPluginData, pdata);
 }
 
 
-G_MODULE_EXPORT void
-plugin_init(GeanyData *data)
+static gboolean geanypy_init(GeanyPlugin *plugin_, gpointer pdata)
 {
-    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);
+	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;
 }
 
 
-G_MODULE_EXPORT void plugin_cleanup(void)
+static void geanypy_cleanup(GeanyPlugin *plugin, gpointer pdata)
 {
-    signal_manager_free(signal_manager);
-    Py_XDECREF(manager);
+	GeanyPyData *state = pdata;
+	signal_manager_free(state->signal_manager);
+	Py_DECREF(state->base);
 	GeanyPy_stop_interpreter();
-    gtk_widget_destroy(loader_item);
-    g_free(plugin_dir);
+}
+
+G_MODULE_EXPORT void
+geany_load_module(GeanyPlugin *plugin)
+{
+	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 <mbrush at codebrainz.ca>";
+	plugin->funcs->init       = geanypy_init;
+	plugin->funcs->cleanup    = geanypy_cleanup;
+
+	GEANY_PLUGIN_REGISTER_FULL(plugin, 226, state, g_free);
 }


Modified: geanypy/src/geanypy-plugin.h
12 lines changed, 8 insertions(+), 4 deletions(-)
===================================================================
@@ -26,16 +26,20 @@
 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
 
+static inline GeanyPlugin *plugin_get(PyObject *self)
+{
+	PyObject *caps = PyObject_GetAttrString(self, "__geany_plugin__");
+	return PyCapsule_GetPointer(caps, "GeanyPlugin");
+}
+
+
 
 #ifdef __cplusplus
 } /* extern "C" */


Modified: geanypy/src/geanypy-signalmanager.c
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -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);



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Plugins-Commits mailing list