Add PluginManager

This commit is contained in:
Florian Märkl 2019-02-09 14:05:06 +01:00
parent 280e10d154
commit 986041380c
11 changed files with 209 additions and 98 deletions

View File

@ -2617,16 +2617,6 @@ QList<QString> CutterCore::getColorThemes()
return r; return r;
} }
void CutterCore::setCutterPlugins(QList<CutterPlugin *> plugins)
{
this->plugins = plugins;
}
QList<CutterPlugin *> CutterCore::getCutterPlugins()
{
return plugins;
}
QString CutterCore::ansiEscapeToHtml(const QString &text) QString CutterCore::ansiEscapeToHtml(const QString &text)
{ {
int len; int len;

View File

@ -723,9 +723,6 @@ public:
void message(const QString &msg, bool debug = false); void message(const QString &msg, bool debug = false);
void setCutterPlugins(QList<CutterPlugin*> plugins);
QList<CutterPlugin*> getCutterPlugins();
QStringList getSectionList(); QStringList getSectionList();
RCoreLocked core() const; RCoreLocked core() const;
@ -787,7 +784,6 @@ private:
bool emptyGraph = false; bool emptyGraph = false;
QList<CutterPlugin*> plugins;
}; };
class ccClass : public CutterCore class ccClass : public CutterCore

View File

@ -230,7 +230,8 @@ SOURCES += \
dialogs/LoadNewTypesDialog.cpp \ dialogs/LoadNewTypesDialog.cpp \
widgets/SdbWidget.cpp \ widgets/SdbWidget.cpp \
plugins/CutterPythonPlugin.cpp \ plugins/CutterPythonPlugin.cpp \
common/PythonManager.cpp common/PythonManager.cpp \
plugins/PluginManager.cpp
HEADERS += \ HEADERS += \
Cutter.h \ Cutter.h \
@ -343,7 +344,8 @@ HEADERS += \
dialogs/LoadNewTypesDialog.h \ dialogs/LoadNewTypesDialog.h \
widgets/SdbWidget.h \ widgets/SdbWidget.h \
plugins/CutterPythonPlugin.h \ plugins/CutterPythonPlugin.h \
common/PythonManager.h common/PythonManager.h \
plugins/PluginManager.h
FORMS += \ FORMS += \
dialogs/AboutDialog.ui \ dialogs/AboutDialog.ui \

View File

@ -3,7 +3,7 @@
#ifdef CUTTER_ENABLE_JUPYTER #ifdef CUTTER_ENABLE_JUPYTER
#include "common/JupyterConnection.h" #include "common/JupyterConnection.h"
#endif #endif
#include "plugins/CutterPythonPlugin.h" #include "plugins/PluginManager.h"
#include <QApplication> #include <QApplication>
#include <QFileOpenEvent> #include <QFileOpenEvent>
@ -119,7 +119,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc
} }
} }
loadPlugins(); Plugins()->loadPlugins();
mainWindow = new MainWindow(); mainWindow = new MainWindow();
installEventFilter(mainWindow); installEventFilter(mainWindow);
@ -170,11 +170,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc
CutterApplication::~CutterApplication() CutterApplication::~CutterApplication()
{ {
QList<CutterPlugin *> plugins = Core()->getCutterPlugins(); Plugins()->destroyPlugins();
for (CutterPlugin *plugin : plugins) {
delete plugin;
}
delete mainWindow; delete mainWindow;
Python()->shutdown(); Python()->shutdown();
} }
@ -205,51 +201,6 @@ bool CutterApplication::event(QEvent *e)
return QApplication::event(e); return QApplication::event(e);
} }
void CutterApplication::loadPlugins()
{
QList<CutterPlugin *> 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<CutterPlugin *>(plugin);
}
}
if (cutterPlugin) {
cutterPlugin->setupPlugin(Core());
plugins.append(cutterPlugin);
}
}
qInfo() << "Loaded" << plugins.length() << "plugin(s).";
Core()->setCutterPlugins(plugins);
}
bool CutterApplication::loadTranslations() bool CutterApplication::loadTranslations()
{ {
const QString &language = Config()->getCurrLocale().bcp47Name(); const QString &language = Config()->getCurrLocale().bcp47Name();

View File

@ -22,8 +22,6 @@ public:
return mainWindow; return mainWindow;
} }
void loadPlugins();
protected: protected:
bool event(QEvent *e); bool event(QEvent *e);

