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