Simplify and move Plugin context menu gui code out of core. (#2039)

This commit is contained in:
karliss 2020-01-31 12:13:28 +02:00 committed by GitHub
parent a1b5a41e56
commit 1ae78655fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 109 additions and 80 deletions

View File

@ -155,7 +155,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc
Plugins()->loadPlugins(); Plugins()->loadPlugins();
for (auto *plugin : Plugins()->getPlugins()) { for (auto &plugin : Plugins()->getPlugins()) {
plugin->registerDecompilers(); plugin->registerDecompilers();
} }
@ -248,9 +248,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc
CutterApplication::~CutterApplication() CutterApplication::~CutterApplication()
{ {
#ifdef CUTTER_ENABLE_PYTHON
Plugins()->destroyPlugins(); Plugins()->destroyPlugins();
#endif
delete mainWindow; delete mainWindow;
#ifdef CUTTER_ENABLE_PYTHON #ifdef CUTTER_ENABLE_PYTHON
Python()->shutdown(); Python()->shutdown();

View File

@ -7,11 +7,11 @@
<primitive-type name="bool"/> <primitive-type name="bool"/>
<object-type name="CutterCore"> <object-type name="CutterCore">
<enum-type name="ContextMenuType" />
</object-type> </object-type>
<object-type name="Configuration" /> <object-type name="Configuration" />
<object-type name="MainWindow" > <object-type name="MainWindow" >
<enum-type name="MenuType" /> <enum-type name="MenuType" />
<enum-type name="ContextMenuType" />
</object-type> </object-type>
<object-type name="BasicBlockHighlighter" /> <object-type name="BasicBlockHighlighter" />
<object-type name="CutterDockWidget" /> <object-type name="CutterDockWidget" />

View File

@ -209,13 +209,6 @@ void CutterCore::initialize()
// Initialize Async tasks manager // Initialize Async tasks manager
asyncTaskManager = new AsyncTaskManager(this); asyncTaskManager = new AsyncTaskManager(this);
// Initialize context menu extensions for plugins
disassemblyContextMenuExtensions = new QMenu(tr("Plugins"));
disassemblyContextMenuExtensions->menuAction()->setVisible(false);
disassemblyContextMenuExtensions->setEnabled(false);
addressableContextMenuExtensions = new QMenu(tr("Plugins"));
addressableContextMenuExtensions->menuAction()->setVisible(false);
} }
CutterCore::~CutterCore() CutterCore::~CutterCore()
@ -2301,39 +2294,6 @@ QStringList CutterCore::getAnalPluginNames()
return ret; return ret;
} }
QMenu *CutterCore::getContextMenuExtensions(ContextMenuType type)
{
switch (type) {
case ContextMenuType::Disassembly:
return disassemblyContextMenuExtensions;
case ContextMenuType::Addressable:
return addressableContextMenuExtensions;
default:
return nullptr;
}
}
void CutterCore::addContextMenuExtensionAction(ContextMenuType type, QAction *action)
{
QMenu *menu = getContextMenuExtensions(type);
if (menu) {
// The submenu should be invisible and disabled while it's empty
if (menu->isEmpty()) {
menu->menuAction()->setVisible(true);
menu->setEnabled(true);
}
menu->addAction(action);
}
}
void CutterCore::addContextMenuExtensionSeparator(ContextMenuType type)
{
QMenu *menu = getContextMenuExtensions(type);
if (menu) {
menu->addSeparator();
}
}
QStringList CutterCore::getProjectNames() QStringList CutterCore::getProjectNames()
{ {
CORE_LOCK(); CORE_LOCK();

View File

@ -425,17 +425,6 @@ public:
QStringList getAsmPluginNames(); QStringList getAsmPluginNames();
QStringList getAnalPluginNames(); QStringList getAnalPluginNames();
/* Context menu plugins */
enum class ContextMenuType { Disassembly, Addressable };
/**
* @brief Fetches the pointer to a context menu extension of type
* @param type - the type of the context menu
* @return plugins submenu of the selected context menu
*/
QMenu *getContextMenuExtensions(ContextMenuType type);
void addContextMenuExtensionAction(ContextMenuType type, QAction *action);
void addContextMenuExtensionSeparator(ContextMenuType type);
/* Projects */ /* Projects */
QStringList getProjectNames(); QStringList getProjectNames();
void openProject(const QString &name); void openProject(const QString &name);
@ -649,9 +638,6 @@ private:
QSharedPointer<R2Task> debugTask; QSharedPointer<R2Task> debugTask;
R2TaskDialog *debugTaskDialog; R2TaskDialog *debugTaskDialog;
QMenu *disassemblyContextMenuExtensions = nullptr;
QMenu *addressableContextMenuExtensions = nullptr;
}; };
class RCoreLocked class RCoreLocked

