1 /*
2 This is part of TeXworks, an environment for working with TeX documents
3 Copyright (C) 2010-2013 Jonathan Kew, Stefan Löffler, Charlie Sharpsteen
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 For links to further information, or to contact the authors,
19 see <http://www.tug.org/texworks/>.
20 */
21
22 #include "TWPythonPlugin.h"
23 #include "TWScriptAPI.h"
24
25 #include <QCoreApplication>
26 #include <QtPlugin>
27 #include <QMetaObject>
28 #include <QStringList>
29 #include <QTextStream>
30
31 /* macros that may not be available in older python headers */
32 #ifndef Py_RETURN_NONE
33 #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
34 #endif
35 #ifndef Py_RETURN_TRUE
36 #define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True
37 #endif
38 #ifndef Py_RETURN_FALSE
39 #define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False
40 #endif
41
42 /* To encapsulate C pointers, PyCObject was replaced by PyCapsule in Python 3.2 */
43 #if PY_VERSION_HEX < 0x03020000
44 #define ENCAPSULATE_C_POINTER(ptr) PyCObject_FromVoidPtr((ptr), NULL)
45 #define IS_ENCAPSULATED_C_POINTER(obj) PyCObject_Check((obj))
46 #define GET_ENCAPSULATED_C_POINTER(obj) PyCObject_AsVoidPtr((obj))
47 #else
48 #define ENCAPSULATE_C_POINTER(ptr) PyCapsule_New((ptr), NULL, NULL)
49 #define IS_ENCAPSULATED_C_POINTER(obj) PyCapsule_CheckExact((obj))
50 #define GET_ENCAPSULATED_C_POINTER(obj) PyCapsule_GetPointer((obj), NULL)
51 #endif
52
53 /* Py_ssize_t is new in Python 2.5 */
54 #if PY_VERSION_HEX < 0x02050000
55 typedef int Py_ssize_t;
56 #endif
57
58 /** \brief Structure to hold data for the pyQObject wrapper */
59 typedef struct {
60 PyObject_HEAD
61 /* Type-specific fields go here. */
62 PyObject * _TWcontext; ///< pointer to the QObject wrapped by this object
63 } pyQObject;
64
65 /** \brief Structure to hold data for the pyQObjectMethodObject wrapper */
66 typedef struct {
67 PyObject_HEAD
68 /* Type-specific fields go here. */
69 PyObject * _TWcontext; ///< pointer to the QObject the method wrapped by this object belongs to
70 PyObject * _methodName; ///< string describing the method name wrapped by this object
71 } pyQObjectMethodObject;
72 static PyTypeObject pyQObjectType;
73 static PyTypeObject pyQObjectMethodType;
74
75
QObjectDealloc(pyQObject * self)76 static void QObjectDealloc(pyQObject * self) {
77 Py_XDECREF(self->_TWcontext);
78 ((PyObject*)self)->ob_type->tp_free((PyObject*)self);
79 }
QObjectMethodDealloc(pyQObjectMethodObject * self)80 static void QObjectMethodDealloc(pyQObjectMethodObject * self) {
81 Py_XDECREF(self->_TWcontext);
82 Py_XDECREF(self->_methodName);
83 ((PyObject*)self)->ob_type->tp_free((PyObject*)self);
84 }
85
TWPythonPlugin()86 TWPythonPlugin::TWPythonPlugin()
87 {
88 // Initialize the python interpretor
89 Py_Initialize();
90 }
91
~TWPythonPlugin()92 TWPythonPlugin::~TWPythonPlugin()
93 {
94 // Uninitialize the python interpreter
95 Py_Finalize();
96 }
97
newScript(const QString & fileName)98 TWScript* TWPythonPlugin::newScript(const QString& fileName)
99 {
100 return new PythonScript(this, fileName);
101 }
102
103 #if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(TWPythonPlugin,TWPythonPlugin)104 Q_EXPORT_PLUGIN2(TWPythonPlugin, TWPythonPlugin)
105 #endif
106
107
108 bool PythonScript::execute(TWScriptAPI *tw) const
109 {
110 PyObject * tmp;
111
112 // Load the script
113 QFile scriptFile(m_Filename);
114 if (!scriptFile.open(QIODevice::ReadOnly)) {
115 // handle error
116 return false;
117 }
118 QString contents = m_Codec->toUnicode(scriptFile.readAll());
119 scriptFile.close();
120
121 // Python seems to require Unix style line endings
122 if (contents.contains("\r"))
123 contents.replace(QRegExp("\r\n?"), "\n");
124
125 // Create a separate sub-interpreter for this script
126 PyThreadState* interpreter = Py_NewInterpreter();
127
128 // Register the types
129 if (!registerPythonTypes(tw->GetResult())) {
130 Py_EndInterpreter(interpreter);
131 return false;
132 }
133
134 pyQObject *TW;
135
136 TW = (pyQObject*)QObjectToPython(tw);
137 if (!TW) {
138 tw->SetResult(tr("Could not create TW"));
139 Py_EndInterpreter(interpreter);
140 return false;
141 }
142
143 // Run the script
144 PyObject * globals, * locals;
145 globals = PyDict_New();
146 locals = PyDict_New();
147
148 // Create a dictionary of global variables
149 // without the __builtins__ module, nothing would work!
150 PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
151 PyDict_SetItemString(globals, "TW", (PyObject*)TW);
152
153 PyObject * ret = NULL;
154
155 if (globals && locals)
156 ret = PyRun_String(qPrintable(contents), Py_file_input, globals, locals);
157
158 Py_XDECREF(globals);
159 Py_XDECREF(locals);
160 Py_XDECREF(ret);
161 Py_XDECREF(TW);
162
163 // Check for exceptions
164 if (PyErr_Occurred()) {
165 PyObject * errType, * errValue, * errTraceback;
166 PyErr_Fetch(&errType, &errValue, &errTraceback);
167
168 tmp = PyObject_Str(errValue);
169 QString errString;
170 if (!asQString(tmp, errString)) {
171 Py_XDECREF(tmp);
172 tw->SetResult(tr("Unknown error"));
173 return false;
174 }
175 Py_XDECREF(tmp);
176 tw->SetResult(errString);
177
178 /////////////////////DEBUG
179 // This prints the python error in the usual python way to stdout
180 // Simply comment this block to prevent this behavior
181 Py_XINCREF(errType);
182 Py_XINCREF(errValue);
183 Py_XINCREF(errTraceback);
184 PyErr_Restore(errType, errValue, errTraceback);
185 PyErr_Print();
186 /////////////////////DEBUG
187
188 Py_XDECREF(errType);
189 Py_XDECREF(errValue);
190 Py_XDECREF(errTraceback);
191
192 Py_EndInterpreter(interpreter);
193 return false;
194 }
195
196 // Finish
197 Py_EndInterpreter(interpreter);
198 return true;
199 }
200
registerPythonTypes(QVariant & errMsg) const201 bool PythonScript::registerPythonTypes(QVariant & errMsg) const
202 {
203 // Register the Qobject wrapper
204 pyQObjectType.tp_name = "QObject";
205 pyQObjectType.tp_basicsize = sizeof(pyQObject);
206 pyQObjectType.tp_dealloc = (destructor)QObjectDealloc;
207 pyQObjectType.tp_flags = Py_TPFLAGS_DEFAULT;
208 pyQObjectType.tp_doc = "QObject wrapper";
209 pyQObjectType.tp_getattro = PythonScript::getAttribute;
210 pyQObjectType.tp_setattro = PythonScript::setAttribute;
211
212 if (PyType_Ready(&pyQObjectType) < 0) {
213 errMsg = "Could not register QObject wrapper";
214 return false;
215 }
216
217 // Register the TW method object
218 pyQObjectMethodType.tp_name = "QObjectMethod";
219 pyQObjectMethodType.tp_basicsize = sizeof(pyQObjectMethodObject);
220 pyQObjectMethodType.tp_dealloc = (destructor)QObjectMethodDealloc;
221 pyQObjectMethodType.tp_flags = Py_TPFLAGS_DEFAULT;
222 pyQObjectMethodType.tp_doc = "QObject method wrapper";
223 pyQObjectMethodType.tp_call = PythonScript::callMethod;
224
225
226 if (PyType_Ready(&pyQObjectMethodType) < 0) {
227 errMsg = "Could not register QObject method wrapper";
228 return false;
229 }
230 return true;
231 }
232
233 /*static*/
QObjectToPython(QObject * o)234 PyObject * PythonScript::QObjectToPython(QObject * o)
235 {
236 pyQObject * obj;
237 obj = PyObject_New(pyQObject, &pyQObjectType);
238
239 if (!obj) return NULL;
240
241 obj = (pyQObject*)PyObject_Init((PyObject*)obj, &pyQObjectType);
242 obj->_TWcontext = ENCAPSULATE_C_POINTER(o);
243 return (PyObject*)obj;
244 }
245
246 /*static*/
getAttribute(PyObject * o,PyObject * attr_name)247 PyObject* PythonScript::getAttribute(PyObject * o, PyObject * attr_name)
248 {
249 QObject * obj;
250 QMetaMethod method;
251 QString propName;
252 QVariant result;
253 pyQObjectMethodObject * pyMethod;
254
255 // Get the QObject* we operate on
256 if (!PyObject_TypeCheck(o, &pyQObjectType)) {
257 PyErr_SetString(PyExc_TypeError, qPrintable(tr("getattr: not a valid TW object")));
258 return NULL;
259 }
260 if (!IS_ENCAPSULATED_C_POINTER(((pyQObject*)o)->_TWcontext)) {
261 PyErr_SetString(PyExc_TypeError, qPrintable(tr("getattr: not a valid TW object")));
262 return NULL;
263 }
264 obj = (QObject*)GET_ENCAPSULATED_C_POINTER((PyObject*)(((pyQObject*)o)->_TWcontext));
265
266 if (!asQString(attr_name, propName)) {
267 PyErr_SetString(PyExc_TypeError, qPrintable(tr("getattr: invalid property name")));
268 return NULL;
269 }
270
271 if (propName.length() > 1 && propName.endsWith(QChar('_')))
272 propName.chop(1);
273
274 switch (doGetProperty(obj, propName, result)) {
275 case Property_DoesNotExist:
276 PyErr_Format(PyExc_AttributeError, qPrintable(tr("getattr: object doesn't have property/method %s")), qPrintable(propName));
277 return NULL;
278 case Property_NotReadable:
279 PyErr_Format(PyExc_AttributeError, qPrintable(tr("getattr: property %s is not readable")), qPrintable(propName));
280 return NULL;
281 case Property_Method:
282 pyMethod = PyObject_New(pyQObjectMethodObject, &pyQObjectMethodType);
283 pyMethod = (pyQObjectMethodObject*)PyObject_Init((PyObject*)pyMethod, &pyQObjectMethodType);
284 Py_INCREF(pyMethod);
285 pyMethod->_TWcontext = ENCAPSULATE_C_POINTER(obj);
286 Py_XINCREF(attr_name);
287 pyMethod->_methodName = (PyObject*)attr_name;
288 return (PyObject*)pyMethod;
289 case Property_OK:
290 return PythonScript::VariantToPython(result);
291 default:
292 break;
293 }
294 // we should never reach this point
295 return NULL;
296 }
297
298 /*static*/
setAttribute(PyObject * o,PyObject * attr_name,PyObject * v)299 int PythonScript::setAttribute(PyObject * o, PyObject * attr_name, PyObject * v)
300 {
301 QObject * obj;
302 QString propName;
303 QMetaProperty prop;
304
305 // Get the QObject* we operate on
306 if (!PyObject_TypeCheck(o, &pyQObjectType)) {
307 PyErr_SetString(PyExc_TypeError, qPrintable(tr("setattr: not a valid TW object")));
308 return -1;
309 }
310 if (!IS_ENCAPSULATED_C_POINTER(((pyQObject*)o)->_TWcontext)) {
311 PyErr_SetString(PyExc_TypeError, qPrintable(tr("setattr: not a valid TW object")));
312 return -1;
313 }
314 obj = (QObject*)GET_ENCAPSULATED_C_POINTER((PyObject*)(((pyQObject*)o)->_TWcontext));
315
316 // Get the parameters
317 if (!asQString(attr_name, propName)) {
318 PyErr_SetString(PyExc_TypeError, qPrintable(tr("setattr: invalid property name")));
319 return -1;
320 }
321
322 switch (doSetProperty(obj, propName, PythonScript::PythonToVariant(v))) {
323 case Property_DoesNotExist:
324 PyErr_Format(PyExc_AttributeError, qPrintable(tr("setattr: object doesn't have property %s")), qPrintable(propName));
325 return -1;
326 case Property_NotWritable:
327 PyErr_Format(PyExc_AttributeError, qPrintable(tr("setattr: property %s is not writable")), qPrintable(propName));
328 return -1;
329 case Property_OK:
330 return 0;
331 default:
332 break;
333 }
334 // we should never reach this point
335 return -1;
336 }
337
338 /*static*/
callMethod(PyObject * o,PyObject * pyArgs,PyObject * kw)339 PyObject * PythonScript::callMethod(PyObject * o, PyObject * pyArgs, PyObject * kw)
340 {
341 Q_UNUSED(kw)
342 QObject * obj;
343 QString methodName;
344 QVariantList args;
345 QVariant result;
346 int i;
347
348 // Get the QObject* we operate on
349 obj = (QObject*)GET_ENCAPSULATED_C_POINTER((PyObject*)(((pyQObjectMethodObject*)o)->_TWcontext));
350
351 if (!asQString((PyObject*)(((pyQObjectMethodObject*)o)->_methodName), methodName)) {
352 PyErr_SetString(PyExc_TypeError, qPrintable(tr("call: invalid method name")));
353 return NULL;
354 }
355
356 for (i = 0; i < PyTuple_Size(pyArgs); ++i) {
357 args.append(PythonScript::PythonToVariant(PyTuple_GetItem(pyArgs, i)));
358 }
359 if (methodName.length() > 1 && methodName.endsWith(QChar('_')))
360 methodName.chop(1);
361 switch (doCallMethod(obj, methodName, args, result)) {
362 case Method_OK:
363 return PythonScript::VariantToPython(result);
364 case Method_DoesNotExist:
365 PyErr_Format(PyExc_TypeError, qPrintable(tr("call: the method %s doesn't exist")), qPrintable(methodName));
366 return NULL;
367 case Method_WrongArgs:
368 PyErr_Format(PyExc_TypeError, qPrintable(tr("call: couldn't call %s with the given arguments")), qPrintable(methodName));
369 return NULL;
370 case Method_Failed:
371 PyErr_Format(PyExc_TypeError, qPrintable(tr("call: internal error while executing %s")), qPrintable(methodName));
372 return NULL;
373 default:
374 break;
375 }
376
377 // we should never reach this point
378 return NULL;
379 }
380
381
382 /*static*/
VariantToPython(const QVariant & v)383 PyObject * PythonScript::VariantToPython(const QVariant & v)
384 {
385 int i;
386 QVariantList::const_iterator iList;
387 QVariantList list;
388 #if QT_VERSION >= 0x040500
389 QVariantHash::const_iterator iHash;
390 QVariantHash hash;
391 #endif
392 QVariantMap::const_iterator iMap;
393 QVariantMap map;
394 PyObject * pyList, * pyDict;
395
396 if (v.isNull()) Py_RETURN_NONE;
397
398 switch ((QMetaType::Type)v.type()) {
399 case QVariant::Double:
400 return Py_BuildValue("d", v.toDouble());
401 case QVariant::Bool:
402 if (v.toBool()) Py_RETURN_TRUE;
403 else Py_RETURN_FALSE;
404 case QVariant::Int:
405 return Py_BuildValue("i", v.toInt());
406 case QVariant::LongLong:
407 return Py_BuildValue("L", v.toLongLong());
408 case QVariant::UInt:
409 return Py_BuildValue("I", v.toUInt());
410 case QVariant::ULongLong:
411 return Py_BuildValue("K", v.toULongLong());
412 case QVariant::Char:
413 case QVariant::String:
414 #ifdef Py_UNICODE_WIDE
415 {
416 QVector<uint> tmp = v.toString().toUcs4();
417 return Py_BuildValue("u#", tmp.constData(), tmp.count());
418 }
419 #else
420 return Py_BuildValue("u", v.toString().constData());
421 #endif
422 case QVariant::List:
423 case QVariant::StringList:
424 list = v.toList();
425
426 pyList = PyList_New(list.size());
427 for (i = 0, iList = list.begin(); iList != list.end(); ++iList, ++i) {
428 PyList_SetItem(pyList, i, PythonScript::VariantToPython(*iList));
429 }
430 return pyList;
431 #if QT_VERSION >= 0x040500
432 case QVariant::Hash:
433 hash = v.toHash();
434
435 pyDict = PyDict_New();
436 for (iHash = hash.begin(); iHash != hash.end(); ++iHash) {
437 PyDict_SetItemString(pyDict, qPrintable(iHash.key()), PythonScript::VariantToPython(iHash.value()));
438 }
439 return pyDict;
440 #endif
441 case QVariant::Map:
442 map = v.toMap();
443
444 pyDict = PyDict_New();
445 for (iMap = map.begin(); iMap != map.end(); ++iMap) {
446 PyDict_SetItemString(pyDict, qPrintable(iMap.key()), PythonScript::VariantToPython(iMap.value()));
447 }
448 return pyDict;
449 case QMetaType::QObjectStar:
450 return PythonScript::QObjectToPython(v.value<QObject*>());
451 #if QT_VERSION < 0x050000
452 case QMetaType::QWidgetStar:
453 return PythonScript::QObjectToPython(qobject_cast<QObject*>(v.value<QWidget*>()));
454 #endif
455 default:
456 PyErr_Format(PyExc_TypeError, qPrintable(tr("the type %s is currently not supported")), v.typeName());
457 return NULL;
458 }
459 Py_RETURN_NONE;
460 }
461
462 /*static*/
PythonToVariant(PyObject * o)463 QVariant PythonScript::PythonToVariant(PyObject * o)
464 {
465 QVariantList list;
466 QVariantMap map;
467 PyObject * key, * value;
468 Py_ssize_t i = 0;
469 QString str;
470
471 if (o == Py_None)
472 return QVariant();
473 // in Python 3.x, the PyInt_* were removed in favor of PyLong_*
474 #if PY_MAJOR_VERSION < 3
475 if (PyInt_Check(o)) return QVariant((int)PyInt_AsLong(o));
476 #endif
477 if (PyBool_Check(o)) return QVariant((o == Py_True));
478 if (PyLong_Check(o)) return QVariant((qlonglong)PyLong_AsLong(o));
479 if (PyFloat_Check(o)) return QVariant(PyFloat_AsDouble(o));
480 if (asQString(o, str)) return str;
481 if (PyTuple_Check(o)) {
482 for (i = 0; i < PyTuple_Size(o); ++i) {
483 list.append(PythonToVariant(PyTuple_GetItem(o, i)));
484 }
485 return list;
486 }
487 if (PyList_Check(o)) {
488 for (i = 0; i < PyList_Size(o); ++i) {
489 list.append(PythonToVariant(PyList_GetItem(o, i)));
490 }
491 return list;
492 }
493 if (PyDict_Check(o)) {
494 while (PyDict_Next(o, &i, &key, &value)) {
495 map.insert(PythonScript::PythonToVariant(key).toString(), PythonScript::PythonToVariant(value));
496 }
497 return map;
498 }
499 if (PyObject_TypeCheck(o, &pyQObjectType)) {
500 return QVariant::fromValue((QObject*)GET_ENCAPSULATED_C_POINTER(((pyQObject*)o)->_TWcontext));
501 }
502 // \TODO Complex numbers, byte arrays
503 PyErr_Format(PyExc_TypeError, qPrintable(tr("the python type %s is currently not supported")), o->ob_type->tp_name);
504 return QVariant();
505 }
506
507 /*static*/
asQString(PyObject * obj,QString & str)508 bool PythonScript::asQString(PyObject * obj, QString & str)
509 {
510 PyObject * tmp;
511
512 // Get the parameters
513 // In Python 3.x, the PyString_* were replaced by PyBytes_*
514 #if PY_MAJOR_VERSION < 3
515 if (PyString_Check(obj)) {
516 str = PyString_AsString(obj);
517 return true;
518 }
519 #else
520 if (PyBytes_Check(obj)) {
521 str = PyBytes_AsString(obj);
522 return true;
523 }
524 #endif
525 if (PyUnicode_Check(obj)) {
526 tmp = PyUnicode_AsUTF8String(obj);
527 #if PY_MAJOR_VERSION < 3
528 str = QString::fromUtf8(PyString_AsString(tmp));
529 #else
530 str = QString::fromUtf8(PyBytes_AsString(tmp));
531 #endif
532 Py_XDECREF(tmp);
533 return true;
534 }
535 return false;
536 }
537