limenka.cpp raw

   1  // Copyright (c) 2011-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <limenka-build-config.h> // IWYU pragma: keep
   6  
   7  #include <qt/limenka.h>
   8  
   9  #include <chainparams.h>
  10  #include <clientversion.h>
  11  #include <common/args.h>
  12  #include <common/init.h>
  13  #include <common/system.h>
  14  #include <init.h>
  15  #include <interfaces/handler.h>
  16  #include <interfaces/init.h>
  17  #include <interfaces/node.h>
  18  #include <kernel/chainparams.h>
  19  #include <logging.h>
  20  #include <node/context.h>
  21  #include <node/interface_ui.h>
  22  #include <noui.h>
  23  #include <qt/limenkagui.h>
  24  #include <qt/clientmodel.h>
  25  #include <qt/guiconstants.h>
  26  #include <qt/guiutil.h>
  27  #include <qt/initexecutor.h>
  28  #include <qt/intro.h>
  29  #include <qt/networkstyle.h>
  30  #include <qt/optionsmodel.h>
  31  #include <qt/platformstyle.h>
  32  #include <qt/splashscreen.h>
  33  #include <qt/utilitydialog.h>
  34  #include <qt/winshutdownmonitor.h>
  35  #include <stats/stats.h>
  36  #include <uint256.h>
  37  #include <util/exception.h>
  38  #include <util/string.h>
  39  #include <util/threadnames.h>
  40  #include <util/translation.h>
  41  #include <univalue.h>
  42  #include <validation.h>
  43  
  44  #ifdef ENABLE_WALLET
  45  #include <qt/paymentserver.h>
  46  #include <qt/walletcontroller.h>
  47  #include <qt/walletmodel.h>
  48  #include <wallet/types.h>
  49  #endif // ENABLE_WALLET
  50  
  51  #include <boost/signals2/connection.hpp>
  52  #include <chrono>
  53  #include <memory>
  54  
  55  #include <QApplication>
  56  #include <QDebug>
  57  #include <QLatin1String>
  58  #include <QLibraryInfo>
  59  #include <QLocale>
  60  #include <QMessageBox>
  61  #include <QSettings>
  62  #include <QString>
  63  #include <QThread>
  64  #include <QTimer>
  65  #include <QTranslator>
  66  #include <QWindow>
  67  
  68  // Declare meta types used for QMetaObject::invokeMethod
  69  Q_DECLARE_METATYPE(bool*)
  70  Q_DECLARE_METATYPE(CAmount)
  71  Q_DECLARE_METATYPE(SynchronizationState)
  72  Q_DECLARE_METATYPE(SyncType)
  73  Q_DECLARE_METATYPE(uint256)
  74  #ifdef ENABLE_WALLET
  75  Q_DECLARE_METATYPE(wallet::AddressPurpose)
  76  #endif // ENABLE_WALLET
  77  
  78  using util::MakeUnorderedList;
  79  
  80  static void RegisterMetaTypes()
  81  {
  82      // Register meta types used for QMetaObject::invokeMethod and Qt::QueuedConnection
  83      qRegisterMetaType<bool*>();
  84      qRegisterMetaType<SynchronizationState>();
  85      qRegisterMetaType<SyncType>();
  86    #ifdef ENABLE_WALLET
  87      qRegisterMetaType<WalletModel*>();
  88      qRegisterMetaType<wallet::AddressPurpose>();
  89    #endif // ENABLE_WALLET
  90      // Register typedefs (see https://doc.qt.io/qt-5/qmetatype.html#qRegisterMetaType)
  91      // IMPORTANT: if CAmount is no longer a typedef use the normal variant above (see https://doc.qt.io/qt-5/qmetatype.html#qRegisterMetaType-1)
  92      qRegisterMetaType<CAmount>("CAmount");
  93      qRegisterMetaType<CTransactionRef>("CTransactionRef");
  94      qRegisterMetaType<size_t>("size_t");
  95  
  96      qRegisterMetaType<std::function<void()>>("std::function<void()>");
  97      qRegisterMetaType<QMessageBox::Icon>("QMessageBox::Icon");
  98      qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
  99  
 100  #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
 101      qRegisterMetaTypeStreamOperators<LimenkaUnit>("LimenkaUnit");
 102  #else
 103      qRegisterMetaType<LimenkaUnit>("LimenkaUnit");
 104  #endif
 105  }
 106  
 107  static QString GetLangTerritory()
 108  {
 109      QSettings settings;
 110      // Get desired locale (e.g. "de_DE")
 111      // 1) System default language
 112      QString lang_territory = QLocale::system().name();
 113      // 2) Language from QSettings
 114      QString lang_territory_qsettings = settings.value("language", "").toString();
 115      if(!lang_territory_qsettings.isEmpty())
 116          lang_territory = lang_territory_qsettings;
 117      // 3) -lang command line argument
 118      lang_territory = QString::fromStdString(gArgs.GetArg("-lang", lang_territory.toStdString()));
 119      return lang_territory;
 120  }
 121  
 122  /** Set up translations */
 123  static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator)
 124  {
 125      // Remove old translators
 126      QApplication::removeTranslator(&qtTranslatorBase);
 127      QApplication::removeTranslator(&qtTranslator);
 128      QApplication::removeTranslator(&translatorBase);
 129      QApplication::removeTranslator(&translator);
 130  
 131      // Get desired locale (e.g. "de_DE")
 132      // 1) System default language
 133      QString lang_territory = GetLangTerritory();
 134  
 135      // Convert to "de" only by truncating "_DE"
 136      QString lang = lang_territory;
 137      lang.truncate(lang_territory.lastIndexOf('_'));
 138  
 139      // Load language files for configured locale:
 140      // - First load the translator for the base language, without territory
 141      // - Then load the more specific locale translator
 142  
 143  #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
 144      const QString translation_path{QLibraryInfo::location(QLibraryInfo::TranslationsPath)};
 145  #else
 146      const QString translation_path{QLibraryInfo::path(QLibraryInfo::TranslationsPath)};
 147  #endif
 148      // Load e.g. qt_de.qm
 149      if (qtTranslatorBase.load("qt_" + lang, translation_path)) {
 150          QApplication::installTranslator(&qtTranslatorBase);
 151      }
 152  
 153      // Load e.g. qt_de_DE.qm
 154      if (qtTranslator.load("qt_" + lang_territory, translation_path)) {
 155          QApplication::installTranslator(&qtTranslator);
 156      }
 157  
 158      // Load e.g. limenka_de.qm (shortcut "de" needs to be defined in limenka.qrc)
 159      if (translatorBase.load(lang, ":/translations/")) {
 160          QApplication::installTranslator(&translatorBase);
 161      }
 162  
 163      // Load e.g. limenka_de_DE.qm (shortcut "de_DE" needs to be defined in limenka.qrc)
 164      if (translator.load(lang_territory, ":/translations/")) {
 165          QApplication::installTranslator(&translator);
 166      }
 167  }
 168  
 169  static bool ErrorSettingsRead(const bilingual_str& error, const std::vector<std::string>& details)
 170  {
 171      QMessageBox messagebox(QMessageBox::Critical, CLIENT_NAME, QString::fromStdString(strprintf("%s.", error.translated)), QMessageBox::Reset | QMessageBox::Abort);
 172      /*: Explanatory text shown on startup when the settings file cannot be read.
 173        Prompts user to make a choice between resetting or aborting. */
 174      messagebox.setInformativeText(QObject::tr("Do you want to reset settings to default values, or to abort without making changes?"));
 175      messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(details)));
 176      messagebox.setTextFormat(Qt::PlainText);
 177      messagebox.setDefaultButton(QMessageBox::Reset);
 178      switch (messagebox.exec()) {
 179      case QMessageBox::Reset:
 180          return false;
 181      case QMessageBox::Abort:
 182          return true;
 183      default:
 184          assert(false);
 185      }
 186  }
 187  
 188  static void ErrorSettingsWrite(const bilingual_str& error, const std::vector<std::string>& details)
 189  {
 190      QMessageBox messagebox(QMessageBox::Critical, CLIENT_NAME, QString::fromStdString(strprintf("%s.", error.translated)), QMessageBox::Ok);
 191      /*: Explanatory text shown on startup when the settings file could not be written.
 192          Prompts user to check that we have the ability to write to the file.
 193          Explains that the user has the option of running without a settings file.*/
 194      messagebox.setInformativeText(QObject::tr("A fatal error occurred. Check that settings file is writable, or try running with -nosettings."));
 195      messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(details)));
 196      messagebox.setTextFormat(Qt::PlainText);
 197      messagebox.setDefaultButton(QMessageBox::Ok);
 198      messagebox.exec();
 199  }
 200  
 201  /* qDebug() message handler --> debug.log */
 202  void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg)
 203  {
 204      Q_UNUSED(context);
 205      if (type == QtDebugMsg) {
 206          LogDebug(BCLog::QT, "GUI: %s\n", msg.toStdString());
 207      } else {
 208          LogPrintf("GUI: %s\n", msg.toStdString());
 209      }
 210  }
 211  
 212  static int qt_argc = 1;
 213  static const char* qt_argv = "limenka-qt";
 214  
 215  LimenkaApplication::LimenkaApplication()
 216      : QApplication(qt_argc, const_cast<char**>(&qt_argv))
 217  {
 218      // Qt runs setlocale(LC_ALL, "") on initialization.
 219      RegisterMetaTypes();
 220      setQuitOnLastWindowClosed(false);
 221  }
 222  
 223  void LimenkaApplication::setupPlatformStyle()
 224  {
 225      // UI per-platform customization
 226      // This must be done inside the LimenkaApplication constructor, or after it, because
 227      // PlatformStyle::instantiate requires a QApplication
 228      std::string platformName;
 229      platformName = gArgs.GetArg("-uiplatform", LimenkaGUI::DEFAULT_UIPLATFORM);
 230      platformStyle = PlatformStyle::instantiate(QString::fromStdString(platformName));
 231      if (!platformStyle) // Fall back to "other" if specified name not found
 232          platformStyle = PlatformStyle::instantiate("other");
 233      assert(platformStyle);
 234  }
 235  
 236  LimenkaApplication::~LimenkaApplication()
 237  {
 238      m_executor.reset();
 239  
 240      delete window;
 241      window = nullptr;
 242      delete platformStyle;
 243      platformStyle = nullptr;
 244  }
 245  
 246  #ifdef ENABLE_WALLET
 247  void LimenkaApplication::createPaymentServer()
 248  {
 249      paymentServer = new PaymentServer(this);
 250  }
 251  #endif
 252  
 253  bool LimenkaApplication::createOptionsModel(bool resetSettings)
 254  {
 255      optionsModel = new OptionsModel(node(), this);
 256      if (resetSettings) {
 257          optionsModel->Reset();
 258      }
 259      bilingual_str error;
 260      if (!optionsModel->Init(error)) {
 261          fs::path settings_path;
 262          if (gArgs.GetSettingsPath(&settings_path)) {
 263              error += Untranslated("\n");
 264              std::string quoted_path = strprintf("%s", fs::quoted(fs::PathToString(settings_path)));
 265              error.original += strprintf("Settings file %s might be corrupt or invalid.", quoted_path);
 266              error.translated += tr("Settings file %1 might be corrupt or invalid.").arg(QString::fromStdString(quoted_path)).toStdString();
 267          }
 268          InitError(error);
 269          QMessageBox::critical(nullptr, CLIENT_NAME, QString::fromStdString(error.translated));
 270          return false;
 271      }
 272      return true;
 273  }
 274  
 275  void LimenkaApplication::createWindow(const NetworkStyle *networkStyle)
 276  {
 277      window = new LimenkaGUI(node(), platformStyle, networkStyle, nullptr);
 278      connect(window, &LimenkaGUI::quitRequested, this, &LimenkaApplication::requestShutdown);
 279  
 280      pollShutdownTimer = new QTimer(window);
 281      connect(pollShutdownTimer, &QTimer::timeout, [this]{
 282          if (!QApplication::activeModalWidget()) {
 283              window->detectShutdown();
 284          }
 285      });
 286  }
 287  
 288  void LimenkaApplication::createSplashScreen(const NetworkStyle *networkStyle)
 289  {
 290      assert(!m_splash);
 291      m_splash = new SplashScreen(networkStyle);
 292      m_splash->show();
 293  }
 294  
 295  void LimenkaApplication::createNode(interfaces::Init& init)
 296  {
 297      assert(!m_node);
 298      m_node = init.makeNode();
 299      if (m_splash) m_splash->setNode(*m_node);
 300  }
 301  
 302  bool LimenkaApplication::baseInitialize()
 303  {
 304      return node().baseInitialize();
 305  }
 306  
 307  void LimenkaApplication::startThread()
 308  {
 309      assert(!m_executor);
 310      m_executor.emplace(node());
 311  
 312      /*  communication to and from thread */
 313      connect(&m_executor.value(), &InitExecutor::initializeResult, this, &LimenkaApplication::initializeResult);
 314      connect(&m_executor.value(), &InitExecutor::shutdownResult, this, [] {
 315          QCoreApplication::exit(0);
 316      });
 317      connect(&m_executor.value(), &InitExecutor::runawayException, this, &LimenkaApplication::handleRunawayException);
 318      connect(this, &LimenkaApplication::requestedInitialize, &m_executor.value(), &InitExecutor::initialize);
 319      connect(this, &LimenkaApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown);
 320  }
 321  
 322  void LimenkaApplication::parameterSetup()
 323  {
 324      // Default printtoconsole to false for the GUI. GUI programs should not
 325      // print to the console unnecessarily.
 326      gArgs.SoftSetBoolArg("-printtoconsole", false);
 327  
 328      InitLogging(gArgs);
 329      InitParameterInteraction(gArgs);
 330  }
 331  
 332  void LimenkaApplication::InitPruneSetting(int64_t prune_MiB)
 333  {
 334      optionsModel->SetPruneTargetMiB(prune_MiB);
 335  }
 336  
 337  void LimenkaApplication::requestInitialize()
 338  {
 339      qDebug() << __func__ << ": Requesting initialize";
 340      startThread();
 341      Q_EMIT requestedInitialize();
 342  }
 343  
 344  void LimenkaApplication::requestShutdown()
 345  {
 346      for (const auto w : QGuiApplication::topLevelWindows()) {
 347          w->hide();
 348      }
 349  
 350      delete m_splash;
 351      m_splash = nullptr;
 352  
 353      // Show a simple window indicating shutdown status
 354      // Do this first as some of the steps may take some time below,
 355      // for example the RPC console may still be executing a command.
 356      shutdownWindow.reset(ShutdownWindow::showShutdownWindow(window));
 357  
 358      qDebug() << __func__ << ": Requesting shutdown";
 359  
 360      // Must disconnect node signals otherwise current thread can deadlock since
 361      // no event loop is running.
 362      window->unsubscribeFromCoreSignals();
 363      // Request node shutdown, which can interrupt long operations, like
 364      // rescanning a wallet.
 365      node().startShutdown();
 366      // Prior to unsetting the client model, stop listening backend signals
 367      if (clientModel) {
 368          clientModel->stop();
 369      }
 370  
 371      // Unsetting the client model can cause the current thread to wait for node
 372      // to complete an operation, like wait for a RPC execution to complete.
 373      window->setClientModel(nullptr);
 374      pollShutdownTimer->stop();
 375  
 376  #ifdef ENABLE_WALLET
 377      // Delete wallet controller here manually, instead of relying on Qt object
 378      // tracking (https://doc.qt.io/qt-5/objecttrees.html). This makes sure
 379      // walletmodel m_handle_* notification handlers are deleted before wallets
 380      // are unloaded, which can simplify wallet implementations. It also avoids
 381      // these notifications having to be handled while GUI objects are being
 382      // destroyed, making GUI code less fragile as well.
 383      delete m_wallet_controller;
 384      m_wallet_controller = nullptr;
 385  #endif // ENABLE_WALLET
 386  
 387      delete clientModel;
 388      clientModel = nullptr;
 389  
 390      // Request shutdown from core thread
 391      Q_EMIT requestedShutdown();
 392  }
 393  
 394  void LimenkaApplication::initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info)
 395  {
 396      qDebug() << __func__ << ": Initialization result: " << success;
 397  
 398      if (success && !m_node->shutdownRequested()) {
 399          delete m_splash;
 400          m_splash = nullptr;
 401  
 402          // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
 403          qInfo() << "Platform customization:" << platformStyle->getName();
 404          clientModel = new ClientModel(node(), optionsModel, *platformStyle);
 405          window->setClientModel(clientModel, &tip_info);
 406  
 407          // If '-min' option passed, start window minimized (iconified) or minimized to tray
 408          bool start_minimized = gArgs.GetBoolArg("-min", false);
 409  #ifdef ENABLE_WALLET
 410          if (WalletModel::isWalletEnabled()) {
 411              m_wallet_controller = new WalletController(*clientModel, platformStyle, this);
 412              window->setWalletController(m_wallet_controller, /*show_loading_minimized=*/start_minimized);
 413              if (paymentServer) {
 414                  paymentServer->setOptionsModel(optionsModel);
 415              }
 416          }
 417  #endif // ENABLE_WALLET
 418  
 419          // Show or minimize window
 420          if (!start_minimized) {
 421              window->show();
 422          } else if (clientModel->getOptionsModel()->getMinimizeToTray() && window->hasTrayIcon()) {
 423              // do nothing as the window is managed by the tray icon
 424          } else {
 425              window->showMinimized();
 426          }
 427          Q_EMIT windowShown(window);
 428  
 429  #ifdef ENABLE_WALLET
 430          // Now that initialization/startup is done, process any command-line
 431          // limenka: URIs or payment requests:
 432          if (paymentServer) {
 433              connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &LimenkaGUI::handlePaymentRequest);
 434              connect(window, &LimenkaGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile);
 435              connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) {
 436                  window->message(title, message, style);
 437              });
 438              QTimer::singleShot(100ms, paymentServer, &PaymentServer::uiReady);
 439          }
 440  #endif
 441          pollShutdownTimer->start(SHUTDOWN_POLLING_DELAY);
 442      } else {
 443          requestShutdown();
 444      }
 445  }
 446  
 447  void LimenkaApplication::handleRunawayException(const QString &message)
 448  {
 449      QMessageBox::critical(
 450          nullptr, tr("Runaway exception"),
 451          tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(CLIENT_NAME) +
 452          QLatin1String("<br><br>") + GUIUtil::MakeHtmlLink(message, CLIENT_BUGREPORT));
 453      ::exit(EXIT_FAILURE);
 454  }
 455  
 456  void LimenkaApplication::handleNonFatalException(const QString& message)
 457  {
 458      assert(QThread::currentThread() == thread());
 459      QMessageBox::warning(
 460          nullptr, tr("Internal error"),
 461          tr("An internal error occurred. %1 will attempt to continue safely. This is "
 462             "an unexpected bug which can be reported as described below.").arg(CLIENT_NAME) +
 463          QLatin1String("<br><br>") + GUIUtil::MakeHtmlLink(message, CLIENT_BUGREPORT));
 464  }
 465  
 466  WId LimenkaApplication::getMainWinId() const
 467  {
 468      if (!window)
 469          return 0;
 470  
 471      return window->winId();
 472  }
 473  
 474  bool LimenkaApplication::event(QEvent* e)
 475  {
 476      if (e->type() == QEvent::Quit) {
 477          requestShutdown();
 478          return true;
 479      }
 480  
 481      return QApplication::event(e);
 482  }
 483  
 484  static void SetupUIArgs(ArgsManager& argsman)
 485  {
 486      argsman.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
 487      argsman.AddArg("-guisettingsdir=<path>", "Choose a custom data directory especially for the Qt Settings", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
 488      argsman.AddArg("-lang=<lang>", "Set language, for example \"de_DE\" (default: system locale)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
 489      argsman.AddArg("-min", "Start minimized", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
 490      argsman.AddArg("-resetguisettings", "Reset all settings changed in the GUI", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
 491      argsman.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
 492      argsman.AddArg("-uiplatform", strprintf("Select platform to customize UI for (one of windows, macosx, other; default: %s)", LimenkaGUI::DEFAULT_UIPLATFORM), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::GUI);
 493  }
 494  
 495  int GuiMain(int argc, char* argv[])
 496  {
 497  #ifdef WIN32
 498      common::WinCmdLineArgs winArgs;
 499      std::tie(argc, argv) = winArgs.get();
 500  #endif
 501  
 502      std::unique_ptr<interfaces::Init> init = interfaces::MakeGuiInit(argc, argv);
 503  
 504      SetupEnvironment();
 505      util::ThreadSetInternalName("main");
 506  
 507      // Subscribe to global signals from core
 508      boost::signals2::scoped_connection handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox);
 509      boost::signals2::scoped_connection handler_question = ::uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion);
 510      boost::signals2::scoped_connection handler_init_message = ::uiInterface.InitMessage_connect(noui_InitMessage);
 511  
 512      // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory
 513  
 514      /// 1. Basic Qt initialization (not dependent on parameters or configuration)
 515      Q_INIT_RESOURCE(limenka);
 516      Q_INIT_RESOURCE(limenka_locale);
 517      Q_INIT_RESOURCE(limenka_rendered);
 518      Q_INIT_RESOURCE(font);
 519  
 520  #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
 521      // Generate high-dpi pixmaps
 522      QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
 523      QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 524  #endif
 525  
 526  #if defined(QT_QPA_PLATFORM_ANDROID)
 527      QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
 528      QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
 529      QApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
 530  #endif
 531  
 532      LimenkaApplication app;
 533      GUIUtil::LoadFont(QStringLiteral(":/fonts/monospace"));
 534  
 535      /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these
 536      // Command-line options take precedence:
 537      SetupServerArgs(gArgs, init->canListenIpc());
 538      SetupUIArgs(gArgs);
 539      std::string error;
 540      if (!gArgs.ParseParameters(argc, argv, error)) {
 541          InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
 542          // Create a message box, because the gui has neither been created nor has subscribed to core signals
 543          QMessageBox::critical(nullptr, CLIENT_NAME,
 544              // message cannot be translated because translations have not been initialized
 545              QString::fromStdString("Error parsing command line arguments: %1.").arg(QString::fromStdString(error)));
 546          return EXIT_FAILURE;
 547      }
 548  
 549      // Error out when loose non-argument tokens are encountered on command line
 550      // However, allow BIP-21 URIs only if no options follow
 551      bool payment_server_token_seen = false;
 552      for (int i = 1; i < argc; i++) {
 553          QString arg(argv[i]);
 554          bool invalid_token = !arg.startsWith("-");
 555  #ifdef ENABLE_WALLET
 556          if (arg.startsWith(LIMENKA_IPC_PREFIX, Qt::CaseInsensitive)) {
 557              invalid_token &= false;
 558              payment_server_token_seen = true;
 559          }
 560  #endif
 561          if (payment_server_token_seen && arg.startsWith("-")) {
 562              InitError(Untranslated(strprintf("Options ('%s') cannot follow a BIP-21 payment URI", argv[i])));
 563              QMessageBox::critical(nullptr, CLIENT_NAME,
 564                                    // message cannot be translated because translations have not been initialized
 565                                    QString::fromStdString("Options ('%1') cannot follow a BIP-21 payment URI").arg(QString::fromStdString(argv[i])));
 566              return EXIT_FAILURE;
 567          }
 568          if (invalid_token) {
 569              InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see limenka-qt -h for a list of options.", argv[i])));
 570              QMessageBox::critical(nullptr, CLIENT_NAME,
 571                                    // message cannot be translated because translations have not been initialized
 572                                    QString::fromStdString("Command line contains unexpected token '%1', see limenka-qt -h for a list of options.").arg(QString::fromStdString(argv[i])));
 573              return EXIT_FAILURE;
 574          }
 575      }
 576  
 577      // Now that the QApplication is setup and we have parsed our parameters, we can set the platform style
 578      app.setupPlatformStyle();
 579  
 580      /// 3. Application identification
 581      // must be set before OptionsModel is initialized or translations are loaded,
 582      // as it is used to locate QSettings
 583      QApplication::setOrganizationName(QAPP_ORG_NAME);
 584      QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN);
 585      QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT);
 586      const std::string qt_settings_path = gArgs.GetArg("-guisettingsdir", "");
 587      if (!qt_settings_path.empty()) {
 588          QSettings::setDefaultFormat(QSettings::IniFormat);
 589          QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QString::fromStdString(qt_settings_path));
 590      }
 591  
 592      /// 4. Initialization of translations, so that intro dialog is in user's language
 593      // Now that QSettings are accessible, initialize translations
 594      QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
 595      initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
 596  
 597      // Show help message immediately after parsing command-line options (for "-lang") and setting locale,
 598      // but before showing splash screen.
 599      if (HelpRequested(gArgs) || gArgs.GetBoolArg("-version", false)) {
 600          HelpMessageDialog help(nullptr, gArgs.GetBoolArg("-version", false));
 601          help.showOrPrint();
 602          return EXIT_SUCCESS;
 603      }
 604  
 605      // Install global event filter that makes sure that long tooltips can be word-wrapped
 606      app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
 607  
 608      /// 5. Now that settings and translations are available, ask user for data directory
 609      // User language is set up: pick a data directory
 610      std::unique_ptr<Intro> intro;
 611      // Gracefully exit if the user cancels
 612      if (!Intro::showIfNeeded(intro)) return EXIT_SUCCESS;
 613  
 614      /// 6-7. Parse limenka.conf, determine network, switch to network specific
 615      /// options, and create datadir and settings.json.
 616      // - Do not call gArgs.GetDataDirNet() before this step finishes
 617      // - Do not call Params() before this step
 618      // - QSettings() will use the new application name after this, resulting in network-specific settings
 619      // - Needs to be done before createOptionsModel
 620      if (auto error = common::InitConfig(gArgs, ErrorSettingsRead)) {
 621          InitError(error->message, error->details);
 622          if (error->status == common::ConfigStatus::FAILED_WRITE) {
 623              // Show a custom error message to provide more information in the
 624              // case of a datadir write error.
 625              ErrorSettingsWrite(error->message, error->details);
 626          } else if (error->status != common::ConfigStatus::ABORTED) {
 627              // Show a generic message in other cases, and no additional error
 628              // message in the case of a read error if the user decided to abort.
 629              QMessageBox::critical(nullptr, CLIENT_NAME, QObject::tr("Error: %1").arg(QString::fromStdString(error->message.translated)));
 630          }
 631          return EXIT_FAILURE;
 632      }
 633  #ifdef ENABLE_WALLET
 634      // Parse URIs on command line
 635      PaymentServer::ipcParseCommandLine(argc, argv);
 636  #endif
 637  
 638      if (g_rdts_consent == RDTSConsentFlag::RUNTIME_WARN) {
 639          // The GUI user is presented with a choice to consent or exit.
 640          // It doesn't make sense to continue running if they choose exit.
 641          // We set this here since it should be after SelectParams loads the test option yet still before AppInitMain acts on it
 642          g_rdts_consent = RDTSConsentFlag::RUNTIME_CHECK;
 643      }
 644  
 645      QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().GetChainType()));
 646      assert(!networkStyle.isNull());
 647      // Allow for separate UI settings for testnets
 648      QApplication::setApplicationName(networkStyle->getAppName());
 649      // Re-initialize translations after changing application name (language in network-specific settings can be different)
 650      initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
 651  
 652  #ifdef ENABLE_WALLET
 653      /// 8. URI IPC sending
 654      // - Do this early as we don't want to bother initializing if we are just calling IPC
 655      // - Do this *after* setting up the data directory, as the data directory hash is used in the name
 656      // of the server.
 657      // - Do this after creating app and setting up translations, so errors are
 658      // translated properly.
 659      if (PaymentServer::ipcSendCommandLine())
 660          exit(EXIT_SUCCESS);
 661  
 662      // Start up the payment server early, too, so impatient users that click on
 663      // limenka: links repeatedly have their payment requests routed to this process:
 664      if (WalletModel::isWalletEnabled()) {
 665          app.createPaymentServer();
 666      }
 667  #endif // ENABLE_WALLET
 668  
 669      /// 9. Main GUI initialization
 670      // Install global event filter that makes sure that out-of-focus labels do not contain text cursor.
 671      app.installEventFilter(new GUIUtil::LabelOutOfFocusEventFilter(&app));
 672  #if defined(Q_OS_WIN)
 673      // Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION)
 674      // Note: it is safe to call app.node() in the lambda below despite the fact
 675      // that app.createNode() hasn't been called yet, because native events will
 676      // not be processed until the Qt event loop is executed.
 677      qApp->installNativeEventFilter(new WinShutdownMonitor([&app] { app.node().startShutdown(); }));
 678  #endif
 679      // Install qDebug() message handler to route to debug.log
 680      qInstallMessageHandler(DebugMessageHandler);
 681      // Allow parameter interaction before we create the options model
 682      app.parameterSetup();
 683      GUIUtil::LogQtInfo();
 684  
 685      // Enable mempool stats by default
 686      gArgs.SoftSetBoolArg("-statsenable", true);
 687  
 688      if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))
 689          app.createSplashScreen(networkStyle.data());
 690  
 691      app.createNode(*init);
 692  
 693      // Load GUI settings from QSettings
 694      if (!app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false))) {
 695          return EXIT_FAILURE;
 696      }
 697  
 698      if (intro) {
 699          // Store intro dialog settings other than datadir (network specific)
 700          app.InitPruneSetting(intro->getPruneMiB());
 701          gArgs.ForceSetArg("-assumevalid", intro->getAssumeValid().toStdString());
 702      }
 703  
 704      try
 705      {
 706          app.createWindow(networkStyle.data());
 707          // Perform base initialization before spinning up initialization/shutdown thread
 708          // This is acceptable because this function only contains steps that are quick to execute,
 709          // so the GUI thread won't be held up.
 710          if (app.baseInitialize()) {
 711              if (intro) {
 712                  // Store intro dialog settings other than datadir (network specific)
 713                  common::SettingsValue intro_assumevalid = intro->getAssumeValid().toStdString();
 714                  app.node().context()->chain->overwriteRwSetting("assumevalid", intro_assumevalid);
 715                  // We can release the Intro widget now
 716                  intro.reset();
 717              }
 718              app.requestInitialize();
 719  #if defined(Q_OS_WIN)
 720              WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely…").arg(CLIENT_NAME), (HWND)app.getMainWinId());
 721  #endif
 722              app.exec();
 723          } else {
 724              // A dialog with detailed error will have been shown by InitError()
 725              return EXIT_FAILURE;
 726          }
 727      } catch (const std::exception& e) {
 728          PrintExceptionContinue(&e, "Runaway exception");
 729          app.handleRunawayException(QString::fromStdString(app.node().getWarnings().translated));
 730      } catch (...) {
 731          PrintExceptionContinue(nullptr, "Runaway exception");
 732          app.handleRunawayException(QString::fromStdString(app.node().getWarnings().translated));
 733      }
 734      return app.node().getExitStatus();
 735  }
 736