29#include <kconfigbase.h>
30#include <kconfiggroup.h>
31#include <klocalizedstring.h>
49 dbgScript <<
"Initializing Python plugin for Python" << PY_MAJOR_VERSION <<
"," << PY_MINOR_VERSION;
59 dbgScript <<
"Plugin Directories: " << pluginDirectories;
74 PyObject* plugins = PyDict_New();
100 dbgScript <<
"Going to destroy the Python engine";
115QLibrary* s_pythonLibrary = 0;
117PyThreadState* s_pythonThreadState = 0;
118bool isPythonPathSet =
false;
140 bool result = !PyList_Insert(list, 0,
u);
149 PyObject*
const result =
functionCall(functionName, moduleName, PyTuple_New(0));
156 const char*
const functionName
157 ,
const char*
const moduleName
158 , PyObject*
const arguments
162 errScript <<
"Missing arguments for" << moduleName << functionName;
165 PyObject*
const func =
itemString(functionName, moduleName);
167 errScript <<
"Failed to resolve" << moduleName << functionName;
170 if (!PyCallable_Check(func)) {
171 traceback(QString(
"Not callable %1.%2").arg(moduleName).arg(functionName));
174 PyObject*
const result = PyObject_CallObject(func, arguments);
175 Py_DECREF(arguments);
177 traceback(QString(
"No result from %1.%2").arg(moduleName).arg(functionName));
184 PyObject*
const dict =
moduleDict(moduleName);
185 const bool result = dict && PyDict_DelItemString(dict, item);
187 traceback(QString(
"Could not delete item string %1.%2").arg(moduleName).arg(item));
196 errScript <<
"Could not get item string" << moduleName << item;
203 if (PyObject*
const value = PyDict_GetItemString(dict, item))
205 traceback(QString(
"Could not get item string %1").arg(item));
211 PyObject*
const dict =
moduleDict(moduleName);
212 const bool result = dict && !PyDict_SetItemString(dict, item,
value);
214 traceback(QString(
"Could not set item string %1.%2").arg(moduleName).arg(item));
221 return functionCall(handler,
"krita", Py_BuildValue(
"(O)", module));
235#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
236 if (!s_pythonLibrary) {
238 QFileInfo fi(PYKRITA_PYTHON_LIBRARY);
240 const QString libraryName = fi.completeBaseName();
242 s_pythonLibrary =
new QLibrary(libraryName,
"1.0");
243 s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint);
244 if (!s_pythonLibrary->load()) {
245 dbgScript << QString(
"Could not load %1 -- Reason: %2").arg(s_pythonLibrary->fileName()).arg(s_pythonLibrary->errorString());
246 delete s_pythonLibrary;
250 dbgScript << QString(
"Loaded %1").arg(s_pythonLibrary->fileName());
259QString findKritaPythonLibsPath(
const QString &libdir)
263 QDir rootDir(rootPath);
264 QDir frameworkDir(rootPath +
"Frameworks/Python.framework/Versions/Current");
266 QFileInfoList candidates =
267 frameworkDir.entryInfoList(
QStringList() <<
"lib", QDir::Dirs | QDir::NoDotAndDotDot) +
268 rootDir.entryInfoList(
QStringList() <<
"lib*", QDir::Dirs | QDir::NoDotAndDotDot) +
269 rootDir.entryInfoList(
QStringList() <<
"Frameworks", QDir::Dirs | QDir::NoDotAndDotDot) +
270 rootDir.entryInfoList(
QStringList() <<
"share", QDir::Dirs | QDir::NoDotAndDotDot);
271 Q_FOREACH (
const QFileInfo &entry, candidates) {
272 QDir libDir(entry.absoluteFilePath());
273 if (libDir.cd(libdir)) {
274 return libDir.absolutePath();
278 Q_FOREACH (
const QFileInfo &subEntry, libDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
279 QDir subDir(subEntry.absoluteFilePath());
280 if (subDir.cd(libdir)) {
281 return subDir.absolutePath();
303 bool runningInBundle =
false;
304#elif defined Q_OS_MAC
307 bool runningInBundle = (!qgetenv(
"APPDIR").isNull() &&
310 dbgScript <<
"Python::setPath. Script paths:" << scriptPaths << runningInBundle;
313 constexpr char pathSeparator =
';';
315 constexpr char pathSeparator =
':';
318 QString originalPath;
323 QString pythonLibsPath = findKritaPythonLibsPath(
"krita-python-libs");
324 dbgScript <<
"pythonLibsPath (krita-python-libs)" << pythonLibsPath;
325 if (pythonLibsPath.isEmpty()) {
326 dbgScript <<
"Cannot find krita-python-libs";
329 dbgScript <<
"Found krita-python-libs at" << pythonLibsPath;
330 paths.append(pythonLibsPath);
334 pythonLibsPath = findKritaPythonLibsPath(
"sip");
335 dbgScript <<
"pythonLibsPath (sip)" << pythonLibsPath;
336 if (!pythonLibsPath.isEmpty()) {
337 dbgScript <<
"Found sip at" << pythonLibsPath;
338 paths.append(pythonLibsPath);
345 if (pythonDir.cd(
"python")) {
346 const QString pythonHome = pythonDir.absolutePath();
347 dbgScript <<
"Found bundled Python at" << pythonHome;
354 paths.append(pythonDir.absoluteFilePath(QStringLiteral(
"python%1%2.zip")
355 .arg(PY_MAJOR_VERSION)
356 .arg(PY_MINOR_VERSION)));
357 paths.append(pythonDir.absolutePath());
359 errScript <<
"Bundled Python not found, cannot set Python library paths";
364 QString distToolsPath = findKritaPythonLibsPath(
"site-packages");
365 dbgScript <<
"distToolsPath (site-packages)" << distToolsPath;
366 if (distToolsPath.isEmpty()) {
367 dbgScript <<
"Cannot find site-packages";
370 dbgScript <<
"Found site-packages at" << distToolsPath;
371 paths.append(distToolsPath);
374 if (runningInBundle) {
376 QString
p = QFileInfo(PYKRITA_PYTHON_LIBRARY).fileName();
378 QString
p2 =
p.remove(
"lib").remove(
"m.dy").remove(
".dy");
380 QString
p2 =
p.remove(
"lib").remove(
"m.so").remove(
".so");
383 originalPath = findKritaPythonLibsPath(
p);
386 if (originalPath.isEmpty()) {
388 originalPath = QString::fromWCharArray(Py_GetPath());
389 QString d = QFileInfo(PYKRITA_PYTHON_LIBRARY).absolutePath();
391 paths.append(d +
"/" +
p2 +
"/site-packages");
392#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
393 paths.append(d +
"/" +
p2 +
"/site-packages/PyQt6");
395 paths.append(d +
"/" +
p2 +
"/site-packages/PyQt5");
401 paths.append(originalPath +
"/lib-dynload");
402 paths.append(originalPath +
"/site-packages");
403#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
404 paths.append(originalPath +
"/site-packages/PyQt6");
406 paths.append(originalPath +
"/site-packages/PyQt5");
414 originalPath = QString::fromLocal8Bit(qgetenv(
"PYTHONPATH"));
418 QString joinedPaths = paths.join(pathSeparator);
419 if (!originalPath.isEmpty()) {
420 joinedPaths = joinedPaths + pathSeparator + originalPath;
422 dbgScript <<
"Setting python paths:" << joinedPaths;
423 qputenv(
"PYTHONPATH", joinedPaths.toLocal8Bit());
425 isPythonPathSet =
true;
431 if (Py_IsInitialized()) {
432 warnScript <<
"Python interpreter is already initialized, not initializing again";
434 dbgScript <<
"Initializing Python interpreter";
436 if (!Py_IsInitialized()) {
437 errScript <<
"Could not initialize Python interpreter";
440 s_pythonThreadState = PyGILState_GetThisThreadState();
441 PyEval_ReleaseThread(s_pythonThreadState);
448 if (!Py_IsInitialized()) {
449 warnScript <<
"Python interpreter not initialized, no need to finalize";
452 PyEval_AcquireThread(s_pythonThreadState);
462 if (s_pythonLibrary) {
464 if (s_pythonLibrary->isLoaded()) {
465 s_pythonLibrary->unload();
467 delete s_pythonLibrary;
480 return kritaHandler(moduleName,
"moduleGetConfigPages");
486 PyObject*
const result =
kritaHandler(moduleName,
"moduleGetHelp");
496 PyObject*
const module = moduleImport(moduleName);
498 if (PyObject*
const dictionary = PyModule_GetDict(module))
501 traceback(QString(
"Could not get dict %1").arg(moduleName));
507 PyObject*
const module = PyImport_ImportModule(moduleName);
511 traceback(QString(
"Could not import %1").arg(moduleName));
519 if (!PyErr_Occurred())
527 PyErr_Fetch(&exc_typ, &exc_val, &exc_tb);
528 PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb);
532 m_traceback =
"Traceback (most recent call last):\n";
533 PyObject*
const arguments = PyTuple_New(1);
534 PyTuple_SetItem(arguments, 0, exc_tb);
535 PyObject*
const result =
functionCall(
"format_tb",
"traceback", arguments);
537 for (
int i = 0, j = PyList_Size(result); i < j; i++) {
538 PyObject*
const tt = PyList_GetItem(result, i);
539 PyObject*
const t = Py_BuildValue(
"(O)", tt);
541 if (!PyArg_ParseTuple(t,
"s", &buffer))
552 PyObject*
const temp = PyObject_GetAttrString(exc_typ,
"__name__");
561 PyObject*
const temp = PyObject_Str(exc_val);
571 Q_FOREACH(
const QString &s, l) {
579 return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND,
586 if (!PyUnicode_Check(
string))
589 const int unichars = PyUnicode_GetLength(
string);
590 if (0 != PyUnicode_READY(
string))
593 switch (PyUnicode_KIND(
string)) {
594 case PyUnicode_1BYTE_KIND:
595 return QString::fromLatin1((
const char*)PyUnicode_1BYTE_DATA(
string), unichars);
596 case PyUnicode_2BYTE_KIND:
597 return QString::fromUtf16(PyUnicode_2BYTE_DATA(
string), unichars);
598 case PyUnicode_4BYTE_KIND:
599 return QString::fromUcs4(PyUnicode_4BYTE_DATA(
string), unichars);
608 return PyUnicode_Check(
string);
613 PyObject* sys_path =
itemString(
"path",
"sys");
619 PyObject* sys_path =
itemString(
"path",
"sys");
628 , std::back_inserter(reversed_paths)
631 Q_FOREACH(
const QString & path, reversed_paths)
640 Q_ASSERT(
"Dir entry expected to be valid" && sys_path);
qreal length(const QPointF &vec)
float value(const T *src, size_t ch)
QList< QString > QStringList
static QString getApplicationRoot()
static QStringList findDirs(const QString &type)
QString moduleHelp(const char *moduleName)
static PyObject * unicode(const QString &string)
Convert a QString to a Python unicode object.
bool itemStringSet(const char *item, PyObject *value, const char *moduleName=PYKRITA_ENGINE)
static bool isUnicode(PyObject *string)
Test if a Python object is compatible with a QString.
bool prependStringToList(PyObject *list, const QString &value)
Prepend a QString to a list as a Python unicode object.
static void libraryUnload()
PyObject * moduleDict(const char *moduleName=PYKRITA_ENGINE)
bool itemStringDel(const char *item, const char *moduleName=PYKRITA_ENGINE)
static bool setPath(const QStringList &scriptPaths)
bool functionCall(const char *functionName, const char *moduleName=PYKRITA_ENGINE)
PyObject * moduleConfigPages(const char *moduleName)
static const char * PYKRITA_ENGINE
PyObject * moduleImport(const char *moduleName)
static void ensureInitialized()
PyObject * moduleActions(const char *moduleName)
bool prependPythonPaths(const QString &path)
void traceback(const QString &description)
PyObject * kritaHandler(const char *moduleName, const char *handler)
static bool libraryLoad()
PyObject * itemString(const char *item, const char *moduleName=PYKRITA_ENGINE)
static void maybeFinalize()
QString lastTraceback(void) const
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
static QScopedPointer< PythonPluginManager > pluginManagerInstance
@ INIT_CANNOT_LOAD_PYKRITA_MODULE
@ INIT_CANNOT_LOAD_PYTHON_LIBRARY
@ INIT_CANNOT_SET_PYTHON_PATHS
static InitResult initStatus
PythonPluginManager * pluginManager()