Python plugin: Also check for _PyGtk_API being a PyCapsule
[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 #ifdef ENABLE_PYTHON
29 #include <Python.h>
30 #include <pygobject.h>
31 #include <pygtk/pygtk.h>
32 #endif // ENABLE_PYTHON
33
34 #include <glib.h>
35 #include <glib/gi18n.h>
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             }
203 #if PY_VERSION_HEX >= 0x02070000
204             else if (PyCapsule_IsValid(cobject, "gtk._gtk._PyGtk_API")) {
205                 _PyGtk_API = (struct _PyGtk_FunctionStruct*)PyCapsule_GetPointer(cobject, "gtk._gtk._PyGtk_API");
206             }
207 #endif
208             else {
209               *error = g_strdup("Parasite: Could not find _PyGtk_API object");
210                 return 0;
211             }
212         }
213     } else {
214         *error = g_strdup("Parasite: Could not import gtk");
215         return 0;
216     }
217
218     python_enabled = TRUE;
219 #endif // ENABLE_PYTHON
220     return !0;
221 }
222
223 void
224 parasite_python_run(const char *command,
225                     ParasitePythonLogger stdout_logger,
226                     ParasitePythonLogger stderr_logger,
227                     gpointer user_data)
228 {
229 #ifdef ENABLE_PYTHON
230     PyGILState_STATE gstate;
231     PyObject *module;
232     PyObject *dict;
233     PyObject *obj;
234     const char *cp;
235
236     /* empty string as command is a noop */
237     if(!strcmp(command, ""))
238       return;
239
240     /* if first non-whitespace character is '#', command is also a noop */
241     cp = command;
242     while(cp && (*cp != '\0') && g_ascii_isspace(*cp))
243       cp++;
244     if(cp && *cp == '#')
245       return;
246
247     gstate = PyGILState_Ensure();
248
249     module = PyImport_AddModule("__main__");
250     dict = PyModule_GetDict(module);
251
252     PyRun_SimpleString("old_stdout = sys.stdout\n"
253                        "old_stderr = sys.stderr\n"
254                        "old_stdin  = sys.stdin\n"
255                        "sys.stdout = StdoutCatcher()\n"
256                        "sys.stderr = StderrCatcher()\n"
257                        "sys.stdin  = StdinCatcher()\n");
258
259     obj = PyRun_String(command, Py_single_input, dict, dict);
260     if(PyErr_Occurred())
261       PyErr_Print();
262     PyRun_SimpleString("sys.stdout = old_stdout\n"
263                        "sys.stderr = old_stderr\n"
264                        "sys.stdin = old_stdin\n");
265
266     if (stdout_logger != NULL)
267         stdout_logger(captured_stdout->str, user_data);
268
269     if (stderr_logger != NULL)
270         stderr_logger(captured_stderr->str, user_data);
271
272     // Print any returned object
273     if (obj != NULL && obj != Py_None) {
274        PyObject *repr = PyObject_Repr(obj);
275        if (repr != NULL) {
276            char *string = PyString_AsString(repr);
277
278            stdout_logger(string, user_data);
279            stdout_logger("\n", user_data);
280         }
281
282         Py_XDECREF(repr);
283     }
284     Py_XDECREF(obj);
285
286     PyGILState_Release(gstate);
287     g_string_erase(captured_stdout, 0, -1);
288     g_string_erase(captured_stderr, 0, -1);
289 #endif // ENABLE_PYTHON
290 }
291
292 gboolean
293 parasite_python_is_enabled(void)
294 {
295     return python_enabled;
296 }
297
298 // vim: set et sw=4 ts=4: