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