diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a38e54c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "singleapplication"] + path = singleapplication + url = git@github.com:itay-grudev/SingleApplication.git diff --git a/singleapplication b/singleapplication new file mode 160000 index 0000000..7163d16 --- /dev/null +++ b/singleapplication @@ -0,0 +1 @@ +Subproject commit 7163d166a1fbce7917015293f8e139bd65604881 diff --git a/src/main.cpp b/src/main.cpp index 44592b9..6b6ae15 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,6 @@ +#include + +#include "precompiled.h" #include "mainwindow.h" #include "rpc.h" #include "settings.h" @@ -155,7 +158,34 @@ public: QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QApplication a(argc, argv); + SingleApplication a(argc, argv, true); + + // 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); + + // No embedded will disable the embedded zcashd node + QCommandLineOption noembeddedOption(QStringList() << "no-embedded", "Disable embedded zcashd"); + parser.addOption(noembeddedOption); + + // Positional argument will specify a zcash payment URI + parser.addPositionalArgument("zcashURI", "An optional zcash URI to pay"); + + parser.process(a); + + // Check for a positional argument indicating a zcash payment URI + if (a.isSecondary()) { + if (parser.positionalArguments().length() > 0) { + a.sendMessage(parser.positionalArguments()[0].toUtf8()); + } + a.exit( 0 ); + return 0; + } QCoreApplication::setOrganizationName("zec-qt-wallet-org"); QCoreApplication::setApplicationName("zec-qt-wallet"); @@ -194,19 +224,7 @@ public: 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); + // Check for embedded option if (parser.isSet(noembeddedOption)) { Settings::getInstance()->setUseEmbedded(false); } else { @@ -216,6 +234,20 @@ public: w = new MainWindow(); w->setWindowTitle("ZecWallet v" + QString(APP_VERSION)); + // If there was a payment URI on the command line, pay it + if (parser.positionalArguments().length() > 0) { + w->payZcashURI(parser.positionalArguments()[0]); + } + + // Listen for any secondary instances telling us about a zcash payment URI + QObject::connect(&a, &SingleApplication::receivedMessage, [=] (quint32, QByteArray msg) { + QString uri(msg); + + // We need to execute this async, otherwise the app seems to crash for some reason. + QTimer::singleShot(1, [=]() { w->payZcashURI(uri); }); + }); + + // Check if starting headless if (parser.isSet(headlessOption)) { Settings::getInstance()->setHeadless(true); a.setQuitOnLastWindowClosed(false); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2a916ba..aea54c6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -46,7 +46,9 @@ MainWindow::MainWindow(QWidget *parent) : }); // Pay Zcash URI - QObject::connect(ui->actionPay_URI, &QAction::triggered, this, &MainWindow::payZcashURI); + QObject::connect(ui->actionPay_URI, &QAction::triggered, [=] () { + payZcashURI(); + }); // Import Private Key QObject::connect(ui->actionImport_Private_Key, &QAction::triggered, this, &MainWindow::importPrivKey); @@ -722,17 +724,50 @@ void MainWindow::doImport(QList* keys) { } } -void MainWindow::payZcashURI() { + +// Callback invoked when the RPC has finished loading all the balances, and the UI +// is now ready to send transactions. +void MainWindow::balancesReady() { + // First-time check + if (uiPaymentsReady) + return; + + uiPaymentsReady = true; + qDebug() << "Payment UI now ready!"; + + // There is a pending URI payment (from the command line, or from a secondary instance), + // process it. + if (!pendingURIPayment.isEmpty()) { + qDebug() << "Paying zcash URI"; + payZcashURI(pendingURIPayment); + pendingURIPayment = ""; + } + +} + +// Pay the Zcash URI by showing a confirmation window. If the URI parameter is empty, the UI +// will prompt for one. +void MainWindow::payZcashURI(QString uri) { + // If the Payments UI is not ready (i.e, all balances have not loaded), defer the payment URI + if (!uiPaymentsReady) { + qDebug() << "Payment UI not ready, waiting for UI to pay URI"; + pendingURIPayment = uri; + return; + } + // Error to display if something goes wrong. auto payZcashURIError = [=] (QString errorDetail = "") { QMessageBox::critical(this, tr("Error paying zcash URI"), tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + errorDetail); }; - // Read a zcash URI and pay it - QString uri = QInputDialog::getText(this, tr("Paste Zcash URI"), - "Zcash URI" + QString(" ").repeated(180)); + // If there was no URI passed, ask the user for one. + if (uri.isEmpty()) { + uri = QInputDialog::getText(this, tr("Paste Zcash URI"), + "Zcash URI" + QString(" ").repeated(180)); + } + // If there's no URI, just exit if (uri.isEmpty()) return; @@ -743,6 +778,7 @@ void MainWindow::payZcashURI() { } // Extract the address + qDebug() << "Recieved URI " << uri; uri = uri.right(uri.length() - QString("zcash:").length()); QRegExp re("([a-zA-Z0-9]+)"); diff --git a/src/mainwindow.h b/src/mainwindow.h index 9173b7c..311f82d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -50,6 +50,9 @@ public: void createWebsocket(QString wormholecode); void stopWebsocket(); + void balancesReady(); + void payZcashURI(QString uri = ""); + void updateLabels(); void updateTAddrCombo(bool checked); void updateFromCombo(); @@ -105,7 +108,6 @@ private: void donate(); void addressBook(); - void payZcashURI(); void postToZBoard(); void importPrivKey(); void exportAllKeys(); @@ -117,6 +119,9 @@ private: void restoreSavedStates(); + bool uiPaymentsReady = false; + QString pendingURIPayment; + WSServer* wsserver = nullptr; WormholeClient* wormhole = nullptr; diff --git a/src/precompiled.h b/src/precompiled.h index 3419af9..ee2dea5 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc.cpp b/src/rpc.cpp index a1bb293..a37c065 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -87,9 +87,8 @@ void RPC::setConnection(Connection* c) { ui->statusBar->showMessage("Ready!"); - refreshZECPrice(); - // Commented for Android beta. - // checkForUpdate(); + refreshZECPrice(); + checkForUpdate(); // Force update, because this might be coming from a settings update // where we need to immediately refresh @@ -772,7 +771,9 @@ void RPC::refreshBalances() { allBalances = newBalances; utxos = newUtxos; - updateUI(anyTUnconfirmed || anyZUnconfirmed); + updateUI(anyTUnconfirmed || anyZUnconfirmed); + + main->balancesReady(); }); }); } @@ -996,7 +997,7 @@ void RPC::checkForUpdate(bool silent) { qDebug() << "Version check: Current " << currentVersion << ", Available " << maxVersion; - if (maxVersion > currentVersion && maxVersion > maxHiddenVersion) { + if (maxVersion > currentVersion && (!silent || maxVersion > maxHiddenVersion)) { auto ans = QMessageBox::information(main, QObject::tr("Update Available"), QObject::tr("A new release v%1 is available! You have v%2.\n\nWould you like to visit the releases page?") .arg(maxVersion.toString()) diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index 62c2a12..b42d058 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -104,6 +104,9 @@ TRANSLATIONS = res/zec_qt_wallet_es.ts \ res/zec_qt_wallet_pt.ts \ res/zec_qt_wallet_it.ts +include(singleapplication/singleapplication.pri) +DEFINES += QAPPLICATION_CLASS=QApplication + win32: RC_ICONS = res/icon.ico ICON = res/logo.icns