diff options
Diffstat (limited to 'plugin-loaders/python/pluma-plugin-loader-python.c')
-rwxr-xr-x | plugin-loaders/python/pluma-plugin-loader-python.c | 719 |
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); +} + |