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