diff --git a/src/connection.cpp b/src/connection.cpp index b2c46f2..444b6e9 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -27,7 +27,8 @@ ConnectionLoader::~ConnectionLoader() { void ConnectionLoader::loadConnection() { QTimer::singleShot(1, [=]() { this->doAutoConnect(); }); - d->exec(); + if (!Settings::getInstance()->isHeadless()) + d->exec(); } void ConnectionLoader::doAutoConnect(bool tryEzcashdStart) { @@ -442,6 +443,7 @@ void ConnectionLoader::refreshZcashdState(Connection* connection, std::function< [=] (auto) { // Success, hide the dialog if it was shown. d->hide(); + main->logger->write("zcashd is online."); this->doRPCSetConnection(connection); }, [=] (auto reply, auto res) { @@ -481,6 +483,8 @@ void ConnectionLoader::refreshZcashdState(Connection* connection, std::function< void ConnectionLoader::showInformation(QString info, QString detail) { connD->status->setText(info); connD->statusDetail->setText(detail); + + main->logger->write(info + ":" + detail); } /** diff --git a/src/main.cpp b/src/main.cpp index cc899a3..e07d60a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,61 +1,322 @@ #include "mainwindow.h" +#include "rpc.h" #include "settings.h" #include "turnstile.h" #include "version.h" -int main(int argc, char *argv[]) +class SignalHandler { - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +public: + SignalHandler(int mask = DEFAULT_SIGNALS); + virtual ~SignalHandler(); - QApplication a(argc, argv); + enum SIGNALS + { + SIG_UNHANDLED = 0, // Physical signal not supported by this class + SIG_NOOP = 1, // The application is requested to do a no-op (only a target that platform-specific signals map to when they can't be raised anyway) + SIG_INT = 2, // Control+C (should terminate but consider that it's a normal way to do so; can delay a bit) + SIG_TERM = 4, // Control+Break (should terminate now without regarding the consquences) + SIG_CLOSE = 8, // Container window closed (should perform normal termination, like Ctrl^C) [Windows only; on Linux it maps to SIG_TERM] + SIG_RELOAD = 16, // Reload the configuration [Linux only, physical signal is SIGHUP; on Windows it maps to SIG_NOOP] + DEFAULT_SIGNALS = SIG_INT | SIG_TERM | SIG_CLOSE, + }; + static const int numSignals = 6; - QCoreApplication::setOrganizationName("zec-qt-wallet-org"); - QCoreApplication::setApplicationName("zec-qt-wallet"); + virtual bool handleSignal(int signal) = 0; - QString locale = QLocale::system().name(); - locale.truncate(locale.lastIndexOf('_')); // Get the language code - qDebug() << "Loading locale " << locale; - - QTranslator translator; - translator.load(QString(":/translations/res/zec_qt_wallet_") + locale); - a.installTranslator(&translator); +private: + int _mask; +}; - QIcon icon(":/icons/res/icon.ico"); - QApplication::setWindowIcon(icon); +#include - #ifdef Q_OS_LINUX - QFontDatabase::addApplicationFont(":/fonts/res/Ubuntu-R.ttf"); - qApp->setFont(QFont("Ubuntu", 11, QFont::Normal, false)); - #endif +#ifndef _WIN32 + +#include + +#else + +#include + +#endif //!_WIN32 + +// There can be only ONE SignalHandler per process +SignalHandler* g_handler(NULL); + +#ifdef _WIN32 + +BOOL WINAPI WIN32_handleFunc(DWORD); +int WIN32_physicalToLogical(DWORD); +DWORD WIN32_logicalToPhysical(int); +std::set g_registry; + +#else //_WIN32 + +void POSIX_handleFunc(int); +int POSIX_physicalToLogical(int); +int POSIX_logicalToPhysical(int); + +#endif //_WIN32 + +SignalHandler::SignalHandler(int mask) : _mask(mask) +{ + assert(g_handler == NULL); + g_handler = this; + +#ifdef _WIN32 + SetConsoleCtrlHandler(WIN32_handleFunc, TRUE); +#endif //_WIN32 + + for (int i=0;i= QT_VERSION_CHECK(5, 10, 0)) - unsigned int seed = QRandomGenerator::securelySeeded().generate(); +#ifdef _WIN32 +BOOL WINAPI WIN32_handleFunc(DWORD signal) +{ + if (g_handler) + { + int signo = WIN32_physicalToLogical(signal); + // The std::set is thread-safe in const reading access and we never + // write to it after the program has started so we don't need to + // protect this search by a mutex + std::set::const_iterator found = g_registry.find(signo); + if (signo != -1 && found != g_registry.end()) + { + return g_handler->handleSignal(signo) ? TRUE : FALSE; + } + else + { + return FALSE; + } + } + else + { + return FALSE; + } +} #else - // This will be used only during debugging for compatibility reasons - unsigned int seed = std::time(0); -#endif - std::srand(seed); +void POSIX_handleFunc(int signal) +{ + if (g_handler) + { + int signo = POSIX_physicalToLogical(signal); + g_handler->handleSignal(signo); + } +} +#endif //_WIN32 + +class Application : public SignalHandler +{ +public: + Application() : SignalHandler(SignalHandler::SIG_INT), w(nullptr) {} + + ~Application() { delete w; } + + int main(int argc, char *argv[]) { + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QApplication a(argc, argv); - Settings::init(); + QCoreApplication::setOrganizationName("zec-qt-wallet-org"); + QCoreApplication::setApplicationName("zec-qt-wallet"); - // Set up libsodium - if (sodium_init() < 0) { - /* panic! the library couldn't be initialized, it is not safe to use */ - qDebug() << "libsodium is not initialized!"; + QString locale = QLocale::system().name(); + locale.truncate(locale.lastIndexOf('_')); // Get the language code + qDebug() << "Loading locale " << locale; + + QTranslator translator; + translator.load(QString(":/translations/res/zec_qt_wallet_") + locale); + a.installTranslator(&translator); + + QIcon icon(":/icons/res/icon.ico"); + QApplication::setWindowIcon(icon); + + #ifdef Q_OS_LINUX + QFontDatabase::addApplicationFont(":/fonts/res/Ubuntu-R.ttf"); + qApp->setFont(QFont("Ubuntu", 11, QFont::Normal, false)); + #endif + + // QRandomGenerator generates a secure random number, which we use to seed. + #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + unsigned int seed = QRandomGenerator::securelySeeded().generate(); + #else + // This will be used only during debugging for compatibility reasons + unsigned int seed = std::time(0); + #endif + std::srand(seed); + + Settings::init(); + + // Set up libsodium + if (sodium_init() < 0) { + /* panic! the library couldn't be initialized, it is not safe to use */ + qDebug() << "libsodium is not initialized!"; + exit(0); + } + + // Command line parser + QCommandLineParser parser; + parser.setApplicationDescription("Shielded desktop wallet and embedded full node for Zcash"); + parser.addHelpOption(); + + // A boolean option for running it headless + QCommandLineOption headlessOption(QStringList() << "headless", "Running it via GUI."); + parser.addOption(headlessOption); + + QCommandLineOption noembeddedOption(QStringList() << "no-embedded", "Disable embedded zcashd"); + parser.addOption(noembeddedOption); + + parser.process(a); + if (parser.isSet(noembeddedOption)) { + Settings::getInstance()->setUseEmbedded(false); + } else { + Settings::getInstance()->setUseEmbedded(true); + } + + w = new MainWindow(); + w->setWindowTitle("zec-qt-wallet v" + QString(APP_VERSION)); + + if (parser.isSet(headlessOption)) { + Settings::getInstance()->setHeadless(true); + a.setQuitOnLastWindowClosed(false); + } else { + Settings::getInstance()->setHeadless(false); + w->show(); + } + + return QApplication::exec(); } - if (argc >= 2 && QString::fromStdString(argv[1]) == "--no-embedded") { - Settings::getInstance()->setUseEmbedded(false); - } else { - Settings::getInstance()->setUseEmbedded(true); + void DispatchToMainThread(std::function callback) + { + // any thread + QTimer* timer = new QTimer(); + timer->moveToThread(qApp->thread()); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, [=]() + { + // main thread + callback(); + timer->deleteLater(); + }); + QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); } - MainWindow w; - w.setWindowTitle("zec-qt-wallet v" + QString(APP_VERSION)); - w.show(); - - return QApplication::exec(); + bool handleSignal(int signal) + { + std::cout << std::endl << "Interrupted with signal " << signal << std::endl; + + if (w && w->getRPC()) { + // Blocking call to closeEvent on the UI thread. + DispatchToMainThread([=] { + w->doClose(); + QApplication::quit(); + }); + } else { + QApplication::quit(); + } + + return true; + } + +private: + MainWindow* w; +}; + +int main(int argc, char* argv[]) +{ + Application app; + return app.main(argc, argv); } + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0a243ca..26a75cc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -154,6 +154,10 @@ void MainWindow::restoreSavedStates() { ui->transactionsTable->horizontalHeader()->restoreState(s.value("tratablegeometry").toByteArray()); } +void MainWindow::doClose() { + closeEvent(nullptr); +} + void MainWindow::closeEvent(QCloseEvent* event) { QSettings s; @@ -167,7 +171,8 @@ void MainWindow::closeEvent(QCloseEvent* event) { rpc->shutdownZcashd(); // Bubble up - QMainWindow::closeEvent(event); + if (event) + QMainWindow::closeEvent(event); } void MainWindow::turnstileProgress() { diff --git a/src/mainwindow.h b/src/mainwindow.h index af267b5..9173b7c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -54,7 +54,6 @@ public: void updateTAddrCombo(bool checked); void updateFromCombo(); - Ui::MainWindow* ui; QLabel* statusLabel; @@ -63,6 +62,9 @@ public: QWidget* zcashdtab; Logger* logger; + + void doClose(); + private: void closeEvent(QCloseEvent* event); diff --git a/src/rpc.cpp b/src/rpc.cpp index 6ae217a..db1740c 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -1116,8 +1116,15 @@ void RPC::shutdownZcashd() { }); waiter.start(1000); - // Wait for the zcash process to exit. - d.exec(); + // Wait for the zcash process to exit. + if (!Settings::getInstance()->isHeadless()) { + d.exec(); + } else { + while (waiter.isActive()) { + QCoreApplication::processEvents(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } } diff --git a/src/settings.h b/src/settings.h index 25a3d9a..680f363 100644 --- a/src/settings.h +++ b/src/settings.h @@ -34,6 +34,9 @@ public: void setUseEmbedded(bool r) { _useEmbedded = r; } bool useEmbedded() { return _useEmbedded; } + void setHeadless(bool h) { _headless = h; } + bool isHeadless() { return _headless; } + int getBlockNumber(); void setBlockNumber(int number); @@ -103,8 +106,10 @@ private: bool _isSyncing = false; int _blockNumber = 0; bool _useEmbedded = false; + bool _headless = false; int _peerConnections = 0; - double zecPrice = 0.0; + + double zecPrice = 0.0; }; #endif // SETTINGS_H \ No newline at end of file