diff --git a/src/Cutter.cpp b/src/Cutter.cpp index 5d565ef3..12bd2a20 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -2617,16 +2617,6 @@ QList CutterCore::getColorThemes() return r; } -void CutterCore::setCutterPlugins(QList plugins) -{ - this->plugins = plugins; -} - -QList CutterCore::getCutterPlugins() -{ - return plugins; -} - QString CutterCore::ansiEscapeToHtml(const QString &text) { int len; diff --git a/src/Cutter.h b/src/Cutter.h index bf4363de..d4580f33 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -723,9 +723,6 @@ public: void message(const QString &msg, bool debug = false); - void setCutterPlugins(QList plugins); - QList getCutterPlugins(); - QStringList getSectionList(); RCoreLocked core() const; @@ -787,7 +784,6 @@ private: bool emptyGraph = false; - QList plugins; }; class ccClass : public CutterCore diff --git a/src/Cutter.pro b/src/Cutter.pro index 8c943494..5b153665 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -230,7 +230,8 @@ SOURCES += \ dialogs/LoadNewTypesDialog.cpp \ widgets/SdbWidget.cpp \ plugins/CutterPythonPlugin.cpp \ - common/PythonManager.cpp + common/PythonManager.cpp \ + plugins/PluginManager.cpp HEADERS += \ Cutter.h \ @@ -343,7 +344,8 @@ HEADERS += \ dialogs/LoadNewTypesDialog.h \ widgets/SdbWidget.h \ plugins/CutterPythonPlugin.h \ - common/PythonManager.h + common/PythonManager.h \ + plugins/PluginManager.h FORMS += \ dialogs/AboutDialog.ui \ diff --git a/src/CutterApplication.cpp b/src/CutterApplication.cpp index 3498d765..0ec19951 100644 --- a/src/CutterApplication.cpp +++ b/src/CutterApplication.cpp @@ -3,7 +3,7 @@ #ifdef CUTTER_ENABLE_JUPYTER #include "common/JupyterConnection.h" #endif -#include "plugins/CutterPythonPlugin.h" +#include "plugins/PluginManager.h" #include #include @@ -119,7 +119,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc } } - loadPlugins(); + Plugins()->loadPlugins(); mainWindow = new MainWindow(); installEventFilter(mainWindow); @@ -170,11 +170,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc CutterApplication::~CutterApplication() { - QList plugins = Core()->getCutterPlugins(); - for (CutterPlugin *plugin : plugins) { - delete plugin; - } - + Plugins()->destroyPlugins(); delete mainWindow; Python()->shutdown(); } @@ -205,51 +201,6 @@ bool CutterApplication::event(QEvent *e) return QApplication::event(e); } -void CutterApplication::loadPlugins() -{ - QList plugins; - QDir pluginsDir(qApp->applicationDirPath()); -#if defined(Q_OS_WIN) - if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release") - pluginsDir.cdUp(); -#elif defined(Q_OS_MAC) - if (pluginsDir.dirName() == "MacOS") { - pluginsDir.cdUp(); - pluginsDir.cdUp(); - pluginsDir.cdUp(); - } -#endif - if (!pluginsDir.cd("plugins")) { - return; - } - - Python()->addPythonPath(pluginsDir.absolutePath().toLatin1().data()); - - foreach (QString fileName, pluginsDir.entryList(QDir::Files)) { - CutterPlugin *cutterPlugin = nullptr; - if (fileName.endsWith(".py")) { - // Load python plugins - QStringList l = fileName.split(".py"); - cutterPlugin = (CutterPlugin*) Python()->loadPlugin(l.at(0).toLatin1().constData()); - } else { - // Load C++ plugins - QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); - QObject *plugin = pluginLoader.instance(); - if (plugin) { - cutterPlugin = qobject_cast(plugin); - } - } - - if (cutterPlugin) { - cutterPlugin->setupPlugin(Core()); - plugins.append(cutterPlugin); - } - } - - qInfo() << "Loaded" << plugins.length() << "plugin(s)."; - Core()->setCutterPlugins(plugins); -} - bool CutterApplication::loadTranslations() { const QString &language = Config()->getCurrLocale().bcp47Name(); diff --git a/src/CutterApplication.h b/src/CutterApplication.h index 7a645504..36118905 100644 --- a/src/CutterApplication.h +++ b/src/CutterApplication.h @@ -22,8 +22,6 @@ public: return mainWindow; } - void loadPlugins(); - protected: bool event(QEvent *e); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index dea444f3..665236d9 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -3,6 +3,7 @@ #include "ui_MainWindow.h" #include "common/Helpers.h" #include "CutterConfig.h" +#include "plugins/PluginManager.h" // Qt Headers #include @@ -276,8 +277,7 @@ void MainWindow::initUI() &MainWindow::updateTasksIndicator); /* Setup plugins interfaces */ - QList plugins = core->getCutterPlugins(); - for (auto plugin : plugins) { + for (auto plugin : Plugins()->getPlugins()) { plugin->setupInterface(this); } } diff --git a/src/common/PythonManager.cpp b/src/common/PythonManager.cpp index e8bf77b9..f2029412 100644 --- a/src/common/PythonManager.cpp +++ b/src/common/PythonManager.cpp @@ -67,6 +67,7 @@ void PythonManager::initialize() PyImport_AppendInittab("CutterBindings", &PyInit_CutterBindings); Py_Initialize(); PyEval_InitThreads(); + pyThreadStateCounter = 1; // we have the thread now => 1 RegQtResImporter(); @@ -109,34 +110,19 @@ void PythonManager::addPythonPath(char *path) { saveThread(); } - -CutterPythonPlugin* PythonManager::loadPlugin(const char *pluginName) { - CutterPythonPlugin *plugin = nullptr; - if (!cutterPluginModule) { - return plugin; - } - - restoreThread(); - PyObject *pluginModule = PyImport_ImportModule(pluginName); - if (!pluginModule) { - qWarning() << "Couldn't load the plugin" << QString(pluginName); - PyErr_PrintEx(10); - } else { - plugin = new CutterPythonPlugin(pluginModule); - } - saveThread(); - - return plugin; -} - void PythonManager::restoreThread() { - if (pyThreadState) { + pyThreadStateCounter++; + if (pyThreadStateCounter == 1 && pyThreadState) { PyEval_RestoreThread(pyThreadState); } } void PythonManager::saveThread() { - pyThreadState = PyEval_SaveThread(); + pyThreadStateCounter--; + assert(pyThreadStateCounter >= 0); + if (pyThreadStateCounter == 0) { + pyThreadState = PyEval_SaveThread(); + } } diff --git a/src/common/PythonManager.h b/src/common/PythonManager.h index 30a81d25..265c775e 100644 --- a/src/common/PythonManager.h +++ b/src/common/PythonManager.h @@ -25,12 +25,23 @@ public: void addPythonPath(char *path); - - CutterPythonPlugin *loadPlugin(const char *pluginName); - void restoreThread(); void saveThread(); + PyObject *getCutterPluginModule() { return cutterPluginModule; } + + /*! + * \brief RAII Helper class to call restoreThread() and saveThread() automatically + * + * As long as an object of this class is in scope, the Python thread will remain restored. + */ + class ThreadHolder + { + public: + ThreadHolder() { getInstance()->restoreThread(); } + ~ThreadHolder() { getInstance()->saveThread(); } + }; + signals: void willShutDown(); @@ -38,6 +49,7 @@ private: QString customPythonHome; wchar_t *pythonHome = nullptr; PyThreadState *pyThreadState = nullptr; + int pyThreadStateCounter = 0; PyObject *cutterPluginModule; }; diff --git a/src/dialogs/R2PluginsDialog.cpp b/src/dialogs/R2PluginsDialog.cpp index 0b0f3ead..93812e6b 100644 --- a/src/dialogs/R2PluginsDialog.cpp +++ b/src/dialogs/R2PluginsDialog.cpp @@ -3,6 +3,7 @@ #include "Cutter.h" #include "common/Helpers.h" +#include "plugins/PluginManager.h" R2PluginsDialog::R2PluginsDialog(QWidget *parent) : QDialog(parent), @@ -51,7 +52,7 @@ R2PluginsDialog::R2PluginsDialog(QWidget *parent) : } qhelpers::adjustColumns(ui->RAsmTreeWidget, 0); - for (CutterPlugin *plugin : Core()->getCutterPlugins()) { + for (CutterPlugin *plugin : Plugins()->getPlugins()) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, plugin->name); item->setText(1, plugin->description); diff --git a/src/plugins/PluginManager.cpp b/src/plugins/PluginManager.cpp new file mode 100644 index 00000000..b8f1165d --- /dev/null +++ b/src/plugins/PluginManager.cpp @@ -0,0 +1,130 @@ + +#ifdef CUTTER_ENABLE_PYTHON +#include +#endif + +#include "PluginManager.h" +#include "PythonManager.h" +#include "CutterPlugin.h" +#include "CutterPythonPlugin.h" + +#include +#include +#include + +Q_GLOBAL_STATIC(PluginManager, uniqueInstance) + +PluginManager *PluginManager::getInstance() +{ + return uniqueInstance; +} + +PluginManager::PluginManager() +{ +} + +PluginManager::~PluginManager() +{ +} + +void PluginManager::loadPlugins() +{ + assert(plugins.isEmpty()); + + QDir pluginsDir(qApp->applicationDirPath()); +#if defined(Q_OS_WIN) + if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release") + pluginsDir.cdUp(); +#elif defined(Q_OS_MAC) + if (pluginsDir.dirName() == "MacOS") { + pluginsDir.cdUp(); + pluginsDir.cdUp(); + pluginsDir.cdUp(); + } +#endif + if (!pluginsDir.cd("plugins")) { + return; + } + + QDir nativePluginsDir = pluginsDir; + if (nativePluginsDir.cd("native")) { + loadNativePlugins(nativePluginsDir); + } + + QDir pythonPluginsDir = pluginsDir; + if (pythonPluginsDir.cd("python")) { + loadPythonPlugins(pythonPluginsDir.absolutePath()); + } + + qInfo() << "Loaded" << plugins.length() << "plugin(s)."; +} + + +void PluginManager::destroyPlugins() +{ + for (CutterPlugin *plugin : plugins) { + delete plugin; + } +} + +void PluginManager::loadNativePlugins(const QDir &directory) +{ + for (const QString &fileName : directory.entryList(QDir::Files)) { + QPluginLoader pluginLoader(directory.absoluteFilePath(fileName)); + QObject *plugin = pluginLoader.instance(); + if (!plugin) { + continue; + } + CutterPlugin *cutterPlugin = qobject_cast(plugin); + if (!cutterPlugin) { + continue; + } + cutterPlugin->setupPlugin(Core()); + plugins.append(cutterPlugin); + } +} + +#ifdef CUTTER_ENABLE_PYTHON + +void PluginManager::loadPythonPlugins(const QDir &directory) +{ + Python()->addPythonPath(directory.absolutePath().toLocal8Bit().data()); + + for (const QString &fileName : directory.entryList(QDir::Files)) { + CutterPlugin *cutterPlugin = nullptr; + QString moduleName; + if (fileName.endsWith(".py")) { + QStringList l = fileName.split(".py"); + moduleName = l[0]; + } + cutterPlugin = loadPythonPlugin(moduleName.toLocal8Bit().constData()); + if (!cutterPlugin) { + continue; + } + cutterPlugin->setupPlugin(Core()); + plugins.append(cutterPlugin); + } + + PythonManager::ThreadHolder threadHolder; +} + +CutterPlugin *PluginManager::loadPythonPlugin(const char *moduleName) +{ + PythonManager::ThreadHolder threadHolder; + PyObject *cutterPluginModule = Python()->getCutterPluginModule(); + CutterPythonPlugin *plugin = nullptr; + if (!cutterPluginModule) { + return static_cast(plugin); + } + + PyObject *pluginModule = PyImport_ImportModule(moduleName); + if (!pluginModule) { + qWarning() << "Couldn't load module for plugin:" << QString(moduleName); + PyErr_PrintEx(10); + } else { + plugin = new CutterPythonPlugin(pluginModule); + } + + return plugin; +} +#endif \ No newline at end of file diff --git a/src/plugins/PluginManager.h b/src/plugins/PluginManager.h new file mode 100644 index 00000000..e02a529b --- /dev/null +++ b/src/plugins/PluginManager.h @@ -0,0 +1,45 @@ + +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include +#include + +class CutterPlugin; + +class PluginManager: public QObject +{ +Q_OBJECT + +public: + static PluginManager *getInstance(); + + PluginManager(); + ~PluginManager(); + + /*! + * \brief Load all plugins, should be called once on application start + */ + void loadPlugins(); + + /*! + * \brief Destroy all loaded plugins, should be called once on application shutdown + */ + void destroyPlugins(); + + const QList &getPlugins() { return plugins; } + +private: + QList plugins; + + void loadNativePlugins(const QDir &directory); + +#ifdef CUTTER_ENABLE_PYTHON + void loadPythonPlugins(const QDir &directory); + CutterPlugin *loadPythonPlugin(const char *moduleName); +#endif +}; + +#define Plugins() (PluginManager::getInstance()) + +#endif //PLUGINMANAGER_H