/ src / qt / bitcoin.cpp
bitcoin.cpp
  1  // Copyright (c) 2011-present 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 || m_node->shutdownRequested()) {
384          requestShutdown();
385          return;
386      }
387  
388      delete m_splash;
389      m_splash = nullptr;
390  
391      // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
392      qInfo() << "Platform customization:" << platformStyle->getName();
393      clientModel = new ClientModel(node(), optionsModel);
394      window->setClientModel(clientModel, &tip_info);
395  
396      // If '-min' option passed, start window minimized (iconified) or minimized to tray
397      bool start_minimized = gArgs.GetBoolArg("-min", false);
398  #ifdef ENABLE_WALLET
399      if (WalletModel::isWalletEnabled()) {
400          m_wallet_controller = new WalletController(*clientModel, platformStyle, this);
401          window->setWalletController(m_wallet_controller, /*show_loading_minimized=*/start_minimized);
402          if (paymentServer) {
403              paymentServer->setOptionsModel(optionsModel);
404          }
405      }
406  #endif // ENABLE_WALLET
407  
408      // Show or minimize window
409      if (!start_minimized) {
410          window->show();
411      } else if (clientModel->getOptionsModel()->getMinimizeToTray() && window->hasTrayIcon()) {
412          // do nothing as the window is managed by the tray icon
413      } else {
414          window->showMinimized();
415      }
416      Q_EMIT windowShown(window);
417  
418  #ifdef ENABLE_WALLET
419      // Now that initialization/startup is done, process any command-line
420      // bitcoin: URIs or payment requests:
421      if (paymentServer) {
422          connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest);
423          connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile);
424          connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) {
425              window->message(title, message, style);
426          });
427          QTimer::singleShot(100ms, paymentServer, &PaymentServer::uiReady);
428      }
429  #endif
430      pollShutdownTimer->start(SHUTDOWN_POLLING_DELAY);
431  }
432  
433  void BitcoinApplication::handleRunawayException(const QString &message)
434  {
435      QMessageBox::critical(
436          nullptr, tr("Runaway exception"),
437          tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(CLIENT_NAME) +
438          QLatin1String("<br><br>") + GUIUtil::MakeHtmlLink(message, CLIENT_BUGREPORT));
439      ::exit(EXIT_FAILURE);
440  }
441  
442  void BitcoinApplication::handleNonFatalException(const QString& message)
443  {
444      assert(QThread::currentThread() == thread());
445      QMessageBox::warning(
446          nullptr, tr("Internal error"),
447          tr("An internal error occurred. %1 will attempt to continue safely. This is "
448             "an unexpected bug which can be reported as described below.").arg(CLIENT_NAME) +
449          QLatin1String("<br><br>") + GUIUtil::MakeHtmlLink(message, CLIENT_BUGREPORT));
450  }
451  
452  WId BitcoinApplication::getMainWinId() const
453  {
454      if (!window)
455          return 0;
456  
457      return window->winId();
458  }
459  
460  bool BitcoinApplication::event(QEvent* e)
461  {
462      if (e->type() == QEvent::Quit) {
463          requestShutdown();
464          return true;
465      }
466  
467      return QApplication::event(e);
468  }
469  
470  static void SetupUIArgs(ArgsManager& argsman)
471  {
472      argsman.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
473      argsman.AddArg("-lang=<lang>", "Set language, for example \"de_DE\" (default: system locale)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
474      argsman.AddArg("-min", "Start minimized", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
475      argsman.AddArg("-resetguisettings", "Reset all settings changed in the GUI", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
476      argsman.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
477      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);
478  }
479  
480  int GuiMain(int argc, char* argv[])
481  {
482      std::unique_ptr<interfaces::Init> init = interfaces::MakeGuiInit(argc, argv);
483  
484      SetupEnvironment();
485      util::ThreadSetInternalName("main");
486  
487      // Subscribe to global signals from core
488      boost::signals2::scoped_connection handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox);
489      boost::signals2::scoped_connection handler_question = ::uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion);
490      boost::signals2::scoped_connection handler_init_message = ::uiInterface.InitMessage_connect(noui_InitMessage);
491  
492      // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory
493  
494      /// 1. Basic Qt initialization (not dependent on parameters or configuration)
495      Q_INIT_RESOURCE(bitcoin);
496      Q_INIT_RESOURCE(bitcoin_locale);
497  
498  #if defined(QT_QPA_PLATFORM_ANDROID)
499      QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
500      QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
501      QApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
502  #endif
503  
504      BitcoinApplication app;
505      GUIUtil::LoadFont(QStringLiteral(":/fonts/monospace"));
506  
507      /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these
508      // Command-line options take precedence:
509      SetupServerArgs(gArgs, init->canListenIpc());
510      SetupUIArgs(gArgs);
511      std::string error;
512      if (!gArgs.ParseParameters(argc, argv, error)) {
513          InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
514          // Create a message box, because the gui has neither been created nor has subscribed to core signals
515          QMessageBox::critical(nullptr, CLIENT_NAME,
516              // message cannot be translated because translations have not been initialized
517              QString::fromStdString("Error parsing command line arguments: %1.").arg(QString::fromStdString(error)));
518          return EXIT_FAILURE;
519      }
520  
521      // Error out when loose non-argument tokens are encountered on command line
522      // However, allow BIP-21 URIs only if no options follow
523      bool payment_server_token_seen = false;
524      for (int i = 1; i < argc; i++) {
525          QString arg(argv[i]);
526          bool invalid_token = !arg.startsWith("-");
527  #ifdef ENABLE_WALLET
528          if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) {
529              invalid_token &= false;
530              payment_server_token_seen = true;
531          }
532  #endif
533          if (payment_server_token_seen && arg.startsWith("-")) {
534              InitError(Untranslated(strprintf("Options ('%s') cannot follow a BIP-21 payment URI", argv[i])));
535              QMessageBox::critical(nullptr, CLIENT_NAME,
536                                    // message cannot be translated because translations have not been initialized
537                                    QString::fromStdString("Options ('%1') cannot follow a BIP-21 payment URI").arg(QString::fromStdString(argv[i])));
538              return EXIT_FAILURE;
539          }
540          if (invalid_token) {
541              InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see bitcoin-qt -h for a list of options.", argv[i])));
542              QMessageBox::critical(nullptr, CLIENT_NAME,
543                                    // message cannot be translated because translations have not been initialized
544                                    QString::fromStdString("Command line contains unexpected token '%1', see bitcoin-qt -h for a list of options.").arg(QString::fromStdString(argv[i])));
545              return EXIT_FAILURE;
546          }
547      }
548  
549      // Now that the QApplication is setup and we have parsed our parameters, we can set the platform style
550      app.setupPlatformStyle();
551  
552      /// 3. Application identification
553      // must be set before OptionsModel is initialized or translations are loaded,
554      // as it is used to locate QSettings
555      QApplication::setOrganizationName(QAPP_ORG_NAME);
556      QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN);
557      QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT);
558  
559      /// 4. Initialization of translations, so that intro dialog is in user's language
560      // Now that QSettings are accessible, initialize translations
561      QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
562      initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
563  
564      // Show help message immediately after parsing command-line options (for "-lang") and setting locale,
565      // but before showing splash screen.
566      if (HelpRequested(gArgs) || gArgs.GetBoolArg("-version", false)) {
567          HelpMessageDialog help(nullptr, gArgs.GetBoolArg("-version", false));
568          help.showOrPrint();
569          return EXIT_SUCCESS;
570      }
571  
572      // Install global event filter that makes sure that long tooltips can be word-wrapped
573      app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
574  
575      /// 5. Now that settings and translations are available, ask user for data directory
576      // User language is set up: pick a data directory
577      bool did_show_intro = false;
578      int64_t prune_MiB = 0;  // Intro dialog prune configuration
579      // Gracefully exit if the user cancels
580      if (!Intro::showIfNeeded(did_show_intro, prune_MiB)) return EXIT_SUCCESS;
581  
582      /// 6-7. Parse bitcoin.conf, determine network, switch to network specific
583      /// options, and create datadir and settings.json.
584      // - Do not call gArgs.GetDataDirNet() before this step finishes
585      // - Do not call Params() before this step
586      // - QSettings() will use the new application name after this, resulting in network-specific settings
587      // - Needs to be done before createOptionsModel
588      if (auto error = common::InitConfig(gArgs, ErrorSettingsRead)) {
589          InitError(error->message, error->details);
590          if (error->status == common::ConfigStatus::FAILED_WRITE) {
591              // Show a custom error message to provide more information in the
592              // case of a datadir write error.
593              ErrorSettingsWrite(error->message, error->details);
594          } else if (error->status != common::ConfigStatus::ABORTED) {
595              // Show a generic message in other cases, and no additional error
596              // message in the case of a read error if the user decided to abort.
597              QMessageBox::critical(nullptr, CLIENT_NAME, QObject::tr("Error: %1").arg(QString::fromStdString(error->message.translated)));
598          }
599          return EXIT_FAILURE;
600      }
601  #ifdef ENABLE_WALLET
602      // Parse URIs on command line
603      PaymentServer::ipcParseCommandLine(argc, argv);
604  #endif
605  
606      QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().GetChainType()));
607      assert(!networkStyle.isNull());
608      // Allow for separate UI settings for testnets
609      QApplication::setApplicationName(networkStyle->getAppName());
610      // Re-initialize translations after changing application name (language in network-specific settings can be different)
611      initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
612  
613  #ifdef ENABLE_WALLET
614      /// 8. URI IPC sending
615      // - Do this early as we don't want to bother initializing if we are just calling IPC
616      // - Do this *after* setting up the data directory, as the data directory hash is used in the name
617      // of the server.
618      // - Do this after creating app and setting up translations, so errors are
619      // translated properly.
620      if (PaymentServer::ipcSendCommandLine())
621          exit(EXIT_SUCCESS);
622  
623      // Start up the payment server early, too, so impatient users that click on
624      // bitcoin: links repeatedly have their payment requests routed to this process:
625      if (WalletModel::isWalletEnabled()) {
626          app.createPaymentServer();
627      }
628  #endif // ENABLE_WALLET
629  
630      /// 9. Main GUI initialization
631      // Install global event filter that makes sure that out-of-focus labels do not contain text cursor.
632      app.installEventFilter(new GUIUtil::LabelOutOfFocusEventFilter(&app));
633  #if defined(Q_OS_WIN)
634      // Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION)
635      // Note: it is safe to call app.node() in the lambda below despite the fact
636      // that app.createNode() hasn't been called yet, because native events will
637      // not be processed until the Qt event loop is executed.
638      qApp->installNativeEventFilter(new WinShutdownMonitor([&app] { app.node().startShutdown(); }));
639  #endif
640      // Install qDebug() message handler to route to debug.log
641      qInstallMessageHandler(DebugMessageHandler);
642      // Allow parameter interaction before we create the options model
643      app.parameterSetup();
644      GUIUtil::LogQtInfo();
645  
646      if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))
647          app.createSplashScreen(networkStyle.data());
648  
649      app.createNode(*init);
650  
651      // Load GUI settings from QSettings
652      if (!app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false))) {
653          return EXIT_FAILURE;
654      }
655  
656      if (did_show_intro) {
657          // Store intro dialog settings other than datadir (network specific)
658          app.InitPruneSetting(prune_MiB);
659      }
660  
661      try
662      {
663          app.createWindow(networkStyle.data());
664          // Perform base initialization before spinning up initialization/shutdown thread
665          // This is acceptable because this function only contains steps that are quick to execute,
666          // so the GUI thread won't be held up.
667          if (app.baseInitialize()) {
668              app.requestInitialize();
669  #if defined(Q_OS_WIN)
670              WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely…").arg(CLIENT_NAME), (HWND)app.getMainWinId());
671  #endif
672              app.exec();
673          } else {
674              // A dialog with detailed error will have been shown by InitError()
675              return EXIT_FAILURE;
676          }
677      } catch (const std::exception& e) {
678          PrintExceptionContinue(&e, "Runaway exception");
679          app.handleRunawayException(QString::fromStdString(app.node().getWarnings().translated));
680      } catch (...) {
681          PrintExceptionContinue(nullptr, "Runaway exception");
682          app.handleRunawayException(QString::fromStdString(app.node().getWarnings().translated));
683      }
684      return app.node().getExitStatus();
685  }