cutter/src/CutterApplication.cpp

312 lines
10 KiB
C++
Raw Normal View History

#include "CutterApplication.h"
#include <QApplication>
#include <QFileOpenEvent>
#include <QEvent>
#include <QMenu>
#include <QMessageBox>
#include <QCommandLineParser>
#include <QTextCodec>
#include <QStringList>
#include <QProcess>
#include <QPluginLoader>
#include <QDir>
2018-10-31 16:07:53 +00:00
#include <QTranslator>
#include <QLibraryInfo>
2018-03-06 17:21:42 +00:00
#ifdef CUTTER_ENABLE_JUPYTER
2018-10-17 07:55:53 +00:00
#include "common/JupyterConnection.h"
2018-03-06 17:21:42 +00:00
#endif
#include "plugins/CutterPlugin.h"
2018-03-06 17:21:42 +00:00
#include "CutterConfig.h"
#include <cstdlib>
2018-03-21 20:32:32 +00:00
CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc, argv)
{
// Setup application information
setApplicationVersion(CUTTER_VERSION_FULL);
setWindowIcon(QIcon(":/img/cutter.svg"));
setAttribute(Qt::AA_DontShowIconsInMenus);
2019-01-12 19:44:23 +00:00
setLayoutDirection(Qt::LeftToRight);
// WARN!!! Put initialization code below this line. Code above this line is mandatory to be run First
2018-11-01 22:23:01 +00:00
// Load translations
if (!loadTranslations()) {
qWarning() << "Cannot load translations";
2018-10-31 16:07:53 +00:00
}
2018-11-01 22:23:01 +00:00
// Load fonts
int ret = QFontDatabase::addApplicationFont(":/fonts/Anonymous Pro.ttf");
if (ret == -1) {
qWarning() << "Cannot load Anonymous Pro font.";
}
ret = QFontDatabase::addApplicationFont(":/fonts/Inconsolata-Regular.ttf");
if (ret == -1) {
qWarning() << "Cannot load Incosolata-Regular font.";
}
// Set QString codec to UTF-8
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
#endif
QCommandLineParser cmd_parser;
2018-03-21 20:32:32 +00:00
cmd_parser.setApplicationDescription(
QObject::tr("A Qt and C++ GUI for radare2 reverse engineering framework"));
cmd_parser.addHelpOption();
cmd_parser.addVersionOption();
cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open."));
QCommandLineOption analOption({"A", "anal"},
2018-03-21 20:32:32 +00:00
QObject::tr("Automatically open file and optionally start analysis. Needs filename to be specified. May be a value between 0 and 2: 0 = no analysis, 1 = aaa, 2 = aaaa (experimental)"),
QObject::tr("level"));
cmd_parser.addOption(analOption);
2018-08-18 16:09:49 +00:00
QCommandLineOption scriptOption("i",
QObject::tr("Run script file"),
QObject::tr("file"));
cmd_parser.addOption(scriptOption);
2018-03-06 17:21:42 +00:00
#ifdef CUTTER_ENABLE_JUPYTER
2018-03-21 20:32:32 +00:00
QCommandLineOption pythonHomeOption("pythonhome", QObject::tr("PYTHONHOME to use for Jupyter"),
"PYTHONHOME");
2018-03-06 17:21:42 +00:00
cmd_parser.addOption(pythonHomeOption);
#endif
cmd_parser.process(*this);
QStringList args = cmd_parser.positionalArguments();
// Check r2 version
QString r2version = r_core_version();
QString localVersion = "" R2_GITTAP;
2018-03-21 20:32:32 +00:00
if (r2version != localVersion) {
QMessageBox msg;
msg.setIcon(QMessageBox::Critical);
msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msg.setWindowTitle(QObject::tr("Version mismatch!"));
2018-03-21 20:32:32 +00:00
msg.setText(QString(
QObject::tr("The version used to compile Cutter (%1) does not match the binary version of radare2 (%2). This could result in unexpected behaviour. Are you sure you want to continue?")).arg(
localVersion, r2version));
if (msg.exec() == QMessageBox::No) {
std::exit(1);
}
}
2018-03-06 17:21:42 +00:00
#ifdef CUTTER_ENABLE_JUPYTER
2018-03-21 20:32:32 +00:00
if (cmd_parser.isSet(pythonHomeOption)) {
2018-03-06 17:21:42 +00:00
Jupyter()->setPythonHome(cmd_parser.value(pythonHomeOption));
}
#endif
bool analLevelSpecified = false;
2018-03-21 20:32:32 +00:00
int analLevel = 0;
// Initialize CutterCore and set default settings
Core()->setSettings();
2018-03-21 20:32:32 +00:00
if (cmd_parser.isSet(analOption)) {
analLevel = cmd_parser.value(analOption).toInt(&analLevelSpecified);
2018-03-21 20:32:32 +00:00
if (!analLevelSpecified || analLevel < 0 || analLevel > 2) {
printf("%s\n",
QObject::tr("Invalid Analysis Level. May be a value between 0 and 2.").toLocal8Bit().constData());
std::exit(1);
}
}
2018-02-27 13:06:04 +00:00
mainWindow = new MainWindow();
installEventFilter(mainWindow);
// set up context menu shortcut display fix
#if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
setStyle(new CutterProxyStyle());
#endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
2018-03-21 20:32:32 +00:00
if (args.empty()) {
if (analLevelSpecified) {
printf("%s\n",
QObject::tr("Filename must be specified to start analysis automatically.").toLocal8Bit().constData());
std::exit(1);
}
2019-01-19 20:54:02 +00:00
// check if this is the first execution of Cutter in this computer
// Note: the execution after the preferences benn reset, will be considered as first-execution
if (Config()->isFirstExecution()) {
mainWindow->displayWelcomeDialog();
}
2018-02-27 13:06:04 +00:00
mainWindow->displayNewFileDialog();
2018-03-21 20:32:32 +00:00
} else { // filename specified as positional argument
InitialOptions options;
options.filename = args[0];
if (analLevelSpecified) {
switch (analLevel) {
case 0:
default:
options.analCmd = {};
break;
case 1:
options.analCmd = { "aaa" };
break;
case 2:
options.analCmd = { "aaaa" };
break;
}
}
2018-08-18 16:09:49 +00:00
options.script = cmd_parser.value(scriptOption);
mainWindow->openNewFile(options, analLevelSpecified);
}
// Load plugins
loadPlugins();
#ifdef CUTTER_APPVEYOR_R2DEC
qputenv("R2DEC_HOME", "radare2\\lib\\plugins\\r2dec-js");
#endif
}
2018-02-27 13:06:04 +00:00
CutterApplication::~CutterApplication()
{
delete mainWindow;
}
bool CutterApplication::event(QEvent *e)
{
2018-03-21 20:32:32 +00:00
if (e->type() == QEvent::FileOpen) {
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(e);
2018-03-21 20:32:32 +00:00
if (openEvent) {
if (m_FileAlreadyDropped) {
// We already dropped a file in macOS, let's spawn another instance
// (Like the File -> Open)
QString fileName = openEvent->file();
QProcess process(this);
process.setEnvironment(QProcess::systemEnvironment());
QStringList args = QStringList(fileName);
process.startDetached(qApp->applicationFilePath(), args);
2018-03-21 20:32:32 +00:00
} else {
QString fileName = openEvent->file();
m_FileAlreadyDropped = true;
2018-02-27 13:06:04 +00:00
mainWindow->closeNewFileDialog();
InitialOptions options;
options.filename = fileName;
mainWindow->openNewFile(options);
}
}
}
return QApplication::event(e);
}
void CutterApplication::loadPlugins()
{
2018-09-30 20:00:53 +00:00
QList<CutterPlugin *> plugins;
QDir pluginsDir(qApp->applicationDirPath());
2018-09-30 20:00:53 +00:00
#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
2018-11-05 21:51:27 +00:00
if (!pluginsDir.cd("plugins")) {
return;
2018-11-05 21:51:27 +00:00
}
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
CutterPlugin *cutterPlugin = qobject_cast<CutterPlugin *>(plugin);
if (cutterPlugin) {
cutterPlugin->setupPlugin(Core());
plugins.append(cutterPlugin);
}
}
}
Core()->setCutterPlugins(plugins);
}
bool CutterApplication::loadTranslations()
{
const QString &language = Config()->getCurrLocale().bcp47Name();
if (language == QStringLiteral("en") || language.startsWith(QStringLiteral("en-"))) {
return true;
}
const auto &allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript,
QLocale::AnyCountry);
bool cutterTrLoaded = false;
for (const QLocale &it : allLocales) {
const QString &langPrefix = it.bcp47Name();
if (langPrefix == language) {
QApplication::setLayoutDirection(it.textDirection());
QLocale::setDefault(it);
QTranslator *trCutter = new QTranslator;
QTranslator *trQtBase = new QTranslator;
QTranslator *trQt = new QTranslator;
const QStringList &cutterTrPaths = Config()->getTranslationsDirectories();
for (const auto &trPath : cutterTrPaths) {
if (trCutter && trCutter->load(it, QLatin1String("cutter"), QLatin1String("_"), trPath)) {
installTranslator(trCutter);
cutterTrLoaded = true;
trCutter = nullptr;
}
if (trQt && trQt->load(it, "qt", "_", trPath)) {
installTranslator(trQt);
trQt = nullptr;
}
if (trQtBase && trQtBase->load(it, "qtbase", "_", trPath)) {
installTranslator(trQtBase);
trQtBase = nullptr;
}
}
if (trCutter) {
delete trCutter;
}
if (trQt) {
delete trQt;
}
if (trQtBase) {
delete trQtBase;
}
return true;
}
}
if (!cutterTrLoaded) {
qWarning() << "Cannot load Cutter's translation for " << language;
}
return false;
}
void CutterProxyStyle::polish(QWidget *widget)
{
QProxyStyle::polish(widget);
#if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
// HACK: This is the only way I've found to force Qt (5.10 and newer) to
// display shortcuts in context menus on all platforms. It's ugly,
// but it gets the job done.
if (auto menu = qobject_cast<QMenu*>(widget)) {
const auto &actions = menu->actions();
for (auto action : actions) {
action->setShortcutVisibleInContextMenu(true);
}
}
#endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
}