9a2305a1b3
Stop the shutdown timer from exiting the main loop when shutdown is already in progress. Fixes seeming hanging window after typing 'stop' in debug console. Also hide the debug console during shutdown as it is useless without a core to connect to.
565 lines
18 KiB
C++
565 lines
18 KiB
C++
// Copyright (c) 2011-2013 The Bitcoin developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "bitcoin-config.h"
|
|
#include "bitcoingui.h"
|
|
|
|
#include "clientmodel.h"
|
|
#include "guiconstants.h"
|
|
#include "guiutil.h"
|
|
#include "intro.h"
|
|
#include "optionsmodel.h"
|
|
#include "paymentserver.h"
|
|
#include "splashscreen.h"
|
|
#include "walletmodel.h"
|
|
|
|
#include "init.h"
|
|
#include "main.h"
|
|
#include "ui_interface.h"
|
|
#include "util.h"
|
|
#include "wallet.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <QApplication>
|
|
#include <QLibraryInfo>
|
|
#include <QLocale>
|
|
#include <QMessageBox>
|
|
#include <QSettings>
|
|
#include <QTimer>
|
|
#include <QTranslator>
|
|
#include <QThread>
|
|
#include <QVBoxLayout>
|
|
#include <QLabel>
|
|
|
|
#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*)
|
|
|
|
static void InitMessage(const std::string &message)
|
|
{
|
|
LogPrintf("init message: %s\n", message.c_str());
|
|
}
|
|
|
|
/*
|
|
Translate string to current locale using Qt.
|
|
*/
|
|
static std::string Translate(const char* psz)
|
|
{
|
|
return QCoreApplication::translate("bitcoin-core", psz).toStdString();
|
|
}
|
|
|
|
/** Set up translations */
|
|
static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator)
|
|
{
|
|
QSettings settings;
|
|
|
|
// Get desired locale (e.g. "de_DE")
|
|
// 1) System default language
|
|
QString lang_territory = QLocale::system().name();
|
|
// 2) Language from QSettings
|
|
QString lang_territory_qsettings = settings.value("language", "").toString();
|
|
if(!lang_territory_qsettings.isEmpty())
|
|
lang_territory = lang_territory_qsettings;
|
|
// 3) -lang command line argument
|
|
lang_territory = QString::fromStdString(GetArg("-lang", lang_territory.toStdString()));
|
|
|
|
// Convert to "de" only by truncating "_DE"
|
|
QString lang = lang_territory;
|
|
lang.truncate(lang_territory.lastIndexOf('_'));
|
|
|
|
// Load language files for configured locale:
|
|
// - First load the translator for the base language, without territory
|
|
// - Then load the more specific locale translator
|
|
|
|
// Load e.g. qt_de.qm
|
|
if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
|
QApplication::installTranslator(&qtTranslatorBase);
|
|
|
|
// Load e.g. qt_de_DE.qm
|
|
if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
|
QApplication::installTranslator(&qtTranslator);
|
|
|
|
// Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc)
|
|
if (translatorBase.load(lang, ":/translations/"))
|
|
QApplication::installTranslator(&translatorBase);
|
|
|
|
// Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc)
|
|
if (translator.load(lang_territory, ":/translations/"))
|
|
QApplication::installTranslator(&translator);
|
|
}
|
|
|
|
/* qDebug() message handler --> debug.log */
|
|
#if QT_VERSION < 0x050000
|
|
void DebugMessageHandler(QtMsgType type, const char *msg)
|
|
{
|
|
Q_UNUSED(type);
|
|
LogPrint("qt", "GUI: %s\n", msg);
|
|
}
|
|
#else
|
|
void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg)
|
|
{
|
|
Q_UNUSED(type);
|
|
Q_UNUSED(context);
|
|
LogPrint("qt", "GUI: %s\n", qPrintable(msg));
|
|
}
|
|
#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();
|
|
|
|
/// Create payment server
|
|
void createPaymentServer();
|
|
/// 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;
|
|
PaymentServer* paymentServer;
|
|
OptionsModel *optionsModel;
|
|
ClientModel *clientModel;
|
|
BitcoinGUI *window;
|
|
WalletModel *walletModel;
|
|
QTimer *pollShutdownTimer;
|
|
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),
|
|
paymentServer(0),
|
|
optionsModel(0),
|
|
clientModel(0),
|
|
window(0),
|
|
walletModel(0),
|
|
pollShutdownTimer(0),
|
|
returnValue(0)
|
|
{
|
|
setQuitOnLastWindowClosed(false);
|
|
startThread();
|
|
}
|
|
|
|
BitcoinApplication::~BitcoinApplication()
|
|
{
|
|
LogPrintf("Stopping thread\n");
|
|
emit stopThread();
|
|
coreThread->wait();
|
|
LogPrintf("Stopped thread\n");
|
|
|
|
delete window;
|
|
delete paymentServer;
|
|
delete optionsModel;
|
|
}
|
|
|
|
void BitcoinApplication::createPaymentServer()
|
|
{
|
|
paymentServer = new PaymentServer(this);
|
|
}
|
|
|
|
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);
|
|
window->removeAllWallets();
|
|
pollShutdownTimer->stop();
|
|
|
|
delete walletModel;
|
|
walletModel = 0;
|
|
delete clientModel;
|
|
clientModel = 0;
|
|
|
|
// Show a simple window indicating shutdown status
|
|
QWidget *shutdownWindow = new QWidget();
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
layout->addWidget(new QLabel(
|
|
tr("Bitcoin Core is shutting down...\n") +
|
|
tr("Do not shut down the computer until this window disappears.")));
|
|
shutdownWindow->setLayout(layout);
|
|
|
|
// Center shutdown window at where main window was
|
|
const QPoint global = window->mapToGlobal(window->rect().center());
|
|
shutdownWindow->move(global.x() - shutdownWindow->width() / 2, global.y() - shutdownWindow->height() / 2);
|
|
shutdownWindow->show();
|
|
|
|
// 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
|
|
|
|
PaymentServer::LoadRootCAs();
|
|
paymentServer->setOptionsModel(optionsModel);
|
|
|
|
emit splashFinished(window);
|
|
|
|
clientModel = new ClientModel(optionsModel);
|
|
window->setClientModel(clientModel);
|
|
|
|
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)));
|
|
}
|
|
|
|
// 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:
|
|
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()));
|
|
|
|
} 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 fSelParFromCLFailed = false;
|
|
/// 1. Parse command-line options. These take precedence over anything else.
|
|
// Command-line options take precedence:
|
|
ParseParameters(argc, argv);
|
|
// Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause)
|
|
if (!SelectParamsFromCommandLine()) {
|
|
fSelParFromCLFailed = true;
|
|
}
|
|
// Parse URIs on command line -- this can affect TestNet() / RegTest() mode
|
|
if (!PaymentServer::ipcParseCommandLine(argc, argv))
|
|
exit(0);
|
|
|
|
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"));
|
|
QTextCodec::setCodecForCStrings(QTextCodec::codecForTr());
|
|
#endif
|
|
|
|
Q_INIT_RESOURCE(bitcoin);
|
|
BitcoinApplication app(argc, argv);
|
|
#if QT_VERSION > 0x050100
|
|
// Generate high-dpi pixmaps
|
|
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
|
#endif
|
|
#ifdef Q_OS_MAC
|
|
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
|
#endif
|
|
|
|
// Register meta types used for QMetaObject::invokeMethod
|
|
qRegisterMetaType< bool* >();
|
|
|
|
/// 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
|
|
QApplication::setApplicationName("Bitcoin-Qt-testnet");
|
|
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);
|
|
|
|
// 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;
|
|
}
|
|
// 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;
|
|
}
|
|
|
|
/// 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);
|
|
|
|
/// 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();
|
|
|
|
/// 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
|
|
#if QT_VERSION < 0x050000
|
|
qInstallMsgHandler(DebugMessageHandler);
|
|
#else
|
|
qInstallMessageHandler(DebugMessageHandler);
|
|
#endif
|
|
// Load GUI settings from QSettings
|
|
app.createOptionsModel();
|
|
|
|
// Subscribe to global signals from core
|
|
uiInterface.InitMessage.connect(InitMessage);
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (GetBoolArg("-splash", true) && !GetBoolArg("-min", 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
|
|
app.createWindow(isaTestNet);
|
|
app.requestInitialize();
|
|
app.exec();
|
|
app.requestShutdown();
|
|
app.exec();
|
|
} catch (std::exception& e) {
|
|
PrintExceptionContinue(&e, "Runaway exception");
|
|
app.handleRunawayException(QString::fromStdString(strMiscWarning));
|
|
} catch (...) {
|
|
PrintExceptionContinue(NULL, "Runaway exception");
|
|
app.handleRunawayException(QString::fromStdString(strMiscWarning));
|
|
}
|
|
return app.getReturnValue();
|
|
}
|
|
#endif // BITCOIN_QT_TEST
|