View File

@ -132,6 +132,10 @@ void MainWindow::initUI()
{ {
ui->setupUi(this); ui->setupUi(this);
// Initialize context menu extensions for plugins
disassemblyContextMenuExtensions = new QMenu(tr("Plugins"), this);
addressableContextMenuExtensions = new QMenu(tr("Plugins"), this);
connect(ui->actionExtraGraph, &QAction::triggered, this, &MainWindow::addExtraGraph); connect(ui->actionExtraGraph, &QAction::triggered, this, &MainWindow::addExtraGraph);
connect(ui->actionExtraDisassembly, &QAction::triggered, this, &MainWindow::addExtraDisassembly); connect(ui->actionExtraDisassembly, &QAction::triggered, this, &MainWindow::addExtraDisassembly);
connect(ui->actionExtraHexdump, &QAction::triggered, this, &MainWindow::addExtraHexdump); connect(ui->actionExtraHexdump, &QAction::triggered, this, &MainWindow::addExtraHexdump);
@ -193,7 +197,7 @@ void MainWindow::initUI()
initBackForwardMenu(); initBackForwardMenu();
/* Setup plugins interfaces */ /* Setup plugins interfaces */
for (auto plugin : Plugins()->getPlugins()) { for (auto &plugin : Plugins()->getPlugins()) {
plugin->setupInterface(this); plugin->setupInterface(this);
} }
@ -1660,3 +1664,15 @@ void MainWindow::onZoomReset()
{ {
Config()->setZoomFactor(1.0); Config()->setZoomFactor(1.0);
} }
QMenu *MainWindow::getContextMenuExtensions(ContextMenuType type)
{
switch (type) {
case ContextMenuType::Disassembly:
return disassemblyContextMenuExtensions;
case ContextMenuType::Addressable:
return addressableContextMenuExtensions;
default:
return nullptr;
}
}

View File

@ -125,6 +125,15 @@ public:
void setCurrentMemoryWidget(MemoryDockWidget* memoryWidget); void setCurrentMemoryWidget(MemoryDockWidget* memoryWidget);
MemoryDockWidget* getLastMemoryWidget(); MemoryDockWidget* getLastMemoryWidget();
/* Context menu plugins */
enum class ContextMenuType { Disassembly, Addressable };
/**
* @brief Fetches the pointer to a context menu extension of type
* @param type - the type of the context menu
* @return plugins submenu of the selected context menu
*/
QMenu *getContextMenuExtensions(ContextMenuType type);
public slots: public slots:
void finalizeOpen(); void finalizeOpen();
@ -263,6 +272,9 @@ private:
QDockWidget *breakpointDock = nullptr; QDockWidget *breakpointDock = nullptr;
QDockWidget *registerRefsDock = nullptr; QDockWidget *registerRefsDock = nullptr;
QMenu *disassemblyContextMenuExtensions = nullptr;
QMenu *addressableContextMenuExtensions = nullptr;
void initUI(); void initUI();
void initToolBar(); void initToolBar();
void initDocks(); void initDocks();

View File

@ -39,7 +39,7 @@ PluginsOptionsWidget::PluginsOptionsWidget(PreferencesDialog *dialog)
tr("Author") tr("Author")
}); });
for (CutterPlugin *plugin : Plugins()->getPlugins()) { for (auto &plugin : Plugins()->getPlugins()) {
auto item = new QTreeWidgetItem(); auto item = new QTreeWidgetItem();
item->setText(0, plugin->getName()); item->setText(0, plugin->getName());
item->setText(1, plugin->getDescription()); item->setText(1, plugin->getDescription());

View File

@ -40,8 +40,8 @@ AddressableItemContextMenu::AddressableItemContextMenu(QWidget *parent, MainWind
addAction(&actionAddcomment); addAction(&actionAddcomment);
addSeparator(); addSeparator();
pluginMenu = Core()->getContextMenuExtensions(CutterCore::ContextMenuType::Addressable); pluginMenu = mainWindow->getContextMenuExtensions(MainWindow::ContextMenuType::Addressable);
addMenu(pluginMenu); pluginMenuAction = addMenu(pluginMenu);
addSeparator(); addSeparator();
setHasTarget(hasTarget); setHasTarget(hasTarget);
@ -105,6 +105,7 @@ void AddressableItemContextMenu::aboutToShowSlot()
} }
actionShowInMenu.setMenu(mainWindow->createShowInMenu(this, offset)); actionShowInMenu.setMenu(mainWindow->createShowInMenu(this, offset));
pluginMenuAction->setVisible(!pluginMenu->isEmpty());
for (QAction *pluginAction : pluginMenu->actions()) { for (QAction *pluginAction : pluginMenu->actions()) {
pluginAction->setData(QVariant::fromValue(offset)); pluginAction->setData(QVariant::fromValue(offset));
} }

View File

@ -32,6 +32,7 @@ private:
virtual void aboutToShowSlot(); virtual void aboutToShowSlot();
QMenu *pluginMenu; QMenu *pluginMenu;
QAction *pluginMenuAction;
MainWindow *mainWindow; MainWindow *mainWindow;
RVA offset; RVA offset;

View File

@ -156,8 +156,8 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main
addSeparator(); addSeparator();
pluginMenu = Core()->getContextMenuExtensions(CutterCore::ContextMenuType::Disassembly); pluginMenu = mainWindow->getContextMenuExtensions(MainWindow::ContextMenuType::Disassembly);
addMenu(pluginMenu); pluginActionMenuAction = addMenu(pluginMenu);
addSeparator(); addSeparator();
@ -529,6 +529,7 @@ void DisassemblyContextMenu::aboutToShowSlot()
QString progCounterName = Core()->getRegisterName("PC").toUpper(); QString progCounterName = Core()->getRegisterName("PC").toUpper();
actionSetPC.setText("Set " + progCounterName + " here"); actionSetPC.setText("Set " + progCounterName + " here");
pluginActionMenuAction->setVisible(!pluginMenu->isEmpty());
for (QAction *pluginAction : pluginMenu->actions()) { for (QAction *pluginAction : pluginMenu->actions()) {
pluginAction->setData(QVariant::fromValue(offset)); pluginAction->setData(QVariant::fromValue(offset));
} }

View File

@ -182,6 +182,7 @@ private:
QAction showInSubmenu; QAction showInSubmenu;
QList<QAction*> showTargetMenuActions; QList<QAction*> showTargetMenuActions;
QAction *pluginActionMenuAction;
// For creating anonymous entries (that are always visible) // For creating anonymous entries (that are always visible)
QAction *addAnonymousAction(QString name, const char *slot, QKeySequence shortcut); QAction *addAnonymousAction(QString name, const char *slot, QKeySequence shortcut);

View File

@ -33,7 +33,7 @@ PluginManager::~PluginManager()
void PluginManager::loadPlugins() void PluginManager::loadPlugins()
{ {
assert(plugins.isEmpty()); assert(plugins.empty());
QString userPluginDir = getUserPluginsDirectory(); QString userPluginDir = getUserPluginsDirectory();
if (!userPluginDir.isEmpty()) { if (!userPluginDir.isEmpty()) {
@ -51,7 +51,7 @@ void PluginManager::loadPlugins()
void PluginManager::loadPluginsFromDir(const QDir &pluginsDir, bool writable) void PluginManager::loadPluginsFromDir(const QDir &pluginsDir, bool writable)
{ {
qInfo() << "Plugins are loaded from" << pluginsDir.absolutePath(); qInfo() << "Plugins are loaded from" << pluginsDir.absolutePath();
int loadedPlugins = plugins.length(); int loadedPlugins = plugins.size();
if (!pluginsDir.exists()) { if (!pluginsDir.exists()) {
return; return;
} }
@ -74,16 +74,19 @@ void PluginManager::loadPluginsFromDir(const QDir &pluginsDir, bool writable)
} }
#endif #endif
loadedPlugins = plugins.length() - loadedPlugins; loadedPlugins = plugins.size() - loadedPlugins;
qInfo() << "Loaded" << loadedPlugins << "plugin(s)."; qInfo() << "Loaded" << loadedPlugins << "plugin(s).";
} }
void PluginManager::PluginTerminator::operator()(CutterPlugin *plugin) const
{
plugin->terminate();
delete plugin;
}
void PluginManager::destroyPlugins() void PluginManager::destroyPlugins()
{ {
for (CutterPlugin *plugin : plugins) { plugins.clear();
plugin->terminate();
delete plugin;
}
} }
QVector<QDir> PluginManager::getPluginDirectories() const QVector<QDir> PluginManager::getPluginDirectories() const
@ -144,12 +147,12 @@ void PluginManager::loadNativePlugins(const QDir &directory)
} }
continue; continue;
} }
CutterPlugin *cutterPlugin = qobject_cast<CutterPlugin *>(plugin); PluginPtr cutterPlugin{qobject_cast<CutterPlugin *>(plugin)};
if (!cutterPlugin) { if (!cutterPlugin) {
continue; continue;
} }
cutterPlugin->setupPlugin(); cutterPlugin->setupPlugin();
plugins.append(cutterPlugin); plugins.push_back(std::move(cutterPlugin));
} }
} }
@ -169,12 +172,12 @@ void PluginManager::loadPythonPlugins(const QDir &directory)
} else { } else {
moduleName = fileName; moduleName = fileName;
} }
CutterPlugin *cutterPlugin = loadPythonPlugin(moduleName.toLocal8Bit().constData()); PluginPtr cutterPlugin{loadPythonPlugin(moduleName.toLocal8Bit().constData())};
if (!cutterPlugin) { if (!cutterPlugin) {
continue; continue;
} }
cutterPlugin->setupPlugin(); cutterPlugin->setupPlugin();
plugins.append(cutterPlugin); plugins.push_back(std::move(cutterPlugin));
} }
PythonManager::ThreadHolder threadHolder; PythonManager::ThreadHolder threadHolder;

