diff --git a/src/CutterApplication.cpp b/src/CutterApplication.cpp index 5d1494a6..3c482ef9 100644 --- a/src/CutterApplication.cpp +++ b/src/CutterApplication.cpp @@ -155,7 +155,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc Plugins()->loadPlugins(); - for (auto *plugin : Plugins()->getPlugins()) { + for (auto &plugin : Plugins()->getPlugins()) { plugin->registerDecompilers(); } @@ -248,9 +248,7 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc CutterApplication::~CutterApplication() { -#ifdef CUTTER_ENABLE_PYTHON Plugins()->destroyPlugins(); -#endif delete mainWindow; #ifdef CUTTER_ENABLE_PYTHON Python()->shutdown(); diff --git a/src/bindings/bindings.xml b/src/bindings/bindings.xml index f4065235..eff5b060 100644 --- a/src/bindings/bindings.xml +++ b/src/bindings/bindings.xml @@ -7,11 +7,11 @@ - + diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 862508fd..26adeece 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -209,13 +209,6 @@ void CutterCore::initialize() // Initialize Async tasks manager 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() @@ -2301,39 +2294,6 @@ QStringList CutterCore::getAnalPluginNames() 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() { CORE_LOCK(); diff --git a/src/core/Cutter.h b/src/core/Cutter.h index b5eed6c6..42cef4ee 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -425,17 +425,6 @@ public: QStringList getAsmPluginNames(); 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 */ QStringList getProjectNames(); void openProject(const QString &name); @@ -649,9 +638,6 @@ private: QSharedPointer debugTask; R2TaskDialog *debugTaskDialog; - - QMenu *disassemblyContextMenuExtensions = nullptr; - QMenu *addressableContextMenuExtensions = nullptr; }; class RCoreLocked diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 7c428bb9..d56fe112 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -132,6 +132,10 @@ void MainWindow::initUI() { 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->actionExtraDisassembly, &QAction::triggered, this, &MainWindow::addExtraDisassembly); connect(ui->actionExtraHexdump, &QAction::triggered, this, &MainWindow::addExtraHexdump); @@ -193,7 +197,7 @@ void MainWindow::initUI() initBackForwardMenu(); /* Setup plugins interfaces */ - for (auto plugin : Plugins()->getPlugins()) { + for (auto &plugin : Plugins()->getPlugins()) { plugin->setupInterface(this); } @@ -1660,3 +1664,15 @@ void MainWindow::onZoomReset() { Config()->setZoomFactor(1.0); } + +QMenu *MainWindow::getContextMenuExtensions(ContextMenuType type) +{ + switch (type) { + case ContextMenuType::Disassembly: + return disassemblyContextMenuExtensions; + case ContextMenuType::Addressable: + return addressableContextMenuExtensions; + default: + return nullptr; + } +} diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index 5ef23b30..a180e0f3 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -125,6 +125,15 @@ public: void setCurrentMemoryWidget(MemoryDockWidget* memoryWidget); 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: void finalizeOpen(); @@ -263,6 +272,9 @@ private: QDockWidget *breakpointDock = nullptr; QDockWidget *registerRefsDock = nullptr; + QMenu *disassemblyContextMenuExtensions = nullptr; + QMenu *addressableContextMenuExtensions = nullptr; + void initUI(); void initToolBar(); void initDocks(); diff --git a/src/dialogs/preferences/PluginsOptionsWidget.cpp b/src/dialogs/preferences/PluginsOptionsWidget.cpp index 32ccda74..f5c8b2b1 100644 --- a/src/dialogs/preferences/PluginsOptionsWidget.cpp +++ b/src/dialogs/preferences/PluginsOptionsWidget.cpp @@ -39,7 +39,7 @@ PluginsOptionsWidget::PluginsOptionsWidget(PreferencesDialog *dialog) tr("Author") }); - for (CutterPlugin *plugin : Plugins()->getPlugins()) { + for (auto &plugin : Plugins()->getPlugins()) { auto item = new QTreeWidgetItem(); item->setText(0, plugin->getName()); item->setText(1, plugin->getDescription()); diff --git a/src/menus/AddressableItemContextMenu.cpp b/src/menus/AddressableItemContextMenu.cpp index 48504edf..0efd9256 100644 --- a/src/menus/AddressableItemContextMenu.cpp +++ b/src/menus/AddressableItemContextMenu.cpp @@ -40,8 +40,8 @@ AddressableItemContextMenu::AddressableItemContextMenu(QWidget *parent, MainWind addAction(&actionAddcomment); addSeparator(); - pluginMenu = Core()->getContextMenuExtensions(CutterCore::ContextMenuType::Addressable); - addMenu(pluginMenu); + pluginMenu = mainWindow->getContextMenuExtensions(MainWindow::ContextMenuType::Addressable); + pluginMenuAction = addMenu(pluginMenu); addSeparator(); setHasTarget(hasTarget); @@ -105,6 +105,7 @@ void AddressableItemContextMenu::aboutToShowSlot() } actionShowInMenu.setMenu(mainWindow->createShowInMenu(this, offset)); + pluginMenuAction->setVisible(!pluginMenu->isEmpty()); for (QAction *pluginAction : pluginMenu->actions()) { pluginAction->setData(QVariant::fromValue(offset)); } diff --git a/src/menus/AddressableItemContextMenu.h b/src/menus/AddressableItemContextMenu.h index d23d3d47..21fdb066 100644 --- a/src/menus/AddressableItemContextMenu.h +++ b/src/menus/AddressableItemContextMenu.h @@ -32,6 +32,7 @@ private: virtual void aboutToShowSlot(); QMenu *pluginMenu; + QAction *pluginMenuAction; MainWindow *mainWindow; RVA offset; diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index 5c012204..654ea0e3 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -156,8 +156,8 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main addSeparator(); - pluginMenu = Core()->getContextMenuExtensions(CutterCore::ContextMenuType::Disassembly); - addMenu(pluginMenu); + pluginMenu = mainWindow->getContextMenuExtensions(MainWindow::ContextMenuType::Disassembly); + pluginActionMenuAction = addMenu(pluginMenu); addSeparator(); @@ -529,6 +529,7 @@ void DisassemblyContextMenu::aboutToShowSlot() QString progCounterName = Core()->getRegisterName("PC").toUpper(); actionSetPC.setText("Set " + progCounterName + " here"); + pluginActionMenuAction->setVisible(!pluginMenu->isEmpty()); for (QAction *pluginAction : pluginMenu->actions()) { pluginAction->setData(QVariant::fromValue(offset)); } diff --git a/src/menus/DisassemblyContextMenu.h b/src/menus/DisassemblyContextMenu.h index 0375ccc4..c0a42c4b 100644 --- a/src/menus/DisassemblyContextMenu.h +++ b/src/menus/DisassemblyContextMenu.h @@ -182,6 +182,7 @@ private: QAction showInSubmenu; QList showTargetMenuActions; + QAction *pluginActionMenuAction; // For creating anonymous entries (that are always visible) QAction *addAnonymousAction(QString name, const char *slot, QKeySequence shortcut); diff --git a/src/plugins/PluginManager.cpp b/src/plugins/PluginManager.cpp index 62ecd5e0..15b5a9d0 100644 --- a/src/plugins/PluginManager.cpp +++ b/src/plugins/PluginManager.cpp @@ -33,7 +33,7 @@ PluginManager::~PluginManager() void PluginManager::loadPlugins() { - assert(plugins.isEmpty()); + assert(plugins.empty()); QString userPluginDir = getUserPluginsDirectory(); if (!userPluginDir.isEmpty()) { @@ -51,7 +51,7 @@ void PluginManager::loadPlugins() void PluginManager::loadPluginsFromDir(const QDir &pluginsDir, bool writable) { qInfo() << "Plugins are loaded from" << pluginsDir.absolutePath(); - int loadedPlugins = plugins.length(); + int loadedPlugins = plugins.size(); if (!pluginsDir.exists()) { return; } @@ -74,16 +74,19 @@ void PluginManager::loadPluginsFromDir(const QDir &pluginsDir, bool writable) } #endif - loadedPlugins = plugins.length() - loadedPlugins; + loadedPlugins = plugins.size() - loadedPlugins; qInfo() << "Loaded" << loadedPlugins << "plugin(s)."; } +void PluginManager::PluginTerminator::operator()(CutterPlugin *plugin) const +{ + plugin->terminate(); + delete plugin; +} + void PluginManager::destroyPlugins() { - for (CutterPlugin *plugin : plugins) { - plugin->terminate(); - delete plugin; - } + plugins.clear(); } QVector PluginManager::getPluginDirectories() const @@ -144,12 +147,12 @@ void PluginManager::loadNativePlugins(const QDir &directory) } continue; } - CutterPlugin *cutterPlugin = qobject_cast(plugin); + PluginPtr cutterPlugin{qobject_cast(plugin)}; if (!cutterPlugin) { continue; } cutterPlugin->setupPlugin(); - plugins.append(cutterPlugin); + plugins.push_back(std::move(cutterPlugin)); } } @@ -169,12 +172,12 @@ void PluginManager::loadPythonPlugins(const QDir &directory) } else { moduleName = fileName; } - CutterPlugin *cutterPlugin = loadPythonPlugin(moduleName.toLocal8Bit().constData()); + PluginPtr cutterPlugin{loadPythonPlugin(moduleName.toLocal8Bit().constData())}; if (!cutterPlugin) { continue; } cutterPlugin->setupPlugin(); - plugins.append(cutterPlugin); + plugins.push_back(std::move(cutterPlugin)); } PythonManager::ThreadHolder threadHolder; diff --git a/src/plugins/PluginManager.h b/src/plugins/PluginManager.h index 2c0c8071..4310e196 100644 --- a/src/plugins/PluginManager.h +++ b/src/plugins/PluginManager.h @@ -4,6 +4,8 @@ #include #include +#include +#include class CutterPlugin; @@ -14,6 +16,13 @@ Q_OBJECT public: static PluginManager *getInstance(); + class PluginTerminator + { + public: + void operator()(CutterPlugin*) const; + }; + using PluginPtr = std::unique_ptr; + PluginManager(); ~PluginManager(); @@ -27,13 +36,13 @@ public: */ void destroyPlugins(); - const QList &getPlugins() { return plugins; } + const std::vector &getPlugins() { return plugins; } QVector getPluginDirectories() const; QString getUserPluginsDirectory() const; private: - QList plugins; + std::vector plugins; void loadNativePlugins(const QDir &directory); void loadPluginsFromDir(const QDir &pluginsDir, bool writable = false); diff --git a/src/plugins/sample-python/sample_python.py b/src/plugins/sample-python/sample_python.py index 34eb208a..7165d4d0 100644 --- a/src/plugins/sample-python/sample_python.py +++ b/src/plugins/sample-python/sample_python.py @@ -42,25 +42,66 @@ class FortuneWidget(cutter.CutterDockWidget): class CutterSamplePlugin(cutter.CutterPlugin): - name = "SamplePlugin" + name = "Sample Plugin" description = "A sample plugin written in python." - version = "1.0" - author = "xarkes and thestr4ng3r :-P" + version = "1.1" + author = "Cutter developers" + + # Override CutterPlugin methods def __init__(self): 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): pass def setupInterface(self, main): + # Dock widget action = QAction("Sample Python Plugin", main) action.setCheckable(True) widget = FortuneWidget(main, 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 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.