Python plugin: Improve error reporting during plugin init
[claws.git] / src / plugins / python / python-hooks.c
1 /*
2  * Copyright (c) 2008-2009  Christian Hammond
3  * Copyright (c) 2008-2009  David Trowbridge
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21  * THE SOFTWARE.
22  */
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30
31 #ifdef ENABLE_PYTHON
32 #include <Python.h>
33 #include <pygobject.h>
34 #include <pygtk/pygtk.h>
35 #endif // ENABLE_PYTHON
36
37 #include <dlfcn.h>
38
39 #include <signal.h>
40
41 #include "python-hooks.h"
42
43
44 static gboolean python_enabled = FALSE;
45
46 #ifdef ENABLE_PYTHON
47 static GString *captured_stdout = NULL;
48 static GString *captured_stderr = NULL;
49
50
51 static PyObject *
52 capture_stdout(PyObject *self, PyObject *args)
53 {
54     char *str = NULL;
55
56     if (!PyArg_ParseTuple(args, "s", &str))
57         return NULL;
58
59     g_string_append(captured_stdout, str);
60
61     Py_INCREF(Py_None);
62     return Py_None;
63 }
64
65 static PyObject *
66 capture_stderr(PyObject *self, PyObject *args)
67 {
68     char *str = NULL;
69
70     if (!PyArg_ParseTuple(args, "s", &str))
71         return NULL;
72
73     g_string_append(captured_stderr, str);
74
75     Py_INCREF(Py_None);
76     return Py_None;
77 }
78
79 static PyObject *
80 capture_stdin(PyObject *self, PyObject *args)
81 {
82     /* Return an empty string.
83      * This is what read() returns when hitting EOF. */
84     return PyString_FromString("");
85 }
86
87 static PyObject *
88 wrap_gobj(PyObject *self, PyObject *args)
89 {
90     void *addr;
91     GObject *obj;
92
93     if (!PyArg_ParseTuple(args, "l", &addr))
94         return NULL;
95
96     if (!G_IS_OBJECT(addr))
97         return NULL; // XXX
98
99     obj = G_OBJECT(addr);
100
101     if (!obj)
102         return NULL; // XXX
103
104     return pygobject_new(obj);
105 }
106
107 static PyMethodDef parasite_python_methods[] = {
108     {"capture_stdout", capture_stdout, METH_VARARGS, "Captures stdout"},
109     {"capture_stderr", capture_stderr, METH_VARARGS, "Captures stderr"},
110     {"capture_stdin", capture_stdin, METH_VARARGS, "Captures stdin"},
111     {"gobj", wrap_gobj, METH_VARARGS, "Wraps a C GObject"},
112     {NULL, NULL, 0, NULL}
113 };
114
115
116 static gboolean
117 is_blacklisted(void)
118 {
119     const char *prgname = g_get_prgname();
120
121     return (!strcmp(prgname, "gimp"));
122 }
123 #endif // ENABLE_PYTHON
124
125 int
126 parasite_python_init(char **error)
127 {
128 #ifdef ENABLE_PYTHON
129     struct sigaction old_sigint;
130     PyObject *pygtk;
131
132     if (is_blacklisted()) {
133       *error = g_strdup("Application is blacklisted");
134       return 0;
135     }
136
137     /* This prevents errors such as "undefined symbol: PyExc_ImportError" */
138     if (!dlopen(PYTHON_SHARED_LIB, RTLD_NOW | RTLD_GLOBAL))
139     {
140         *error = g_strdup_printf("Parasite: Error on dlopen(): %s\n", dlerror());
141         return 0;
142     }
143
144     captured_stdout = g_string_new("");
145     captured_stderr = g_string_new("");
146
147     /* Back up and later restore SIGINT so Python doesn't steal it from us. */
148     sigaction(SIGINT, NULL, &old_sigint);
149
150     if (!Py_IsInitialized())
151         Py_Initialize();
152
153     sigaction(SIGINT, &old_sigint, NULL);
154
155     Py_InitModule("parasite", parasite_python_methods);
156     if(PyRun_SimpleString(
157         "import parasite\n"
158         "import sys\n"
159         "\n"
160         "class StdoutCatcher:\n"
161         "    def write(self, str):\n"
162         "        parasite.capture_stdout(str)\n"
163         "    def flush(self):\n"
164         "        pass\n"
165         "\n"
166         "class StderrCatcher:\n"
167         "    def write(self, str):\n"
168         "        parasite.capture_stderr(str)\n"
169         "    def flush(self):\n"
170         "        pass\n"
171         "\n"
172         "class StdinCatcher:\n"
173         "    def readline(self, size=-1):\n"
174         "        return parasite.capture_stdin(size)\n"
175         "    def read(self, size=-1):\n"
176         "        return parasite.capture_stdin(size)\n"
177         "    def flush(self):\n"
178         "        pass\n"
179         "\n"
180     ) == -1)
181       return 0;
182
183     if (!pygobject_init(-1, -1, -1))
184         return 0;
185
186     pygtk = PyImport_ImportModule("gtk");
187
188     if (pygtk != NULL)
189     {
190         PyObject *module_dict = PyModule_GetDict(pygtk);
191         PyObject *cobject = PyDict_GetItemString(module_dict, "_PyGtk_API");
192
193         /*
194          * This seems to be NULL when we're running a PyGTK program.
195          * We really need to find out why.
196          */
197         if (cobject != NULL)
198         {
199             if (PyCObject_Check(cobject))
200                 _PyGtk_API = (struct _PyGtk_FunctionStruct*)
201                 PyCObject_AsVoidPtr(cobject);
202             else {
203               *error = g_strdup("Parasite: Could not find _PyGtk_API object");
204                 return 0;
205             }
206         }
207     } else {
208         *error = g_strdup("Parasite: Could not import gtk");
209         return 0;
210     }
211
212     python_enabled = TRUE;
213 #endif // ENABLE_PYTHON
214     return !0;
215 }
216
217 void
218 parasite_python_run(const char *command,
219                     ParasitePythonLogger stdout_logger,
220                     ParasitePythonLogger stderr_logger,
221                     gpointer user_data)
222 {
223 #ifdef ENABLE_PYTHON
224     PyGILState_STATE gstate;
225     PyObject *module;
226     PyObject *dict;
227     PyObject *obj;
228     const char *cp;
229
230     /* empty string as command is a noop */
231     if(!strcmp(command, ""))
232       return;
233
234     /* if first non-whitespace character is '#', command is also a noop */
235     cp = command;
236     while(cp && (*cp != '\0') && g_ascii_isspace(*cp))
237       cp++;
238     if(cp && *cp == '#')
239       return;
240
241     gstate = PyGILState_Ensure();
242
243     module = PyImport_AddModule("__main__");
244     dict = PyModule_GetDict(module);
245
246     PyRun_SimpleString("old_stdout = sys.stdout\n"
247                        "old_stderr = sys.stderr\n"
248                        "old_stdin  = sys.stdin\n"
249                        "sys.stdout = StdoutCatcher()\n"
250                        "sys.stderr = StderrCatcher()\n"
251                        "sys.stdin  = StdinCatcher()\n");
252
253     obj = PyRun_String(command, Py_single_input, dict, dict);
254     if(PyErr_Occurred())
255       PyErr_Print();
256     PyRun_SimpleString("sys.stdout = old_stdout\n"
257                        "sys.stderr = old_stderr\n"
258                        "sys.stdin = old_stdin\n");
259
260     if (stdout_logger != NULL)
261         stdout_logger(captured_stdout->str, user_data);
262
263     if (stderr_logger != NULL)
264         stderr_logger(captured_stderr->str, user_data);
265
266     // Print any returned object
267     if (obj != NULL && obj != Py_None) {
268        PyObject *repr = PyObject_Repr(obj);
269        if (repr != NULL) {
270            char *string = PyString_AsString(repr);
271
272            stdout_logger(string, user_data);
273            stdout_logger("\n", user_data);
274         }
275
276         Py_XDECREF(repr);
277     }
278     Py_XDECREF(obj);
279
280     PyGILState_Release(gstate);
281     g_string_erase(captured_stdout, 0, -1);
282     g_string_erase(captured_stderr, 0, -1);
283 #endif // ENABLE_PYTHON
284 }
285
286 gboolean
287 parasite_python_is_enabled(void)
288 {
289     return python_enabled;
290 }
291
292 // vim: set et sw=4 ts=4: