diff options
Diffstat (limited to 'src/qt/bitcoin.cpp')
-rw-r--r-- | src/qt/bitcoin.cpp | 576 |
1 files changed, 386 insertions, 190 deletions
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 9565d3ef7a..848e50deb0 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include "bitcoin-config.h" +#endif + #include "bitcoingui.h" #include "clientmodel.h" @@ -9,9 +13,12 @@ #include "guiutil.h" #include "intro.h" #include "optionsmodel.h" -#include "paymentserver.h" #include "splashscreen.h" +#include "utilitydialog.h" +#ifdef ENABLE_WALLET +#include "paymentserver.h" #include "walletmodel.h" +#endif #include "init.h" #include "main.h" @@ -29,59 +36,31 @@ #include <QSettings> #include <QTimer> #include <QTranslator> +#include <QThread> -#if QT_VERSION < 0x050000 -#include <QTextCodec> -#endif - -#if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED) -#define _BITCOIN_QT_PLUGINS_INCLUDED -#define __INSURE__ +#if defined(QT_STATICPLUGIN) #include <QtPlugin> +#if QT_VERSION < 0x050000 Q_IMPORT_PLUGIN(qcncodecs) Q_IMPORT_PLUGIN(qjpcodecs) Q_IMPORT_PLUGIN(qtwcodecs) Q_IMPORT_PLUGIN(qkrcodecs) Q_IMPORT_PLUGIN(qtaccessiblewidgets) +#else +Q_IMPORT_PLUGIN(AccessibleFactory) +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#endif +#endif + +#if QT_VERSION < 0x050000 +#include <QTextCodec> #endif // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool*) -// Need a global reference for the notifications to find the GUI -static BitcoinGUI *guiref; -static SplashScreen *splashref; - -static bool ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style) -{ - if(guiref) - { - bool modal = (style & CClientUIInterface::MODAL); - bool ret = false; - // In case of modal message, use blocking connection to wait for user to click a button - QMetaObject::invokeMethod(guiref, "message", - modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(caption)), - Q_ARG(QString, QString::fromStdString(message)), - Q_ARG(unsigned int, style), - Q_ARG(bool*, &ret)); - return ret; - } - else - { - LogPrintf("%s: %s\n", caption.c_str(), message.c_str()); - fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); - return false; - } -} - static void InitMessage(const std::string &message) { - if(splashref) - { - splashref->showMessage(QString::fromStdString(message), Qt::AlignBottom|Qt::AlignHCenter, QColor(55,55,55)); - qApp->processEvents(); - } LogPrintf("init message: %s\n", message.c_str()); } @@ -93,15 +72,6 @@ static std::string Translate(const char* psz) return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } -/* Handle runaway exceptions. Shows a message box with the problem and quits the program. - */ -static void handleRunawayException(std::exception *e) -{ - PrintExceptionContinue(e, "Runaway exception"); - QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + QString::fromStdString(strMiscWarning)); - exit(1); -} - /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { @@ -158,25 +128,331 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } #endif +/** Class encapsulating Bitcoin Core startup and shutdown. + * Allows running startup and shutdown in a different thread from the UI thread. + */ +class BitcoinCore: public QObject +{ + Q_OBJECT +public: + explicit BitcoinCore(); + +public slots: + void initialize(); + void shutdown(); + +signals: + void initializeResult(int retval); + void shutdownResult(int retval); + void runawayException(const QString &message); + +private: + boost::thread_group threadGroup; + + /// Pass fatal exception message to UI thread + void handleRunawayException(std::exception *e); +}; + +/** Main Bitcoin application object */ +class BitcoinApplication: public QApplication +{ + Q_OBJECT +public: + explicit BitcoinApplication(int &argc, char **argv); + ~BitcoinApplication(); + +#ifdef ENABLE_WALLET + /// Create payment server + void createPaymentServer(); +#endif + /// Create options model + void createOptionsModel(); + /// Create main window + void createWindow(bool isaTestNet); + /// Create splash screen + void createSplashScreen(bool isaTestNet); + + /// Request core initialization + void requestInitialize(); + /// Request core shutdown + void requestShutdown(); + + /// Get process return value + int getReturnValue() { return returnValue; } + +public slots: + void initializeResult(int retval); + void shutdownResult(int retval); + /// Handle runaway exceptions. Shows a message box with the problem and quits the program. + void handleRunawayException(const QString &message); + +signals: + void requestedInitialize(); + void requestedShutdown(); + void stopThread(); + void splashFinished(QWidget *window); + +private: + QThread *coreThread; + OptionsModel *optionsModel; + ClientModel *clientModel; + BitcoinGUI *window; + QTimer *pollShutdownTimer; +#ifdef ENABLE_WALLET + PaymentServer* paymentServer; + WalletModel *walletModel; +#endif + int returnValue; + + void startThread(); +}; + +#include "bitcoin.moc" + +BitcoinCore::BitcoinCore(): + QObject() +{ +} + +void BitcoinCore::handleRunawayException(std::exception *e) +{ + PrintExceptionContinue(e, "Runaway exception"); + emit runawayException(QString::fromStdString(strMiscWarning)); +} + +void BitcoinCore::initialize() +{ + try + { + LogPrintf("Running AppInit2 in thread\n"); + int rv = AppInit2(threadGroup); + emit initializeResult(rv); + } catch (std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(NULL); + } +} + +void BitcoinCore::shutdown() +{ + try + { + LogPrintf("Running Shutdown in thread\n"); + threadGroup.interrupt_all(); + threadGroup.join_all(); + Shutdown(); + LogPrintf("Shutdown finished\n"); + emit shutdownResult(1); + } catch (std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(NULL); + } +} + +BitcoinApplication::BitcoinApplication(int &argc, char **argv): + QApplication(argc, argv), + coreThread(0), + optionsModel(0), + clientModel(0), + window(0), + pollShutdownTimer(0), +#ifdef ENABLE_WALLET + paymentServer(0), + walletModel(0), +#endif + returnValue(0) +{ + setQuitOnLastWindowClosed(false); + startThread(); +} + +BitcoinApplication::~BitcoinApplication() +{ + LogPrintf("Stopping thread\n"); + emit stopThread(); + coreThread->wait(); + LogPrintf("Stopped thread\n"); + + delete window; + window = 0; +#ifdef ENABLE_WALLET + delete paymentServer; + paymentServer = 0; +#endif + delete optionsModel; + optionsModel = 0; +} + +#ifdef ENABLE_WALLET +void BitcoinApplication::createPaymentServer() +{ + paymentServer = new PaymentServer(this); +} +#endif + +void BitcoinApplication::createOptionsModel() +{ + optionsModel = new OptionsModel(); +} + +void BitcoinApplication::createWindow(bool isaTestNet) +{ + window = new BitcoinGUI(isaTestNet, 0); + + pollShutdownTimer = new QTimer(window); + connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); + pollShutdownTimer->start(200); +} + +void BitcoinApplication::createSplashScreen(bool isaTestNet) +{ + SplashScreen *splash = new SplashScreen(QPixmap(), 0, isaTestNet); + splash->setAttribute(Qt::WA_DeleteOnClose); + splash->show(); + connect(this, SIGNAL(splashFinished(QWidget*)), splash, SLOT(slotFinish(QWidget*))); +} + +void BitcoinApplication::startThread() +{ + coreThread = new QThread(this); + BitcoinCore *executor = new BitcoinCore(); + executor->moveToThread(coreThread); + + /* communication to and from thread */ + connect(executor, SIGNAL(initializeResult(int)), this, SLOT(initializeResult(int))); + connect(executor, SIGNAL(shutdownResult(int)), this, SLOT(shutdownResult(int))); + connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); + connect(this, SIGNAL(requestedInitialize()), executor, SLOT(initialize())); + connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); + /* make sure executor object is deleted in its own thread */ + connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); + connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); + + coreThread->start(); +} + +void BitcoinApplication::requestInitialize() +{ + LogPrintf("Requesting initialize\n"); + emit requestedInitialize(); +} + +void BitcoinApplication::requestShutdown() +{ + LogPrintf("Requesting shutdown\n"); + window->hide(); + window->setClientModel(0); + pollShutdownTimer->stop(); + +#ifdef ENABLE_WALLET + window->removeAllWallets(); + delete walletModel; + walletModel = 0; +#endif + delete clientModel; + clientModel = 0; + + // Show a simple window indicating shutdown status + ShutdownWindow::showShutdownWindow(window); + + // Request shutdown from core thread + emit requestedShutdown(); +} + +void BitcoinApplication::initializeResult(int retval) +{ + LogPrintf("Initialization result: %i\n", retval); + // Set exit result: 0 if successful, 1 if failure + returnValue = retval ? 0 : 1; + if(retval) + { + // Miscellaneous initialization after core is initialized + optionsModel->Upgrade(); // Must be done after AppInit2 + +#ifdef ENABLE_WALLET + PaymentServer::LoadRootCAs(); + paymentServer->setOptionsModel(optionsModel); +#endif + + emit splashFinished(window); + + clientModel = new ClientModel(optionsModel); + window->setClientModel(clientModel); + +#ifdef ENABLE_WALLET + if(pwalletMain) + { + walletModel = new WalletModel(pwalletMain, optionsModel); + + window->addWallet("~Default", walletModel); + window->setCurrentWallet("~Default"); + + connect(walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), + paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); + } +#endif + + // If -min option passed, start window minimized. + if(GetBoolArg("-min", false)) + { + window->showMinimized(); + } + else + { + window->show(); + } +#ifdef ENABLE_WALLET + // Now that initialization/startup is done, process any command-line + // bitcoin: URIs or payment requests: + connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), + window, SLOT(handlePaymentRequest(SendCoinsRecipient))); + connect(window, SIGNAL(receivedURI(QString)), + paymentServer, SLOT(handleURIOrFile(QString))); + connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), + window, SLOT(message(QString,QString,unsigned int))); + QTimer::singleShot(100, paymentServer, SLOT(uiReady())); +#endif + } else { + quit(); // Exit main loop + } +} + +void BitcoinApplication::shutdownResult(int retval) +{ + LogPrintf("Shutdown result: %i\n", retval); + quit(); // Exit main loop after shutdown finished +} + +void BitcoinApplication::handleRunawayException(const QString &message) +{ + QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + message); + ::exit(1); +} + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { - bool fMissingDatadir = false; bool fSelParFromCLFailed = false; - + /// 1. Parse command-line options. These take precedence over anything else. // Command-line options take precedence: ParseParameters(argc, argv); - // ... then bitcoin.conf: - if (!boost::filesystem::is_directory(GetDataDir(false))) { - fMissingDatadir = true; - } else { - ReadConfigFile(mapArgs, mapMultiArgs); - } // Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause) if (!SelectParamsFromCommandLine()) { fSelParFromCLFailed = true; } +#ifdef ENABLE_WALLET + // Parse URIs on command line -- this can affect TestNet() / RegTest() mode + if (!PaymentServer::ipcParseCommandLine(argc, argv)) + exit(0); +#endif + + bool isaTestNet = TestNet() || RegTest(); + + // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory + /// 2. Basic Qt initialization (not dependent on parameters or configuration) #if QT_VERSION < 0x050000 // Internal string conversion is all UTF-8 QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); @@ -184,7 +460,7 @@ int main(int argc, char *argv[]) #endif Q_INIT_RESOURCE(bitcoin); - QApplication app(argc, argv); + BitcoinApplication app(argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); @@ -196,9 +472,9 @@ int main(int argc, char *argv[]) // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); - // Application identification (must be set before OptionsModel is initialized, - // as it is used to locate QSettings) - bool isaTestNet = TestNet() || RegTest(); + /// 3. Application identification + // must be set before OptionsModel is initialized or translations are loaded, + // as it is used to locate QSettings QApplication::setOrganizationName("Bitcoin"); QApplication::setOrganizationDomain("bitcoin.org"); if (isaTestNet) // Separate UI settings for testnets @@ -206,34 +482,55 @@ int main(int argc, char *argv[]) else QApplication::setApplicationName("Bitcoin-Qt"); + /// 4. Initialization of translations, so that intro dialog is in user's language // Now that QSettings are accessible, initialize translations QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); + uiInterface.Translate.connect(Translate); - // Do this early as we don't want to bother initializing if we are just calling IPC - // ... but do it after creating app and setting up translations, so errors are - // translated properly. - if (PaymentServer::ipcSendCommandLine(argc, argv)) - exit(0); - - // Now that translations are initialized check for errors and allow a translatable error message - if (fMissingDatadir) { - QMessageBox::critical(0, QObject::tr("Bitcoin"), - QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); + // Show help message immediately after parsing command-line options (for "-lang") and setting locale, + // but before showing splash screen. + if (mapArgs.count("-?") || mapArgs.count("--help")) + { + HelpMessageDialog help(NULL); + help.showOrPrint(); return 1; } - else if (fSelParFromCLFailed) { + // Now that translations are initialized, check for earlier errors and show a translatable error message + if (fSelParFromCLFailed) { QMessageBox::critical(0, QObject::tr("Bitcoin"), QObject::tr("Error: Invalid combination of -regtest and -testnet.")); return 1; } - // Start up the payment server early, too, so impatient users that click on - // bitcoin: links repeatedly have their payment requests routed to this process: - PaymentServer* paymentServer = new PaymentServer(&app); - + /// 5. Now that settings and translations are available, ask user for data directory // User language is set up: pick a data directory Intro::pickDataDirectory(isaTestNet); + /// 6. Determine availability of data directory and parse bitcoin.conf + if (!boost::filesystem::is_directory(GetDataDir(false))) + { + QMessageBox::critical(0, QObject::tr("Bitcoin"), + QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); + return 1; + } + ReadConfigFile(mapArgs, mapMultiArgs); + +#ifdef ENABLE_WALLET + /// 7. URI IPC sending + // - Do this early as we don't want to bother initializing if we are just calling IPC + // - Do this *after* setting up the data directory, as the data directory hash is used in the name + // of the server. + // - Do this after creating app and setting up translations, so errors are + // translated properly. + if (PaymentServer::ipcSendCommandLine()) + exit(0); + + // Start up the payment server early, too, so impatient users that click on + // bitcoin: links repeatedly have their payment requests routed to this process: + app.createPaymentServer(); +#endif + + /// 8. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); // Install qDebug() message handler to route to debug.log @@ -242,130 +539,29 @@ int main(int argc, char *argv[]) #else qInstallMessageHandler(DebugMessageHandler); #endif - - // ... now GUI settings: - OptionsModel optionsModel; + // Load GUI settings from QSettings + app.createOptionsModel(); // Subscribe to global signals from core - uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); uiInterface.InitMessage.connect(InitMessage); - uiInterface.Translate.connect(Translate); - - // Show help message immediately after parsing command-line options (for "-lang") and setting locale, - // but before showing splash screen. - if (mapArgs.count("-?") || mapArgs.count("--help")) - { - GUIUtil::HelpMessageBox help; - help.showOrPrint(); - return 1; - } - SplashScreen splash(QPixmap(), 0, isaTestNet); if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) - { - splash.show(); - splash.setAutoFillBackground(true); - splashref = &splash; - } - - app.processEvents(); - app.setQuitOnLastWindowClosed(false); + app.createSplashScreen(isaTestNet); try { -#ifndef Q_OS_MAC - // Regenerate startup link, to fix links to old versions - // OSX: makes no sense on mac and might also scan/mount external (and sleeping) volumes (can take up some secs) - if (GUIUtil::GetStartOnSystemStartup()) - GUIUtil::SetStartOnSystemStartup(true); -#endif - - boost::thread_group threadGroup; - - BitcoinGUI window(isaTestNet, 0); - guiref = &window; - - QTimer* pollShutdownTimer = new QTimer(guiref); - QObject::connect(pollShutdownTimer, SIGNAL(timeout()), guiref, SLOT(detectShutdown())); - pollShutdownTimer->start(200); - - if(AppInit2(threadGroup)) - { - { - // Put this in a block, so that the Model objects are cleaned up before - // calling Shutdown(). - - optionsModel.Upgrade(); // Must be done after AppInit2 - - PaymentServer::LoadRootCAs(); - paymentServer->setOptionsModel(&optionsModel); - - if (splashref) - splash.finish(&window); - - ClientModel clientModel(&optionsModel); - window.setClientModel(&clientModel); - - WalletModel *walletModel = 0; - if(pwalletMain) - walletModel = new WalletModel(pwalletMain, &optionsModel); - - if(walletModel) - { - window.addWallet("~Default", walletModel); - window.setCurrentWallet("~Default"); - } - - // If -min option passed, start window minimized. - if(GetBoolArg("-min", false)) - { - window.showMinimized(); - } - else - { - window.show(); - } - - // Now that initialization/startup is done, process any command-line - // bitcoin: URIs or payment requests: - QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), - &window, SLOT(handlePaymentRequest(SendCoinsRecipient))); - QObject::connect(&window, SIGNAL(receivedURI(QString)), - paymentServer, SLOT(handleURIOrFile(QString))); - if(walletModel) - { - QObject::connect(walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), - paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); - } - QObject::connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), - guiref, SLOT(message(QString,QString,unsigned int))); - QTimer::singleShot(100, paymentServer, SLOT(uiReady())); - - app.exec(); - - window.hide(); - window.setClientModel(0); - window.removeAllWallets(); - guiref = 0; - delete walletModel; - } - // Shutdown the core and its threads, but don't exit the GUI here - threadGroup.interrupt_all(); - threadGroup.join_all(); - Shutdown(); - } - else - { - threadGroup.interrupt_all(); - threadGroup.join_all(); - Shutdown(); - return 1; - } + app.createWindow(isaTestNet); + app.requestInitialize(); + app.exec(); + app.requestShutdown(); + app.exec(); } catch (std::exception& e) { - handleRunawayException(&e); + PrintExceptionContinue(&e, "Runaway exception"); + app.handleRunawayException(QString::fromStdString(strMiscWarning)); } catch (...) { - handleRunawayException(NULL); + PrintExceptionContinue(NULL, "Runaway exception"); + app.handleRunawayException(QString::fromStdString(strMiscWarning)); } - return 0; + return app.getReturnValue(); } #endif // BITCOIN_QT_TEST |