1 // Copyright (c) 2011-2016 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.
5 #if defined(HAVE_CONFIG_H)
6 #include "config/bitcoin-config.h"
9 #include "bitcoingui.h"
11 #include "chainparams.h"
12 #include "clientmodel.h"
14 #include "guiconstants.h"
17 #include "networkstyle.h"
18 #include "optionsmodel.h"
19 #include "platformstyle.h"
20 #include "splashscreen.h"
21 #include "utilitydialog.h"
22 #include "winshutdownmonitor.h"
25 #include "paymentserver.h"
26 #include "walletmodel.h"
30 #include "rpc/server.h"
31 #include "scheduler.h"
32 #include "ui_interface.h"
37 #include "wallet/wallet.h"
42 #include <boost/thread.hpp>
44 #include <QApplication>
46 #include <QLibraryInfo>
48 #include <QMessageBox>
52 #include <QTranslator>
53 #include <QSslConfiguration>
55 #if defined(QT_STATICPLUGIN)
57 #if QT_VERSION < 0x050000
58 Q_IMPORT_PLUGIN(qcncodecs
)
59 Q_IMPORT_PLUGIN(qjpcodecs
)
60 Q_IMPORT_PLUGIN(qtwcodecs
)
61 Q_IMPORT_PLUGIN(qkrcodecs
)
62 Q_IMPORT_PLUGIN(qtaccessiblewidgets
)
64 #if QT_VERSION < 0x050400
65 Q_IMPORT_PLUGIN(AccessibleFactory
)
67 #if defined(QT_QPA_PLATFORM_XCB)
68 Q_IMPORT_PLUGIN(QXcbIntegrationPlugin
);
69 #elif defined(QT_QPA_PLATFORM_WINDOWS)
70 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin
);
71 #elif defined(QT_QPA_PLATFORM_COCOA)
72 Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin
);
77 #if QT_VERSION < 0x050000
81 // Declare meta types used for QMetaObject::invokeMethod
82 Q_DECLARE_METATYPE(bool*)
83 Q_DECLARE_METATYPE(CAmount
)
85 static void InitMessage(const std::string
&message
)
87 LogPrintf("init message: %s\n", message
);
91 Translate string to current locale using Qt.
93 static std::string
Translate(const char* psz
)
95 return QCoreApplication::translate("bitcoin-core", psz
).toStdString();
98 static QString
GetLangTerritory()
101 // Get desired locale (e.g. "de_DE")
102 // 1) System default language
103 QString lang_territory
= QLocale::system().name();
104 // 2) Language from QSettings
105 QString lang_territory_qsettings
= settings
.value("language", "").toString();
106 if(!lang_territory_qsettings
.isEmpty())
107 lang_territory
= lang_territory_qsettings
;
108 // 3) -lang command line argument
109 lang_territory
= QString::fromStdString(gArgs
.GetArg("-lang", lang_territory
.toStdString()));
110 return lang_territory
;
113 /** Set up translations */
114 static void initTranslations(QTranslator
&qtTranslatorBase
, QTranslator
&qtTranslator
, QTranslator
&translatorBase
, QTranslator
&translator
)
116 // Remove old translators
117 QApplication::removeTranslator(&qtTranslatorBase
);
118 QApplication::removeTranslator(&qtTranslator
);
119 QApplication::removeTranslator(&translatorBase
);
120 QApplication::removeTranslator(&translator
);
122 // Get desired locale (e.g. "de_DE")
123 // 1) System default language
124 QString lang_territory
= GetLangTerritory();
126 // Convert to "de" only by truncating "_DE"
127 QString lang
= lang_territory
;
128 lang
.truncate(lang_territory
.lastIndexOf('_'));
130 // Load language files for configured locale:
131 // - First load the translator for the base language, without territory
132 // - Then load the more specific locale translator
134 // Load e.g. qt_de.qm
135 if (qtTranslatorBase
.load("qt_" + lang
, QLibraryInfo::location(QLibraryInfo::TranslationsPath
)))
136 QApplication::installTranslator(&qtTranslatorBase
);
138 // Load e.g. qt_de_DE.qm
139 if (qtTranslator
.load("qt_" + lang_territory
, QLibraryInfo::location(QLibraryInfo::TranslationsPath
)))
140 QApplication::installTranslator(&qtTranslator
);
142 // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc)
143 if (translatorBase
.load(lang
, ":/translations/"))
144 QApplication::installTranslator(&translatorBase
);
146 // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc)
147 if (translator
.load(lang_territory
, ":/translations/"))
148 QApplication::installTranslator(&translator
);
151 /* qDebug() message handler --> debug.log */
152 #if QT_VERSION < 0x050000
153 void DebugMessageHandler(QtMsgType type
, const char *msg
)
155 if (type
== QtDebugMsg
) {
156 LogPrint(BCLog::QT
, "GUI: %s\n", msg
);
158 LogPrintf("GUI: %s\n", msg
);
162 void DebugMessageHandler(QtMsgType type
, const QMessageLogContext
& context
, const QString
&msg
)
165 if (type
== QtDebugMsg
) {
166 LogPrint(BCLog::QT
, "GUI: %s\n", msg
.toStdString());
168 LogPrintf("GUI: %s\n", msg
.toStdString());
173 /** Class encapsulating Bitcoin Core startup and shutdown.
174 * Allows running startup and shutdown in a different thread from the UI thread.
176 class BitcoinCore
: public QObject
180 explicit BitcoinCore();
181 /** Basic initialization, before starting initialization/shutdown thread.
182 * Return true on success.
184 static bool baseInitialize();
191 void initializeResult(bool success
);
192 void shutdownResult();
193 void runawayException(const QString
&message
);
196 boost::thread_group threadGroup
;
197 CScheduler scheduler
;
199 /// Pass fatal exception message to UI thread
200 void handleRunawayException(const std::exception
*e
);
203 /** Main Bitcoin application object */
204 class BitcoinApplication
: public QApplication
208 explicit BitcoinApplication(int &argc
, char **argv
);
209 ~BitcoinApplication();
212 /// Create payment server
213 void createPaymentServer();
215 /// parameter interaction/setup based on rules
216 void parameterSetup();
217 /// Create options model
218 void createOptionsModel(bool resetSettings
);
219 /// Create main window
220 void createWindow(const NetworkStyle
*networkStyle
);
221 /// Create splash screen
222 void createSplashScreen(const NetworkStyle
*networkStyle
);
224 /// Request core initialization
225 void requestInitialize();
226 /// Request core shutdown
227 void requestShutdown();
229 /// Get process return value
230 int getReturnValue() const { return returnValue
; }
232 /// Get window identifier of QMainWindow (BitcoinGUI)
233 WId
getMainWinId() const;
236 void initializeResult(bool success
);
237 void shutdownResult();
238 /// Handle runaway exceptions. Shows a message box with the problem and quits the program.
239 void handleRunawayException(const QString
&message
);
242 void requestedInitialize();
243 void requestedShutdown();
245 void splashFinished(QWidget
*window
);
249 OptionsModel
*optionsModel
;
250 ClientModel
*clientModel
;
252 QTimer
*pollShutdownTimer
;
254 PaymentServer
* paymentServer
;
255 WalletModel
*walletModel
;
258 const PlatformStyle
*platformStyle
;
259 std::unique_ptr
<QWidget
> shutdownWindow
;
264 #include "bitcoin.moc"
266 BitcoinCore::BitcoinCore():
271 void BitcoinCore::handleRunawayException(const std::exception
*e
)
273 PrintExceptionContinue(e
, "Runaway exception");
274 Q_EMIT
runawayException(QString::fromStdString(GetWarnings("gui")));
277 bool BitcoinCore::baseInitialize()
279 if (!AppInitBasicSetup())
283 if (!AppInitParameterInteraction())
287 if (!AppInitSanityChecks())
291 if (!AppInitLockDataDirectory())
298 void BitcoinCore::initialize()
302 qDebug() << __func__
<< ": Running initialization in thread";
303 bool rv
= AppInitMain(threadGroup
, scheduler
);
304 Q_EMIT
initializeResult(rv
);
305 } catch (const std::exception
& e
) {
306 handleRunawayException(&e
);
308 handleRunawayException(nullptr);
312 void BitcoinCore::shutdown()
316 qDebug() << __func__
<< ": Running Shutdown in thread";
317 Interrupt(threadGroup
);
318 threadGroup
.join_all();
320 qDebug() << __func__
<< ": Shutdown finished";
321 Q_EMIT
shutdownResult();
322 } catch (const std::exception
& e
) {
323 handleRunawayException(&e
);
325 handleRunawayException(nullptr);
329 BitcoinApplication::BitcoinApplication(int &argc
, char **argv
):
330 QApplication(argc
, argv
),
335 pollShutdownTimer(0),
342 setQuitOnLastWindowClosed(false);
344 // UI per-platform customization
345 // This must be done inside the BitcoinApplication constructor, or after it, because
346 // PlatformStyle::instantiate requires a QApplication
347 std::string platformName
;
348 platformName
= gArgs
.GetArg("-uiplatform", BitcoinGUI::DEFAULT_UIPLATFORM
);
349 platformStyle
= PlatformStyle::instantiate(QString::fromStdString(platformName
));
350 if (!platformStyle
) // Fall back to "other" if specified name not found
351 platformStyle
= PlatformStyle::instantiate("other");
352 assert(platformStyle
);
355 BitcoinApplication::~BitcoinApplication()
359 qDebug() << __func__
<< ": Stopping thread";
362 qDebug() << __func__
<< ": Stopped thread";
368 delete paymentServer
;
373 delete platformStyle
;
378 void BitcoinApplication::createPaymentServer()
380 paymentServer
= new PaymentServer(this);
384 void BitcoinApplication::createOptionsModel(bool resetSettings
)
386 optionsModel
= new OptionsModel(nullptr, resetSettings
);
389 void BitcoinApplication::createWindow(const NetworkStyle
*networkStyle
)
391 window
= new BitcoinGUI(platformStyle
, networkStyle
, 0);
393 pollShutdownTimer
= new QTimer(window
);
394 connect(pollShutdownTimer
, SIGNAL(timeout()), window
, SLOT(detectShutdown()));
395 pollShutdownTimer
->start(200);
398 void BitcoinApplication::createSplashScreen(const NetworkStyle
*networkStyle
)
400 SplashScreen
*splash
= new SplashScreen(0, networkStyle
);
401 // We don't hold a direct pointer to the splash screen after creation, but the splash
402 // screen will take care of deleting itself when slotFinish happens.
404 connect(this, SIGNAL(splashFinished(QWidget
*)), splash
, SLOT(slotFinish(QWidget
*)));
405 connect(this, SIGNAL(requestedShutdown()), splash
, SLOT(close()));
408 void BitcoinApplication::startThread()
412 coreThread
= new QThread(this);
413 BitcoinCore
*executor
= new BitcoinCore();
414 executor
->moveToThread(coreThread
);
416 /* communication to and from thread */
417 connect(executor
, SIGNAL(initializeResult(bool)), this, SLOT(initializeResult(bool)));
418 connect(executor
, SIGNAL(shutdownResult()), this, SLOT(shutdownResult()));
419 connect(executor
, SIGNAL(runawayException(QString
)), this, SLOT(handleRunawayException(QString
)));
420 connect(this, SIGNAL(requestedInitialize()), executor
, SLOT(initialize()));
421 connect(this, SIGNAL(requestedShutdown()), executor
, SLOT(shutdown()));
422 /* make sure executor object is deleted in its own thread */
423 connect(this, SIGNAL(stopThread()), executor
, SLOT(deleteLater()));
424 connect(this, SIGNAL(stopThread()), coreThread
, SLOT(quit()));
429 void BitcoinApplication::parameterSetup()
432 InitParameterInteraction();
435 void BitcoinApplication::requestInitialize()
437 qDebug() << __func__
<< ": Requesting initialize";
439 Q_EMIT
requestedInitialize();
442 void BitcoinApplication::requestShutdown()
444 // Show a simple window indicating shutdown status
445 // Do this first as some of the steps may take some time below,
446 // for example the RPC console may still be executing a command.
447 shutdownWindow
.reset(ShutdownWindow::showShutdownWindow(window
));
449 qDebug() << __func__
<< ": Requesting shutdown";
452 window
->setClientModel(0);
453 pollShutdownTimer
->stop();
456 window
->removeAllWallets();
465 // Request shutdown from core thread
466 Q_EMIT
requestedShutdown();
469 void BitcoinApplication::initializeResult(bool success
)
471 qDebug() << __func__
<< ": Initialization result: " << success
;
473 returnValue
= success
? EXIT_SUCCESS
: EXIT_FAILURE
;
476 // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
477 qWarning() << "Platform customization:" << platformStyle
->getName();
479 PaymentServer::LoadRootCAs();
480 paymentServer
->setOptionsModel(optionsModel
);
483 clientModel
= new ClientModel(optionsModel
);
484 window
->setClientModel(clientModel
);
487 // TODO: Expose secondary wallets
488 if (!vpwallets
.empty())
490 walletModel
= new WalletModel(platformStyle
, vpwallets
[0], optionsModel
);
492 window
->addWallet(BitcoinGUI::DEFAULT_WALLET
, walletModel
);
493 window
->setCurrentWallet(BitcoinGUI::DEFAULT_WALLET
);
495 connect(walletModel
, SIGNAL(coinsSent(CWallet
*,SendCoinsRecipient
,QByteArray
)),
496 paymentServer
, SLOT(fetchPaymentACK(CWallet
*,const SendCoinsRecipient
&,QByteArray
)));
500 // If -min option passed, start window minimized.
501 if(gArgs
.GetBoolArg("-min", false))
503 window
->showMinimized();
509 Q_EMIT
splashFinished(window
);
512 // Now that initialization/startup is done, process any command-line
513 // bitcoin: URIs or payment requests:
514 connect(paymentServer
, SIGNAL(receivedPaymentRequest(SendCoinsRecipient
)),
515 window
, SLOT(handlePaymentRequest(SendCoinsRecipient
)));
516 connect(window
, SIGNAL(receivedURI(QString
)),
517 paymentServer
, SLOT(handleURIOrFile(QString
)));
518 connect(paymentServer
, SIGNAL(message(QString
,QString
,unsigned int)),
519 window
, SLOT(message(QString
,QString
,unsigned int)));
520 QTimer::singleShot(100, paymentServer
, SLOT(uiReady()));
523 quit(); // Exit main loop
527 void BitcoinApplication::shutdownResult()
529 quit(); // Exit main loop after shutdown finished
532 void BitcoinApplication::handleRunawayException(const QString
&message
)
534 QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + message
);
535 ::exit(EXIT_FAILURE
);
538 WId
BitcoinApplication::getMainWinId() const
543 return window
->winId();
546 #ifndef BITCOIN_QT_TEST
547 int main(int argc
, char *argv
[])
551 /// 1. Parse command-line options. These take precedence over anything else.
552 // Command-line options take precedence:
553 gArgs
.ParseParameters(argc
, argv
);
555 // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory
557 /// 2. Basic Qt initialization (not dependent on parameters or configuration)
558 #if QT_VERSION < 0x050000
559 // Internal string conversion is all UTF-8
560 QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
561 QTextCodec::setCodecForCStrings(QTextCodec::codecForTr());
564 Q_INIT_RESOURCE(bitcoin
);
565 Q_INIT_RESOURCE(bitcoin_locale
);
567 BitcoinApplication
app(argc
, argv
);
568 #if QT_VERSION > 0x050100
569 // Generate high-dpi pixmaps
570 QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps
);
572 #if QT_VERSION >= 0x050600
573 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling
);
576 QApplication::setAttribute(Qt::AA_DontShowIconsInMenus
);
578 #if QT_VERSION >= 0x050500
579 // Because of the POODLE attack it is recommended to disable SSLv3 (https://disablessl3.com/),
580 // so set SSL protocols to TLS1.0+.
581 QSslConfiguration sslconf
= QSslConfiguration::defaultConfiguration();
582 sslconf
.setProtocol(QSsl::TlsV1_0OrLater
);
583 QSslConfiguration::setDefaultConfiguration(sslconf
);
586 // Register meta types used for QMetaObject::invokeMethod
587 qRegisterMetaType
< bool* >();
588 // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType)
589 // IMPORTANT if it is no longer a typedef use the normal variant above
590 qRegisterMetaType
< CAmount
>("CAmount");
591 qRegisterMetaType
< std::function
<void(void)> >("std::function<void(void)>");
593 /// 3. Application identification
594 // must be set before OptionsModel is initialized or translations are loaded,
595 // as it is used to locate QSettings
596 QApplication::setOrganizationName(QAPP_ORG_NAME
);
597 QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN
);
598 QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT
);
599 GUIUtil::SubstituteFonts(GetLangTerritory());
601 /// 4. Initialization of translations, so that intro dialog is in user's language
602 // Now that QSettings are accessible, initialize translations
603 QTranslator qtTranslatorBase
, qtTranslator
, translatorBase
, translator
;
604 initTranslations(qtTranslatorBase
, qtTranslator
, translatorBase
, translator
);
605 translationInterface
.Translate
.connect(Translate
);
607 // Show help message immediately after parsing command-line options (for "-lang") and setting locale,
608 // but before showing splash screen.
609 if (gArgs
.IsArgSet("-?") || gArgs
.IsArgSet("-h") || gArgs
.IsArgSet("-help") || gArgs
.IsArgSet("-version"))
611 HelpMessageDialog
help(nullptr, gArgs
.IsArgSet("-version"));
616 /// 5. Now that settings and translations are available, ask user for data directory
617 // User language is set up: pick a data directory
618 if (!Intro::pickDataDirectory())
621 /// 6. Determine availability of data directory and parse bitcoin.conf
622 /// - Do not call GetDataDir(true) before this step finishes
623 if (!fs::is_directory(GetDataDir(false)))
625 QMessageBox::critical(0, QObject::tr(PACKAGE_NAME
),
626 QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs
.GetArg("-datadir", ""))));
630 gArgs
.ReadConfigFile(gArgs
.GetArg("-conf", BITCOIN_CONF_FILENAME
));
631 } catch (const std::exception
& e
) {
632 QMessageBox::critical(0, QObject::tr(PACKAGE_NAME
),
633 QObject::tr("Error: Cannot parse configuration file: %1. Only use key=value syntax.").arg(e
.what()));
637 /// 7. Determine network (and switch to network specific options)
638 // - Do not call Params() before this step
639 // - Do this after parsing the configuration file, as the network can be switched there
640 // - QSettings() will use the new application name after this, resulting in network-specific settings
641 // - Needs to be done before createOptionsModel
643 // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
645 SelectParams(ChainNameFromCommandLine());
646 } catch(std::exception
&e
) {
647 QMessageBox::critical(0, QObject::tr(PACKAGE_NAME
), QObject::tr("Error: %1").arg(e
.what()));
651 // Parse URIs on command line -- this can affect Params()
652 PaymentServer::ipcParseCommandLine(argc
, argv
);
655 QScopedPointer
<const NetworkStyle
> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
656 assert(!networkStyle
.isNull());
657 // Allow for separate UI settings for testnets
658 QApplication::setApplicationName(networkStyle
->getAppName());
659 // Re-initialize translations after changing application name (language in network-specific settings can be different)
660 initTranslations(qtTranslatorBase
, qtTranslator
, translatorBase
, translator
);
663 /// 8. URI IPC sending
664 // - Do this early as we don't want to bother initializing if we are just calling IPC
665 // - Do this *after* setting up the data directory, as the data directory hash is used in the name
667 // - Do this after creating app and setting up translations, so errors are
668 // translated properly.
669 if (PaymentServer::ipcSendCommandLine())
672 // Start up the payment server early, too, so impatient users that click on
673 // bitcoin: links repeatedly have their payment requests routed to this process:
674 app
.createPaymentServer();
677 /// 9. Main GUI initialization
678 // Install global event filter that makes sure that long tooltips can be word-wrapped
679 app
.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD
, &app
));
680 #if QT_VERSION < 0x050000
681 // Install qDebug() message handler to route to debug.log
682 qInstallMsgHandler(DebugMessageHandler
);
684 #if defined(Q_OS_WIN)
685 // Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION)
686 qApp
->installNativeEventFilter(new WinShutdownMonitor());
688 // Install qDebug() message handler to route to debug.log
689 qInstallMessageHandler(DebugMessageHandler
);
691 // Allow parameter interaction before we create the options model
692 app
.parameterSetup();
693 // Load GUI settings from QSettings
694 app
.createOptionsModel(gArgs
.IsArgSet("-resetguisettings"));
696 // Subscribe to global signals from core
697 uiInterface
.InitMessage
.connect(InitMessage
);
699 if (gArgs
.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN
) && !gArgs
.GetBoolArg("-min", false))
700 app
.createSplashScreen(networkStyle
.data());
702 int rv
= EXIT_SUCCESS
;
705 app
.createWindow(networkStyle
.data());
706 // Perform base initialization before spinning up initialization/shutdown thread
707 // This is acceptable because this function only contains steps that are quick to execute,
708 // so the GUI thread won't be held up.
709 if (BitcoinCore::baseInitialize()) {
710 app
.requestInitialize();
711 #if defined(Q_OS_WIN) && QT_VERSION >= 0x050000
712 WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME
)), (HWND
)app
.getMainWinId());
715 app
.requestShutdown();
717 rv
= app
.getReturnValue();
719 // A dialog with detailed error will have been shown by InitError()
722 } catch (const std::exception
& e
) {
723 PrintExceptionContinue(&e
, "Runaway exception");
724 app
.handleRunawayException(QString::fromStdString(GetWarnings("gui")));
726 PrintExceptionContinue(nullptr, "Runaway exception");
727 app
.handleRunawayException(QString::fromStdString(GetWarnings("gui")));
731 #endif // BITCOIN_QT_TEST