diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 358ed0f49..a53357a76 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -1,796 +1,796 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #if defined(QT_STATICPLUGIN) #include #if QT_VERSION < 0x050400 Q_IMPORT_PLUGIN(AccessibleFactory) #endif #if defined(QT_QPA_PLATFORM_XCB) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_WINDOWS) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif #include // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool *) Q_DECLARE_METATYPE(Amount) Q_DECLARE_METATYPE(uint256) // Config is non-copyable so we can only register pointers to it Q_DECLARE_METATYPE(Config *) static void InitMessage(const std::string &message) { LogPrintf("init message: %s\n", message); } /** * Translate string to current locale using Qt. */ static std::string Translate(const char *psz) { return QCoreApplication::translate("bitcoin-abc", psz).toStdString(); } static QString GetLangTerritory() { QSettings settings; // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = QLocale::system().name(); // 2) Language from QSettings QString lang_territory_qsettings = settings.value("language", "").toString(); if (!lang_territory_qsettings.isEmpty()) { lang_territory = lang_territory_qsettings; } // 3) -lang command line argument lang_territory = QString::fromStdString( gArgs.GetArg("-lang", lang_territory.toStdString())); return lang_territory; } /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { // Remove old translators QApplication::removeTranslator(&qtTranslatorBase); QApplication::removeTranslator(&qtTranslator); QApplication::removeTranslator(&translatorBase); QApplication::removeTranslator(&translator); // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = GetLangTerritory(); // Convert to "de" only by truncating "_DE" QString lang = lang_territory; lang.truncate(lang_territory.lastIndexOf('_')); // Load language files for configured locale: // - First load the translator for the base language, without territory // - Then load the more specific locale translator // Load e.g. qt_de.qm if (qtTranslatorBase.load( "qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { QApplication::installTranslator(&qtTranslatorBase); } // Load e.g. qt_de_DE.qm if (qtTranslator.load( "qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { QApplication::installTranslator(&qtTranslator); } // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in // bitcoin.qrc) if (translatorBase.load(lang, ":/translations/")) { QApplication::installTranslator(&translatorBase); } // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in // bitcoin.qrc) if (translator.load(lang_territory, ":/translations/")) { QApplication::installTranslator(&translator); } } /* qDebug() message handler --> debug.log */ void DebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context); if (type == QtDebugMsg) { LogPrint(BCLog::QT, "GUI: %s\n", msg.toStdString()); } else { LogPrintf("GUI: %s\n", msg.toStdString()); } } /** * Class encapsulating Bitcoin ABC startup and shutdown. * Allows running startup and shutdown in a different thread from the UI thread. */ class BitcoinABC : public QObject { Q_OBJECT public: explicit BitcoinABC(interfaces::Node &node); public Q_SLOTS: void initialize(Config *config, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void shutdown(); Q_SIGNALS: void initializeResult(bool success); void shutdownResult(); void runawayException(const QString &message); private: /// Pass fatal exception message to UI thread void handleRunawayException(const std::exception *e); interfaces::Node &m_node; }; /** Main Bitcoin application object */ class BitcoinApplication : public QApplication { Q_OBJECT public: explicit BitcoinApplication(interfaces::Node &node, int &argc, char **argv); ~BitcoinApplication(); #ifdef ENABLE_WALLET /// Create payment server void createPaymentServer(); #endif /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model void createOptionsModel(bool resetSettings); /// Create main window void createWindow(const Config *, const NetworkStyle *networkStyle); /// Create splash screen void createSplashScreen(const NetworkStyle *networkStyle); /// Request core initialization void requestInitialize(Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor); /// Request core shutdown void requestShutdown(Config &config); /// Get process return value int getReturnValue() const { return returnValue; } /// Get window identifier of QMainWindow (BitcoinGUI) WId getMainWinId() const; public Q_SLOTS: void initializeResult(bool success); void shutdownResult(); /// Handle runaway exceptions. Shows a message box with the problem and /// quits the program. void handleRunawayException(const QString &message); Q_SIGNALS: void requestedInitialize(Config *config, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); private: QThread *coreThread; interfaces::Node &m_node; OptionsModel *optionsModel; ClientModel *clientModel; BitcoinGUI *window; QTimer *pollShutdownTimer; #ifdef ENABLE_WALLET PaymentServer *paymentServer; std::vector m_wallet_models; #endif int returnValue; const PlatformStyle *platformStyle; std::unique_ptr shutdownWindow; void startThread(); }; -#include "bitcoin.moc" +#include BitcoinABC::BitcoinABC(interfaces::Node &node) : QObject(), m_node(node) {} void BitcoinABC::handleRunawayException(const std::exception *e) { PrintExceptionContinue(e, "Runaway exception"); Q_EMIT runawayException(QString::fromStdString(m_node.getWarnings("gui"))); } void BitcoinABC::initialize(Config *cfg, HTTPRPCRequestProcessor *httpRPCRequestProcessor) { Config &config(*cfg); try { qDebug() << __func__ << ": Running initialization in thread"; bool rv = m_node.appInitMain(config, *httpRPCRequestProcessor); Q_EMIT initializeResult(rv); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } void BitcoinABC::shutdown() { try { qDebug() << __func__ << ": Running Shutdown in thread"; m_node.appShutdown(); qDebug() << __func__ << ": Shutdown finished"; Q_EMIT shutdownResult(); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } BitcoinApplication::BitcoinApplication(interfaces::Node &node, int &argc, char **argv) : QApplication(argc, argv), coreThread(0), m_node(node), optionsModel(0), clientModel(0), window(0), pollShutdownTimer(0), #ifdef ENABLE_WALLET paymentServer(0), m_wallet_models(), #endif returnValue(0) { setQuitOnLastWindowClosed(false); // UI per-platform customization. // This must be done inside the BitcoinApplication constructor, or after it, // because PlatformStyle::instantiate requires a QApplication. std::string platformName; platformName = gArgs.GetArg("-uiplatform", BitcoinGUI::DEFAULT_UIPLATFORM); platformStyle = PlatformStyle::instantiate(QString::fromStdString(platformName)); // Fall back to "other" if specified name not found. if (!platformStyle) { platformStyle = PlatformStyle::instantiate("other"); } assert(platformStyle); } BitcoinApplication::~BitcoinApplication() { if (coreThread) { qDebug() << __func__ << ": Stopping thread"; Q_EMIT stopThread(); coreThread->wait(); qDebug() << __func__ << ": Stopped thread"; } delete window; window = 0; #ifdef ENABLE_WALLET delete paymentServer; paymentServer = 0; #endif delete optionsModel; optionsModel = 0; delete platformStyle; platformStyle = 0; } #ifdef ENABLE_WALLET void BitcoinApplication::createPaymentServer() { paymentServer = new PaymentServer(this); } #endif void BitcoinApplication::createOptionsModel(bool resetSettings) { optionsModel = new OptionsModel(m_node, nullptr, resetSettings); } void BitcoinApplication::createWindow(const Config *config, const NetworkStyle *networkStyle) { window = new BitcoinGUI(m_node, config, platformStyle, networkStyle, 0); pollShutdownTimer = new QTimer(window); connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); } void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle) { SplashScreen *splash = new SplashScreen(m_node, 0, networkStyle); // We don't hold a direct pointer to the splash screen after creation, but // the splash screen will take care of deleting itself when slotFinish // happens. splash->show(); connect(this, SIGNAL(splashFinished(QWidget *)), splash, SLOT(slotFinish(QWidget *))); connect(this, SIGNAL(requestedShutdown()), splash, SLOT(close())); } void BitcoinApplication::startThread() { if (coreThread) { return; } coreThread = new QThread(this); BitcoinABC *executor = new BitcoinABC(m_node); executor->moveToThread(coreThread); /* communication to and from thread */ connect(executor, SIGNAL(initializeResult(bool)), this, SLOT(initializeResult(bool))); connect(executor, SIGNAL(shutdownResult()), this, SLOT(shutdownResult())); connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); // Note on how Qt works: it tries to directly invoke methods if the signal // is emitted on the same thread that the target object 'lives' on. // But if the target object 'lives' on another thread (executor here does) // the SLOT will be invoked asynchronously at a later time in the thread // of the target object. So.. we pass a pointer around. If you pass // a reference around (even if it's non-const) you'll get Qt generating // code to copy-construct the parameter in question (Q_DECLARE_METATYPE // and qRegisterMetaType generate this code). For the Config class, // which is noncopyable, we can't do this. So.. we have to pass // pointers to Config around. Make sure Config &/Config * isn't a // temporary (eg it lives somewhere aside from the stack) or this will // crash because initialize() gets executed in another thread at some // unspecified time (after) requestedInitialize() is emitted! connect(this, SIGNAL(requestedInitialize(Config *, HTTPRPCRequestProcessor *)), executor, SLOT(initialize(Config *, HTTPRPCRequestProcessor *))); connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); /* make sure executor object is deleted in its own thread */ connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); coreThread->start(); } void BitcoinApplication::parameterSetup() { m_node.initLogging(); m_node.initParameterInteraction(); } void BitcoinApplication::requestInitialize( Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor) { qDebug() << __func__ << ": Requesting initialize"; startThread(); // IMPORTANT: config must NOT be a reference to a temporary because below // signal may be connected to a slot that will be executed as a queued // connection in another thread! Q_EMIT requestedInitialize(&config, &httpRPCRequestProcessor); } void BitcoinApplication::requestShutdown(Config &config) { // Show a simple window indicating shutdown status. Do this first as some of // the steps may take some time below, for example the RPC console may still // be executing a command. shutdownWindow.reset(ShutdownWindow::showShutdownWindow(window)); qDebug() << __func__ << ": Requesting shutdown"; startThread(); window->hide(); window->setClientModel(0); pollShutdownTimer->stop(); #ifdef ENABLE_WALLET window->removeAllWallets(); for (WalletModel *walletModel : m_wallet_models) { delete walletModel; } m_wallet_models.clear(); #endif delete clientModel; clientModel = 0; m_node.startShutdown(); // Request shutdown from core thread Q_EMIT requestedShutdown(); } void BitcoinApplication::initializeResult(bool success) { qDebug() << __func__ << ": Initialization result: " << success; returnValue = success ? EXIT_SUCCESS : EXIT_FAILURE; if (!success) { // Make sure splash screen doesn't stick around during shutdown. Q_EMIT splashFinished(window); // Exit first main loop invocation. quit(); return; } // Log this only after AppInit2 finishes, as then logging setup is // guaranteed complete. qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET PaymentServer::LoadRootCAs(); paymentServer->setOptionsModel(optionsModel); #endif clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); #ifdef ENABLE_WALLET bool fFirstWallet = true; auto wallets = m_node.getWallets(); for (auto &wallet : wallets) { WalletModel *const walletModel = new WalletModel( std::move(wallet), m_node, platformStyle, optionsModel); window->addWallet(walletModel); if (fFirstWallet) { window->setCurrentWallet(walletModel->getWalletName()); fFirstWallet = false; } connect( walletModel, SIGNAL(coinsSent(WalletModel *, SendCoinsRecipient, QByteArray)), paymentServer, SLOT(fetchPaymentACK(WalletModel *, const SendCoinsRecipient &, QByteArray))); m_wallet_models.push_back(walletModel); } #endif // If -min option passed, start window minimized. if (gArgs.GetBoolArg("-min", false)) { window->showMinimized(); } else { window->show(); } Q_EMIT splashFinished(window); #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // bitcoincash: URIs or payment requests: connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), window, SLOT(handlePaymentRequest(SendCoinsRecipient))); connect(window, SIGNAL(receivedURI(QString)), paymentServer, SLOT(handleURIOrFile(QString))); connect(paymentServer, SIGNAL(message(QString, QString, unsigned int)), window, SLOT(message(QString, QString, unsigned int))); QTimer::singleShot(100, paymentServer, SLOT(uiReady())); #endif pollShutdownTimer->start(200); } void BitcoinApplication::shutdownResult() { // Exit second main loop invocation after shutdown finished. quit(); } void BitcoinApplication::handleRunawayException(const QString &message) { QMessageBox::critical( 0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue " "safely and will quit.") + QString("\n\n") + message); ::exit(EXIT_FAILURE); } WId BitcoinApplication::getMainWinId() const { if (!window) { return 0; } return window->winId(); } #ifndef BITCOIN_QT_TEST static void MigrateSettings() { assert(!QApplication::applicationName().isEmpty()); static const QString legacyAppName("Bitcoin-Qt"), #ifdef Q_OS_DARWIN // Macs and/or iOS et al use a domain-style name for Settings // files. All other platforms use a simple orgname. This // difference is documented in the QSettings class documentation. legacyOrg("bitcoin.org"); #else legacyOrg("Bitcoin"); #endif QSettings // below picks up settings file location based on orgname,appname legacy(legacyOrg, legacyAppName), // default c'tor below picks up settings file location based on // QApplication::applicationName(), et al -- which was already set // in main() abc; #ifdef Q_OS_DARWIN // Disable bogus OSX keys from MacOS system-wide prefs that may cloud our // judgement ;) (this behavior is also documented in QSettings docs) legacy.setFallbacksEnabled(false); abc.setFallbacksEnabled(false); #endif const QStringList legacyKeys(legacy.allKeys()); // We only migrate settings if we have Core settings but no Bitcoin-ABC // settings if (!legacyKeys.isEmpty() && abc.allKeys().isEmpty()) { for (const QString &key : legacyKeys) { // now, copy settings over abc.setValue(key, legacy.value(key)); } } } int main(int argc, char *argv[]) { SetupEnvironment(); std::unique_ptr node = interfaces::MakeNode(); /// 1. Parse command-line options. These take precedence over anything else. // Command-line options take precedence: node->parseParameters(argc, argv); // Do not refer to data directory yet, this can be overridden by // Intro::pickDataDirectory /// 2. Basic Qt initialization (not dependent on parameters or /// configuration) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); BitcoinApplication app(*node, argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if QT_VERSION >= 0x050600 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #ifdef Q_OS_MAC QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType(); // Need to pass name here as Amount is a typedef (see // http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType("Amount"); qRegisterMetaType>("std::function"); // Need to register any types Qt doesn't know about if you intend // to use them with the signal/slot mechanism Qt provides. Even pointers. // Note that class Config is noncopyable and so we can't register a // non-pointer version of it with Qt, because Qt expects to be able to // copy-construct non-pointers to objects for invoking slots // behind-the-scenes in the 'Queued' connection case. qRegisterMetaType(); /// 3. Application identification // must be set before OptionsModel is initialized or translations are // loaded, as it is used to locate QSettings. // Note: If you move these calls somewhere else, be sure to bring // MigrateSettings() below along for the ride. QApplication::setOrganizationName(QAPP_ORG_NAME); QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN); QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT); // Migrate settings from core's/our old GUI settings to Bitcoin ABC // only if core's exist but Bitcoin ABC's doesn't. // NOTE -- this function needs to be called *after* the above 3 lines // that set the app orgname and app name! If you move the above 3 lines // to elsewhere, take this call with you! MigrateSettings(); GUIUtil::SubstituteFonts(GetLangTerritory()); /// 4. Initialization of translations, so that intro dialog is in user's /// language. Now that QSettings are accessible, initialize translations. QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); translationInterface.Translate.connect(Translate); // Show help message immediately after parsing command-line options (for // "-lang") and setting locale, but before showing splash screen. if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { HelpMessageDialog help(*node, nullptr, gArgs.IsArgSet("-version")); help.showOrPrint(); return EXIT_SUCCESS; } /// 5. Now that settings and translations are available, ask user for data /// directory. User language is set up: pick a data directory. if (!Intro::pickDataDirectory(*node)) { return EXIT_SUCCESS; } /// 6. Determine availability of data directory and parse bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes. if (!fs::is_directory(GetDataDir(false))) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr( "Error: Specified data directory \"%1\" does not exist.") .arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } try { node->readConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); } catch (const std::exception &e) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: Cannot parse configuration file: %1. Only use " "key=value syntax.") .arg(e.what())); return EXIT_FAILURE; } /// 7. Determine network (and switch to network specific options) // - Do not call Params() before this step. // - Do this after parsing the configuration file, as the network can be // switched there. // - QSettings() will use the new application name after this, resulting in // network-specific settings. // - Needs to be done before createOptionsModel. // Check for -testnet or -regtest parameter (Params() calls are only valid // after this clause) try { node->selectParams(gArgs.GetChainName()); } catch (std::exception &e) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); return EXIT_FAILURE; } #ifdef ENABLE_WALLET // Parse URIs on command line -- this can affect Params() PaymentServer::ipcParseCommandLine(*node, argc, argv); #endif QScopedPointer networkStyle(NetworkStyle::instantiate( QString::fromStdString(Params().NetworkIDString()))); assert(!networkStyle.isNull()); // Allow for separate UI settings for testnets QApplication::setApplicationName(networkStyle->getAppName()); // Re-initialize translations after changing application name (language in // network-specific settings can be different) initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); #ifdef ENABLE_WALLET /// 8. URI IPC sending // - Do this early as we don't want to bother initializing if we are just // calling IPC // - Do this *after* setting up the data directory, as the data directory // hash is used in the name // of the server. // - Do this after creating app and setting up translations, so errors are // translated properly. if (PaymentServer::ipcSendCommandLine()) { exit(EXIT_SUCCESS); } // Start up the payment server early, too, so impatient users that click on // bitcoincash: links repeatedly have their payment requests routed to this // process: app.createPaymentServer(); #endif /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be // word-wrapped. app.installEventFilter( new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); #if defined(Q_OS_WIN) // Install global event filter for processing Windows session related // Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) qApp->installNativeEventFilter(new WinShutdownMonitor()); #endif // Install qDebug() message handler to route to debug.log qInstallMessageHandler(DebugMessageHandler); // Allow parameter interaction before we create the options model app.parameterSetup(); // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); // Subscribe to global signals from core std::unique_ptr handler = node->handleInitMessage(InitMessage); // Get global config Config &config = const_cast(GetConfig()); if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) { app.createSplashScreen(networkStyle.data()); } RPCServer rpcServer; HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); try { app.createWindow(&config, networkStyle.data()); // Perform base initialization before spinning up // initialization/shutdown thread. This is acceptable because this // function only contains steps that are quick to execute, so the GUI // thread won't be held up. if (!node->baseInitialize(config, rpcServer)) { // A dialog with detailed error will have been shown by InitError() return EXIT_FAILURE; } app.requestInitialize(config, httpRPCRequestProcessor); #if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason( QObject::tr("%1 didn't yet exit safely...") .arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); #endif app.exec(); app.requestShutdown(config); app.exec(); return app.getReturnValue(); } catch (const std::exception &e) { PrintExceptionContinue(&e, "Runaway exception"); app.handleRunawayException( QString::fromStdString(node->getWarnings("gui"))); } catch (...) { PrintExceptionContinue(nullptr, "Runaway exception"); app.handleRunawayException( QString::fromStdString(node->getWarnings("gui"))); } return EXIT_FAILURE; } #endif // BITCOIN_QT_TEST diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 16eab1020..8309fd6e5 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -1,309 +1,309 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include +#include #include #include -#include #include #include #include #include #include static const uint64_t GB_BYTES = 1000000000LL; /** * Minimum free space (in GB) needed for data directory. */ static const uint64_t BLOCK_CHAIN_SIZE = 200; /** * Minimum free space (in GB) needed for data directory when pruned; Does not * include prune target. */ static const uint64_t CHAIN_STATE_SIZE = 4; /** * Total required space (in GB) depending on user choice (prune, not prune). */ static uint64_t requiredSpace; /* Check free space asynchronously to prevent hanging the UI thread. Up to one request to check a path is in flight to this thread; when the check() function runs, the current path is requested from the associated Intro object. The reply is sent back through a signal. This ensures that no queue of checking requests is built up while the user is still entering the path, and that always the most recently entered path is checked as soon as the thread becomes available. */ class FreespaceChecker : public QObject { Q_OBJECT public: explicit FreespaceChecker(Intro *intro); enum Status { ST_OK, ST_ERROR }; public Q_SLOTS: void check(); Q_SIGNALS: void reply(int status, const QString &message, quint64 available); private: Intro *intro; }; -#include "intro.moc" +#include FreespaceChecker::FreespaceChecker(Intro *_intro) { this->intro = _intro; } void FreespaceChecker::check() { QString dataDirStr = intro->getPathToCheck(); fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr); uint64_t freeBytesAvailable = 0; int replyStatus = ST_OK; QString replyMessage = tr("A new data directory will be created."); /* Find first parent that exists, so that fs::space does not fail */ fs::path parentDir = dataDir; fs::path parentDirOld = fs::path(); while (parentDir.has_parent_path() && !fs::exists(parentDir)) { parentDir = parentDir.parent_path(); /* Check if we make any progress, break if not to prevent an infinite * loop here */ if (parentDirOld == parentDir) break; parentDirOld = parentDir; } try { freeBytesAvailable = fs::space(parentDir).available; if (fs::exists(dataDir)) { if (fs::is_directory(dataDir)) { QString separator = "" + QDir::toNativeSeparators("/") + tr("name") + ""; replyStatus = ST_OK; replyMessage = tr("Directory already exists. Add %1 if you " "intend to create a new directory here.") .arg(separator); } else { replyStatus = ST_ERROR; replyMessage = tr("Path already exists, and is not a directory."); } } } catch (const fs::filesystem_error &) { /* Parent directory does not exist or is not accessible */ replyStatus = ST_ERROR; replyMessage = tr("Cannot create data directory here."); } Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable); } Intro::Intro(QWidget *parent) : QDialog(parent), ui(new Ui::Intro), thread(0), signalled(false) { ui->setupUi(this); ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(tr(PACKAGE_NAME))); ui->storageLabel->setText(ui->storageLabel->text().arg(tr(PACKAGE_NAME))); uint64_t pruneTarget = std::max(0, gArgs.GetArg("-prune", 0)); requiredSpace = BLOCK_CHAIN_SIZE; if (pruneTarget) { uint64_t prunedGBs = std::ceil(pruneTarget * 1024 * 1024.0 / GB_BYTES); if (prunedGBs <= requiredSpace) { requiredSpace = prunedGBs; } } requiredSpace += CHAIN_STATE_SIZE; ui->sizeWarningLabel->setText( ui->sizeWarningLabel->text().arg(tr(PACKAGE_NAME)).arg(requiredSpace)); startThread(); } Intro::~Intro() { delete ui; /* Ensure thread is finished before it is deleted */ Q_EMIT stopThread(); thread->wait(); } QString Intro::getDataDirectory() { return ui->dataDirectory->text(); } void Intro::setDataDirectory(const QString &dataDir) { ui->dataDirectory->setText(dataDir); if (dataDir == getDefaultDataDirectory()) { ui->dataDirDefault->setChecked(true); ui->dataDirectory->setEnabled(false); ui->ellipsisButton->setEnabled(false); } else { ui->dataDirCustom->setChecked(true); ui->dataDirectory->setEnabled(true); ui->ellipsisButton->setEnabled(true); } } QString Intro::getDefaultDataDirectory() { return GUIUtil::boostPathToQString(GetDefaultDataDir()); } bool Intro::pickDataDirectory(interfaces::Node &node) { QSettings settings; /* If data directory provided on command line, no need to look at settings or show a picking dialog */ if (!gArgs.GetArg("-datadir", "").empty()) return true; /* 1) Default data directory for operating system */ QString dataDir = getDefaultDataDirectory(); /* 2) Allow QSettings to override default dir */ dataDir = settings.value("strDataDir", dataDir).toString(); if (!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || gArgs.GetBoolArg("-resetguisettings", false)) { /* If current default data directory does not exist, let the user choose * one */ Intro intro; intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); while (true) { if (!intro.exec()) { /* Cancel clicked */ return false; } dataDir = intro.getDataDirectory(); try { if (TryCreateDirectories( GUIUtil::qstringToBoostPath(dataDir))) { // If a new data directory has been created, make wallets // subdirectory too TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir) / "wallets"); } break; } catch (const fs::filesystem_error &) { QMessageBox::critical(0, tr(PACKAGE_NAME), tr("Error: Specified data directory " "\"%1\" cannot be created.") .arg(dataDir)); /* fall through, back to choosing screen */ } } settings.setValue("strDataDir", dataDir); settings.setValue("fReset", false); } /* Only override -datadir if different from the default, to make it possible * to * override -datadir in the bitcoin.conf file in the default data directory * (to be consistent with bitcoind behavior) */ if (dataDir != getDefaultDataDirectory()) { // use OS locale for path setting node.softSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); } return true; } void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable) { switch (status) { case FreespaceChecker::ST_OK: ui->errorMessage->setText(message); ui->errorMessage->setStyleSheet(""); break; case FreespaceChecker::ST_ERROR: ui->errorMessage->setText(tr("Error") + ": " + message); ui->errorMessage->setStyleSheet("QLabel { color: #800000 }"); break; } /* Indicate number of bytes available */ if (status == FreespaceChecker::ST_ERROR) { ui->freeSpace->setText(""); } else { QString freeString = tr("%n GB of free space available", "", bytesAvailable / GB_BYTES); if (bytesAvailable < requiredSpace * GB_BYTES) { freeString += " " + tr("(of %n GB needed)", "", requiredSpace); ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); } else { ui->freeSpace->setStyleSheet(""); } ui->freeSpace->setText(freeString + "."); } /* Don't allow confirm in ERROR state */ ui->buttonBox->button(QDialogButtonBox::Ok) ->setEnabled(status != FreespaceChecker::ST_ERROR); } void Intro::on_dataDirectory_textChanged(const QString &dataDirStr) { /* Disable OK button until check result comes in */ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); checkPath(dataDirStr); } void Intro::on_ellipsisButton_clicked() { QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory( 0, "Choose data directory", ui->dataDirectory->text())); if (!dir.isEmpty()) ui->dataDirectory->setText(dir); } void Intro::on_dataDirDefault_clicked() { setDataDirectory(getDefaultDataDirectory()); } void Intro::on_dataDirCustom_clicked() { ui->dataDirectory->setEnabled(true); ui->ellipsisButton->setEnabled(true); } void Intro::startThread() { thread = new QThread(this); FreespaceChecker *executor = new FreespaceChecker(this); executor->moveToThread(thread); connect(executor, SIGNAL(reply(int, QString, quint64)), this, SLOT(setStatus(int, QString, quint64))); connect(this, SIGNAL(requestCheck()), executor, SLOT(check())); /* make sure executor object is deleted in its own thread */ connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); connect(this, SIGNAL(stopThread()), thread, SLOT(quit())); thread->start(); } void Intro::checkPath(const QString &dataDir) { mutex.lock(); pathToCheck = dataDir; if (!signalled) { signalled = true; Q_EMIT requestCheck(); } mutex.unlock(); } QString Intro::getPathToCheck() { QString retval; mutex.lock(); retval = pathToCheck; signalled = false; /* new request can be queued now */ mutex.unlock(); return retval; } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index e664b5091..a5b872d42 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1,1435 +1,1435 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include // TODO: add a scrollback limit, as there is currently none // TODO: make it possible to filter out categories (esp debug messages when // implemented) // TODO: receive errors and debug messages through ClientModel const int CONSOLE_HISTORY = 50; const int INITIAL_TRAFFIC_GRAPH_MINS = 30; const QSize FONT_RANGE(4, 40); const char fontSizeSettingsKey[] = "consoleFontSize"; const struct { const char *url; const char *source; } ICON_MAPPING[] = {{"cmd-request", ":/icons/tx_input"}, {"cmd-reply", ":/icons/tx_output"}, {"cmd-error", ":/icons/tx_output"}, {"misc", ":/icons/tx_inout"}, {nullptr, nullptr}}; namespace { // don't add private key handling cmd's to the history const QStringList historyFilter = QStringList() << "importprivkey" << "importmulti" << "signmessagewithprivkey" << "signrawtransaction" << "signrawtransactionwithkey" << "walletpassphrase" << "walletpassphrasechange" << "encryptwallet"; } // namespace /* Object for executing console RPC commands in a separate thread. */ class RPCExecutor : public QObject { Q_OBJECT public: RPCExecutor(interfaces::Node &node) : m_node(node) {} public Q_SLOTS: void request(const QString &command, const QString &walletID); Q_SIGNALS: void reply(int category, const QString &command); private: interfaces::Node &m_node; }; /** Class for handling RPC timers * (used for e.g. re-locking the wallet after a timeout) */ class QtRPCTimerBase : public QObject, public RPCTimerBase { Q_OBJECT public: QtRPCTimerBase(std::function &_func, int64_t millis) : func(_func) { timer.setSingleShot(true); connect(&timer, SIGNAL(timeout()), this, SLOT(timeout())); timer.start(millis); } ~QtRPCTimerBase() {} private Q_SLOTS: void timeout() { func(); } private: QTimer timer; std::function func; }; class QtRPCTimerInterface : public RPCTimerInterface { public: ~QtRPCTimerInterface() {} const char *Name() override { return "Qt"; } RPCTimerBase *NewTimer(std::function &func, int64_t millis) override { return new QtRPCTimerBase(func, millis); } }; -#include "rpcconsole.moc" +#include /** * Split shell command line into a list of arguments and optionally execute the * command(s). * Aims to emulate \c bash and friends. * * - Command nesting is possible with parenthesis; for example: * validateaddress(getnewaddress()) * - Arguments are delimited with whitespace or comma * - Extra whitespace at the beginning and end and between arguments will be * ignored * - Text can be "double" or 'single' quoted * - The backslash \c \ is used as escape character * - Outside quotes, any character can be escaped * - Within double quotes, only escape \c " and backslashes before a \c " or * another backslash * - Within single quotes, no escaping is possible and no special * interpretation takes place * * @param[in] node optional node to execute command on * @param[out] result stringified Result from the executed command(chain) * @param[in] strCommand Command line to split * @param[in] fExecute set true if you want the command to be executed * @param[out] pstrFilteredOut Command line, filtered to remove any sensitive * data */ bool RPCConsole::RPCParseCommandLine(interfaces::Node *node, std::string &strResult, const std::string &strCommand, const bool fExecute, std::string *const pstrFilteredOut, const std::string *walletID) { std::vector> stack; stack.push_back(std::vector()); enum CmdParseState { STATE_EATING_SPACES, STATE_EATING_SPACES_IN_ARG, STATE_EATING_SPACES_IN_BRACKETS, STATE_ARGUMENT, STATE_SINGLEQUOTED, STATE_DOUBLEQUOTED, STATE_ESCAPE_OUTER, STATE_ESCAPE_DOUBLEQUOTED, STATE_COMMAND_EXECUTED, STATE_COMMAND_EXECUTED_INNER } state = STATE_EATING_SPACES; std::string curarg; UniValue lastResult; unsigned nDepthInsideSensitive = 0; size_t filter_begin_pos = 0, chpos; std::vector> filter_ranges; auto add_to_current_stack = [&](const std::string &strArg) { if (stack.back().empty() && (!nDepthInsideSensitive) && historyFilter.contains(QString::fromStdString(strArg), Qt::CaseInsensitive)) { nDepthInsideSensitive = 1; filter_begin_pos = chpos; } // Make sure stack is not empty before adding something if (stack.empty()) { stack.push_back(std::vector()); } stack.back().push_back(strArg); }; auto close_out_params = [&]() { if (nDepthInsideSensitive) { if (!--nDepthInsideSensitive) { assert(filter_begin_pos); filter_ranges.push_back( std::make_pair(filter_begin_pos, chpos)); filter_begin_pos = 0; } } stack.pop_back(); }; std::string strCommandTerminated = strCommand; if (strCommandTerminated.back() != '\n') strCommandTerminated += "\n"; for (chpos = 0; chpos < strCommandTerminated.size(); ++chpos) { char ch = strCommandTerminated[chpos]; switch (state) { case STATE_COMMAND_EXECUTED_INNER: case STATE_COMMAND_EXECUTED: { bool breakParsing = true; switch (ch) { case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break; default: if (state == STATE_COMMAND_EXECUTED_INNER) { if (ch != ']') { // append char to the current argument (which is // also used for the query command) curarg += ch; break; } if (curarg.size() && fExecute) { // if we have a value query, query arrays with // index and objects with a string key UniValue subelement; if (lastResult.isArray()) { for (char argch : curarg) { if (!std::isdigit(argch)) { throw std::runtime_error( "Invalid result query"); } } subelement = lastResult[atoi(curarg.c_str())]; } else if (lastResult.isObject()) { subelement = find_value(lastResult, curarg); } else { // no array or object: abort throw std::runtime_error( "Invalid result query"); } lastResult = subelement; } state = STATE_COMMAND_EXECUTED; break; } // don't break parsing when the char is required for the // next argument breakParsing = false; // pop the stack and return the result to the current // command arguments close_out_params(); // don't stringify the json in case of a string to avoid // doublequotes if (lastResult.isStr()) { curarg = lastResult.get_str(); } else { curarg = lastResult.write(2); } // if we have a non empty result, use it as stack // argument otherwise as general result if (curarg.size()) { if (stack.size()) { add_to_current_stack(curarg); } else { strResult = curarg; } } curarg.clear(); // assume eating space state state = STATE_EATING_SPACES; } if (breakParsing) { break; } } // FALLTHROUGH case STATE_ARGUMENT: // In or after argument case STATE_EATING_SPACES_IN_ARG: case STATE_EATING_SPACES_IN_BRACKETS: case STATE_EATING_SPACES: // Handle runs of whitespace switch (ch) { case '"': state = STATE_DOUBLEQUOTED; break; case '\'': state = STATE_SINGLEQUOTED; break; case '\\': state = STATE_ESCAPE_OUTER; break; case '(': case ')': case '\n': if (state == STATE_EATING_SPACES_IN_ARG) { throw std::runtime_error("Invalid Syntax"); } if (state == STATE_ARGUMENT) { if (ch == '(' && stack.size() && stack.back().size() > 0) { if (nDepthInsideSensitive) { ++nDepthInsideSensitive; } stack.push_back(std::vector()); } // don't allow commands after executed commands on // baselevel if (!stack.size()) { throw std::runtime_error("Invalid Syntax"); } add_to_current_stack(curarg); curarg.clear(); state = STATE_EATING_SPACES_IN_BRACKETS; } if ((ch == ')' || ch == '\n') && stack.size() > 0) { if (fExecute) { // Convert argument list to JSON objects in // method-dependent way, and pass it along with // the method name to the dispatcher. UniValue params = RPCConvertValues( stack.back()[0], std::vector( stack.back().begin() + 1, stack.back().end())); std::string method = stack.back()[0]; std::string uri; #ifdef ENABLE_WALLET if (walletID && !walletID->empty()) { QByteArray encodedName = QUrl::toPercentEncoding( QString::fromStdString(*walletID)); uri = "/wallet/" + std::string(encodedName.constData(), encodedName.length()); } #endif GlobalConfig config; assert(node); lastResult = node->executeRpc(config, method, params, uri); } state = STATE_COMMAND_EXECUTED; curarg.clear(); } break; case ' ': case ',': case '\t': if (state == STATE_EATING_SPACES_IN_ARG && curarg.empty() && ch == ',') { throw std::runtime_error("Invalid Syntax"); } else if (state == STATE_ARGUMENT) { // Space ends argument add_to_current_stack(curarg); curarg.clear(); } if ((state == STATE_EATING_SPACES_IN_BRACKETS || state == STATE_ARGUMENT) && ch == ',') { state = STATE_EATING_SPACES_IN_ARG; break; } state = STATE_EATING_SPACES; break; default: curarg += ch; state = STATE_ARGUMENT; } break; case STATE_SINGLEQUOTED: // Single-quoted string switch (ch) { case '\'': state = STATE_ARGUMENT; break; default: curarg += ch; } break; case STATE_DOUBLEQUOTED: // Double-quoted string switch (ch) { case '"': state = STATE_ARGUMENT; break; case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break; default: curarg += ch; } break; case STATE_ESCAPE_OUTER: // '\' outside quotes curarg += ch; state = STATE_ARGUMENT; break; case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text if (ch != '"' && ch != '\\') { // keep '\' for everything but the quote and '\' itself curarg += '\\'; } curarg += ch; state = STATE_DOUBLEQUOTED; break; } } if (pstrFilteredOut) { if (STATE_COMMAND_EXECUTED == state) { assert(!stack.empty()); close_out_params(); } *pstrFilteredOut = strCommand; for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) { pstrFilteredOut->replace(i->first, i->second - i->first, "(…)"); } } // final state switch (state) { case STATE_COMMAND_EXECUTED: if (lastResult.isStr()) { strResult = lastResult.get_str(); } else { strResult = lastResult.write(2); } // FALLTHROUGH case STATE_ARGUMENT: case STATE_EATING_SPACES: return true; default: // ERROR to end in one of the other states return false; } } void RPCExecutor::request(const QString &command, const QString &walletID) { try { std::string result; std::string executableCommand = command.toStdString() + "\n"; // Catch the console-only-help command before RPC call is executed and // reply with help text as-if a RPC reply. if (executableCommand == "help-console\n") { Q_EMIT reply( RPCConsole::CMD_REPLY, QString(("\n" "This console accepts RPC commands using the standard " "syntax.\n" " example: getblockhash 0\n\n" "This console can also accept RPC commands using " "parenthesized syntax.\n" " example: getblockhash(0)\n\n" "Commands may be nested when specified with the " "parenthesized syntax.\n" " example: getblock(getblockhash(0) 1)\n\n" "A space or a comma can be used to delimit arguments " "for either syntax.\n" " example: getblockhash 0\n" " getblockhash,0\n\n" "Named results can be queried with a non-quoted key " "string in brackets.\n" " example: getblock(getblockhash(0) true)[tx]\n\n" "Results without keys can be queried using an integer " "in brackets.\n" " example: " "getblock(getblockhash(0),true)[tx][0]\n\n"))); return; } std::string wallet_id = walletID.toStdString(); if (!RPCConsole::RPCExecuteCommandLine( m_node, result, executableCommand, nullptr, &wallet_id)) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); return; } Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } catch (UniValue &objError) { // Nice formatting for standard-format error try { int code = find_value(objError, "code").get_int(); std::string message = find_value(objError, "message").get_str(); Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); } catch (const std::runtime_error &) { // raised when converting to invalid type, i.e. missing code or // message. Show raw JSON object. Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write())); } } catch (const std::exception &e) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); } } RPCConsole::RPCConsole(interfaces::Node &node, const PlatformStyle *_platformStyle, QWidget *parent) : QWidget(parent), m_node(node), ui(new Ui::RPCConsole), clientModel(0), historyPtr(0), platformStyle(_platformStyle), peersTableContextMenu(0), banTableContextMenu(0), consoleFontSize(0) { ui->setupUi(this); QSettings settings; if (!restoreGeometry( settings.value("RPCConsoleWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); } ui->openDebugLogfileButton->setToolTip( ui->openDebugLogfileButton->toolTip().arg(tr(PACKAGE_NAME))); if (platformStyle->getImagesOnButtons()) { ui->openDebugLogfileButton->setIcon( platformStyle->SingleColorIcon(":/icons/export")); } ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); ui->fontBiggerButton->setIcon( platformStyle->SingleColorIcon(":/icons/fontbigger")); ui->fontSmallerButton->setIcon( platformStyle->SingleColorIcon(":/icons/fontsmaller")); // Install event filter for up and down arrow ui->lineEdit->installEventFilter(this); ui->messagesWidget->installEventFilter(this); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(ui->fontBiggerButton, SIGNAL(clicked()), this, SLOT(fontBigger())); connect(ui->fontSmallerButton, SIGNAL(clicked()), this, SLOT(fontSmaller())); connect(ui->btnClearTrafficGraph, SIGNAL(clicked()), ui->trafficGraph, SLOT(clear())); // disable the wallet selector by default ui->WalletSelector->setVisible(false); ui->WalletSelectorLabel->setVisible(false); // set library version labels #ifdef ENABLE_WALLET ui->berkeleyDBVersion->setText(DbEnv::version(0, 0, 0)); #else ui->label_berkeleyDBVersion->hide(); ui->berkeleyDBVersion->hide(); #endif // Register RPC timer interface rpcTimerInterface = new QtRPCTimerInterface(); // avoid accidentally overwriting an existing, non QTThread // based timer interface m_node.rpcSetTimerInterfaceIfUnset(rpcTimerInterface); setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); ui->detailWidget->hide(); ui->peerHeading->setText(tr("Select a peer to view detailed information.")); consoleFontSize = settings.value(fontSizeSettingsKey, QFontInfo(QFont()).pointSize()) .toInt(); clear(); } RPCConsole::~RPCConsole() { QSettings settings; settings.setValue("RPCConsoleWindowGeometry", saveGeometry()); m_node.rpcUnsetTimerInterface(rpcTimerInterface); delete rpcTimerInterface; delete ui; } bool RPCConsole::eventFilter(QObject *obj, QEvent *event) { // Special key handling if (event->type() == QEvent::KeyPress) { QKeyEvent *keyevt = static_cast(event); int key = keyevt->key(); Qt::KeyboardModifiers mod = keyevt->modifiers(); switch (key) { case Qt::Key_Up: if (obj == ui->lineEdit) { browseHistory(-1); return true; } break; case Qt::Key_Down: if (obj == ui->lineEdit) { browseHistory(1); return true; } break; case Qt::Key_PageUp: /* pass paging keys to messages widget */ case Qt::Key_PageDown: if (obj == ui->lineEdit) { QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt)); return true; } break; case Qt::Key_Return: case Qt::Key_Enter: // forward these events to lineEdit if (obj == autoCompleter->popup()) { QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); return true; } break; default: // Typing in messages widget brings focus to line edit, and // redirects key there. Exclude most combinations and keys that // emit no text, except paste shortcuts. if (obj == ui->messagesWidget && ((!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) || ((mod & Qt::ControlModifier) && key == Qt::Key_V) || ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert))) { ui->lineEdit->setFocus(); QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); return true; } } } return QWidget::eventFilter(obj, event); } void RPCConsole::setClientModel(ClientModel *model) { clientModel = model; ui->trafficGraph->setClientModel(model); if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) { // Keep up to date with client setNumConnections(model->getNumConnections()); connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); interfaces::Node &node = clientModel->node(); setNumBlocks(node.getNumBlocks(), QDateTime::fromTime_t(node.getLastBlockTime()), node.getVerificationProgress(), false); connect(model, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this, SLOT(setNumBlocks(int, QDateTime, double, bool))); updateNetworkState(); connect(model, SIGNAL(networkActiveChanged(bool)), this, SLOT(setNetworkActive(bool))); updateTrafficStats(node.getTotalBytesRecv(), node.getTotalBytesSent()); connect(model, SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(updateTrafficStats(quint64, quint64))); connect(model, SIGNAL(mempoolSizeChanged(long, size_t)), this, SLOT(setMempoolSize(long, size_t))); // set up peer table ui->peerWidget->setModel(model->getPeerTableModel()); ui->peerWidget->verticalHeader()->hide(); ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH); ui->peerWidget->horizontalHeader()->setStretchLastSection(true); // create peer table context menu actions QAction *disconnectAction = new QAction(tr("&Disconnect"), this); QAction *banAction1h = new QAction(tr("Ban for") + " " + tr("1 &hour"), this); QAction *banAction24h = new QAction(tr("Ban for") + " " + tr("1 &day"), this); QAction *banAction7d = new QAction(tr("Ban for") + " " + tr("1 &week"), this); QAction *banAction365d = new QAction(tr("Ban for") + " " + tr("1 &year"), this); // create peer table context menu peersTableContextMenu = new QMenu(this); peersTableContextMenu->addAction(disconnectAction); peersTableContextMenu->addAction(banAction1h); peersTableContextMenu->addAction(banAction24h); peersTableContextMenu->addAction(banAction7d); peersTableContextMenu->addAction(banAction365d); // Add a signal mapping to allow dynamic context menu arguments. We need // to use int (instead of int64_t), because signal mapper only supports // int or objects, which is okay because max bantime (1 year) is < // int_max. QSignalMapper *signalMapper = new QSignalMapper(this); signalMapper->setMapping(banAction1h, 60 * 60); signalMapper->setMapping(banAction24h, 60 * 60 * 24); signalMapper->setMapping(banAction7d, 60 * 60 * 24 * 7); signalMapper->setMapping(banAction365d, 60 * 60 * 24 * 365); connect(banAction1h, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(banAction24h, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(banAction7d, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(banAction365d, SIGNAL(triggered()), signalMapper, SLOT(map())); connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(banSelectedNode(int))); // peer table context menu signals connect(ui->peerWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showPeersTableContextMenu(const QPoint &))); connect(disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectSelectedNode())); // peer table signal handling - update peer details when selecting new // node connect( ui->peerWidget->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(peerSelected(const QItemSelection &, const QItemSelection &))); // peer table signal handling - update peer details when new nodes are // added to the model connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this, SLOT(peerLayoutChanged())); // peer table signal handling - cache selected node ids connect(model->getPeerTableModel(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(peerLayoutAboutToChange())); // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); ui->banlistWidget->verticalHeader()->hide(); ui->banlistWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection); ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH); ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH); ui->banlistWidget->horizontalHeader()->setStretchLastSection(true); // create ban table context menu action QAction *unbanAction = new QAction(tr("&Unban"), this); // create ban table context menu banTableContextMenu = new QMenu(this); banTableContextMenu->addAction(unbanAction); // ban table context menu signals connect(ui->banlistWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showBanTableContextMenu(const QPoint &))); connect(unbanAction, SIGNAL(triggered()), this, SLOT(unbanSelectedNode())); // ban table signal handling - clear peer details when clicking a peer // in the ban table connect(ui->banlistWidget, SIGNAL(clicked(const QModelIndex &)), this, SLOT(clearSelectedNode())); // ban table signal handling - ensure ban table is shown or hidden (if // empty) connect(model->getBanTableModel(), SIGNAL(layoutChanged()), this, SLOT(showOrHideBanTableIfRequired())); showOrHideBanTableIfRequired(); // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); ui->clientUserAgent->setText(model->formatSubVersion()); ui->dataDir->setText(model->dataDir()); ui->startupTime->setText(model->formatClientStartupTime()); ui->networkName->setText( QString::fromStdString(Params().NetworkIDString())); // Setup autocomplete and attach it QStringList wordList; std::vector commandList = m_node.listRpcCommands(); for (size_t i = 0; i < commandList.size(); ++i) { wordList << commandList[i].c_str(); wordList << ("help " + commandList[i]).c_str(); } wordList << "help-console"; wordList.sort(); autoCompleter = new QCompleter(wordList, this); autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel); ui->lineEdit->setCompleter(autoCompleter); autoCompleter->popup()->installEventFilter(this); // Start thread to execute RPC commands. startExecutor(); } if (!model) { // Client model is being set to 0, this means shutdown() is about to be // called. Make sure we clean up the executor thread Q_EMIT stopExecutor(); thread.wait(); } } #ifdef ENABLE_WALLET void RPCConsole::addWallet(WalletModel *const walletModel) { const QString name = walletModel->getWalletName(); // use name for text and internal data object (to allow to move to a wallet // id later) ui->WalletSelector->addItem(name, name); if (ui->WalletSelector->count() == 2 && !isVisible()) { // First wallet added, set to default so long as the window isn't // presently visible (and potentially in use) ui->WalletSelector->setCurrentIndex(1); } if (ui->WalletSelector->count() > 2) { ui->WalletSelector->setVisible(true); ui->WalletSelectorLabel->setVisible(true); } } #endif static QString categoryClass(int category) { switch (category) { case RPCConsole::CMD_REQUEST: return "cmd-request"; break; case RPCConsole::CMD_REPLY: return "cmd-reply"; break; case RPCConsole::CMD_ERROR: return "cmd-error"; break; default: return "misc"; } } void RPCConsole::fontBigger() { setFontSize(consoleFontSize + 1); } void RPCConsole::fontSmaller() { setFontSize(consoleFontSize - 1); } void RPCConsole::setFontSize(int newSize) { QSettings settings; // don't allow a insane font size if (newSize < FONT_RANGE.width() || newSize > FONT_RANGE.height()) return; // temp. store the console content QString str = ui->messagesWidget->toHtml(); // replace font tags size in current content str.replace(QString("font-size:%1pt").arg(consoleFontSize), QString("font-size:%1pt").arg(newSize)); // store the new font size consoleFontSize = newSize; settings.setValue(fontSizeSettingsKey, consoleFontSize); // clear console (reset icon sizes, default stylesheet) and re-add the // content float oldPosFactor = 1.0 / ui->messagesWidget->verticalScrollBar()->maximum() * ui->messagesWidget->verticalScrollBar()->value(); clear(false); ui->messagesWidget->setHtml(str); ui->messagesWidget->verticalScrollBar()->setValue( oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum()); } void RPCConsole::clear(bool clearHistory) { ui->messagesWidget->clear(); if (clearHistory) { history.clear(); historyPtr = 0; } ui->lineEdit->clear(); ui->lineEdit->setFocus(); // Add smoothly scaled icon images. // (when using width/height on an img, Qt uses nearest instead of linear // interpolation) for (int i = 0; ICON_MAPPING[i].url; ++i) { ui->messagesWidget->document()->addResource( QTextDocument::ImageResource, QUrl(ICON_MAPPING[i].url), platformStyle->SingleColorImage(ICON_MAPPING[i].source) .scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } // Set default style sheet QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont()); ui->messagesWidget->document()->setDefaultStyleSheet( QString("table { }" "td.time { color: #808080; font-size: %2; padding-top: 3px; } " "td.message { font-family: %1; font-size: %2; " "white-space:pre-wrap; } " "td.cmd-request { color: #006060; } " "td.cmd-error { color: red; } " ".secwarning { color: red; }" "b { color: #006060; } ") .arg(fixedFontInfo.family(), QString("%1pt").arg(consoleFontSize))); #ifdef Q_OS_MAC QString clsKey = "(⌘)-L"; #else QString clsKey = "Ctrl-L"; #endif message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "
" + tr("Use up and down arrows to navigate history, and " "%1 to clear screen.") .arg("" + clsKey + "") + "
" + tr("Type %1 for an overview of available commands.") .arg("help") + "
" + tr("For more information on using this console type %1.") .arg("help-console") + "

