1 #ifdef CUTTER_ENABLE_PYTHON
2
3 #include <cassert>
4
5 #include "PythonAPI.h"
6 #include "PythonManager.h"
7 #include "Cutter.h"
8
9 #include <QDebug>
10 #include <QFile>
11 #include <QDebug>
12 #include <QCoreApplication>
13 #include <QDir>
14
15 #ifdef CUTTER_ENABLE_PYTHON_BINDINGS
16 #include <shiboken.h>
17 #include <pyside.h>
18 #include <signalmanager.h>
19 #endif
20
21 #include "QtResImporter.h"
22
23 static PythonManager *uniqueInstance = nullptr;
24
getInstance()25 PythonManager *PythonManager::getInstance()
26 {
27 if (!uniqueInstance) {
28 uniqueInstance = new PythonManager();
29 }
30 return uniqueInstance;
31 }
32
PythonManager()33 PythonManager::PythonManager()
34 {
35 }
36
~PythonManager()37 PythonManager::~PythonManager()
38 {
39 }
40
initPythonHome()41 void PythonManager::initPythonHome()
42 {
43 #if defined(APPIMAGE) || defined(MACOS_PYTHON_FRAMEWORK_BUNDLED)
44 if (customPythonHome.isNull()) {
45 auto pythonHomeDir = QDir(QCoreApplication::applicationDirPath());
46 # ifdef APPIMAGE
47 // Executable is in appdir/bin
48 pythonHomeDir.cdUp();
49 qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << " for AppImage.";
50 # else // MACOS_PYTHON_FRAMEWORK_BUNDLED
51 // @executable_path/../Frameworks/Python.framework/Versions/Current
52 pythonHomeDir.cd("../Frameworks/Python.framework/Versions/Current");
53 qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() <<
54 " for macOS Application Bundle.";
55 # endif
56 customPythonHome = pythonHomeDir.absolutePath();
57 }
58 #endif
59
60 if (!customPythonHome.isNull()) {
61 qInfo() << "PYTHONHOME =" << customPythonHome;
62 pythonHome = Py_DecodeLocale(customPythonHome.toLocal8Bit().constData(), nullptr);
63 Py_SetPythonHome(pythonHome);
64 }
65 }
66
67 #ifdef CUTTER_ENABLE_PYTHON_BINDINGS
68 extern "C" PyObject *PyInit_CutterBindings();
69 #endif
70
initialize()71 void PythonManager::initialize()
72 {
73 initPythonHome();
74
75 PyImport_AppendInittab("_cutter", &PyInit_api);
76 PyImport_AppendInittab("_qtres", &PyInit_qtres);
77 #ifdef CUTTER_ENABLE_PYTHON_BINDINGS
78 PyImport_AppendInittab("CutterBindings", &PyInit_CutterBindings);
79 #endif
80 Py_Initialize();
81 PyEval_InitThreads();
82 pyThreadStateCounter = 1; // we have the thread now => 1
83
84 RegQtResImporter();
85
86 saveThread();
87 }
88
89 #ifdef CUTTER_ENABLE_PYTHON_BINDINGS
pySideDestructionVisitor(SbkObject * pyObj,void * data)90 static void pySideDestructionVisitor(SbkObject* pyObj, void* data)
91 {
92 void **realData = reinterpret_cast<void**>(data);
93 auto pyQApp = reinterpret_cast<SbkObject*>(realData[0]);
94 auto pyQObjectType = reinterpret_cast<PyTypeObject*>(realData[1]);
95
96 if (pyObj == pyQApp || !PyObject_TypeCheck(pyObj, pyQObjectType)) {
97 return;
98 }
99 if (!Shiboken::Object::hasOwnership(pyObj) || !Shiboken::Object::isValid(pyObj, false)) {
100 return;
101 }
102
103 const char *reprStr = "";
104 PyObject *repr = PyObject_Repr(reinterpret_cast<PyObject *>(pyObj));
105 PyObject *reprBytes;
106 if (repr) {
107 reprBytes = PyUnicode_AsUTF8String(repr);
108 reprStr = PyBytes_AsString(reprBytes);
109 }
110 qWarning() << "Warning: QObject from Python remaining (leaked from plugin?):" << reprStr;
111 if (repr) {
112 Py_DecRef(reprBytes);
113 Py_DecRef(repr);
114 }
115
116 Shiboken::Object::setValidCpp(pyObj, false);
117 Py_BEGIN_ALLOW_THREADS
118 Shiboken::callCppDestructor<QObject>(Shiboken::Object::cppPointer(pyObj, pyQObjectType));
119 Py_END_ALLOW_THREADS
120 };
121 #endif
122
shutdown()123 void PythonManager::shutdown()
124 {
125 emit willShutDown();
126
127 restoreThread();
128
129 #ifdef CUTTER_ENABLE_PYTHON_BINDINGS
130 // This is necessary to prevent a segfault when the CutterCore instance is deleted after the Shiboken::BindingManager
131 Core()->setProperty("_PySideInvalidatePtr", QVariant());
132
133 // see PySide::destroyQCoreApplication()
134 PySide::SignalManager::instance().clear();
135 Shiboken::BindingManager& bm = Shiboken::BindingManager::instance();
136 SbkObject* pyQApp = bm.retrieveWrapper(QCoreApplication::instance());
137 PyTypeObject* pyQObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*");
138 void* data[2] = {pyQApp, pyQObjectType};
139 bm.visitAllPyObjects(&pySideDestructionVisitor, &data);
140
141 PySide::runCleanupFunctions();
142 #endif
143
144 if (pythonHome) {
145 PyMem_Free(pythonHome);
146 }
147
148 Py_Finalize();
149 }
150
addPythonPath(char * path)151 void PythonManager::addPythonPath(char *path) {
152 restoreThread();
153
154 PyObject *sysModule = PyImport_ImportModule("sys");
155 if (!sysModule) {
156 return;
157 }
158 PyObject *pythonPath = PyObject_GetAttrString(sysModule, "path");
159 if (!pythonPath) {
160 return;
161 }
162 PyObject *append = PyObject_GetAttrString(pythonPath, "append");
163 if (!append) {
164 return;
165 }
166 PyEval_CallFunction(append, "(s)", path);
167
168 saveThread();
169 }
170
restoreThread()171 void PythonManager::restoreThread()
172 {
173 pyThreadStateCounter++;
174 if (pyThreadStateCounter == 1 && pyThreadState) {
175 PyEval_RestoreThread(pyThreadState);
176 }
177 }
178
saveThread()179 void PythonManager::saveThread()
180 {
181 pyThreadStateCounter--;
182 assert(pyThreadStateCounter >= 0);
183 if (pyThreadStateCounter == 0) {
184 pyThreadState = PyEval_SaveThread();
185 }
186 }
187
188 #endif
189