View File

@ -4,6 +4,8 @@
#include <QObject> #include <QObject>
#include <QDir> #include <QDir>
#include <memory>
#include <vector>
class CutterPlugin; class CutterPlugin;
@ -14,6 +16,13 @@ Q_OBJECT
public: public:
static PluginManager *getInstance(); static PluginManager *getInstance();
class PluginTerminator
{
public:
void operator()(CutterPlugin*) const;
};
using PluginPtr = std::unique_ptr<CutterPlugin, PluginTerminator>;
PluginManager(); PluginManager();
~PluginManager(); ~PluginManager();
@ -27,13 +36,13 @@ public:
*/ */
void destroyPlugins(); void destroyPlugins();
const QList<CutterPlugin *> &getPlugins() { return plugins; } const std::vector<PluginPtr> &getPlugins() { return plugins; }
QVector<QDir> getPluginDirectories() const; QVector<QDir> getPluginDirectories() const;
QString getUserPluginsDirectory() const; QString getUserPluginsDirectory() const;
private: private:
QList<CutterPlugin *> plugins; std::vector<PluginPtr> plugins;
void loadNativePlugins(const QDir &directory); void loadNativePlugins(const QDir &directory);
void loadPluginsFromDir(const QDir &pluginsDir, bool writable = false); void loadPluginsFromDir(const QDir &pluginsDir, bool writable = false);

