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( " " ) );
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