" + tr("WARNING: Scammers have been active, telling users to type " "commands here, stealing their wallet contents. Do not use " "this console without fully understanding the ramification " "of a command.") + "
"), true); } void RPCConsole::keyPressEvent(QKeyEvent *event) { if (windowType() != Qt::Widget && event->key() == Qt::Key_Escape) { close(); } } void RPCConsole::message(int category, const QString &message, bool html) { QTime time = QTime::currentTime(); QString timeString = time.toString(); QString out; out += ""; out += ""; out += "
" + timeString + ""; if (html) { out += message; } else { out += GUIUtil::HtmlEscape(message, false); } out += "
"; ui->messagesWidget->append(out); } void RPCConsole::updateNetworkState() { QString connections = QString::number(clientModel->getNumConnections()) + " ("; connections += tr("In:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / "; connections += tr("Out:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")"; if (!clientModel->node().getNetworkActive()) { connections += " (" + tr("Network activity disabled") + ")"; } ui->numberOfConnections->setText(connections); } void RPCConsole::setNumConnections(int count) { if (!clientModel) { return; } updateNetworkState(); } void RPCConsole::setNetworkActive(bool networkActive) { updateNetworkState(); } void RPCConsole::setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool headers) { if (!headers) { ui->numberOfBlocks->setText(QString::number(count)); ui->lastBlockTime->setText(blockDate.toString()); } } void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage) { ui->mempoolNumberTxs->setText(QString::number(numberOfTxs)); if (dynUsage < 1000000) { ui->mempoolSize->setText(QString::number(dynUsage / 1000.0, 'f', 2) + " KB"); } else { ui->mempoolSize->setText(QString::number(dynUsage / 1000000.0, 'f', 2) + " MB"); } } void RPCConsole::on_lineEdit_returnPressed() { QString cmd = ui->lineEdit->text(); if (!cmd.isEmpty()) { std::string strFilteredCmd; try { std::string dummy; if (!RPCParseCommandLine(nullptr, dummy, cmd.toStdString(), false, &strFilteredCmd)) { // Failed to parse command, so we cannot even filter it for the // history throw std::runtime_error("Invalid command line"); } } catch (const std::exception &e) { QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what())); return; } ui->lineEdit->clear(); cmdBeforeBrowsing = QString(); QString walletID; #ifdef ENABLE_WALLET const int wallet_index = ui->WalletSelector->currentIndex(); if (wallet_index > 0) { walletID = (QString)ui->WalletSelector->itemData(wallet_index) .value(); } if (m_last_wallet_id != walletID) { if (walletID.isEmpty()) { message(CMD_REQUEST, tr("Executing command without any wallet")); } else { message( CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(walletID)); } m_last_wallet_id = walletID; } #endif message(CMD_REQUEST, QString::fromStdString(strFilteredCmd)); Q_EMIT cmdRequest(cmd, walletID); cmd = QString::fromStdString(strFilteredCmd); // Remove command, if already in history history.removeOne(cmd); // Append command to history history.append(cmd); // Enforce maximum history size while (history.size() > CONSOLE_HISTORY) history.removeFirst(); // Set pointer to end of history historyPtr = history.size(); // Scroll console view to end scrollToEnd(); } } void RPCConsole::browseHistory(int offset) { // store current text when start browsing through the history if (historyPtr == history.size()) { cmdBeforeBrowsing = ui->lineEdit->text(); } historyPtr += offset; if (historyPtr < 0) { historyPtr = 0; } if (historyPtr > history.size()) { historyPtr = history.size(); } QString cmd; if (historyPtr < history.size()) { cmd = history.at(historyPtr); } else if (!cmdBeforeBrowsing.isNull()) { cmd = cmdBeforeBrowsing; } ui->lineEdit->setText(cmd); } void RPCConsole::startExecutor() { RPCExecutor *executor = new RPCExecutor(m_node); executor->moveToThread(&thread); // Replies from executor object must go to this object connect(executor, SIGNAL(reply(int, QString)), this, SLOT(message(int, QString))); // Requests from this object must go to executor connect(this, SIGNAL(cmdRequest(QString, QString)), executor, SLOT(request(QString, QString))); // On stopExecutor signal // - quit the Qt event loop in the execution thread connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit())); // - queue executor for deletion (in execution thread) connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()), Qt::DirectConnection); // Default implementation of QThread::run() simply spins up an event loop in // the thread, which is what we want. thread.start(); } void RPCConsole::on_tabWidget_currentChanged(int index) { if (ui->tabWidget->widget(index) == ui->tab_console) { ui->lineEdit->setFocus(); } else if (ui->tabWidget->widget(index) != ui->tab_peers) { clearSelectedNode(); } } void RPCConsole::on_openDebugLogfileButton_clicked() { GUIUtil::openDebugLogfile(); } void RPCConsole::scrollToEnd() { QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar(); scrollbar->setValue(scrollbar->maximum()); } void RPCConsole::on_sldGraphRange_valueChanged(int value) { const int multiplier = 5; // each position on the slider represents 5 min int mins = value * multiplier; setTrafficGraphRange(mins); } void RPCConsole::setTrafficGraphRange(int mins) { ui->trafficGraph->setGraphRangeMins(mins); ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60)); } void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) { ui->lblBytesIn->setText(GUIUtil::formatBytes(totalBytesIn)); ui->lblBytesOut->setText(GUIUtil::formatBytes(totalBytesOut)); } void RPCConsole::peerSelected(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected); if (!clientModel || !clientModel->getPeerTableModel() || selected.indexes().isEmpty()) return; const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats( selected.indexes().first().row()); if (stats) updateNodeDetail(stats); } void RPCConsole::peerLayoutAboutToChange() { QModelIndexList selected = ui->peerWidget->selectionModel()->selectedIndexes(); cachedNodeids.clear(); for (int i = 0; i < selected.size(); i++) { const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats( selected.at(i).row()); cachedNodeids.append(stats->nodeStats.nodeid); } } void RPCConsole::peerLayoutChanged() { if (!clientModel || !clientModel->getPeerTableModel()) { return; } const CNodeCombinedStats *stats = nullptr; bool fUnselect = false; bool fReselect = false; if (cachedNodeids.empty()) // no node selected yet return; // find the currently selected row int selectedRow = -1; QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes(); if (!selectedModelIndex.isEmpty()) { selectedRow = selectedModelIndex.first().row(); } // check if our detail node has a row in the table (it may not necessarily // be at selectedRow since its position can change after a layout change) int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.first()); if (detailNodeRow < 0) { // detail node disappeared from table (node disconnected) fUnselect = true; } else { if (detailNodeRow != selectedRow) { // detail node moved position fUnselect = true; fReselect = true; } // get fresh stats on the detail node. stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); } if (fUnselect && selectedRow >= 0) { clearSelectedNode(); } if (fReselect) { for (int i = 0; i < cachedNodeids.size(); i++) { ui->peerWidget->selectRow( clientModel->getPeerTableModel()->getRowByNodeId( cachedNodeids.at(i))); } } if (stats) updateNodeDetail(stats); } void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) { // update the detail ui with latest node information QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " "); peerAddrDetails += tr("(node id: %1)").arg(QString::number(stats->nodeStats.nodeid)); if (!stats->nodeStats.addrLocal.empty()) { peerAddrDetails += "
" + tr("via %1").arg(QString::fromStdString( stats->nodeStats.addrLocal)); } ui->peerHeading->setText(peerAddrDetails); ui->peerServices->setText( GUIUtil::formatServicesStr(stats->nodeStats.nServices)); ui->peerLastSend->setText( stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastSend) : tr("never")); ui->peerLastRecv->setText( stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastRecv) : tr("never")); ui->peerBytesSent->setText( GUIUtil::formatBytes(stats->nodeStats.nSendBytes)); ui->peerBytesRecv->setText( GUIUtil::formatBytes(stats->nodeStats.nRecvBytes)); ui->peerConnTime->setText(GUIUtil::formatDurationStr( GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected)); ui->peerPingTime->setText( GUIUtil::formatPingTime(stats->nodeStats.dPingTime)); ui->peerPingWait->setText( GUIUtil::formatPingTime(stats->nodeStats.dPingWait)); ui->peerMinPing->setText( GUIUtil::formatPingTime(stats->nodeStats.dMinPing)); ui->timeoffset->setText( GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); ui->peerVersion->setText( QString("%1").arg(QString::number(stats->nodeStats.nVersion))); ui->peerSubversion->setText( QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound")); ui->peerHeight->setText( QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight))); ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No")); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. if (stats->fNodeStateStatsAvailable) { // Ban score is init to 0 ui->peerBanScore->setText( QString("%1").arg(stats->nodeStateStats.nMisbehavior)); // Sync height is init to -1 if (stats->nodeStateStats.nSyncHeight > -1) { ui->peerSyncHeight->setText( QString("%1").arg(stats->nodeStateStats.nSyncHeight)); } else { ui->peerSyncHeight->setText(tr("Unknown")); } // Common height is init to -1 if (stats->nodeStateStats.nCommonHeight > -1) { ui->peerCommonHeight->setText( QString("%1").arg(stats->nodeStateStats.nCommonHeight)); } else { ui->peerCommonHeight->setText(tr("Unknown")); } } ui->detailWidget->show(); } void RPCConsole::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); } void RPCConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); if (!clientModel || !clientModel->getPeerTableModel()) { return; } // start PeerTableModel auto refresh clientModel->getPeerTableModel()->startAutoRefresh(); } void RPCConsole::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); if (!clientModel || !clientModel->getPeerTableModel()) { return; } // stop PeerTableModel auto refresh clientModel->getPeerTableModel()->stopAutoRefresh(); } void RPCConsole::showPeersTableContextMenu(const QPoint &point) { QModelIndex index = ui->peerWidget->indexAt(point); if (index.isValid()) peersTableContextMenu->exec(QCursor::pos()); } void RPCConsole::showBanTableContextMenu(const QPoint &point) { QModelIndex index = ui->banlistWidget->indexAt(point); if (index.isValid()) banTableContextMenu->exec(QCursor::pos()); } void RPCConsole::disconnectSelectedNode() { // Get selected peer addresses QList nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId); for (int i = 0; i < nodes.count(); i++) { // Get currently selected peer address NodeId id = nodes.at(i).data().toLongLong(); // Find the node, disconnect it and clear the selected node if (m_node.disconnect(id)) { clearSelectedNode(); } } } void RPCConsole::banSelectedNode(int bantime) { if (!clientModel) { return; } // Get selected peer addresses QList nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId); for (int i = 0; i < nodes.count(); i++) { // Get currently selected peer address NodeId id = nodes.at(i).data().toLongLong(); // Get currently selected peer address int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id); if (detailNodeRow < 0) { return; } // Find possible nodes, ban it and clear the selected node const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); if (stats) { m_node.ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime); } } clearSelectedNode(); clientModel->getBanTableModel()->refresh(); } void RPCConsole::unbanSelectedNode() { if (!clientModel) { return; } // Get selected ban addresses QList nodes = GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address); for (int i = 0; i < nodes.count(); i++) { // Get currently selected ban address QString strNode = nodes.at(i).data().toString(); CSubNet possibleSubnet; LookupSubNet(strNode.toStdString().c_str(), possibleSubnet); if (possibleSubnet.IsValid() && m_node.unban(possibleSubnet)) { clientModel->getBanTableModel()->refresh(); } } } void RPCConsole::clearSelectedNode() { ui->peerWidget->selectionModel()->clearSelection(); cachedNodeids.clear(); ui->detailWidget->hide(); ui->peerHeading->setText(tr("Select a peer to view detailed information.")); } void RPCConsole::showOrHideBanTableIfRequired() { if (!clientModel) { return; } bool visible = clientModel->getBanTableModel()->shouldShow(); ui->banlistWidget->setVisible(visible); ui->banHeading->setVisible(visible); } void RPCConsole::setTabFocus(enum TabTypes tabType) { ui->tabWidget->setCurrentIndex(tabType); }