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