View File

@ -42,25 +42,66 @@ class FortuneWidget(cutter.CutterDockWidget):
class CutterSamplePlugin(cutter.CutterPlugin): class CutterSamplePlugin(cutter.CutterPlugin):
name = "SamplePlugin" name = "Sample Plugin"
description = "A sample plugin written in python." description = "A sample plugin written in python."
version = "1.0" version = "1.1"
author = "xarkes and thestr4ng3r :-P" author = "Cutter developers"
# Override CutterPlugin methods
def __init__(self): def __init__(self):
super(CutterSamplePlugin, self).__init__() super(CutterSamplePlugin, self).__init__()
self.disassembly_actions = []
self.addressable_item_actions = []
self.disas_action = None
self.addr_submenu = None
self.main = None
def setupPlugin(self): def setupPlugin(self):
pass pass
def setupInterface(self, main): def setupInterface(self, main):
# Dock widget
action = QAction("Sample Python Plugin", main) action = QAction("Sample Python Plugin", main)
action.setCheckable(True) action.setCheckable(True)
widget = FortuneWidget(main, action) widget = FortuneWidget(main, action)
main.addPluginDockWidget(widget, action) main.addPluginDockWidget(widget, action)
# Dissassembly context menu
menu = main.getContextMenuExtensions(cutter.MainWindow.ContextMenuType.Disassembly)
self.disas_action = menu.addAction("CutterSamplePlugin dissassembly action")
self.disas_action.triggered.connect(self.handle_disassembler_action)
self.main = main
# Context menu for tables with addressable items like Flags,Functions,Strings,Search results,...
addressable_item_menu = main.getContextMenuExtensions(cutter.MainWindow.ContextMenuType.Addressable)
self.addr_submenu = addressable_item_menu.addMenu("CutterSamplePlugin") # create submenu
adrr_action = self.addr_submenu.addAction("Action 1")
self.addr_submenu.addSeparator() # can use separator and other qt functionality
adrr_action2 = self.addr_submenu.addAction("Action 2")
adrr_action.triggered.connect(self.handle_addressable_item_action)
adrr_action2.triggered.connect(self.handle_addressable_item_action)
def terminate(self): # optional def terminate(self): # optional
print("CutterSamplePlugin shutting down") print("CutterSamplePlugin shutting down")
if self.main:
menu = self.main.getContextMenuExtensions(cutter.MainWindow.ContextMenuType.Disassembly)
menu.removeAction(self.disas_action)
addressable_item_menu = self.main.getContextMenuExtensions(cutter.MainWindow.ContextMenuType.Addressable)
submenu_action = self.addr_submenu.menuAction()
addressable_item_menu.removeAction(submenu_action)
print("CutterSamplePlugin finished clean up")
# Plugin methods
def handle_addressable_item_action(self):
# for actions in plugin menu Cutter sets data to current item address
submenu_action = self.addr_submenu.menuAction()
cutter.message("Context menu action callback 0x{:x}".format(submenu_action.data()))
def handle_disassembler_action(self):
# for actions in plugin menu Cutter sets data to address for current dissasembly line
cutter.message("Dissasembly menu action callback 0x{:x}".format(self.disas_action.data()))
# This function will be called by Cutter and should return an instance of the plugin. # This function will be called by Cutter and should return an instance of the plugin.