View File

@ -3,6 +3,7 @@
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include "common/Helpers.h" #include "common/Helpers.h"
#include "CutterConfig.h" #include "CutterConfig.h"
#include "plugins/PluginManager.h"
// Qt Headers // Qt Headers
#include <QApplication> #include <QApplication>
@ -276,8 +277,7 @@ void MainWindow::initUI()
&MainWindow::updateTasksIndicator); &MainWindow::updateTasksIndicator);
/* Setup plugins interfaces */ /* Setup plugins interfaces */
QList<CutterPlugin *> plugins = core->getCutterPlugins(); for (auto plugin : Plugins()->getPlugins()) {
for (auto plugin : plugins) {
plugin->setupInterface(this); plugin->setupInterface(this);
} }
} }

View File

@ -67,6 +67,7 @@ void PythonManager::initialize()
PyImport_AppendInittab("CutterBindings", &PyInit_CutterBindings); PyImport_AppendInittab("CutterBindings", &PyInit_CutterBindings);
Py_Initialize(); Py_Initialize();
PyEval_InitThreads(); PyEval_InitThreads();
pyThreadStateCounter = 1; // we have the thread now => 1
RegQtResImporter(); RegQtResImporter();
@ -109,34 +110,19 @@ void PythonManager::addPythonPath(char *path) {
saveThread(); 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() void PythonManager::restoreThread()
{ {
if (pyThreadState) { pyThreadStateCounter++;
if (pyThreadStateCounter == 1 && pyThreadState) {
PyEval_RestoreThread(pyThreadState); PyEval_RestoreThread(pyThreadState);
} }
} }
void PythonManager::saveThread() void PythonManager::saveThread()
{ {
pyThreadStateCounter--;
assert(pyThreadStateCounter >= 0);
if (pyThreadStateCounter == 0) {
pyThreadState = PyEval_SaveThread(); pyThreadState = PyEval_SaveThread();
}
} }

View File

@ -25,12 +25,23 @@ public:
void addPythonPath(char *path); void addPythonPath(char *path);
CutterPythonPlugin *loadPlugin(const char *pluginName);
void restoreThread(); void restoreThread();
void saveThread(); 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: signals:
void willShutDown(); void willShutDown();
@ -38,6 +49,7 @@ private:
QString customPythonHome; QString customPythonHome;
wchar_t *pythonHome = nullptr; wchar_t *pythonHome = nullptr;
PyThreadState *pyThreadState = nullptr; PyThreadState *pyThreadState = nullptr;
int pyThreadStateCounter = 0;
PyObject *cutterPluginModule; PyObject *cutterPluginModule;
}; };

View File

@ -3,6 +3,7 @@
#include "Cutter.h" #include "Cutter.h"
#include "common/Helpers.h" #include "common/Helpers.h"
#include "plugins/PluginManager.h"
R2PluginsDialog::R2PluginsDialog(QWidget *parent) : R2PluginsDialog::R2PluginsDialog(QWidget *parent) :
QDialog(parent), QDialog(parent),
@ -51,7 +52,7 @@ R2PluginsDialog::R2PluginsDialog(QWidget *parent) :
} }
qhelpers::adjustColumns(ui->RAsmTreeWidget, 0); qhelpers::adjustColumns(ui->RAsmTreeWidget, 0);
for (CutterPlugin *plugin : Core()->getCutterPlugins()) { for (CutterPlugin *plugin : Plugins()->getPlugins()) {
QTreeWidgetItem *item = new QTreeWidgetItem(); QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0, plugin->name); item->setText(0, plugin->name);
item->setText(1, plugin->description); item->setText(1, plugin->description);

View File

@ -0,0 +1,130 @@
#ifdef CUTTER_ENABLE_PYTHON
#include <Python.h>
#endif
#include "PluginManager.h"
#include "PythonManager.h"
#include "CutterPlugin.h"
#include "CutterPythonPlugin.h"
#include <QDir>
#include <QCoreApplication>
#include <QPluginLoader>
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<CutterPlugin *>(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<CutterPlugin *>(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

View File

@ -0,0 +1,45 @@
#ifndef PLUGINMANAGER_H
#define PLUGINMANAGER_H
#include <QObject>
#include <QDir>
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<CutterPlugin *> &getPlugins() { return plugins; }
private:
QList<CutterPlugin *> 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