* Copyright (c) 2020-2023 Google, Inc. All rights reserved.
* Copyright (c) 2013 Branden Clark All rights reserved.
* ***************************************************************************/
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Google, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
*
* Provides a main structure for users to interface with tools.
*/
#ifdef __CLASS__
# undef __CLASS__
#endif
#define __CLASS__ "drgui_main_window_t::"
#include <QMenu>
#include <QAction>
#include <QString>
#include <QTabWidget>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QActionGroup>
#include <QSettings>
#include <QPluginLoader>
#include <QStatusBar>
#include <QMenuBar>
#include <QCloseEvent>
#include <QDebug>
#include <QApplication>
#include <cassert>
#include "drgui_tool_interface.h"
#include "drgui_options_window.h"
#include "drgui_main_window.h"
namespace dynamorio {
namespace drgui {
* Constructor, everything begins here
*/
drgui_main_window_t::drgui_main_window_t(QString tool_name, QStringList tool_args)
: tool_to_auto_load(tool_name)
, tool_to_auto_load_args(tool_args)
{
qDebug().nospace() << "INFO: Entering " << __CLASS__ << __FUNCTION__;
tab_area = new QTabWidget(this);
tab_area->setTabsClosable(true);
tab_area->setMovable(true);
connect(tab_area, SIGNAL(tabCloseRequested(int)), this, SLOT(maybe_close(int)));
setCentralWidget(tab_area);
connect(tab_area, SIGNAL(currentChanged(int)), this, SLOT(update_menus()));
create_actions();
create_menus();
create_status_bar();
update_menus();
opt_win = new drgui_options_window_t(tool_action_group);
read_settings();
load_tools();
setWindowTitle(tr("DrGUI"));
setUnifiedTitleAndToolBarOnMac(true);
}
* Destructor
*/
drgui_main_window_t::~drgui_main_window_t(void)
{
qDebug().nospace() << "INFO: Entering " << __CLASS__ << __FUNCTION__;
while (plugins.count() > 0) {
drgui_tool_interface_t *plugin = plugins.back();
plugins.pop_back();
delete plugin;
}
delete opt_win;
}
* Handles closing of all tabs
*/
void
drgui_main_window_t::closeEvent(QCloseEvent *event)
{
qDebug().nospace() << "INFO: Entering " << __CLASS__ << __FUNCTION__;
close_all_tabs();
if (tab_area->currentWidget() != NULL) {
event->ignore();
} else {
write_settings();
event->accept();
qApp->quit();
}
}
* Shows About page for this program
*/
void
drgui_main_window_t::about(void)
{
QMessageBox::about(this, tr("About Dr. GUI"),
tr("<center><b>Dr. GUI</b></center><br>"
"Interface for DynamoRIO and various extensions"));
}
* Updates the menus to reflect current tab's abilities
*/
void
drgui_main_window_t::update_menus(void)
{
bool has_tool_base = (active_tool() != 0);
close_act->setEnabled(has_tool_base);
close_all_act->setEnabled(has_tool_base);
next_act->setEnabled(has_tool_base);
previous_act->setEnabled(has_tool_base);
separator_act->setVisible(has_tool_base);
}
* Updates the Window menu to reflect current tab's abilities
*/
void
drgui_main_window_t::update_window_menu(void)
{
window_menu->clear();
window_menu->addAction(close_act);
window_menu->addAction(close_all_act);
window_menu->addSeparator();
window_menu->addAction(next_act);
window_menu->addAction(previous_act);
window_menu->addAction(separator_act);
separator_act->setVisible(tab_area->currentWidget() != NULL);
static const int MAX_SHORTCUT_KEY = 9;
for (int i = 0; i < tab_area->count(); ++i) {
QWidget *tool = qobject_cast<QWidget *>(tab_area->widget(i));
QString text;
if (i < MAX_SHORTCUT_KEY) {
text = tr("&%1 %2").arg(i + 1).arg(tab_area->tabText(i));
} else {
text = tr("%1 %2").arg(i + 1).arg(tab_area->tabText(i));
}
QAction *action = window_menu->addAction(text);
action->setCheckable(true);
action->setChecked(tool == active_tool());
connect(action, &QAction::triggered,
[=]() { emit tab_area->setCurrentIndex(i); });
}
}
* Creates and connects the actions for the mainwindow
*/
void
drgui_main_window_t::create_actions(void)
{
separator_act = new QAction(this);
separator_act->setSeparator(true);
exit_act = new QAction(tr("E&xit"), this);
exit_act->setShortcuts(QKeySequence::Quit);
exit_act->setStatusTip(tr("Exit the application"));
connect(exit_act, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));
preferences_act = new QAction(tr("&Preferences"), this);
preferences_act->setStatusTip(tr("Edit Preferences"));
connect(preferences_act, SIGNAL(triggered()), this, SLOT(show_preferences_dialog()));
close_act = new QAction(tr("Cl&ose"), this);
close_act->setStatusTip(tr("Close the active tab"));
connect(close_act, SIGNAL(triggered()), this, SLOT(maybe_close_me()));
close_all_act = new QAction(tr("Close &All"), this);
close_all_act->setStatusTip(tr("Close all the tabs"));
connect(close_all_act, SIGNAL(triggered()), this, SLOT(close_all_tabs()));
next_act = new QAction(tr("Ne&xt"), this);
next_act->setShortcuts(QKeySequence::NextChild);
next_act->setStatusTip(tr("Move the focus to the next tab"));
connect(next_act, SIGNAL(triggered()), this, SLOT(activate_next_tab()));
previous_act = new QAction(tr("Pre&vious"), this);
previous_act->setShortcuts(QKeySequence::PreviousChild);
previous_act->setStatusTip(tr("Move the focus to the previous tab"));
connect(previous_act, SIGNAL(triggered()), this, SLOT(activate_previous_tab()));
tool_action_group = new QActionGroup(this);
load_tools_act = new QAction(tr("Load tools"), this);
load_tools_act->setStatusTip(tr("Choose another directory to search for tools"));
connect(load_tools_act, SIGNAL(triggered()), this, SLOT(add_tool()));
about_act = new QAction(tr("&About"), this);
about_act->setStatusTip(tr("Show the application's About box"));
connect(about_act, SIGNAL(triggered()), this, SLOT(about()));
about_qt_act = new QAction(tr("About &Qt"), this);
about_qt_act->setStatusTip(tr("Show the Qt library's About box"));
connect(about_qt_act, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}
* Creates the menus in the menu bar of the mainwindow
*/
void
drgui_main_window_t::create_menus(void)
{
file_menu = menuBar()->addMenu(tr("&File"));
file_menu->addSeparator();
QAction *action = file_menu->addAction(tr("Switch layout direction"));
connect(action, SIGNAL(triggered()), this, SLOT(switch_layout_direction()));
file_menu->addAction(exit_act);
edit_menu = menuBar()->addMenu(tr("&Edit"));
edit_menu->addAction(preferences_act);
window_menu = menuBar()->addMenu(tr("&Window"));
update_window_menu();
connect(window_menu, SIGNAL(aboutToShow()), this, SLOT(update_window_menu()));
menuBar()->addSeparator();
tool_menu = menuBar()->addMenu(tr("&Tools"));
* future tools to this menu. If a change is made here, it should be
* reflected in add_tool_to_menu()
*/
tool_menu->addSeparator();
tool_menu->addAction(load_tools_act);
help_menu = menuBar()->addMenu(tr("&Help"));
help_menu->addAction(about_act);
help_menu->addAction(about_qt_act);
}
* Creates status bar for displaying status tips
*/
void
drgui_main_window_t::create_status_bar(void)
{
statusBar()->showMessage(tr("Ready"));
}
* Loads settings for mainwindow
*/
void
drgui_main_window_t::read_settings(void)
{
qDebug().nospace() << "INFO: Entering " << __CLASS__ << __FUNCTION__;
* issues as DrGUI will become a registry key on Windows, and
* a filename on *nix.
*/
QSettings settings("DynamoRIO", "DrGUI");
QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("size", QSize(800, 600)).toSize();
settings.beginGroup("Tools_to_load");
int count = settings.value("Number_of_tools", 0).toInt();
for (int i = 0; i < count; i++) {
tool_files << settings.value(QString::number(i), QString()).toString();
}
settings.endGroup();
move(pos);
resize(size);
* list of tools to be loaded if it is a file.
*/
QFile tool_file(tool_to_auto_load);
if (tool_file.exists())
tool_files << tool_file.fileName();
;
}
* Saves settings for mainwindow
*/
void
drgui_main_window_t::write_settings(void)
{
qDebug().nospace() << "INFO: Entering " << __CLASS__ << __FUNCTION__;
QSettings settings("DynamoRIO", "DrGUI");
settings.setValue("pos", pos());
settings.setValue("size", size());
settings.beginGroup("Tools_to_load");
settings.setValue("Number_of_tools", tool_files.count());
for (int i = 0; i < tool_files.count(); i++) {
settings.setValue(QString::number(i), tool_files.at(i));
}
settings.endGroup();
}
* Switches direction of layout for mainwindow
*/
void
drgui_main_window_t::switch_layout_direction(void)
{
if (layoutDirection() == Qt::LeftToRight)
qApp->setLayoutDirection(Qt::RightToLeft);
else
qApp->setLayoutDirection(Qt::LeftToRight);
}
* Finds and returns the active tool tab, if there is one
*/
QWidget *
drgui_main_window_t::active_tool(void)
{
int active_tab = tab_area->currentIndex();
if (active_tab != -1)
return qobject_cast<QWidget *>(tab_area->currentWidget());
return 0;
}
* Closes every tab in mainwindow
*/
void
drgui_main_window_t::close_all_tabs(void)
{
while (tab_area->count() > 0) {
tab_area->removeTab(0);
}
}
* Helper for closing current tab
*/
void
drgui_main_window_t::maybe_close_me(void)
{
maybe_close(tab_area->currentIndex());
}
* Confirms closing of a tab
*/
void
drgui_main_window_t::maybe_close(int index)
{
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(
this, tr("Confirm"),
tr("Are you sure you want to close '%1'?").arg(tab_area->tabText(index)),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (ret == QMessageBox::Yes)
hide_tab(index);
}
* Closes the tab at index
*/
void
drgui_main_window_t::hide_tab(int index)
{
tab_area->removeTab(index);
}
* Moves view to next tab in order, circular
*/
void
drgui_main_window_t::activate_next_tab(void)
{
int index = tab_area->currentIndex() + 1;
if (index == tab_area->count())
index = 0;
tab_area->setCurrentIndex(index);
}
* Moves view to previous tab in order, wraps around
*/
void
drgui_main_window_t::activate_previous_tab(void)
{
int index = tab_area->currentIndex() - 1;
if (index == -1)
index = tab_area->count() - 1;
tab_area->setCurrentIndex(index);
}
* Displays preferences dialog
*/
void
drgui_main_window_t::show_preferences_dialog(void)
{
if (opt_win != NULL) {
opt_win->display();
}
}
* Creates a new instance of a tool and displays it in the tab interface
*/
void
drgui_main_window_t::add_tab(void)
{
QAction *action = qobject_cast<QAction *>(sender());
QWidget *tool =
qobject_cast<drgui_tool_interface_t *>(action->parent())->create_instance();
const QString tool_name = action->text();
new_tool_instance(tool, tool_name);
}
* Creates a new instance of a tool with the specified arguments and displays
* it in the tab interface.
*/
void
drgui_main_window_t::add_tab(drgui_tool_interface_t *factory, const QStringList &args)
{
QWidget *tool = factory->create_instance(args);
const QString tool_name = factory->tool_names().front();
new_tool_instance(tool, tool_name);
}
* Lets user choose more tools to load
*/
void
drgui_main_window_t::add_tool(void)
{
qDebug().nospace() << "INFO: Entering " << __CLASS__ << __FUNCTION__;
QStringList test_locs;
test_locs = QFileDialog::getOpenFileNames(this, tr("Load tool"), "",
tr("Tools (*.so *.dll)"));
foreach (QString test_loc, test_locs) {
QFile test_file(test_loc);
if (test_file.exists() && !tool_files.contains(test_loc)) {
tool_files << test_loc;
load_tools();
}
}
}
* Loads available tools
*/
void
drgui_main_window_t::load_tools(void)
{
foreach (QString tool_loc, tool_files) {
QFile tool_file(tool_loc);
QPluginLoader loader(tool_file.fileName(), this);
QObject *plugin = loader.instance();
if (plugin != NULL) {
drgui_tool_interface_t *i_tool;
i_tool = qobject_cast<drgui_tool_interface_t *>(plugin);
bool stop = false;
foreach (QString name, i_tool->tool_names()) {
if (plugin_names.contains(name)) {
stop = true;
break;
}
}
if (stop)
break;
connect(i_tool, SIGNAL(new_instance_requested(QWidget *, QString)), this,
SLOT(new_tool_instance(QWidget *, QString)));
if (i_tool != NULL) {
add_tool_to_menu(plugin, i_tool->tool_names(), SLOT(add_tab()),
tool_action_group);
plugins.append(i_tool);
qDebug() << "INFO: Loaded Plugins" << i_tool->tool_names();
plugin_names << i_tool->tool_names();
if (i_tool->tool_names().contains(tool_to_auto_load) ||
tool_to_auto_load == tool_loc) {
add_tab(i_tool, tool_to_auto_load_args);
}
}
} else {
qDebug() << "INFO: Denied Plugins" << loader.errorString();
}
}
}
* Adds a tool to tools_menu
*/
void
drgui_main_window_t::add_tool_to_menu(QObject *plugin, const QStringList &texts,
const char *member, QActionGroup *action_group)
{
foreach (QString text, texts) {
QAction *action = new QAction(text, plugin);
connect(action, SIGNAL(triggered()), this, member);
* and 'Load tools' actions. If this is changed then create_menus()
* should be updated.
*/
const QList<QAction *> &action_list = tool_menu->actions();
static const unsigned int num_actions_to_skip = 2;
const unsigned int pos = action_list.count() - num_actions_to_skip;
assert(pos >= 0);
tool_menu->insertAction(action_list.at(pos), action);
if (action_group != NULL) {
action->setCheckable(true);
action_group->addAction(action);
}
}
}
* Displays the new tool instance in the tab interface
*/
void
drgui_main_window_t::new_tool_instance(QWidget *tool, QString tool_name)
{
tab_area->setCurrentIndex(tab_area->addTab(tool, tool_name));
}
}
}