summaryrefslogtreecommitdiff
path: root/plugin-loaders/python/pluma-plugin-loader-python.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugin-loaders/python/pluma-plugin-loader-python.c')
-rwxr-xr-xplugin-loaders/python/pluma-plugin-loader-python.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/plugin-loaders/python/pluma-plugin-loader-python.c b/plugin-loaders/python/pluma-plugin-loader-python.c
new file mode 100755
index 00000000..140fbb7d
--- /dev/null
+++ b/plugin-loaders/python/pluma-plugin-loader-python.c
@@ -0,0 +1,719 @@
+/*
+ * pluma-plugin-loader-python.c
+ * This file is part of pluma
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ *
+ * 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., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "pluma-plugin-loader-python.h"
+#include "pluma-plugin-python.h"
+#include <pluma/pluma-object-module.h>
+
+#define NO_IMPORT_PYGOBJECT
+#define NO_IMPORT_PYGTK
+
+#include <Python.h>
+#include <pygobject.h>
+#include <pygtk/pygtk.h>
+#include <signal.h>
+#include "config.h"
+
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
+#define PLUMA_PLUGIN_LOADER_PYTHON_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), PLUMA_TYPE_PLUGIN_LOADER_PYTHON, PlumaPluginLoaderPythonPrivate))
+
+struct _PlumaPluginLoaderPythonPrivate
+{
+ GHashTable *loaded_plugins;
+ guint idle_gc;
+ gboolean init_failed;
+};
+
+typedef struct
+{
+ PyObject *type;
+ PyObject *instance;
+ gchar *path;
+} PythonInfo;
+
+static void pluma_plugin_loader_iface_init (gpointer g_iface, gpointer iface_data);
+
+/* Exported by pypluma module */
+void pypluma_register_classes (PyObject *d);
+void pypluma_add_constants (PyObject *module, const gchar *strip_prefix);
+extern PyMethodDef pypluma_functions[];
+
+/* Exported by pyplumautils module */
+void pyplumautils_register_classes (PyObject *d);
+extern PyMethodDef pyplumautils_functions[];
+
+/* Exported by pyplumacommands module */
+void pyplumacommands_register_classes (PyObject *d);
+extern PyMethodDef pyplumacommands_functions[];
+
+/* We retreive this to check for correct class hierarchy */
+static PyTypeObject *PyPlumaPlugin_Type;
+
+PLUMA_PLUGIN_LOADER_REGISTER_TYPE (PlumaPluginLoaderPython, pluma_plugin_loader_python, G_TYPE_OBJECT, pluma_plugin_loader_iface_init);
+
+
+static PyObject *
+find_python_plugin_type (PlumaPluginInfo *info,
+ PyObject *pymodule)
+{
+ PyObject *locals, *key, *value;
+ Py_ssize_t pos = 0;
+
+ locals = PyModule_GetDict (pymodule);
+
+ while (PyDict_Next (locals, &pos, &key, &value))
+ {
+ if (!PyType_Check(value))
+ continue;
+
+ if (PyObject_IsSubclass (value, (PyObject*) PyPlumaPlugin_Type))
+ return value;
+ }
+
+ g_warning ("No PlumaPlugin derivative found in Python plugin '%s'",
+ pluma_plugin_info_get_name (info));
+ return NULL;
+}
+
+static PlumaPlugin *
+new_plugin_from_info (PlumaPluginLoaderPython *loader,
+ PlumaPluginInfo *info)
+{
+ PythonInfo *pyinfo;
+ PyTypeObject *pytype;
+ PyObject *pyobject;
+ PyGObject *pygobject;
+ PlumaPlugin *instance;
+ PyObject *emptyarg;
+
+ pyinfo = (PythonInfo *)g_hash_table_lookup (loader->priv->loaded_plugins, info);
+
+ if (pyinfo == NULL)
+ return NULL;
+
+ pytype = (PyTypeObject *)pyinfo->type;
+
+ if (pytype->tp_new == NULL)
+ return NULL;
+
+ emptyarg = PyTuple_New(0);
+ pyobject = pytype->tp_new (pytype, emptyarg, NULL);
+ Py_DECREF (emptyarg);
+
+ if (pyobject == NULL)
+ {
+ g_error ("Could not create instance for %s.", pluma_plugin_info_get_name (info));
+ return NULL;
+ }
+
+ pygobject = (PyGObject *)pyobject;
+
+ if (pygobject->obj != NULL)
+ {
+ Py_DECREF (pyobject);
+ g_error ("Could not create instance for %s (GObject already initialized).", pluma_plugin_info_get_name (info));
+ return NULL;
+ }
+
+ pygobject_construct (pygobject,
+ "install-dir", pyinfo->path,
+ "data-dir-name", pluma_plugin_info_get_module_name (info),
+ NULL);
+
+ if (pygobject->obj == NULL)
+ {
+ g_error ("Could not create instance for %s (GObject not constructed).", pluma_plugin_info_get_name (info));
+ Py_DECREF (pyobject);
+
+ return NULL;
+ }
+
+ /* now call tp_init manually */
+ if (PyType_IsSubtype (pyobject->ob_type, pytype) &&
+ pyobject->ob_type->tp_init != NULL)
+ {
+ emptyarg = PyTuple_New(0);
+ pyobject->ob_type->tp_init (pyobject, emptyarg, NULL);
+ Py_DECREF (emptyarg);
+ }
+
+ instance = PLUMA_PLUGIN (pygobject->obj);
+ pyinfo->instance = (PyObject *)pygobject;
+
+ /* make sure to register the python instance for the PlumaPluginPython
+ object to it can wrap the virtual pluma plugin funcs back to python */
+ _pluma_plugin_python_set_instance (PLUMA_PLUGIN_PYTHON (instance), (PyObject *)pygobject);
+
+ /* we return a reference here because the other is owned by python */
+ return PLUMA_PLUGIN (g_object_ref (instance));
+}
+
+static PlumaPlugin *
+add_python_info (PlumaPluginLoaderPython *loader,
+ PlumaPluginInfo *info,
+ PyObject *module,
+ const gchar *path,
+ PyObject *type)
+{
+ PythonInfo *pyinfo;
+
+ pyinfo = g_new (PythonInfo, 1);
+ pyinfo->path = g_strdup (path);
+ pyinfo->type = type;
+
+ Py_INCREF (pyinfo->type);
+
+ g_hash_table_insert (loader->priv->loaded_plugins, info, pyinfo);
+
+ return new_plugin_from_info (loader, info);
+}
+
+static const gchar *
+pluma_plugin_loader_iface_get_id (void)
+{
+ return "Python";
+}
+
+static PlumaPlugin *
+pluma_plugin_loader_iface_load (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info,
+ const gchar *path)
+{
+ PlumaPluginLoaderPython *pyloader = PLUMA_PLUGIN_LOADER_PYTHON (loader);
+ PyObject *main_module, *main_locals, *pytype;
+ PyObject *pymodule, *fromlist;
+ gchar *module_name;
+ PlumaPlugin *result;
+
+ if (pyloader->priv->init_failed)
+ {
+ g_warning ("Cannot load python plugin Python '%s' since pluma was"
+ "not able to initialize the Python interpreter.",
+ pluma_plugin_info_get_name (info));
+ return NULL;
+ }
+
+ /* see if py definition for the plugin is already loaded */
+ result = new_plugin_from_info (pyloader, info);
+
+ if (result != NULL)
+ return result;
+
+ main_module = PyImport_AddModule ("pluma.plugins");
+ if (main_module == NULL)
+ {
+ g_warning ("Could not get pluma.plugins.");
+ return NULL;
+ }
+
+ /* If we have a special path, we register it */
+ if (path != NULL)
+ {
+ PyObject *sys_path = PySys_GetObject ("path");
+ PyObject *pypath = PyString_FromString (path);
+
+ if (PySequence_Contains (sys_path, pypath) == 0)
+ PyList_Insert (sys_path, 0, pypath);
+
+ Py_DECREF (pypath);
+ }
+
+ main_locals = PyModule_GetDict (main_module);
+
+ /* we need a fromlist to be able to import modules with a '.' in the
+ name. */
+ fromlist = PyTuple_New(0);
+ module_name = g_strdup (pluma_plugin_info_get_module_name (info));
+
+ pymodule = PyImport_ImportModuleEx (module_name,
+ main_locals,
+ main_locals,
+ fromlist);
+
+ Py_DECREF(fromlist);
+
+ if (!pymodule)
+ {
+ g_free (module_name);
+ PyErr_Print ();
+ return NULL;
+ }
+
+ PyDict_SetItemString (main_locals, module_name, pymodule);
+ g_free (module_name);
+
+ pytype = find_python_plugin_type (info, pymodule);
+
+ if (pytype)
+ return add_python_info (pyloader, info, pymodule, path, pytype);
+
+ return NULL;
+}
+
+static void
+pluma_plugin_loader_iface_unload (PlumaPluginLoader *loader,
+ PlumaPluginInfo *info)
+{
+ PlumaPluginLoaderPython *pyloader = PLUMA_PLUGIN_LOADER_PYTHON (loader);
+ PythonInfo *pyinfo;
+ PyGILState_STATE state;
+
+ pyinfo = (PythonInfo *)g_hash_table_lookup (pyloader->priv->loaded_plugins, info);
+
+ if (!pyinfo)
+ return;
+
+ state = pyg_gil_state_ensure ();
+ Py_XDECREF (pyinfo->instance);
+ pyg_gil_state_release (state);
+
+ pyinfo->instance = NULL;
+}
+
+static gboolean
+run_gc (PlumaPluginLoaderPython *loader)
+{
+ while (PyGC_Collect ())
+ ;
+
+ loader->priv->idle_gc = 0;
+ return FALSE;
+}
+
+static void
+pluma_plugin_loader_iface_garbage_collect (PlumaPluginLoader *loader)
+{
+ PlumaPluginLoaderPython *pyloader;
+
+ if (!Py_IsInitialized())
+ return;
+
+ pyloader = PLUMA_PLUGIN_LOADER_PYTHON (loader);
+
+ /*
+ * We both run the GC right now and we schedule
+ * a further collection in the main loop.
+ */
+
+ while (PyGC_Collect ())
+ ;
+
+ if (pyloader->priv->idle_gc == 0)
+ pyloader->priv->idle_gc = g_idle_add ((GSourceFunc)run_gc, pyloader);
+}
+
+static void
+pluma_plugin_loader_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ PlumaPluginLoaderInterface *iface = (PlumaPluginLoaderInterface *)g_iface;
+
+ iface->get_id = pluma_plugin_loader_iface_get_id;
+ iface->load = pluma_plugin_loader_iface_load;
+ iface->unload = pluma_plugin_loader_iface_unload;
+ iface->garbage_collect = pluma_plugin_loader_iface_garbage_collect;
+}
+
+static void
+pluma_python_shutdown (PlumaPluginLoaderPython *loader)
+{
+ if (!Py_IsInitialized ())
+ return;
+
+ if (loader->priv->idle_gc != 0)
+ {
+ g_source_remove (loader->priv->idle_gc);
+ loader->priv->idle_gc = 0;
+ }
+
+ while (PyGC_Collect ())
+ ;
+
+ Py_Finalize ();
+}
+
+
+/* C equivalent of
+ * import pygtk
+ * pygtk.require ("2.0")
+ */
+static gboolean
+pluma_check_pygtk2 (void)
+{
+ PyObject *pygtk, *mdict, *require;
+
+ /* pygtk.require("2.0") */
+ pygtk = PyImport_ImportModule ("pygtk");
+ if (pygtk == NULL)
+ {
+ g_warning ("Error initializing Python interpreter: could not import pygtk.");
+ return FALSE;
+ }
+
+ mdict = PyModule_GetDict (pygtk);
+ require = PyDict_GetItemString (mdict, "require");
+ PyObject_CallObject (require,
+ Py_BuildValue ("(S)", PyString_FromString ("2.0")));
+ if (PyErr_Occurred())
+ {
+ g_warning ("Error initializing Python interpreter: pygtk 2 is required.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Note: the following two functions are needed because
+ * init_pyobject and init_pygtk which are *macros* which in case
+ * case of error set the PyErr and then make the calling
+ * function return behind our back.
+ * It's up to the caller to check the result with PyErr_Occurred()
+ */
+static void
+pluma_init_pygobject (void)
+{
+ init_pygobject_check (2, 11, 5); /* FIXME: get from config */
+}
+
+static void
+pluma_init_pygtk (void)
+{
+ PyObject *gtk, *mdict, *version, *required_version;
+
+ init_pygtk ();
+
+ /* there isn't init_pygtk_check(), do the version
+ * check ourselves */
+ gtk = PyImport_ImportModule("gtk");
+ mdict = PyModule_GetDict(gtk);
+ version = PyDict_GetItemString (mdict, "pygtk_version");
+ if (!version)
+ {
+ PyErr_SetString (PyExc_ImportError,
+ "PyGObject version too old");
+ return;
+ }
+
+ required_version = Py_BuildValue ("(iii)", 2, 4, 0); /* FIXME */
+
+ if (PyObject_Compare (version, required_version) == -1)
+ {
+ PyErr_SetString (PyExc_ImportError,
+ "PyGObject version too old");
+ Py_DECREF (required_version);
+ return;
+ }
+
+ Py_DECREF (required_version);
+}
+
+static void
+old_gtksourceview_init (void)
+{
+ PyErr_SetString(PyExc_ImportError,
+ "gtksourceview module not allowed, use gtksourceview2");
+}
+
+static void
+pluma_init_pygtksourceview (void)
+{
+ PyObject *gtksourceview, *mdict, *version, *required_version;
+
+ gtksourceview = PyImport_ImportModule("gtksourceview2");
+ if (gtksourceview == NULL)
+ {
+ PyErr_SetString (PyExc_ImportError,
+ "could not import gtksourceview");
+ return;
+ }
+
+ mdict = PyModule_GetDict (gtksourceview);
+ version = PyDict_GetItemString (mdict, "pygtksourceview2_version");
+ if (!version)
+ {
+ PyErr_SetString (PyExc_ImportError,
+ "PyGtkSourceView version too old");
+ return;
+ }
+
+ required_version = Py_BuildValue ("(iii)", 0, 8, 0); /* FIXME */
+
+ if (PyObject_Compare (version, required_version) == -1)
+ {
+ PyErr_SetString (PyExc_ImportError,
+ "PyGtkSourceView version too old");
+ Py_DECREF (required_version);
+ return;
+ }
+
+ Py_DECREF (required_version);
+
+ /* Create a dummy 'gtksourceview' module to prevent
+ * loading of the old 'gtksourceview' modules that
+ * has conflicting symbols with the gtksourceview2 module.
+ * Raise an exception when trying to import it.
+ */
+ PyImport_AppendInittab ("gtksourceview", old_gtksourceview_init);
+}
+
+static gboolean
+pluma_python_init (PlumaPluginLoaderPython *loader)
+{
+ PyObject *mdict, *tuple;
+ PyObject *pluma, *plumautils, *plumacommands, *plumaplugins;
+ PyObject *gettext, *install, *gettext_args;
+ //char *argv[] = { "pluma", NULL };
+ char *argv[] = { PLUMA_PLUGINS_LIBS_DIR, NULL };
+#ifdef HAVE_SIGACTION
+ gint res;
+ struct sigaction old_sigint;
+#endif
+
+ if (loader->priv->init_failed)
+ {
+ /* We already failed to initialized Python, don't need to
+ * retry again */
+ return FALSE;
+ }
+
+ if (Py_IsInitialized ())
+ {
+ /* Python has already been successfully initialized */
+ return TRUE;
+ }
+
+ /* We are trying to initialize Python for the first time,
+ set init_failed to FALSE only if the entire initialization process
+ ends with success */
+ loader->priv->init_failed = TRUE;
+
+ /* Hack to make python not overwrite SIGINT: this is needed to avoid
+ * the crash reported on bug #326191 */
+
+ /* CHECK: can't we use Py_InitializeEx instead of Py_Initialize in order
+ to avoid to manage signal handlers ? - Paolo (Dec. 31, 2006) */
+
+#ifdef HAVE_SIGACTION
+ /* Save old handler */
+ res = sigaction (SIGINT, NULL, &old_sigint);
+ if (res != 0)
+ {
+ g_warning ("Error initializing Python interpreter: cannot get "
+ "handler to SIGINT signal (%s)",
+ g_strerror (errno));
+
+ return FALSE;
+ }
+#endif
+
+ /* Python initialization */
+ Py_Initialize ();
+
+#ifdef HAVE_SIGACTION
+ /* Restore old handler */
+ res = sigaction (SIGINT, &old_sigint, NULL);
+ if (res != 0)
+ {
+ g_warning ("Error initializing Python interpreter: cannot restore "
+ "handler to SIGINT signal (%s).",
+ g_strerror (errno));
+
+ goto python_init_error;
+ }
+#endif
+
+ PySys_SetArgv (1, argv);
+
+ if (!pluma_check_pygtk2 ())
+ {
+ /* Warning message already printed in check_pygtk2 */
+ goto python_init_error;
+ }
+
+ /* import gobject */
+ pluma_init_pygobject ();
+ if (PyErr_Occurred ())
+ {
+ g_warning ("Error initializing Python interpreter: could not import pygobject.");
+
+ goto python_init_error;
+ }
+
+ /* import gtk */
+ pluma_init_pygtk ();
+ if (PyErr_Occurred ())
+ {
+ g_warning ("Error initializing Python interpreter: could not import pygtk.");
+
+ goto python_init_error;
+ }
+
+ /* import gtksourceview */
+ pluma_init_pygtksourceview ();
+ if (PyErr_Occurred ())
+ {
+ PyErr_Print ();
+
+ g_warning ("Error initializing Python interpreter: could not import pygtksourceview.");
+
+ goto python_init_error;
+ }
+
+ /* import pluma */
+ pluma = Py_InitModule ("pluma", pypluma_functions);
+ mdict = PyModule_GetDict (pluma);
+
+ pypluma_register_classes (mdict);
+ pypluma_add_constants (pluma, "PLUMA_");
+
+ /* pluma version */
+ tuple = Py_BuildValue("(iii)",
+ PLUMA_MAJOR_VERSION,
+ PLUMA_MINOR_VERSION,
+ PLUMA_MICRO_VERSION);
+ PyDict_SetItemString(mdict, "version", tuple);
+ Py_DECREF(tuple);
+
+ /* Retrieve the Python type for pluma.Plugin */
+ PyPlumaPlugin_Type = (PyTypeObject *) PyDict_GetItemString (mdict, "Plugin");
+ if (PyPlumaPlugin_Type == NULL)
+ {
+ PyErr_Print ();
+
+ goto python_init_error;
+ }
+
+ /* import pluma.utils */
+ plumautils = Py_InitModule ("pluma.utils", pyplumautils_functions);
+ PyDict_SetItemString (mdict, "utils", plumautils);
+
+ /* import pluma.commands */
+ plumacommands = Py_InitModule ("pluma.commands", pyplumacommands_functions);
+ PyDict_SetItemString (mdict, "commands", plumacommands);
+
+ /* initialize empty pluma.plugins module */
+ plumaplugins = Py_InitModule ("pluma.plugins", NULL);
+ PyDict_SetItemString (mdict, "plugins", plumaplugins);
+
+ mdict = PyModule_GetDict (plumautils);
+ pyplumautils_register_classes (mdict);
+
+ mdict = PyModule_GetDict (plumacommands);
+ pyplumacommands_register_classes (mdict);
+
+ /* i18n support */
+ gettext = PyImport_ImportModule ("gettext");
+ if (gettext == NULL)
+ {
+ g_warning ("Error initializing Python interpreter: could not import gettext.");
+
+ goto python_init_error;
+ }
+
+ mdict = PyModule_GetDict (gettext);
+ install = PyDict_GetItemString (mdict, "install");
+ gettext_args = Py_BuildValue ("ss", GETTEXT_PACKAGE, PLUMA_LOCALEDIR);
+ PyObject_CallObject (install, gettext_args);
+ Py_DECREF (gettext_args);
+
+ /* Python has been successfully initialized */
+ loader->priv->init_failed = FALSE;
+
+ return TRUE;
+
+python_init_error:
+
+ g_warning ("Please check the installation of all the Python related packages required "
+ "by pluma and try again.");
+
+ PyErr_Clear ();
+
+ pluma_python_shutdown (loader);
+
+ return FALSE;
+}
+
+static void
+pluma_plugin_loader_python_finalize (GObject *object)
+{
+ PlumaPluginLoaderPython *pyloader = PLUMA_PLUGIN_LOADER_PYTHON (object);
+
+ g_hash_table_destroy (pyloader->priv->loaded_plugins);
+ pluma_python_shutdown (pyloader);
+
+ G_OBJECT_CLASS (pluma_plugin_loader_python_parent_class)->finalize (object);
+}
+
+static void
+pluma_plugin_loader_python_class_init (PlumaPluginLoaderPythonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pluma_plugin_loader_python_finalize;
+
+ g_type_class_add_private (object_class, sizeof (PlumaPluginLoaderPythonPrivate));
+}
+
+static void
+pluma_plugin_loader_python_class_finalize (PlumaPluginLoaderPythonClass *klass)
+{
+}
+
+static void
+destroy_python_info (PythonInfo *info)
+{
+ PyGILState_STATE state = pyg_gil_state_ensure ();
+ Py_XDECREF (info->type);
+ pyg_gil_state_release (state);
+
+ g_free (info->path);
+ g_free (info);
+}
+
+static void
+pluma_plugin_loader_python_init (PlumaPluginLoaderPython *self)
+{
+ self->priv = PLUMA_PLUGIN_LOADER_PYTHON_GET_PRIVATE (self);
+
+ /* initialize python interpreter */
+ pluma_python_init (self);
+
+ /* loaded_plugins maps PlumaPluginInfo to a PythonInfo */
+ self->priv->loaded_plugins = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)destroy_python_info);
+}
+
+PlumaPluginLoaderPython *
+pluma_plugin_loader_python_new ()
+{
+ GObject *loader = g_object_new (PLUMA_TYPE_PLUGIN_LOADER_PYTHON, NULL);
+
+ return PLUMA_PLUGIN_LOADER_PYTHON (loader);
+}
+