1 /***************************************************************************
2     qgspythonutils.cpp - routines for interfacing Python
3     ---------------------
4     begin                : October 2006
5     copyright            : (C) 2006 by Martin Dobias
6     email                : wonder.sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 // python should be first include
17 // otherwise issues some warnings
18 #ifdef _MSC_VER
19 #ifdef _DEBUG
20 #undef _DEBUG
21 #endif
22 #endif
23 #include <Python.h>
24 
25 #include "qgis.h"
26 #include "qgspythonutilsimpl.h"
27 
28 #include "qgsapplication.h"
29 #include "qgslogger.h"
30 #include "qgsmessageoutput.h"
31 #include "qgssettings.h"
32 
33 #include <QStringList>
34 #include <QDir>
35 #include <QStandardPaths>
36 #include <QDebug>
37 
38 PyThreadState *_mainState = nullptr;
39 
QgsPythonUtilsImpl()40 QgsPythonUtilsImpl::QgsPythonUtilsImpl()
41 {
42 }
43 
~QgsPythonUtilsImpl()44 QgsPythonUtilsImpl::~QgsPythonUtilsImpl()
45 {
46 #if SIP_VERSION >= 0x40e06
47   exitPython();
48 #endif
49 }
50 
checkSystemImports()51 bool QgsPythonUtilsImpl::checkSystemImports()
52 {
53   runString( QStringLiteral( "import sys" ) ); // import sys module (for display / exception hooks)
54   runString( QStringLiteral( "import os" ) ); // import os module (for user paths)
55 
56   // support for PYTHONSTARTUP-like environment variable: PYQGIS_STARTUP
57   // (unlike PYTHONHOME and PYTHONPATH, PYTHONSTARTUP is not supported for embedded interpreter by default)
58   // this is different than user's 'startup.py' (below), since it is loaded just after Py_Initialize
59   // it is very useful for cleaning sys.path, which may have undesirable paths, or for
60   // isolating/loading the initial environ without requiring a virt env, e.g. homebrew or MacPorts installs on Mac
61   runString( QStringLiteral( "pyqgstart = os.getenv('PYQGIS_STARTUP')\n" ) );
62   runString( QStringLiteral( "if pyqgstart is not None and os.path.exists(pyqgstart):\n    with open(pyqgstart) as f:\n        exec(f.read())\n" ) );
63   runString( QStringLiteral( "if pyqgstart is not None and os.path.exists(os.path.join('%1', pyqgstart)):\n    with open(os.path.join('%1', pyqgstart)) as f:\n        exec(f.read())\n" ).arg( pythonPath() ) );
64 
65 #ifdef Q_OS_WIN
66   runString( "oldhome=None" );
67   runString( "if 'HOME' in os.environ: oldhome=os.environ['HOME']\n" );
68   runString( "os.environ['HOME']=os.environ['USERPROFILE']\n" );
69 #endif
70 
71   // construct a list of plugin paths
72   // plugin dirs passed in QGIS_PLUGINPATH env. variable have highest priority (usually empty)
73   // locally installed plugins have priority over the system plugins
74   // use os.path.expanduser to support usernames with special characters (see #2512)
75   QStringList pluginpaths;
76   Q_FOREACH ( QString p, extraPluginsPaths() )
77   {
78     if ( !QDir( p ).exists() )
79     {
80       QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
81       msg->setTitle( QObject::tr( "Python error" ) );
82       msg->setMessage( QObject::tr( "The extra plugin path '%1' does not exist!" ).arg( p ), QgsMessageOutput::MessageText );
83       msg->showMessage();
84     }
85 #ifdef Q_OS_WIN
86     p.replace( '\\', "\\\\" );
87 #endif
88     // we store here paths in unicode strings
89     // the str constant will contain utf8 code (through runString)
90     // so we call '...'.decode('utf-8') to make a unicode string
91     pluginpaths << '"' + p + '"';
92   }
93   pluginpaths << homePluginsPath();
94   pluginpaths << '"' + pluginsPath() + '"';
95 
96   // expect that bindings are installed locally, so add the path to modules
97   // also add path to plugins
98   QStringList newpaths;
99   newpaths << '"' + pythonPath() + '"';
100   newpaths << homePythonPath();
101   newpaths << pluginpaths;
102   runString( "sys.path = [" + newpaths.join( QLatin1Char( ',' ) ) + "] + sys.path" );
103 
104   // import SIP
105   if ( !runString( QStringLiteral( "from qgis.PyQt import sip" ),
106                    QObject::tr( "Couldn't load SIP module." ) + '\n' + QObject::tr( "Python support will be disabled." ) ) )
107   {
108     return false;
109   }
110 
111   // set PyQt api versions
112   QStringList apiV2classes;
113   apiV2classes << QStringLiteral( "QDate" ) << QStringLiteral( "QDateTime" ) << QStringLiteral( "QString" ) << QStringLiteral( "QTextStream" ) << QStringLiteral( "QTime" ) << QStringLiteral( "QUrl" ) << QStringLiteral( "QVariant" );
114   Q_FOREACH ( const QString &clsName, apiV2classes )
115   {
116     if ( !runString( QStringLiteral( "sip.setapi('%1', 2)" ).arg( clsName ),
117                      QObject::tr( "Couldn't set SIP API versions." ) + '\n' + QObject::tr( "Python support will be disabled." ) ) )
118     {
119       return false;
120     }
121   }
122   // import Qt bindings
123   if ( !runString( QStringLiteral( "from PyQt5 import QtCore, QtGui" ),
124                    QObject::tr( "Couldn't load PyQt." ) + '\n' + QObject::tr( "Python support will be disabled." ) ) )
125   {
126     return false;
127   }
128 
129   // import QGIS bindings
130   QString error_msg = QObject::tr( "Couldn't load PyQGIS." ) + '\n' + QObject::tr( "Python support will be disabled." );
131   if ( !runString( QStringLiteral( "from qgis.core import *" ), error_msg ) || !runString( QStringLiteral( "from qgis.gui import *" ), error_msg ) )
132   {
133     return false;
134   }
135 
136   // import QGIS utils
137   error_msg = QObject::tr( "Couldn't load QGIS utils." ) + '\n' + QObject::tr( "Python support will be disabled." );
138   if ( !runString( QStringLiteral( "import qgis.utils" ), error_msg ) )
139   {
140     return false;
141   }
142 
143   // tell the utils script where to look for the plugins
144   runString( QStringLiteral( "qgis.utils.plugin_paths = [%1]" ).arg( pluginpaths.join( ',' ) ) );
145   runString( QStringLiteral( "qgis.utils.sys_plugin_path = \"%1\"" ).arg( pluginsPath() ) );
146   runString( QStringLiteral( "qgis.utils.home_plugin_path = %1" ).arg( homePluginsPath() ) ); // note - homePluginsPath() returns a python expression, not a string literal
147 
148 #ifdef Q_OS_WIN
149   runString( "if oldhome: os.environ['HOME']=oldhome\n" );
150 #endif
151 
152   return true;
153 }
154 
init()155 void QgsPythonUtilsImpl::init()
156 {
157 #if defined(PY_MAJOR_VERSION) && defined(PY_MINOR_VERSION) && ((PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 8) || PY_MAJOR_VERSION > 3)
158   PyStatus status;
159   PyPreConfig preconfig;
160   PyPreConfig_InitPythonConfig( &preconfig );
161 
162   preconfig.utf8_mode = 1;
163 
164   status = Py_PreInitialize( &preconfig );
165   if ( PyStatus_Exception( status ) )
166   {
167     Py_ExitStatusException( status );
168   }
169 #endif
170 
171   // initialize python
172   Py_Initialize();
173   // initialize threading AND acquire GIL
174   PyEval_InitThreads();
175 
176   mPythonEnabled = true;
177 
178   mMainModule = PyImport_AddModule( "__main__" ); // borrowed reference
179   mMainDict = PyModule_GetDict( mMainModule ); // borrowed reference
180 }
181 
finish()182 void QgsPythonUtilsImpl::finish()
183 {
184   // release GIL!
185   // Later on, we acquire GIL just before doing some Python calls and
186   // release GIL again when the work with Python API is done.
187   // (i.e. there must be PyGILState_Ensure + PyGILState_Release pair
188   // around any calls to Python API, otherwise we may segfault!)
189   _mainState = PyEval_SaveThread();
190 }
191 
checkQgisUser()192 bool QgsPythonUtilsImpl::checkQgisUser()
193 {
194   // import QGIS user
195   QString error_msg = QObject::tr( "Couldn't load qgis.user." ) + '\n' + QObject::tr( "Python support will be disabled." );
196   if ( !runString( QStringLiteral( "import qgis.user" ), error_msg ) )
197   {
198     // Should we really bail because of this?!
199     return false;
200   }
201   return true;
202 }
203 
doCustomImports()204 void QgsPythonUtilsImpl::doCustomImports()
205 {
206   QStringList startupPaths = QStandardPaths::locateAll( QStandardPaths::AppDataLocation, QStringLiteral( "startup.py" ) );
207   if ( startupPaths.isEmpty() )
208   {
209     return;
210   }
211 
212   runString( QStringLiteral( "import importlib.util" ) );
213 
214   QStringList::const_iterator iter = startupPaths.constBegin();
215   for ( ; iter != startupPaths.constEnd(); ++iter )
216   {
217     runString( QStringLiteral( "spec = importlib.util.spec_from_file_location('startup','%1')" ).arg( *iter ) );
218     runString( QStringLiteral( "module = importlib.util.module_from_spec(spec)" ) );
219     runString( QStringLiteral( "spec.loader.exec_module(module)" ) );
220   }
221 }
222 
initPython(QgisInterface * interface,const bool installErrorHook)223 void QgsPythonUtilsImpl::initPython( QgisInterface *interface, const bool installErrorHook )
224 {
225   init();
226   if ( !checkSystemImports() )
227   {
228     exitPython();
229     return;
230   }
231 
232   if ( interface )
233   {
234     // initialize 'iface' object
235     runString( QStringLiteral( "qgis.utils.initInterface(%1)" ).arg( reinterpret_cast< quint64 >( interface ) ) );
236   }
237 
238   if ( !checkQgisUser() )
239   {
240     exitPython();
241     return;
242   }
243   doCustomImports();
244   if ( installErrorHook )
245     QgsPythonUtilsImpl::installErrorHook();
246   finish();
247 }
248 
249 
250 #ifdef HAVE_SERVER_PYTHON_PLUGINS
initServerPython(QgsServerInterface * interface)251 void QgsPythonUtilsImpl::initServerPython( QgsServerInterface *interface )
252 {
253   init();
254   if ( !checkSystemImports() )
255   {
256     exitPython();
257     return;
258   }
259 
260   // This is the main difference with initInterface() for desktop plugins
261   // import QGIS Server bindings
262   QString error_msg = QObject::tr( "Couldn't load PyQGIS Server." ) + '\n' + QObject::tr( "Python support will be disabled." );
263   if ( !runString( QStringLiteral( "from qgis.server import *" ), error_msg ) )
264   {
265     return;
266   }
267 
268   // This is the other main difference with initInterface() for desktop plugins
269   runString( QStringLiteral( "qgis.utils.initServerInterface(%1)" ).arg( reinterpret_cast< quint64 >( interface ) ) );
270 
271   doCustomImports();
272   finish();
273 }
274 
startServerPlugin(QString packageName)275 bool QgsPythonUtilsImpl::startServerPlugin( QString packageName )
276 {
277   QString output;
278   evalString( QStringLiteral( "qgis.utils.startServerPlugin('%1')" ).arg( packageName ), output );
279   return ( output == QLatin1String( "True" ) );
280 }
281 
282 #endif // End HAVE_SERVER_PYTHON_PLUGINS
283 
exitPython()284 void QgsPythonUtilsImpl::exitPython()
285 {
286   if ( mErrorHookInstalled )
287     uninstallErrorHook();
288   // causes segfault!
289   //Py_Finalize();
290   mMainModule = nullptr;
291   mMainDict = nullptr;
292   mPythonEnabled = false;
293 }
294 
295 
isEnabled()296 bool QgsPythonUtilsImpl::isEnabled()
297 {
298   return mPythonEnabled;
299 }
300 
installErrorHook()301 void QgsPythonUtilsImpl::installErrorHook()
302 {
303   runString( QStringLiteral( "qgis.utils.installErrorHook()" ) );
304   mErrorHookInstalled = true;
305 }
306 
uninstallErrorHook()307 void QgsPythonUtilsImpl::uninstallErrorHook()
308 {
309   runString( QStringLiteral( "qgis.utils.uninstallErrorHook()" ) );
310 }
311 
runStringUnsafe(const QString & command,bool single)312 QString QgsPythonUtilsImpl::runStringUnsafe( const QString &command, bool single )
313 {
314   // acquire global interpreter lock to ensure we are in a consistent state
315   PyGILState_STATE gstate;
316   gstate = PyGILState_Ensure();
317   QString ret;
318 
319   // TODO: convert special characters from unicode strings u"…" to \uXXXX
320   // so that they're not mangled to utf-8
321   // (non-unicode strings can be mangled)
322   PyObject *obj = PyRun_String( command.toUtf8().constData(), single ? Py_single_input : Py_file_input, mMainDict, mMainDict );
323   PyObject *errobj = PyErr_Occurred();
324   if ( nullptr != errobj )
325   {
326     ret = getTraceback();
327   }
328   Py_XDECREF( obj );
329 
330   // we are done calling python API, release global interpreter lock
331   PyGILState_Release( gstate );
332 
333   return ret;
334 }
335 
runString(const QString & command,QString msgOnError,bool single)336 bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError, bool single )
337 {
338   bool res = true;
339   QString traceback = runStringUnsafe( command, single );
340   if ( traceback.isEmpty() )
341     return true;
342   else
343     res = false;
344 
345   if ( msgOnError.isEmpty() )
346   {
347     // use some default message if custom hasn't been specified
348     msgOnError = QObject::tr( "An error occurred during execution of following code:" ) + "\n<tt>" + command + "</tt>";
349   }
350 
351   // TODO: use python implementation
352 
353   QString path, version;
354   evalString( QStringLiteral( "str(sys.path)" ), path );
355   evalString( QStringLiteral( "sys.version" ), version );
356 
357   QString str = "<font color=\"red\">" + msgOnError + "</font><br><pre>\n" + traceback + "\n</pre>"
358                 + QObject::tr( "Python version:" ) + "<br>" + version + "<br><br>"
359                 + QObject::tr( "QGIS version:" ) + "<br>" + QStringLiteral( "%1 '%2', %3" ).arg( Qgis::version(), Qgis::releaseName(), Qgis::devVersion() ) + "<br><br>"
360                 + QObject::tr( "Python path:" ) + "<br>" + path;
361   str.replace( '\n', QLatin1String( "<br>" ) ).replace( QLatin1String( "  " ), QLatin1String( "&nbsp; " ) );
362 
363   qDebug() << str;
364   QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
365   msg->setTitle( QObject::tr( "Python error" ) );
366   msg->setMessage( str, QgsMessageOutput::MessageHtml );
367   msg->showMessage();
368 
369   return res;
370 }
371 
372 
getTraceback()373 QString QgsPythonUtilsImpl::getTraceback()
374 {
375 #define TRACEBACK_FETCH_ERROR(what) {errMsg = what; goto done;}
376 
377   QString errMsg;
378   QString result;
379 
380   PyObject *modStringIO = nullptr;
381   PyObject *modTB = nullptr;
382   PyObject *obStringIO = nullptr;
383   PyObject *obResult = nullptr;
384 
385   PyObject *type = nullptr, *value = nullptr, *traceback = nullptr;
386 
387   PyErr_Fetch( &type, &value, &traceback );
388   PyErr_NormalizeException( &type, &value, &traceback );
389 
390   const char *iomod = "io";
391 
392   modStringIO = PyImport_ImportModule( iomod );
393   if ( !modStringIO )
394     TRACEBACK_FETCH_ERROR( QStringLiteral( "can't import %1" ).arg( iomod ) );
395 
396   obStringIO = PyObject_CallMethod( modStringIO, reinterpret_cast< const char * >( "StringIO" ), nullptr );
397 
398   /* Construct a cStringIO object */
399   if ( !obStringIO )
400     TRACEBACK_FETCH_ERROR( QStringLiteral( "cStringIO.StringIO() failed" ) );
401 
402   modTB = PyImport_ImportModule( "traceback" );
403   if ( !modTB )
404     TRACEBACK_FETCH_ERROR( QStringLiteral( "can't import traceback" ) );
405 
406   obResult = PyObject_CallMethod( modTB,  reinterpret_cast< const char * >( "print_exception" ),
407                                   reinterpret_cast< const char * >( "OOOOO" ),
408                                   type, value ? value : Py_None,
409                                   traceback ? traceback : Py_None,
410                                   Py_None,
411                                   obStringIO );
412 
413   if ( !obResult )
414     TRACEBACK_FETCH_ERROR( QStringLiteral( "traceback.print_exception() failed" ) );
415 
416   Py_DECREF( obResult );
417 
418   obResult = PyObject_CallMethod( obStringIO,  reinterpret_cast< const char * >( "getvalue" ), nullptr );
419   if ( !obResult )
420     TRACEBACK_FETCH_ERROR( QStringLiteral( "getvalue() failed." ) );
421 
422   /* And it should be a string all ready to go - duplicate it. */
423   if ( !PyUnicode_Check( obResult ) )
424     TRACEBACK_FETCH_ERROR( QStringLiteral( "getvalue() did not return a string" ) );
425 
426   result = QString::fromUtf8( PyUnicode_AsUTF8( obResult ) );
427 
428 done:
429 
430   // All finished - first see if we encountered an error
431   if ( result.isEmpty() && !errMsg.isEmpty() )
432   {
433     result = errMsg;
434   }
435 
436   Py_XDECREF( modStringIO );
437   Py_XDECREF( modTB );
438   Py_XDECREF( obStringIO );
439   Py_XDECREF( obResult );
440   Py_XDECREF( value );
441   Py_XDECREF( traceback );
442   Py_XDECREF( type );
443 
444   return result;
445 }
446 
getTypeAsString(PyObject * obj)447 QString QgsPythonUtilsImpl::getTypeAsString( PyObject *obj )
448 {
449   if ( !obj )
450     return QString();
451 
452   if ( PyType_Check( obj ) )
453   {
454     QgsDebugMsg( QStringLiteral( "got type" ) );
455     return QString( ( ( PyTypeObject * )obj )->tp_name );
456   }
457   else
458   {
459     QgsDebugMsg( QStringLiteral( "got object" ) );
460     return PyObjectToQString( obj );
461   }
462 }
463 
getError(QString & errorClassName,QString & errorText)464 bool QgsPythonUtilsImpl::getError( QString &errorClassName, QString &errorText )
465 {
466   // acquire global interpreter lock to ensure we are in a consistent state
467   PyGILState_STATE gstate;
468   gstate = PyGILState_Ensure();
469 
470   if ( !PyErr_Occurred() )
471   {
472     PyGILState_Release( gstate );
473     return false;
474   }
475 
476   PyObject *err_type = nullptr;
477   PyObject *err_value = nullptr;
478   PyObject *err_tb = nullptr;
479 
480   // get the exception information and clear error
481   PyErr_Fetch( &err_type, &err_value, &err_tb );
482 
483   // get exception's class name
484   errorClassName = getTypeAsString( err_type );
485 
486   // get exception's text
487   if ( nullptr != err_value && err_value != Py_None )
488   {
489     errorText = PyObjectToQString( err_value );
490   }
491   else
492     errorText.clear();
493 
494   // cleanup
495   Py_XDECREF( err_type );
496   Py_XDECREF( err_value );
497   Py_XDECREF( err_tb );
498 
499   // we are done calling python API, release global interpreter lock
500   PyGILState_Release( gstate );
501 
502   return true;
503 }
504 
505 
PyObjectToQString(PyObject * obj)506 QString QgsPythonUtilsImpl::PyObjectToQString( PyObject *obj )
507 {
508   QString result;
509 
510   // is it None?
511   if ( obj == Py_None )
512   {
513     return QString();
514   }
515 
516   // check whether the object is already a unicode string
517   if ( PyUnicode_Check( obj ) )
518   {
519     result = QString::fromUtf8( PyUnicode_AsUTF8( obj ) );
520     return result;
521   }
522 
523   // if conversion to Unicode failed, try to convert it to classic string, i.e. str(obj)
524   PyObject *obj_str = PyObject_Str( obj ); // new reference
525   if ( obj_str )
526   {
527     result = QString::fromUtf8( PyUnicode_AsUTF8( obj_str ) );
528     Py_XDECREF( obj_str );
529     return result;
530   }
531 
532   // some problem with conversion to Unicode string
533   QgsDebugMsg( QStringLiteral( "unable to convert PyObject to a QString!" ) );
534   return QStringLiteral( "(qgis error)" );
535 }
536 
537 
evalString(const QString & command,QString & result)538 bool QgsPythonUtilsImpl::evalString( const QString &command, QString &result )
539 {
540   // acquire global interpreter lock to ensure we are in a consistent state
541   PyGILState_STATE gstate;
542   gstate = PyGILState_Ensure();
543 
544   PyObject *res = PyRun_String( command.toUtf8().constData(), Py_eval_input, mMainDict, mMainDict );
545   bool success = nullptr != res;
546 
547   // TODO: error handling
548 
549   if ( success )
550     result = PyObjectToQString( res );
551 
552   Py_XDECREF( res );
553 
554   // we are done calling python API, release global interpreter lock
555   PyGILState_Release( gstate );
556 
557   return success;
558 }
559 
pythonPath() const560 QString QgsPythonUtilsImpl::pythonPath() const
561 {
562   if ( QgsApplication::isRunningFromBuildDir() )
563     return QgsApplication::buildOutputPath() + QStringLiteral( "/python" );
564   else
565     return QgsApplication::pkgDataPath() + QStringLiteral( "/python" );
566 }
567 
pluginsPath() const568 QString QgsPythonUtilsImpl::pluginsPath() const
569 {
570   return pythonPath() + QStringLiteral( "/plugins" );
571 }
572 
homePythonPath() const573 QString QgsPythonUtilsImpl::homePythonPath() const
574 {
575   QString settingsDir = QgsApplication::qgisSettingsDirPath();
576   if ( QDir::cleanPath( settingsDir ) == QDir::homePath() + QStringLiteral( "/.qgis3" ) )
577   {
578     return QStringLiteral( "\"%1/.qgis3/python\"" ).arg( QDir::homePath() );
579   }
580   else
581   {
582     return QStringLiteral( "\"" ) + settingsDir.replace( '\\', QLatin1String( "\\\\" ) ) + QStringLiteral( "python\"" );
583   }
584 }
585 
homePluginsPath() const586 QString QgsPythonUtilsImpl::homePluginsPath() const
587 {
588   return homePythonPath() + QStringLiteral( " + \"/plugins\"" );
589 }
590 
extraPluginsPaths() const591 QStringList QgsPythonUtilsImpl::extraPluginsPaths() const
592 {
593   const char *cpaths = getenv( "QGIS_PLUGINPATH" );
594   if ( !cpaths )
595     return QStringList();
596 
597   QString paths = QString::fromLocal8Bit( cpaths );
598 #ifndef Q_OS_WIN
599   if ( paths.contains( ':' ) )
600     return paths.split( ':', QString::SkipEmptyParts );
601 #endif
602   if ( paths.contains( ';' ) )
603     return paths.split( ';', QString::SkipEmptyParts );
604   else
605     return QStringList( paths );
606 }
607 
608 
pluginList()609 QStringList QgsPythonUtilsImpl::pluginList()
610 {
611   runString( QStringLiteral( "qgis.utils.updateAvailablePlugins()" ) );
612 
613   QString output;
614   evalString( QStringLiteral( "'\\n'.join(qgis.utils.available_plugins)" ), output );
615   return output.split( QChar( '\n' ), QString::SkipEmptyParts );
616 }
617 
getPluginMetadata(const QString & pluginName,const QString & function)618 QString QgsPythonUtilsImpl::getPluginMetadata( const QString &pluginName, const QString &function )
619 {
620   QString res;
621   QString str = QStringLiteral( "qgis.utils.pluginMetadata('%1', '%2')" ).arg( pluginName, function );
622   evalString( str, res );
623   //QgsDebugMsg("metadata "+pluginName+" - '"+function+"' = "+res);
624   return res;
625 }
626 
pluginHasProcessingProvider(const QString & pluginName)627 bool QgsPythonUtilsImpl::pluginHasProcessingProvider( const QString &pluginName )
628 {
629   return getPluginMetadata( pluginName, QStringLiteral( "hasProcessingProvider" ) ).compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0;
630 }
631 
loadPlugin(const QString & packageName)632 bool QgsPythonUtilsImpl::loadPlugin( const QString &packageName )
633 {
634   QString output;
635   evalString( QStringLiteral( "qgis.utils.loadPlugin('%1')" ).arg( packageName ), output );
636   return ( output == QLatin1String( "True" ) );
637 }
638 
startPlugin(const QString & packageName)639 bool QgsPythonUtilsImpl::startPlugin( const QString &packageName )
640 {
641   QString output;
642   evalString( QStringLiteral( "qgis.utils.startPlugin('%1')" ).arg( packageName ), output );
643   return ( output == QLatin1String( "True" ) );
644 }
645 
startProcessingPlugin(const QString & packageName)646 bool QgsPythonUtilsImpl::startProcessingPlugin( const QString &packageName )
647 {
648   QString output;
649   evalString( QStringLiteral( "qgis.utils.startProcessingPlugin('%1')" ).arg( packageName ), output );
650   return ( output == QLatin1String( "True" ) );
651 }
652 
canUninstallPlugin(const QString & packageName)653 bool QgsPythonUtilsImpl::canUninstallPlugin( const QString &packageName )
654 {
655   QString output;
656   evalString( QStringLiteral( "qgis.utils.canUninstallPlugin('%1')" ).arg( packageName ), output );
657   return ( output == QLatin1String( "True" ) );
658 }
659 
unloadPlugin(const QString & packageName)660 bool QgsPythonUtilsImpl::unloadPlugin( const QString &packageName )
661 {
662   QString output;
663   evalString( QStringLiteral( "qgis.utils.unloadPlugin('%1')" ).arg( packageName ), output );
664   return ( output == QLatin1String( "True" ) );
665 }
666 
isPluginEnabled(const QString & packageName) const667 bool QgsPythonUtilsImpl::isPluginEnabled( const QString &packageName ) const
668 {
669   return QgsSettings().value( QStringLiteral( "/PythonPlugins/" ) + packageName, QVariant( false ) ).toBool();
670 }
671 
isPluginLoaded(const QString & packageName)672 bool QgsPythonUtilsImpl::isPluginLoaded( const QString &packageName )
673 {
674   QString output;
675   evalString( QStringLiteral( "qgis.utils.isPluginLoaded('%1')" ).arg( packageName ), output );
676   return ( output == QLatin1String( "True" ) );
677 }
678 
listActivePlugins()679 QStringList QgsPythonUtilsImpl::listActivePlugins()
680 {
681   QString output;
682   evalString( QStringLiteral( "'\\n'.join(qgis.utils.active_plugins)" ), output );
683   return output.split( QChar( '\n' ), QString::SkipEmptyParts );
684 }
685