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.