diff --git a/CMakeLists.txt b/CMakeLists.txt index e6f30be6..8e803939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,10 @@ project(feather) message(STATUS "Initiating compile using CMake ${CMAKE_VERSION}") -set(VERSION_MAJOR "0") -set(VERSION_MINOR "1") +set(VERSION_MAJOR "1") +set(VERSION_MINOR "0") set(VERSION_REVISION "0") -set(VERSION "beta-9") +set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}") option(STATIC "Link libraries statically, requires static Qt") @@ -32,7 +32,7 @@ if(DEBUG) set(CMAKE_VERBOSE_MAKEFILE ON) endif() -set(MONERO_HEAD "d4257af2e7503fc6dc09fc704606230d353a0a02") +set(MONERO_HEAD "bdd284b35d2e2c9c6ac733b4bc5ce8bd3b1162dd") set(BUILD_GUI_DEPS ON) option(ARCH "Target architecture" "x86-64") set(BUILD_64 ON) diff --git a/Makefile b/Makefile index c2a7a3ef..5a633726 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,12 @@ CMAKEFLAGS = \ release: mkdir -p build/release && \ cd build/release && \ - cmake -D CMAKE_BUILD_TYPE=Release $(CMAKEFLAGS) ../.. && \ + cmake \ + -DARCH=x86-64 \ + -D BUILD_TAG="linux-x64" \ + -D CMAKE_BUILD_TYPE=Release \ + $(CMAKEFLAGS) \ + ../.. && \ $(MAKE) release-static: diff --git a/PKGBUILD b/PKGBUILD index 11de9ccb..f80c21ca 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: wowario pkgname='monero-feather-git' -pkgver=0.8.0.d3791cbd26 +pkgver=1.0.0 pkgrel=1 pkgdesc='A free Monero desktop wallet' license=('BSD') diff --git a/contrib/installers/windows/LICENSE.txt b/contrib/installers/windows/LICENSE.txt new file mode 100644 index 00000000..1c64b26a --- /dev/null +++ b/contrib/installers/windows/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright (c) 2020-2021, The Monero Project + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/contrib/installers/windows/appicon.ico b/contrib/installers/windows/appicon.ico new file mode 100644 index 00000000..a2092a85 Binary files /dev/null and b/contrib/installers/windows/appicon.ico differ diff --git a/contrib/installers/windows/feather.iss b/contrib/installers/windows/feather.iss new file mode 100644 index 00000000..ef6d158d --- /dev/null +++ b/contrib/installers/windows/feather.iss @@ -0,0 +1,67 @@ +; Feather Wallet Installer for Windows +; Copyright (c) 2021-2021, The Monero Project + +#define AppName "Feather Wallet" +#define AppVersion "1.0.2" +#define AppPublisher "Feather Wallet" +#define AppURL "https://featherwallet.org" +#define AppExeName "feather.exe" + +[Setup] +AppId={{E3C599C7-4DF1-49F2-9C35-918A288677A4} +AppName={#AppName} +AppVersion={#AppVersion} +;AppVerName={#AppName} {#AppVersion} +AppPublisher={#AppPublisher} +AppPublisherURL={#AppURL} +AppSupportURL={#AppURL} +AppUpdatesURL={#AppURL} +DefaultDirName={autopf}\{#AppName} +DisableDirPage=yes +DisableProgramGroupPage=yes +; Uncomment the following line to run in non administrative install mode (install for current user only.) +;PrivilegesRequired=lowest +OutputBaseFilename=FeatherWalletSetup +SetupIconFile=appicon.ico +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +ArchitecturesInstallIn64BitMode=x64 +ArchitecturesAllowed=x64 +DisableReadyPage=yes + +WizardSmallImageFile=compiler:WizClassicSmallImage.bmp +WizardImageFile=compiler:WizClassicImage.bmp + +UninstallDisplayIcon={app}\{#AppExeName} + +[Messages] +SetupWindowTitle=Setup - Feather Wallet {#AppVersion} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; + +[Files] +Source: "bin\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion + +;Source: "C:\Users\dev\Desktop\feather setup\finishbanner.bmp"; Flags: dontcopy +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Code] +procedure CurPageChanged(CurPageID: Integer); +begin + if CurPageID = wpSelectTasks then + WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall) +end; + +[Icons] +Name: "{autoprograms}\{#AppName}"; Filename: "{app}\{#AppExeName}" +Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent diff --git a/monero b/monero index d4257af2..bdd284b3 160000 --- a/monero +++ b/monero @@ -1 +1 @@ -Subproject commit d4257af2e7503fc6dc09fc704606230d353a0a02 +Subproject commit bdd284b35d2e2c9c6ac733b4bc5ce8bd3b1162dd diff --git a/src/CalcWidget.cpp b/src/CalcWidget.cpp index f7737167..8bc654df 100644 --- a/src/CalcWidget.cpp +++ b/src/CalcWidget.cpp @@ -23,13 +23,12 @@ CalcWidget::CalcWidget(QWidget *parent) ui->imageExchange->setFixedSize(26, 26); // validator/locale for input - QLocale lo(QLocale::C); - lo.setNumberOptions(QLocale::RejectGroupSeparator); - auto dv = new QDoubleValidator(0.0, 2147483647, 10, this); // [0, 32bit max], 10 decimals of precision - dv->setNotation(QDoubleValidator::StandardNotation); - dv->setLocale(lo); - ui->lineFrom->setValidator(dv); - ui->lineTo->setValidator(dv); + QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}$)"; + QRegExp rx; + rx.setPattern(amount_rx); + QValidator *validator = new QRegExpValidator(rx, this); + ui->lineFrom->setValidator(validator); + ui->lineTo->setValidator(validator); connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::onPricesReceived); connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::onPricesReceived); diff --git a/src/CoinsWidget.cpp b/src/CoinsWidget.cpp index 2a972943..1808c70e 100644 --- a/src/CoinsWidget.cpp +++ b/src/CoinsWidget.cpp @@ -26,8 +26,7 @@ CoinsWidget::CoinsWidget(QSharedPointer ctx, QWidget *parent) connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu); // copy menu - m_copyMenu->setIcon(icons()->icon("copy.png")); - m_copyMenu->addAction("Public key", this, [this]{copy(copyField::PubKey);}); + m_copyMenu->addAction("Public Key", this, [this]{copy(copyField::PubKey);}); m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);}); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Address", this, [this]{copy(copyField::Address);}); @@ -47,12 +46,12 @@ CoinsWidget::CoinsWidget(QSharedPointer ctx, QWidget *parent) m_freezeAllSelectedAction = new QAction("Freeze selected", this); m_thawAllSelectedAction = new QAction("Thaw selected", this); - m_viewOutputAction = new QAction(icons()->icon("info2.svg"), "Details", this); + m_viewOutputAction = new QAction("Details", this); m_sweepOutputAction = new QAction("Sweep output", this); m_sweepOutputsAction = new QAction("Sweep selected outputs", this); - connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput); - connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput); + connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeAllSelected); + connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawAllSelected); connect(m_viewOutputAction, &QAction::triggered, this, &CoinsWidget::viewOutput); connect(m_sweepOutputAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs); connect(m_sweepOutputsAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs); @@ -155,36 +154,24 @@ void CoinsWidget::setSearchFilter(const QString &filter) { m_proxyModel->setSearchFilter(filter); } -void CoinsWidget::freezeOutput() { - QModelIndex index = ui->coins->currentIndex(); - QVector indexes = {m_proxyModel->mapToSource(index).row()}; - this->freezeCoins(indexes); +QStringList CoinsWidget::selectedPubkeys() { + QModelIndexList list = ui->coins->selectionModel()->selectedRows(); + + QStringList pubkeys; + for (QModelIndex index: list) { + pubkeys << m_model->entryFromIndex(m_proxyModel->mapToSource(index))->pubKey(); + } + return pubkeys; } void CoinsWidget::freezeAllSelected() { - QModelIndexList list = ui->coins->selectionModel()->selectedRows(); - - QVector indexes; - for (QModelIndex index: list) { - indexes.push_back(m_proxyModel->mapToSource(index).row()); // todo: will segfault if index get invalidated - } - this->freezeCoins(indexes); -} - -void CoinsWidget::thawOutput() { - QModelIndex index = ui->coins->currentIndex(); - QVector indexes = {m_proxyModel->mapToSource(index).row()}; - this->thawCoins(indexes); + QStringList pubkeys = this->selectedPubkeys(); + this->freezeCoins(pubkeys); } void CoinsWidget::thawAllSelected() { - QModelIndexList list = ui->coins->selectionModel()->selectedRows(); - - QVector indexes; - for (QModelIndex index: list) { - indexes.push_back(m_proxyModel->mapToSource(index).row()); - } - this->thawCoins(indexes); + QStringList pubkeys = this->selectedPubkeys(); + this->thawCoins(pubkeys); } void CoinsWidget::viewOutput() { @@ -291,17 +278,17 @@ QVector CoinsWidget::currentEntries() { return selectedCoins; } -void CoinsWidget::freezeCoins(const QVector& indexes) { - for (int i : indexes) { - m_ctx->wallet->coins()->freeze(i); +void CoinsWidget::freezeCoins(QStringList &pubkeys) { + for (auto &pubkey : pubkeys) { + m_ctx->wallet->coins()->freeze(pubkey); } m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->updateBalance(); } -void CoinsWidget::thawCoins(const QVector &indexes) { - for (int i : indexes) { - m_ctx->wallet->coins()->thaw(i); +void CoinsWidget::thawCoins(QStringList &pubkeys) { + for (auto &pubkey : pubkeys) { + m_ctx->wallet->coins()->thaw(pubkey); } m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->updateBalance(); diff --git a/src/CoinsWidget.h b/src/CoinsWidget.h index 90cbb534..6c48e24c 100644 --- a/src/CoinsWidget.h +++ b/src/CoinsWidget.h @@ -33,9 +33,7 @@ public slots: private slots: void showHeaderMenu(const QPoint& position); void setShowSpent(bool show); - void freezeOutput(); void freezeAllSelected(); - void thawOutput(); void thawAllSelected(); void viewOutput(); void onSweepOutputs(); @@ -43,8 +41,8 @@ private slots: void editLabel(); private: - void freezeCoins(const QVector& indexes); - void thawCoins(const QVector& indexes); + void freezeCoins(QStringList &pubkeys); + void thawCoins(QStringList &pubkeys); enum copyField { PubKey = 0, @@ -79,6 +77,7 @@ private: void copy(copyField field); CoinsInfo* currentEntry(); QVector currentEntries(); + QStringList selectedPubkeys(); }; diff --git a/src/ContactsWidget.cpp b/src/ContactsWidget.cpp index c43a4b62..bb2380d3 100644 --- a/src/ContactsWidget.cpp +++ b/src/ContactsWidget.cpp @@ -18,6 +18,11 @@ ContactsWidget::ContactsWidget(QSharedPointer ctx, QWidget *parent) { ui->setupUi(this); + m_btn_addContact = new QPushButton(this); + m_btn_addContact->setIcon(icons()->icon("localMonero_register.svg")); + ui->searchLayout->addWidget(m_btn_addContact, 0, Qt::AlignRight); + connect(m_btn_addContact, &QPushButton::clicked, [this]{this->newContact();}); + m_model = m_ctx->wallet->addressBookModel(); m_proxyModel = new AddressBookProxyModel; m_proxyModel->setSourceModel(m_model); @@ -45,7 +50,7 @@ ContactsWidget::ContactsWidget(QSharedPointer ctx, QWidget *parent) // context menu ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu); m_contextMenu = new QMenu(ui->contacts); - m_contextMenu->addAction(icons()->icon("person.svg"), "New contact", [this]{ + m_contextMenu->addAction("New contact", [this]{ this->newContact(); }); diff --git a/src/ContactsWidget.h b/src/ContactsWidget.h index e800a0ea..9a71fa92 100644 --- a/src/ContactsWidget.h +++ b/src/ContactsWidget.h @@ -4,6 +4,7 @@ #ifndef FEATHER_CONTACTSWIDGET_H #define FEATHER_CONTACTSWIDGET_H +#include #include #include @@ -51,6 +52,7 @@ private: QMenu *m_headerMenu; AddressBookModel * m_model; AddressBookProxyModel * m_proxyModel; + QPushButton *m_btn_addContact; }; #endif // FEATHER_CONTACTSWIDGET_H diff --git a/src/ContactsWidget.ui b/src/ContactsWidget.ui index 69151e64..8281279d 100644 --- a/src/ContactsWidget.ui +++ b/src/ContactsWidget.ui @@ -6,8 +6,8 @@ 0 0 - 589 - 416 + 914 + 763 @@ -30,14 +30,24 @@ 0 - - - Search contacts... - - + + + + + Search contacts... + + + + + + + 0 + 0 + + false diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index 4499a5d5..c4465172 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -11,6 +11,7 @@ #include "dialog/TxProofDialog.h" #include "utils/config.h" #include "utils/Icons.h" +#include "WebsocketNotifier.h" HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) : QWidget(parent) @@ -26,10 +27,9 @@ HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); // copy menu - m_copyMenu->setIcon(icons()->icon("copy.png")); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); - m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);}); m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);}); + m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);}); m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);}); ui->history->setContextMenuPolicy(Qt::CustomContextMenu); @@ -53,7 +53,10 @@ HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool()); ui->history->setHistoryModel(m_model); - m_ctx->wallet->transactionHistoryModel()->amountPrecision = config()->get(Config::amountPrecision).toInt(); + + connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, [this]{ + ui->history->update(); + }); // Load view state QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()); @@ -88,9 +91,9 @@ void HistoryWidget::showContextMenu(const QPoint &point) { } menu.addMenu(m_copyMenu); - menu.addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails); - menu.addAction(icons()->icon("network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); - menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof); + menu.addAction("Show details", this, &HistoryWidget::showTxDetails); + menu.addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); + menu.addAction("Create Tx Proof", this, &HistoryWidget::createTxProof); menu.exec(ui->history->viewport()->mapToGlobal(point)); } @@ -121,6 +124,7 @@ void HistoryWidget::showTxDetails() { emit resendTransaction(txid); }); dialog->show(); + dialog->setAttribute(Qt::WA_DeleteOnClose); } void HistoryWidget::onViewOnBlockExplorer() { @@ -157,10 +161,13 @@ void HistoryWidget::copy(copyField field) { switch(field) { case copyField::TxID: return tx->hash(); + case copyField::Description: + return tx->description(); case copyField::Date: - return tx->timestamp().toString("yyyy-MM-dd HH:mm"); + return tx->timestamp().toString(QString("%1 %2").arg(config()->get(Config::dateFormat).toString(), + config()->get(Config::timeFormat).toString())); case copyField::Amount: - return tx->displayAmount(); + return WalletManager::displayAmount(tx->balanceDelta()); default: return QString(""); } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index df001c38..0dfcbb0d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -47,7 +47,6 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa // Ensure the destructor is called after closeEvent() setAttribute(Qt::WA_DeleteOnClose); - m_windowSettings = new Settings(m_ctx, this); m_windowCalc = new CalcWindow(this); m_splashDialog = new SplashDialog(this); @@ -68,21 +67,12 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa #endif websocketNotifier()->emitCache(); // Get cached data - // Settings - for (const auto &widget: m_priceTickerWidgets) - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx.get(), &AppContext::onPreferredFiatCurrencyChanged); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); - connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx.get(), &AppContext::onAmountPrecisionChanged); - connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged); - QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); - connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); this->onTorConnectionStateChanged(torManager()->torConnected); ColorScheme::updateFromWidget(this); + QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); // Timers connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats); @@ -105,6 +95,10 @@ void MainWindow::initStatusBar() { this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}"); #endif +#if defined(Q_OS_MACOS) + this->patchStylesheetMac(); +#endif + this->statusBar()->setFixedHeight(30); m_statusLabelStatus = new QLabel("Idle", this); @@ -327,6 +321,7 @@ void MainWindow::initMenu() { connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked); connect(ui->actionOfficialWebsite, &QAction::triggered, [this](){Utils::externalLinkWarning(this, "https://featherwallet.org");}); connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked); + connect(ui->actionDocumentation, &QAction::triggered, this, &MainWindow::onShowDocumentaton); connect(ui->actionReport_bug, &QAction::triggered, this, &MainWindow::onReportBug); connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo); @@ -341,6 +336,7 @@ void MainWindow::initMenu() { ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S")); ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U")); ui->actionShow_Searchbar->setShortcut(QKeySequence("Ctrl+F")); + ui->actionDocumentation->setShortcut(QKeySequence("F1")); } void MainWindow::initHome() { @@ -626,9 +622,9 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto m_ctx->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0)); // Show advanced dialog on multi-destination transactions - if (address.size() > 1) { + if (address.size() > 1 || m_ctx->wallet->viewOnly()) { TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this}; - dialog_adv.setTransaction(tx); + dialog_adv.setTransaction(tx, !m_ctx->wallet->viewOnly()); dialog_adv.exec(); return; } @@ -642,7 +638,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto break; } case QDialog::Accepted: - m_ctx->commitTransaction(tx); + m_ctx->commitTransaction(tx, m_ctx->tmpTxDescription); break; } @@ -666,9 +662,10 @@ void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, con if (msgBox.clickedButton() == showDetailsButton) { this->showHistoryTab(); TransactionInfo *txInfo = m_ctx->wallet->history()->transaction(txid.first()); - TxInfoDialog dialog{m_ctx, txInfo, this}; - connect(&dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction); - dialog.exec(); + auto *dialog = new TxInfoDialog(m_ctx, txInfo, this); + connect(dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction); + dialog->show(); + dialog->setAttribute(Qt::WA_DeleteOnClose); } m_sendWidget->clearFields(); @@ -832,9 +829,14 @@ void MainWindow::menuAboutClicked() { } void MainWindow::menuSettingsClicked() { - m_windowSettings->raise(); - m_windowSettings->show(); - m_windowSettings->activateWindow(); + Settings settings{m_ctx, this}; + for (const auto &widget: m_priceTickerWidgets) { + connect(&settings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay); + } + connect(&settings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay); + connect(&settings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); + connect(&settings, &Settings::skinChanged, this, &MainWindow::skinChanged); + settings.exec(); } void MainWindow::menuSignVerifyClicked() { @@ -851,6 +853,10 @@ void MainWindow::skinChanged(const QString &skinName) { m_windowManager->changeSkin(skinName); ColorScheme::updateFromWidget(this); this->updateWidgetIcons(); + +#if defined(Q_OS_MACOS) + this->patchStylesheetMac(); +#endif } void MainWindow::updateWidgetIcons() { @@ -1042,7 +1048,7 @@ void MainWindow::showWSNodeExhaustedMessage() { } void MainWindow::exportKeyImages() { - QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QDir::homePath(), "Key Images (*_keyImages)"); + QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Key Images (*_keyImages)"); if (fn.isEmpty()) return; if (!fn.endsWith("_keyImages")) fn += "_keyImages"; m_ctx->wallet->exportKeyImages(fn, true); @@ -1068,7 +1074,7 @@ void MainWindow::importKeyImages() { } void MainWindow::exportOutputs() { - QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QDir::homePath(), "Outputs (*_outputs)"); + QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Outputs (*_outputs)"); if (fn.isEmpty()) return; if (!fn.endsWith("_outputs")) fn += "_outputs"; m_ctx->wallet->exportOutputs(fn, true); @@ -1464,14 +1470,12 @@ void MainWindow::onCreateDesktopEntry(bool checked) { QMessageBox::information(this, "Desktop entry", msg); } +void MainWindow::onShowDocumentaton() { + Utils::externalLinkWarning(this, "https://docs.featherwallet.org"); +} + void MainWindow::onReportBug(bool checked) { - QMessageBox::information(this, "Reporting Bugs", - "Please report any bugs as issues on our git repo:
\n" - "https://git.featherwallet.org/feather/feather/issues

" - "\n" - "Before reporting a bug, upgrade to the most recent version of Feather " - "(latest release or git HEAD), and include the version number in your report. " - "Try to explain not only what the bug is, but how it occurs."); + Utils::externalLinkWarning(this, "https://docs.featherwallet.org/guides/report-an-issue"); } QString MainWindow::getPlatformTag() { @@ -1590,6 +1594,14 @@ bool MainWindow::verifyPassword() { return true; } +void MainWindow::patchStylesheetMac() { + auto patch = Utils::fileOpenQRC(":assets/macStylesheet.patch"); + auto patch_text = Utils::barrayToString(patch); + + QString styleSheet = qApp->styleSheet() + patch_text; + qApp->setStyleSheet(styleSheet); +} + void MainWindow::toggleSearchbar(bool visible) { config()->set(Config::showSearchbar, visible); diff --git a/src/MainWindow.h b/src/MainWindow.h index 883329d0..a4fc5903 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -117,6 +117,7 @@ private slots: void onExportHistoryCSV(bool checked); void onExportContactsCSV(bool checked); void onCreateDesktopEntry(bool checked); + void onShowDocumentaton(); void onReportBug(bool checked); // offline tx signing @@ -214,6 +215,7 @@ private: void updateRecentlyOpenedMenu(); void updateWidgetIcons(); bool verifyPassword(); + void patchStylesheetMac(); QIcon hardwareDevicePairedIcon(); QIcon hardwareDeviceUnpairedIcon(); @@ -222,7 +224,6 @@ private: WindowManager *m_windowManager; QSharedPointer m_ctx; - Settings *m_windowSettings = nullptr; CalcWindow *m_windowCalc = nullptr; SplashDialog *m_splashDialog = nullptr; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 6b886813..3d82bf17 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -465,10 +465,12 @@ - + + +
@@ -803,6 +805,11 @@ Address checker + + + Documentation + + diff --git a/src/ReceiveWidget.cpp b/src/ReceiveWidget.cpp index 477e51cf..881c0334 100644 --- a/src/ReceiveWidget.cpp +++ b/src/ReceiveWidget.cpp @@ -7,6 +7,7 @@ #include #include +#include "dialog/PaymentRequestDialog.h" #include "dialog/QrCodeDialog.h" #include "model/ModelUtils.h" #include "utils/Icons.h" @@ -43,8 +44,6 @@ ReceiveWidget::ReceiveWidget(QSharedPointer ctx, QWidget *parent) m_headerMenu = new QMenu(this); m_showFullAddressesAction = m_headerMenu->addAction("Show full addresses", this, &ReceiveWidget::setShowFullAddresses); m_showFullAddressesAction->setCheckable(true); - m_showUsedAddressesAction = m_headerMenu->addAction("Show used addresses", this, &ReceiveWidget::setShowUsedAddresses); - m_showUsedAddressesAction->setCheckable(true); connect(ui->addresses->header(), &QHeaderView::customContextMenuRequested, this, &ReceiveWidget::showHeaderMenu); // context menu @@ -60,6 +59,8 @@ ReceiveWidget::ReceiveWidget(QSharedPointer ctx, QWidget *parent) connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses); connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses); + + connect(ui->btn_createPaymentRequest, &QPushButton::clicked, this, &ReceiveWidget::createPaymentRequest); } void ReceiveWidget::setSearchbarVisible(bool visible) { @@ -96,9 +97,9 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { auto *menu = new QMenu(ui->addresses); - menu->addAction(icons()->icon("copy.png"), "Copy address", this, &ReceiveWidget::copyAddress); - menu->addAction(icons()->icon("copy.png"), "Copy label", this, &ReceiveWidget::copyLabel); - menu->addAction(icons()->icon("edit.png"), "Edit label", this, &ReceiveWidget::editLabel); + menu->addAction("Copy address", this, &ReceiveWidget::copyAddress); + menu->addAction("Copy label", this, &ReceiveWidget::copyLabel); + menu->addAction("Edit label", this, &ReceiveWidget::editLabel); if (isUsed) { menu->addAction(m_showTransactionsAction); @@ -106,7 +107,7 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { QStringList hiddenAddresses = this->getHiddenAddresses(); if (hiddenAddresses.contains(address)) { - menu->addAction("Show address", this, &ReceiveWidget::showAddress); + menu->addAction("Unhide address", this, &ReceiveWidget::showAddress); } else { menu->addAction("Hide address", this, &ReceiveWidget::hideAddress); } @@ -118,6 +119,18 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { menu->popup(ui->addresses->viewport()->mapToGlobal(point)); } +void ReceiveWidget::createPaymentRequest() { + QModelIndex index = ui->addresses->currentIndex(); + if (!index.isValid()) { + return; + } + + QString address = index.model()->data(index.siblingAtColumn(SubaddressModel::Address), Qt::UserRole).toString(); + + PaymentRequestDialog dialog{this, m_ctx, address}; + dialog.exec(); +} + void ReceiveWidget::onShowTransactions() { QModelIndex index = ui->addresses->currentIndex(); if (!index.isValid()) { @@ -190,6 +203,7 @@ void ReceiveWidget::updateQrCode(){ QModelIndex index = ui->addresses->currentIndex(); if (!index.isValid()) { ui->qrCode->clear(); + ui->btn_createPaymentRequest->hide(); return; } @@ -199,6 +213,7 @@ void ReceiveWidget::updateQrCode(){ int width = ui->qrCode->width() - 4; if (qrc.isValid()) { ui->qrCode->setPixmap(qrc.toPixmap(1).scaled(width, width, Qt::KeepAspectRatio)); + ui->btn_createPaymentRequest->show(); } } diff --git a/src/ReceiveWidget.h b/src/ReceiveWidget.h index 23ee78b1..e01f93fa 100644 --- a/src/ReceiveWidget.h +++ b/src/ReceiveWidget.h @@ -39,6 +39,7 @@ public slots: void setShowHiddenAddresses(bool show); void setSearchFilter(const QString &filter); void onShowTransactions(); + void createPaymentRequest(); signals: void showTransactions(const QString& address); diff --git a/src/ReceiveWidget.ui b/src/ReceiveWidget.ui index 0d420817..b84ed585 100644 --- a/src/ReceiveWidget.ui +++ b/src/ReceiveWidget.ui @@ -69,6 +69,13 @@
+ + + + Payment Request + + + diff --git a/src/SendWidget.cpp b/src/SendWidget.cpp index b460a083..b1f0f8bf 100644 --- a/src/SendWidget.cpp +++ b/src/SendWidget.cpp @@ -26,13 +26,13 @@ SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)"; QRegExp rx; rx.setPattern(amount_rx); - QValidator *validator = new QRegExpValidator(rx, this); + QValidator *validator = new QRegExpValidator(rx, this); ui->lineAmount->setValidator(validator); connect(m_ctx.get(), &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction); connect(m_ctx.get(), &AppContext::endTransaction, this, &SendWidget::onEndTransaction); - connect(m_ctx.get(), &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved); - connect(m_ctx.get(), &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError); + + connect(WalletManager::instance(), &WalletManager::openAliasResolved, this, &SendWidget::onOpenAliasResolved); connect(ui->btnScan, &QPushButton::clicked, this, &SendWidget::scanClicked); connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked); @@ -139,8 +139,8 @@ void SendWidget::sendClicked() { QString currency = ui->comboCurrencySelection->currentText(); QString recipient = ui->lineAddress->text().simplified().remove(' '); QString description = ui->lineDescription->text(); - if(recipient.isEmpty()) { - QMessageBox::warning(this, "Malformed recipient", "The recipient address was not correct"); + if (recipient.isEmpty()) { + QMessageBox::warning(this, "Malformed recipient", "No destination address was entered."); return; } @@ -178,7 +178,7 @@ void SendWidget::sendClicked() { amount = this->amount(); bool sendAll = (ui->lineAmount->text() == "all"); if (amount == 0 && !sendAll) { - QMessageBox::warning(this, "Amount error", "Invalid amount specified."); + QMessageBox::warning(this, "Amount error", "No amount was entered."); return; } m_ctx->onCreateTransaction(recipient, amount, description, sendAll); @@ -193,8 +193,9 @@ void SendWidget::sendClicked() { } void SendWidget::aliasClicked() { - auto address = ui->lineAddress->text(); - m_ctx->onOpenAliasResolve(address); + ui->btn_openAlias->setEnabled(false); + auto alias = ui->lineAddress->text(); + WalletManager::instance()->resolveOpenAliasAsync(alias); } void SendWidget::clearClicked() { @@ -254,7 +255,26 @@ double SendWidget::amountDouble() { return amount / constants::cdiv; } -void SendWidget::onOpenAliasResolved(const QString &address, const QString &openAlias) { +void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid) { + ui->btn_openAlias->setEnabled(true); + + if (address.isEmpty()) { + this->onOpenAliasResolveError("Could not resolve OpenAlias."); + return; + } + + if (!dnssecValid) { + this->onOpenAliasResolveError("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed."); + return; + } + + bool valid = WalletManager::addressValid(address, constants::networkType); + if (!valid) { + this->onOpenAliasResolveError(QString("Address validation error. Perhaps it is of the wrong network type.\n\n" + "OpenAlias: %1\nAddress: %2").arg(openAlias, address)); + return; + } + this->fill(address, openAlias); ui->btn_openAlias->hide(); } diff --git a/src/SendWidget.h b/src/SendWidget.h index 31c9c0a0..00199203 100644 --- a/src/SendWidget.h +++ b/src/SendWidget.h @@ -38,7 +38,7 @@ public slots: void fillAddress(const QString &address); void updateConversionLabel(); void onOpenAliasResolveError(const QString &err); - void onOpenAliasResolved(const QString &address, const QString &openAlias); + void onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid); void onPreferredFiatCurrencyChanged(); void onInitiateTransaction(); diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index d948f96e..5084db53 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -79,8 +79,9 @@ Settings::Settings(QSharedPointer ctx, QWidget *parent) // Preferred fiat currency combobox QStringList fiatCurrencies; - for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) + for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) { fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index); + } auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); if(!preferredFiatCurrency.isEmpty()) diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp index c4c6025e..717e1d8b 100644 --- a/src/WindowManager.cpp +++ b/src/WindowManager.cpp @@ -102,14 +102,6 @@ void WindowManager::startupWarning() { this->showWarningMessageBox("Warning", worthlessWarning.arg("testnet")); config()->set(Config::warnOnTestnet, false); } - - // Beta - if (config()->get(Config::warnOnAlpha).toBool()) { - QString warning = "Feather Wallet is currently in beta.\n\nPlease report any bugs " - "you encounter on our Git repository, IRC or on /r/FeatherWallet."; - this->showWarningMessageBox("Beta warning", warning); - config()->set(Config::warnOnAlpha, false); - } } void WindowManager::showWarningMessageBox(const QString &title, const QString &message) { @@ -460,8 +452,6 @@ void WindowManager::initTor() { torManager()->init(); torManager()->start(); - connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect); - this->onTorSettingsChanged(); } diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 2714b125..2318557f 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -55,9 +55,6 @@ AppContext::AppContext(Wallet *wallet) this->updateBalance(); - // force trigger preferredFiat signal for history model - this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString()); - connect(this->wallet->history(), &TransactionHistory::txNoteChanged, [this]{ this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); }); @@ -75,7 +72,8 @@ void AppContext::onCreateTransaction(const QString &address, quint64 amount, con quint64 unlocked_balance = this->wallet->unlockedBalance(); if (!all && amount > unlocked_balance) { - emit createTransactionError("Not enough money to spend"); + emit createTransactionError(QString("Not enough money to spend.\n\n" + "Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance))); return; } else if (unlocked_balance == 0) { emit createTransactionError("No money to spend"); @@ -132,14 +130,14 @@ void AppContext::onCancelTransaction(PendingTransaction *tx, const QVectorwallet->disposeTransaction(tx); } -void AppContext::commitTransaction(PendingTransaction *tx) { +void AppContext::commitTransaction(PendingTransaction *tx, const QString &description) { // Nodes - even well-connected, properly configured ones - consistently fail to relay transactions // To mitigate transactions failing we just send the transaction to every node we know about over Tor if (config()->get(Config::multiBroadcast).toBool()) { this->onMultiBroadcast(tx); } - this->wallet->commitTransactionAsync(tx); + this->wallet->commitTransactionAsync(tx, description); } void AppContext::onMultiBroadcast(PendingTransaction *tx) { @@ -167,21 +165,6 @@ QString AppContext::getCacheTransaction(const QString &txid) const { return txHex; } -// ################## Models ################## - -void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) { - auto *model = this->wallet->transactionHistoryModel(); - if (model != nullptr) { - model->preferredFiatSymbol = symbol; - } -} - -void AppContext::onAmountPrecisionChanged(int precision) { - auto *model = this->wallet->transactionHistoryModel(); - if (!model) return; - model->amountPrecision = precision; -} - // ################## Device ################## void AppContext::onDeviceButtonRequest(quint64 code) { @@ -228,48 +211,6 @@ void AppContext::onSetRestoreHeight(quint64 height){ emit customRestoreHeightSet(height); } -void AppContext::onOpenAliasResolve(const QString &openAlias) { - // @TODO: calling this freezes for about 1-2 seconds :/ - const auto result = WalletManager::instance()->resolveOpenAlias(openAlias);; // TODO: async call - const auto spl = result.split("|"); - auto msg = QString(""); - if(spl.count() != 2) { - msg = "Internal error"; - emit openAliasResolveError(msg); - return; - } - - const auto &status = spl.at(0); - const auto &address = spl.at(1); - const auto valid = WalletManager::addressValid(address, constants::networkType); - if(status == "false"){ - if(valid){ - msg = "Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed"; - emit openAliasResolveError(msg); - return; - } else { - msg = "No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed"; - emit openAliasResolveError(msg); - return; - } - } else if(status != "true") { - msg = "Internal error"; - emit openAliasResolveError(msg); - return; - } - - if(valid){ - emit openAliasResolved(address, openAlias); - return; - } - - msg = QString("Address validation error."); - if(!address.isEmpty()) - msg += QString(" Perhaps it is of the wrong network type." - "\n\nOpenAlias: %1\nAddress: %2").arg(openAlias).arg(address); - emit openAliasResolveError(msg); -} - void AppContext::stopTimers() { m_storeTimer.stop(); } @@ -365,13 +306,6 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVectorwallet->setUserNote(entry, this->tmpTxDescription); - } - this->tmpTxDescription = ""; - } - // Store wallet immediately so we don't risk losing tx key if wallet crashes this->wallet->store(); diff --git a/src/appcontext.h b/src/appcontext.h index 46fd215c..806bb88a 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -9,7 +9,6 @@ #include "utils/os/whonix.h" #include "utils/networking.h" -#include "utils/wsclient.h" #include "utils/FeatherSeed.h" #include "utils/daemonrpc.h" #include "utils/RestoreHeightLookup.h" @@ -38,7 +37,7 @@ public: // libwalletqt bool refreshed = false; - void commitTransaction(PendingTransaction *tx); + void commitTransaction(PendingTransaction *tx, const QString &description=""); void syncStatusUpdated(quint64 height, quint64 target); void updateBalance(); void refreshModels(); @@ -56,10 +55,7 @@ public slots: void onCancelTransaction(PendingTransaction *tx, const QVector &address); void onSweepOutputs(const QVector &keyImages, QString address, bool churn, int outputs); void onCreateTransactionError(const QString &msg); - void onOpenAliasResolve(const QString &openAlias); void onSetRestoreHeight(quint64 height); - void onPreferredFiatCurrencyChanged(const QString &symbol); - void onAmountPrecisionChanged(int precision); void onMultiBroadcast(PendingTransaction *tx); void onDeviceButtonRequest(quint64 code); void onDeviceButtonPressed(); @@ -89,8 +85,6 @@ signals: void createTransactionError(QString message); void createTransactionCancelled(const QVector &address, quint64 amount); void createTransactionSuccess(PendingTransaction *tx, const QVector &address); - void openAliasResolveError(const QString &msg); - void openAliasResolved(const QString &address, const QString &openAlias); void setRestoreHeightError(const QString &msg); void customRestoreHeightSet(int height); void initiateTransaction(); diff --git a/src/assets.qrc b/src/assets.qrc index c2512404..cf9e9abf 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -2,8 +2,8 @@ assets/about.txt assets/ack.txt - assets/contributors.txt assets/feather.desktop + assets/macStylesheet.patch assets/nodes.json assets/gpg_keys/featherwallet.asc assets/images/appicons/32x32.png @@ -35,6 +35,7 @@ assets/images/copy.png assets/images/cutexmrfox.png assets/images/edit.png + assets/images/external-link.svg assets/images/exchange.png assets/images/exchange_white.png assets/images/expired.png diff --git a/src/assets/about.txt b/src/assets/about.txt index 84af5c19..db366e94 100644 --- a/src/assets/about.txt +++ b/src/assets/about.txt @@ -1,12 +1,5 @@ Feather () -Website: https://featherwallet.org -E-mail: dev@featherwallet.org - -Created by dsc, tobtoht, and contributors. - -Uses icons from the Icons8 icon pack (icons8.com). - Copyright (c) 2020-, The Monero Project All rights reserved. diff --git a/src/assets/ack.txt b/src/assets/ack.txt index c0259860..a226a52d 100644 --- a/src/assets/ack.txt +++ b/src/assets/ack.txt @@ -1,12 +1,82 @@ -The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin. +• The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin. -Feather uses monero-seed written by Tevador, for 14 word mnemonic seeds. +• Feather uses the monero-seed libary written by Tevador for 14 word mnemonic seeds. -Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art). +• Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art). -Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon. +• Uses icons from the Icons8 icon pack (icons8.com). -Huge thanks to nioc, fluffypony, wowario, thrmo for help during development. +• Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon. -Some more shoutouts for people for hosting nodes and/or having good ideas: -dnale0r, dEBRUYNE, binaryFate, lza_menace, jwinterm, kico, wowario +Shoutouts: + +Alex_LocalMonero +anhdres +authentic +BigNastyHammer +binaryFate +bits-of-change +blasty +boldsuck +boogerlad +bruh +btsfav +csd +CzarekNakamoto +deanguss +dEBRUYNE +dnale0r +fluffypony +geonic +GhostintheQubes +GriftKilla +jberman +john_r365 +jwinterm +kayabaNerve +kico +kinghat +lozbek +Lyza +lza_menace +Matt Smith +MoneroArbo +moneromooo +mrdeveloper +Nekun +netrik182 +nikg83 +nioc +noobmaximus +n-peugnet +onionltd +Paris +phoenix1213 +qvqc +rating89us +rbrunner +rehrar +rottenwheel +samsunggalaxyplayer +samwhiskey +scoobybejesus +selsta +sethsimmons +Shakybeats +SmashTR +SovereignStreetArt +spoontechtips +stickyf00t +strace +rottenwheel +tevador +thrmo +tinkerwithtor +Vespco +viperperidot +witchman05 +wowario +xiphon +xmrscott +xubuntu +yanmaani \ No newline at end of file diff --git a/src/assets/contributors.txt b/src/assets/contributors.txt deleted file mode 100644 index 92c91224..00000000 --- a/src/assets/contributors.txt +++ /dev/null @@ -1,5 +0,0 @@ -dsc -tobtoht -selsta -Diego Salazar -Matt Smith \ No newline at end of file diff --git a/src/assets/images/external-link.svg b/src/assets/images/external-link.svg new file mode 100644 index 00000000..b0f7a16b --- /dev/null +++ b/src/assets/images/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/macStylesheet.patch b/src/assets/macStylesheet.patch new file mode 100644 index 00000000..955debd9 --- /dev/null +++ b/src/assets/macStylesheet.patch @@ -0,0 +1,31 @@ +/* From electrum/gui/qt/stylesheet_patcher.py */ +StatusBarButton { + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; + margin: 0px; + padding: 2px; +} + +StatusBarButton:checked { + background-color: transparent; + border: 1px solid #1464A0; +} + +StatusBarButton:checked:disabled { + border: 1px solid #14506E; +} + +StatusBarButton:pressed { + margin: 1px; + background-color: transparent; + border: 1px solid #1464A0; +} + +StatusBarButton:disabled { + border: none; +} + +StatusBarButton:hover { + border: 1px solid #148CD2; +} \ No newline at end of file diff --git a/src/assets/nodes.json b/src/assets/nodes.json index e95c3f53..8ed4e562 100644 --- a/src/assets/nodes.json +++ b/src/assets/nodes.json @@ -1,16 +1,22 @@ { "mainnet": { "tor": [ + "sfprpc5klzs5vyitq2mrooicgk2wcs5ho2nm3niqduvzn5o6ylaslaqd.onion:18089", + "sfprpc2fws6ltnq4hyr7lvpul3nank5layd7q7tyc5h4gy4h77gtabad.onion:18089", "mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081", "moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089", - "rbpgdckle3h3vi4wwwrh75usqtoc5r3alohy7yyx57isynvay63nacyd.onion:18089", "6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081", "56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089", - "melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081" + "melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081", + "ip4zpbps7unk6xhlanqtw24f75akfbl3upeckfjqjks7ftfnk4i73oid.onion:18081", + "ghziyspoobhmp5oun2xcomrmetqiwbvuaegmte3s47nnqv7hkaa64sid.onion:18089", + "xmrnodesarnt4w35aqmu66aart3o324yw6qbnv6pglpof6uqaydzk5id.onion:18081", + "usexmr2eeexmlwpuvsfe6tyjmdqliplb2b7uxju6yrrziq3n7fksnxyd.onion:18081" ], "clearnet": [ "node.melo.tools:18081", - "node-1.sethsimmons.me:18089", + "node.sethforprivacy.com:18089", + "node2.sethforprivacy.com:18089", "selsta1.featherwallet.net:18081", "selsta2.featherwallet.net:18081", "node.monerooutreach.org:18081", diff --git a/src/components.cpp b/src/components.cpp index 4e02c65f..c7261ce3 100644 --- a/src/components.cpp +++ b/src/components.cpp @@ -113,4 +113,12 @@ ClickableLabel::~ClickableLabel() = default; void ClickableLabel::mousePressEvent(QMouseEvent* event) { emit clicked(); +} + +WindowModalDialog::WindowModalDialog(QWidget *parent) + : QDialog(parent) +{ +#ifndef Q_OS_MACOS + this->setWindowModality(Qt::WindowModal); +#endif } \ No newline at end of file diff --git a/src/components.h b/src/components.h index eeb38049..6f26014e 100644 --- a/src/components.h +++ b/src/components.h @@ -121,4 +121,11 @@ protected: }; +class WindowModalDialog : public QDialog { + Q_OBJECT + +public: + explicit WindowModalDialog(QWidget *parent); +}; + #endif //FEATHER_COMPONENTS_H diff --git a/src/dialog/AboutDialog.cpp b/src/dialog/AboutDialog.cpp index 6825400b..82130e2f 100644 --- a/src/dialog/AboutDialog.cpp +++ b/src/dialog/AboutDialog.cpp @@ -8,12 +8,11 @@ #include "utils/Utils.h" AboutDialog::AboutDialog(QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::AboutDialog) - , m_model(new QStringListModel(this)) { ui->setupUi(this); - this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png")); + // cute fox (c) Diego "rehrar" Salazar :-D QPixmap p(":assets/images/cutexmrfox.png"); ui->aboutImage->setPixmap(p.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -28,14 +27,6 @@ AboutDialog::AboutDialog(QWidget *parent) auto ack_text = Utils::barrayToString(ack); ui->ackText->setText(ack_text); - QString contributors = Utils::barrayToString(Utils::fileOpenQRC(":assets/contributors.txt")); - QStringList contributor_list = contributors.split("\n"); - m_model->setStringList(contributor_list); - - ui->authorView->setHeaderHidden(true); - ui->authorView->setModel(this->m_model); - ui->authorView->header()->setSectionResizeMode(QHeaderView::Stretch); - this->adjustSize(); } diff --git a/src/dialog/AboutDialog.h b/src/dialog/AboutDialog.h index 4853e4fb..4dcc8a2b 100644 --- a/src/dialog/AboutDialog.h +++ b/src/dialog/AboutDialog.h @@ -7,11 +7,13 @@ #include #include +#include "components.h" + namespace Ui { class AboutDialog; } -class AboutDialog : public QDialog +class AboutDialog : public WindowModalDialog { Q_OBJECT @@ -21,7 +23,6 @@ public: private: QScopedPointer ui; - QStringListModel *m_model; }; #endif // FEATHER_ABOUT_H diff --git a/src/dialog/AboutDialog.ui b/src/dialog/AboutDialog.ui index 84f2a76c..3436c18e 100644 --- a/src/dialog/AboutDialog.ui +++ b/src/dialog/AboutDialog.ui @@ -43,7 +43,7 @@ - a free, open-source Monero wallet + <html><head/><body><p><span style=" font-weight:600;">Feather</span>: a free, open-source Monero wallet</p><p>Created by dsc, tobtoht and contributors</p></body></html> Qt::AlignCenter @@ -71,18 +71,144 @@ - + - Authors + Info - - - - - QAbstractItemView::NoEditTriggers + + + + + Website: - - false + + + + + + Docs: + + + + + + + Git: + + + + + + + Reddit: + + + + + + + IRC: + + + + + + + Matrix: + + + + + + + Email: + + + + + + + featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + docs.featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + git.featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + /r/FeatherWallet + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + #feather on OFTC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + #_oftc_#feather:matrix.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + dev@featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Twitter: + + + + + + + @featherwallet + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse diff --git a/src/dialog/AccountSwitcherDialog.cpp b/src/dialog/AccountSwitcherDialog.cpp index ee1e10e8..9a006131 100644 --- a/src/dialog/AccountSwitcherDialog.cpp +++ b/src/dialog/AccountSwitcherDialog.cpp @@ -11,7 +11,7 @@ #include "utils/Icons.h" AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::AccountSwitcherDialog) , m_ctx(std::move(ctx)) , m_model(m_ctx->wallet->subaddressAccountModel()) @@ -25,6 +25,8 @@ AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer ctx, QWi ui->label_totalBalance->setFont(ModelUtils::getMonospaceFont()); ui->label_totalBalance->setText(WalletManager::displayAmount(m_ctx->wallet->balanceAll())); + this->setWindowModality(Qt::WindowModal); + ui->accounts->setModel(m_proxyModel); ui->accounts->setContextMenuPolicy(Qt::CustomContextMenu); ui->accounts->setSelectionMode(QAbstractItemView::SingleSelection); diff --git a/src/dialog/AccountSwitcherDialog.h b/src/dialog/AccountSwitcherDialog.h index 08c15be0..e31f419f 100644 --- a/src/dialog/AccountSwitcherDialog.h +++ b/src/dialog/AccountSwitcherDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "model/SubaddressAccountModel.h" namespace Ui { class AccountSwitcherDialog; } -class AccountSwitcherDialog : public QDialog +class AccountSwitcherDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/BalanceDialog.cpp b/src/dialog/BalanceDialog.cpp index 7c490007..2fab5676 100644 --- a/src/dialog/BalanceDialog.cpp +++ b/src/dialog/BalanceDialog.cpp @@ -8,7 +8,7 @@ #include "model/ModelUtils.h" BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::BalanceDialog) { ui->setupUi(this); diff --git a/src/dialog/BalanceDialog.h b/src/dialog/BalanceDialog.h index 1880ae9f..8c32c2e1 100644 --- a/src/dialog/BalanceDialog.h +++ b/src/dialog/BalanceDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class BalanceDialog; } -class BalanceDialog : public QDialog +class BalanceDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/CalcConfigDialog.cpp b/src/dialog/CalcConfigDialog.cpp index caafb941..7bf7a079 100644 --- a/src/dialog/CalcConfigDialog.cpp +++ b/src/dialog/CalcConfigDialog.cpp @@ -8,7 +8,7 @@ #include "utils/config.h" CalcConfigDialog::CalcConfigDialog(QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::CalcConfigDialog) { ui->setupUi(this); diff --git a/src/dialog/CalcConfigDialog.h b/src/dialog/CalcConfigDialog.h index 37f7a2d2..5209bd1c 100644 --- a/src/dialog/CalcConfigDialog.h +++ b/src/dialog/CalcConfigDialog.h @@ -7,11 +7,13 @@ #include #include +#include "components.h" + namespace Ui { class CalcConfigDialog; } -class CalcConfigDialog : public QDialog +class CalcConfigDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/ContactsDialog.cpp b/src/dialog/ContactsDialog.cpp index 439208d6..381f66c7 100644 --- a/src/dialog/ContactsDialog.cpp +++ b/src/dialog/ContactsDialog.cpp @@ -5,7 +5,7 @@ #include "ContactsDialog.h" ContactsDialog::ContactsDialog(QWidget *parent, const QString &address, const QString &name) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::ContactsDialog) { ui->setupUi(this); diff --git a/src/dialog/ContactsDialog.h b/src/dialog/ContactsDialog.h index 03b81c4e..f0a74bc4 100644 --- a/src/dialog/ContactsDialog.h +++ b/src/dialog/ContactsDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class ContactsDialog; } -class ContactsDialog : public QDialog +class ContactsDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/DebugInfoDialog.cpp b/src/dialog/DebugInfoDialog.cpp index bbc1b025..c77f2963 100644 --- a/src/dialog/DebugInfoDialog.cpp +++ b/src/dialog/DebugInfoDialog.cpp @@ -11,7 +11,7 @@ #include "utils/WebsocketNotifier.h" DebugInfoDialog::DebugInfoDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::DebugInfoDialog) , m_ctx(std::move(ctx)) { @@ -139,7 +139,7 @@ void DebugInfoDialog::copyToClipboad() { text += QString("Seed type: %1 \n").arg(ui->label_seedType->text()); text += QString("Device type: %1 \n").arg(ui->label_deviceType->text()); text += QString("View only: %1 \n").arg(ui->label_viewOnly->text()); - text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text()); + text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text()); text += QString("Operating system: %1 \n").arg(ui->label_OS->text()); text += QString("Timestamp: %1 \n").arg(ui->label_timestamp->text()); diff --git a/src/dialog/DebugInfoDialog.h b/src/dialog/DebugInfoDialog.h index d2fbc83f..d94493e0 100644 --- a/src/dialog/DebugInfoDialog.h +++ b/src/dialog/DebugInfoDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class DebugInfoDialog; } -class DebugInfoDialog : public QDialog +class DebugInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/InfoDialog.cpp b/src/dialog/InfoDialog.cpp index 976d96cd..2de636db 100644 --- a/src/dialog/InfoDialog.cpp +++ b/src/dialog/InfoDialog.cpp @@ -5,7 +5,7 @@ #include "ui_InfoDialog.h" InfoDialog::InfoDialog(QWidget *parent, const QString &title, const QString &infoData) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::InfoDialog) { ui->setupUi(this); diff --git a/src/dialog/InfoDialog.h b/src/dialog/InfoDialog.h index fa18b5b4..a3348283 100644 --- a/src/dialog/InfoDialog.h +++ b/src/dialog/InfoDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class InfoDialog; } -class InfoDialog : public QDialog +class InfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/KeysDialog.cpp b/src/dialog/KeysDialog.cpp index 7fe362d8..12af0e3a 100644 --- a/src/dialog/KeysDialog.cpp +++ b/src/dialog/KeysDialog.cpp @@ -5,7 +5,7 @@ #include "ui_KeysDialog.h" KeysDialog::KeysDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::KeysDialog) { ui->setupUi(this); diff --git a/src/dialog/KeysDialog.h b/src/dialog/KeysDialog.h index 5684075c..bb08c494 100644 --- a/src/dialog/KeysDialog.h +++ b/src/dialog/KeysDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class KeysDialog; } -class KeysDialog : public QDialog +class KeysDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/LocalMoneroInfoDialog.cpp b/src/dialog/LocalMoneroInfoDialog.cpp index d3df1a7f..f6f1b36e 100644 --- a/src/dialog/LocalMoneroInfoDialog.cpp +++ b/src/dialog/LocalMoneroInfoDialog.cpp @@ -8,7 +8,7 @@ #include "utils/Utils.h" LocalMoneroInfoDialog::LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::LocalMoneroInfoDialog) , m_model(model) , m_row(row) diff --git a/src/dialog/LocalMoneroInfoDialog.h b/src/dialog/LocalMoneroInfoDialog.h index e5f6cd11..273bafc4 100644 --- a/src/dialog/LocalMoneroInfoDialog.h +++ b/src/dialog/LocalMoneroInfoDialog.h @@ -7,13 +7,14 @@ #include #include +#include "components.h" #include "model/LocalMoneroModel.h" namespace Ui { class LocalMoneroInfoDialog; } -class LocalMoneroInfoDialog : public QDialog +class LocalMoneroInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/OutputInfoDialog.cpp b/src/dialog/OutputInfoDialog.cpp index 772d1fb4..4ab953b6 100644 --- a/src/dialog/OutputInfoDialog.cpp +++ b/src/dialog/OutputInfoDialog.cpp @@ -8,7 +8,7 @@ #include "utils/Utils.h" OutputInfoDialog::OutputInfoDialog(CoinsInfo *cInfo, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::OutputInfoDialog) { ui->setupUi(this); diff --git a/src/dialog/OutputInfoDialog.h b/src/dialog/OutputInfoDialog.h index 18dd39cc..c0eb49a1 100644 --- a/src/dialog/OutputInfoDialog.h +++ b/src/dialog/OutputInfoDialog.h @@ -6,6 +6,7 @@ #include +#include "components.h" #include "libwalletqt/Coins.h" #include "libwalletqt/CoinsInfo.h" @@ -13,7 +14,7 @@ namespace Ui { class OutputInfoDialog; } -class OutputInfoDialog : public QDialog +class OutputInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/OutputSweepDialog.cpp b/src/dialog/OutputSweepDialog.cpp index 7c7d2fbc..3883676c 100644 --- a/src/dialog/OutputSweepDialog.cpp +++ b/src/dialog/OutputSweepDialog.cpp @@ -7,7 +7,7 @@ #include "libwalletqt/WalletManager.h" OutputSweepDialog::OutputSweepDialog(QWidget *parent, quint64 amount) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::OutputSweepDialog) , m_amount(amount) { diff --git a/src/dialog/OutputSweepDialog.h b/src/dialog/OutputSweepDialog.h index 4b565fb4..8c5e6b54 100644 --- a/src/dialog/OutputSweepDialog.h +++ b/src/dialog/OutputSweepDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/CoinsInfo.h" namespace Ui { class OutputSweepDialog; } -class OutputSweepDialog : public QDialog +class OutputSweepDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/PasswordChangeDialog.cpp b/src/dialog/PasswordChangeDialog.cpp index 14c5e9d0..d7e1386f 100644 --- a/src/dialog/PasswordChangeDialog.cpp +++ b/src/dialog/PasswordChangeDialog.cpp @@ -7,7 +7,7 @@ #include PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::PasswordChangeDialog) , m_wallet(wallet) { diff --git a/src/dialog/PasswordChangeDialog.h b/src/dialog/PasswordChangeDialog.h index 7fc91e03..d39ec6b6 100644 --- a/src/dialog/PasswordChangeDialog.h +++ b/src/dialog/PasswordChangeDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class PasswordChangeDialog; } -class PasswordChangeDialog : public QDialog +class PasswordChangeDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/PasswordDialog.cpp b/src/dialog/PasswordDialog.cpp index d4e0e4af..5a49b59a 100644 --- a/src/dialog/PasswordDialog.cpp +++ b/src/dialog/PasswordDialog.cpp @@ -7,7 +7,7 @@ #include "utils/Icons.h" PasswordDialog::PasswordDialog(const QString &walletName, bool incorrectPassword, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::PasswordDialog) { ui->setupUi(this); diff --git a/src/dialog/PasswordDialog.h b/src/dialog/PasswordDialog.h index 544331f4..7afc5b0c 100644 --- a/src/dialog/PasswordDialog.h +++ b/src/dialog/PasswordDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class PasswordDialog; } -class PasswordDialog : public QDialog +class PasswordDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/PaymentRequestDialog.cpp b/src/dialog/PaymentRequestDialog.cpp new file mode 100644 index 00000000..dfac97be --- /dev/null +++ b/src/dialog/PaymentRequestDialog.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "PaymentRequestDialog.h" +#include "ui_PaymentRequestDialog.h" + +#include +#include +#include +#include + +#include "WalletManager.h" + +PaymentRequestDialog::PaymentRequestDialog(QWidget *parent, QSharedPointer ctx, QString address) + : WindowModalDialog(parent) + , ui(new Ui::PaymentRequestDialog) + , m_ctx(std::move(ctx)) + , m_address(std::move(address)) +{ + ui->setupUi(this); + + QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}|(all)$)"; + QRegExp rx; + rx.setPattern(amount_rx); + QValidator *validator = new QRegExpValidator(rx, this); + ui->line_amountXMR->setValidator(validator); + + connect(ui->line_amountXMR, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest); + connect(ui->line_description, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest); + connect(ui->line_recipient, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest); + + connect(ui->btn_copyLink, &QPushButton::clicked, this, &PaymentRequestDialog::copyLink); + connect(ui->btn_copyImage, &QPushButton::clicked, this, &PaymentRequestDialog::copyImage); + connect(ui->btn_saveImage, &QPushButton::clicked, this, &PaymentRequestDialog::saveImage); + + this->updatePaymentRequest(); + + ui->line_amountXMR->setFocus(); + + this->adjustSize(); +} + +void PaymentRequestDialog::updatePaymentRequest() { + QString description = ui->line_description->text(); + QString recipient = ui->line_recipient->text(); + quint64 amount = WalletManager::amountFromString(ui->line_amountXMR->text()); + + QString uri = m_ctx->wallet->make_uri(m_address, amount, description, recipient); + + ui->line_paymentRequestUri->setText(uri); + ui->line_paymentRequestUri->setCursorPosition(0); + + // TODO: memory leak, cba to refactor now + m_qrCode = new QrCode(uri, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM); + if (m_qrCode->isValid()) { + ui->qrWidget->setQrCode(m_qrCode); + } +} + +void PaymentRequestDialog::copyLink() { + Utils::copyToClipboard(ui->line_paymentRequestUri->text()); + QMessageBox::information(this, "Information", "Payment request link copied to clipboard."); +} + +void PaymentRequestDialog::copyImage() { + QApplication::clipboard()->setPixmap(m_qrCode->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio)); + QMessageBox::information(this, "Information", "QR code copied to clipboard."); +} + +void PaymentRequestDialog::saveImage() { + QString filename = QFileDialog::getSaveFileName(this, "Select where to save file", QDir::current().filePath("qrcode.png")); + if (filename.isEmpty()) { + return; + } + + QFile file(filename); + file.open(QIODevice::WriteOnly); + m_qrCode->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio).save(&file, "PNG"); + QMessageBox::information(this, "Information", "QR code saved to file"); +} + +PaymentRequestDialog::~PaymentRequestDialog() = default; \ No newline at end of file diff --git a/src/dialog/PaymentRequestDialog.h b/src/dialog/PaymentRequestDialog.h new file mode 100644 index 00000000..ca8274c7 --- /dev/null +++ b/src/dialog/PaymentRequestDialog.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_PAYMENTREQUESTDIALOG_H +#define FEATHER_PAYMENTREQUESTDIALOG_H + +#include + +#include "appcontext.h" +#include "components.h" +#include "qrcode/QrCode.h" + +namespace Ui { + class PaymentRequestDialog; +} + +class PaymentRequestDialog : public WindowModalDialog +{ + Q_OBJECT + +public: + explicit PaymentRequestDialog(QWidget *parent, QSharedPointer ctx, QString address); + ~PaymentRequestDialog() override; + +private slots: + void updatePaymentRequest(); + void copyLink(); + void copyImage(); + void saveImage(); + +private: + QScopedPointer ui; + QSharedPointer m_ctx; + QString m_address; + QrCode *m_qrCode; +}; + +#endif //FEATHER_PAYMENTREQUESTDIALOG_H diff --git a/src/dialog/PaymentRequestDialog.ui b/src/dialog/PaymentRequestDialog.ui new file mode 100644 index 00000000..5b4dac23 --- /dev/null +++ b/src/dialog/PaymentRequestDialog.ui @@ -0,0 +1,203 @@ + + + PaymentRequestDialog + + + + 0 + 0 + 658 + 761 + + + + Create Payment Request + + + + + + + 0 + 0 + + + + + + + + true + + + + + + + Qt::Horizontal + + + + + + + + + Amount: + + + + + + + + + + + + 0 + 0 + + + + + + + + XMR + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Description: + + + + + + + + + + Your name: + + + + + + + + + + + + + + Copy Link + + + + + + + Copy Image + + + + + + + Save Image + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + QrCodeWidget + QWidget +
widgets/QrCodeWidget.h
+ 1 +
+
+ + + + buttonBox + accepted() + PaymentRequestDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PaymentRequestDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/dialog/QrCodeDialog.cpp b/src/dialog/QrCodeDialog.cpp index 4b1c7cf0..41ca49cc 100644 --- a/src/dialog/QrCodeDialog.cpp +++ b/src/dialog/QrCodeDialog.cpp @@ -9,7 +9,7 @@ #include QrCodeDialog::QrCodeDialog(QWidget *parent, QrCode *qrCode, const QString &title) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::QrCodeDialog) { ui->setupUi(this); diff --git a/src/dialog/QrCodeDialog.h b/src/dialog/QrCodeDialog.h index 706b120b..78d7dc30 100644 --- a/src/dialog/QrCodeDialog.h +++ b/src/dialog/QrCodeDialog.h @@ -6,6 +6,7 @@ #include +#include "components.h" #include "qrcode/QrCode.h" #include "widgets/QrCodeWidget.h" @@ -13,7 +14,7 @@ namespace Ui { class QrCodeDialog; } -class QrCodeDialog : public QDialog +class QrCodeDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/RestoreHeightDialog.cpp b/src/dialog/RestoreHeightDialog.cpp index ca7cfda8..5a4045c2 100644 --- a/src/dialog/RestoreHeightDialog.cpp +++ b/src/dialog/RestoreHeightDialog.cpp @@ -8,7 +8,7 @@ #include RestoreHeightDialog::RestoreHeightDialog(QWidget *parent, quint64 currentRestoreHeight) - : QDialog(parent) + : WindowModalDialog(parent) , m_restoreHeightWidget(new RestoreHeightWidget(this)) { auto *layout = new QVBoxLayout(this); diff --git a/src/dialog/RestoreHeightDialog.h b/src/dialog/RestoreHeightDialog.h index 88cad2c8..711882b1 100644 --- a/src/dialog/RestoreHeightDialog.h +++ b/src/dialog/RestoreHeightDialog.h @@ -6,9 +6,10 @@ #include +#include "components.h" #include "widgets/RestoreHeightWidget.h" -class RestoreHeightDialog : public QDialog +class RestoreHeightDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/SeedDialog.cpp b/src/dialog/SeedDialog.cpp index 9153b2a5..cc09e05f 100644 --- a/src/dialog/SeedDialog.cpp +++ b/src/dialog/SeedDialog.cpp @@ -7,7 +7,7 @@ #include "constants.h" SeedDialog::SeedDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::SeedDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/SeedDialog.h b/src/dialog/SeedDialog.h index a0398bd4..7cc656ff 100644 --- a/src/dialog/SeedDialog.h +++ b/src/dialog/SeedDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class SeedDialog; } -class SeedDialog : public QDialog +class SeedDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/SignVerifyDialog.cpp b/src/dialog/SignVerifyDialog.cpp index 490aa566..f65df4c5 100644 --- a/src/dialog/SignVerifyDialog.cpp +++ b/src/dialog/SignVerifyDialog.cpp @@ -9,7 +9,7 @@ #include "utils/Utils.h" SignVerifyDialog::SignVerifyDialog(Wallet *wallet, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::SignVerifyDialog) , m_wallet(wallet) { diff --git a/src/dialog/SignVerifyDialog.h b/src/dialog/SignVerifyDialog.h index d7819f61..18ad34a6 100644 --- a/src/dialog/SignVerifyDialog.h +++ b/src/dialog/SignVerifyDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class SignVerifyDialog; } -class SignVerifyDialog : public QDialog +class SignVerifyDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/SplashDialog.cpp b/src/dialog/SplashDialog.cpp index ba0bc5c2..7f2e9d2c 100644 --- a/src/dialog/SplashDialog.cpp +++ b/src/dialog/SplashDialog.cpp @@ -7,7 +7,7 @@ #include "utils/Icons.h" SplashDialog::SplashDialog(QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::SplashDialog) { ui->setupUi(this); diff --git a/src/dialog/SplashDialog.h b/src/dialog/SplashDialog.h index 7341b7df..8f772a8d 100644 --- a/src/dialog/SplashDialog.h +++ b/src/dialog/SplashDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class SplashDialog; } -class SplashDialog : public QDialog +class SplashDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxBroadcastDialog.cpp b/src/dialog/TxBroadcastDialog.cpp index 58223db1..12585221 100644 --- a/src/dialog/TxBroadcastDialog.cpp +++ b/src/dialog/TxBroadcastDialog.cpp @@ -9,7 +9,7 @@ #include "utils/NetworkManager.h" TxBroadcastDialog::TxBroadcastDialog(QWidget *parent, QSharedPointer ctx, const QString &transactionHex) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxBroadcastDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/TxBroadcastDialog.h b/src/dialog/TxBroadcastDialog.h index 944d463d..98f4e194 100644 --- a/src/dialog/TxBroadcastDialog.h +++ b/src/dialog/TxBroadcastDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "utils/daemonrpc.h" namespace Ui { class TxBroadcastDialog; } -class TxBroadcastDialog : public QDialog +class TxBroadcastDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxConfAdvDialog.cpp b/src/dialog/TxConfAdvDialog.cpp index 8261849f..95db78c0 100644 --- a/src/dialog/TxConfAdvDialog.cpp +++ b/src/dialog/TxConfAdvDialog.cpp @@ -7,6 +7,7 @@ #include #include +#include "constants.h" #include "dialog/QrCodeDialog.h" #include "libwalletqt/Input.h" #include "libwalletqt/Transfer.h" @@ -14,7 +15,7 @@ #include "qrcode/QrCode.h" TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString &description, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxConfAdvDialog) , m_ctx(std::move(ctx)) , m_exportUnsignedMenu(new QMenu(this)) @@ -35,42 +36,51 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString & m_exportTxKeyMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::txKeyCopy); ui->btn_exportTxKey->setMenu(m_exportTxKeyMenu); - if (m_ctx->wallet->viewOnly()) { - ui->btn_exportSigned->hide(); - ui->btn_send->hide(); - } - - ui->label_description->setText(QString("Description: %1").arg(description)); + ui->line_description->setText(description); connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction); connect(ui->btn_send, &QPushButton::clicked, this, &TxConfAdvDialog::broadcastTransaction); connect(ui->btn_close, &QPushButton::clicked, this, &TxConfAdvDialog::closeDialog); + ui->amount->setFont(ModelUtils::getMonospaceFont()); + ui->fee->setFont(ModelUtils::getMonospaceFont()); + ui->total->setFont(ModelUtils::getMonospaceFont()); + ui->inputs->setFont(ModelUtils::getMonospaceFont()); ui->outputs->setFont(ModelUtils::getMonospaceFont()); this->adjustSize(); } -void TxConfAdvDialog::setTransaction(PendingTransaction *tx) { +void TxConfAdvDialog::setTransaction(PendingTransaction *tx, bool isSigned) { ui->btn_sign->hide(); + if (!isSigned) { + ui->btn_exportSigned->hide(); + ui->btn_send->hide(); + } + m_tx = tx; m_tx->refresh(); PendingTransactionInfo *ptx = m_tx->transaction(0); //Todo: support split transactions - ui->txid->setText(tx->txid().first()); + // TODO: implement hasTxKey() + if (!m_ctx->wallet->isHwBacked() && m_tx->transaction(0)->txKey() == "0100000000000000000000000000000000000000000000000000000000000000") { + ui->btn_exportTxKey->hide(); + } - ui->amount->setText(WalletManager::displayAmount(tx->amount())); - ui->fee->setText(WalletManager::displayAmount(ptx->fee())); - ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee())); + m_txid = tx->txid().first(); + ui->txid->setText(m_txid); - auto size_str = [this]{ - if (m_ctx->wallet->viewOnly()) { - return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); - } else { + this->setAmounts(tx->amount(), tx->fee()); + + auto size_str = [this, isSigned]{ + if (isSigned) { auto size = m_tx->signedTxToHex(0).size() / 2; return QString("Size: %1 bytes (%2 bytes unsigned)").arg(QString::number(size), QString::number(m_tx->unsignedTxToBin().size())); + } else { + + return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); } }(); ui->label_size->setText(size_str); @@ -91,14 +101,38 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) { ui->txid->setText("n/a"); ui->label_size->setText("Size: n/a"); - ui->amount->setText(WalletManager::displayAmount(utx->amount(0))); - ui->fee->setText(WalletManager::displayAmount(utx->fee(0))); - ui->total->setText(WalletManager::displayAmount(utx->amount(0) + utx->fee(0))); + this->setAmounts(utx->amount(0), utx->fee(0)); ConstructionInfo *ci = m_utx->constructionInfo(0); this->setupConstructionData(ci); } +void TxConfAdvDialog::setAmounts(quint64 amount, quint64 fee) { + QString preferredCur = config()->get(Config::preferredFiatCurrency).toString(); + + auto convert = [preferredCur](double amount){ + return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2); + }; + + QString amount_str = WalletManager::displayAmount(amount); + QString fee_str = WalletManager::displayAmount(fee); + QString total = WalletManager::displayAmount(amount + fee); + QVector amounts = {amount_str, fee_str, total}; + int maxLength = Utils::maxLength(amounts); + std::for_each(amounts.begin(), amounts.end(), [maxLength](QString& amount){amount = amount.rightJustified(maxLength, ' ');}); + + QString amount_fiat = convert(amount / constants::cdiv); + QString fee_fiat = convert(fee / constants::cdiv); + QString total_fiat = convert((amount + fee) / constants::cdiv); + QVector amounts_fiat = {amount_fiat, fee_fiat, total_fiat}; + int maxLengthFiat = Utils::maxLength(amounts_fiat); + std::for_each(amounts_fiat.begin(), amounts_fiat.end(), [maxLengthFiat](QString& amount){amount = amount.rightJustified(maxLengthFiat, ' ');}); + + ui->amount->setText(QString("%1 (%2 %3)").arg(amounts[0], amounts_fiat[0], preferredCur)); + ui->fee->setText(QString("%1 (%2 %3)").arg(amounts[1], amounts_fiat[1], preferredCur)); + ui->total->setText(QString("%1 (%2 %3)").arg(amounts[2], amounts_fiat[2], preferredCur)); +} + void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { QString inputs_str; auto inputs = ci->inputs(); @@ -122,7 +156,6 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size()))); ui->label_ringSize->setText(QString("Ring size: %1").arg(QString::number(ci->minMixinCount() + 1))); - ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(QString::number(ci->unlockTime()))); } void TxConfAdvDialog::signTransaction() { @@ -180,7 +213,7 @@ void TxConfAdvDialog::signedQrCode() { void TxConfAdvDialog::broadcastTransaction() { if (m_tx == nullptr) return; - m_ctx->commitTransaction(m_tx); + m_ctx->commitTransaction(m_tx, ui->line_description->text()); QDialog::accept(); } diff --git a/src/dialog/TxConfAdvDialog.h b/src/dialog/TxConfAdvDialog.h index 9122261b..3e2a7f05 100644 --- a/src/dialog/TxConfAdvDialog.h +++ b/src/dialog/TxConfAdvDialog.h @@ -10,13 +10,14 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/PendingTransaction.h" namespace Ui { class TxConfAdvDialog; } -class TxConfAdvDialog : public QDialog +class TxConfAdvDialog : public WindowModalDialog { Q_OBJECT @@ -24,7 +25,7 @@ public: explicit TxConfAdvDialog(QSharedPointer ctx, const QString &description, QWidget *parent = nullptr); ~TxConfAdvDialog() override; - void setTransaction(PendingTransaction *tx); + void setTransaction(PendingTransaction *tx, bool isSigned = true); // #TODO: have libwallet return a UnsignedTransaction, this is just dumb void setUnsignedTransaction(UnsignedTransaction *utx); private: @@ -32,6 +33,7 @@ private: void signTransaction(); void broadcastTransaction(); void closeDialog(); + void setAmounts(quint64 amount, quint64 fee); void unsignedCopy(); void unsignedQrCode(); @@ -50,6 +52,7 @@ private: QMenu *m_exportUnsignedMenu; QMenu *m_exportSignedMenu; QMenu *m_exportTxKeyMenu; + QString m_txid; }; #endif //FEATHER_TXCONFADVDIALOG_H diff --git a/src/dialog/TxConfAdvDialog.ui b/src/dialog/TxConfAdvDialog.ui index 7030ee12..80fbc07b 100644 --- a/src/dialog/TxConfAdvDialog.ui +++ b/src/dialog/TxConfAdvDialog.ui @@ -21,15 +21,15 @@
- - + + Transaction ID: - + txid @@ -41,15 +41,56 @@ - - - - Qt::Horizontal - - - + + + + + + + + Description: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + + + Size: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Ringsize: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + Qt::Vertical + + + @@ -112,57 +153,6 @@ - - - - Qt::Vertical - - - - - - - - - Description: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - Size: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - Unlock time: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - Ringsize: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - diff --git a/src/dialog/TxConfDialog.cpp b/src/dialog/TxConfDialog.cpp index 5cb27767..9db85092 100644 --- a/src/dialog/TxConfDialog.cpp +++ b/src/dialog/TxConfDialog.cpp @@ -13,7 +13,7 @@ #include "utils/ColorScheme.h" TxConfDialog::TxConfDialog(QSharedPointer ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxConfDialog) , m_ctx(std::move(ctx)) , m_tx(tx) diff --git a/src/dialog/TxConfDialog.h b/src/dialog/TxConfDialog.h index 4d2ab877..0fd5cf4d 100644 --- a/src/dialog/TxConfDialog.h +++ b/src/dialog/TxConfDialog.h @@ -7,6 +7,7 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/PendingTransaction.h" #include "libwalletqt/WalletManager.h" @@ -14,7 +15,7 @@ namespace Ui { class TxConfDialog; } -class TxConfDialog : public QDialog +class TxConfDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxImportDialog.cpp b/src/dialog/TxImportDialog.cpp index 2a00bcc7..56645ad5 100644 --- a/src/dialog/TxImportDialog.cpp +++ b/src/dialog/TxImportDialog.cpp @@ -9,7 +9,7 @@ #include "utils/NetworkManager.h" TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer ctx) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxImportDialog) , m_ctx(std::move(ctx)) , m_loadTimer(new QTimer(this)) diff --git a/src/dialog/TxImportDialog.h b/src/dialog/TxImportDialog.h index 49192527..4e5f5a85 100644 --- a/src/dialog/TxImportDialog.h +++ b/src/dialog/TxImportDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "utils/daemonrpc.h" namespace Ui { class TxImportDialog; } -class TxImportDialog : public QDialog +class TxImportDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxInfoDialog.cpp b/src/dialog/TxInfoDialog.cpp index 69576e74..eb0bfeff 100644 --- a/src/dialog/TxInfoDialog.cpp +++ b/src/dialog/TxInfoDialog.cpp @@ -9,6 +9,7 @@ #include "appcontext.h" #include "config.h" +#include "constants.h" #include "libwalletqt/Coins.h" #include "libwalletqt/CoinsInfo.h" #include "libwalletqt/TransactionHistory.h" @@ -16,6 +17,7 @@ #include "libwalletqt/WalletManager.h" #include "model/ModelUtils.h" #include "Utils.h" +#include "utils/Icons.h" TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txInfo, QWidget *parent) : QDialog(parent) @@ -26,11 +28,16 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn { ui->setupUi(this); + ui->btn_viewOnBlockExplorer->setIcon(icons()->icon("external-link.svg")); + ui->btn_viewOnBlockExplorer->setToolTip("View on block explorer"); + connect(ui->btn_viewOnBlockExplorer, &QPushButton::clicked, this, &TxInfoDialog::viewOnBlockExplorer); + m_txid = txInfo->hash(); ui->label_txid->setText(m_txid); - connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TxInfoDialog::copyTxKey); - connect(ui->btn_createTxProof, &QPushButton::pressed, this, &TxInfoDialog::createTxProof); + connect(ui->btn_copyTxID, &QPushButton::clicked, this, &TxInfoDialog::copyTxID); + connect(ui->btn_CopyTxKey, &QPushButton::clicked, this, &TxInfoDialog::copyTxKey); + connect(ui->btn_createTxProof, &QPushButton::clicked, this, &TxInfoDialog::createTxProof); connect(m_ctx->wallet, &Wallet::newBlock, this, &TxInfoDialog::updateData); @@ -43,6 +50,12 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn } else { ui->btn_rebroadcastTx->hide(); } + + if (txInfo->direction() == TransactionInfo::Direction_In) { + ui->btn_CopyTxKey->setDisabled(true); + ui->btn_CopyTxKey->setToolTip("No tx secret key available for incoming transactions."); + } + // // if (txInfo->direction() == TransactionInfo::Direction_Out) { // // TODO: this will not properly represent coinjoin-like transactions. @@ -78,6 +91,9 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn } this->adjustSize(); + + // Don't autofocus any of the buttons. There is probably a better way for this. + ui->label_txid->setFocus(); } void TxInfoDialog::adjustHeight(QTextEdit *textEdit, qreal docHeight) { @@ -140,6 +156,10 @@ void TxInfoDialog::updateData() { this->setData(tx); } +void TxInfoDialog::copyTxID() { + Utils::copyToClipboard(m_txid); +} + void TxInfoDialog::copyTxKey() { m_ctx->wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){ QString txKey = map.value("tx_key").toString(); @@ -157,4 +177,8 @@ void TxInfoDialog::createTxProof() { m_txProofDialog->getTxKey(); } +void TxInfoDialog::viewOnBlockExplorer() { + Utils::externalLinkWarning(this, Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, m_txid)); +} + TxInfoDialog::~TxInfoDialog() = default; \ No newline at end of file diff --git a/src/dialog/TxInfoDialog.h b/src/dialog/TxInfoDialog.h index 0041471a..36bc46f0 100644 --- a/src/dialog/TxInfoDialog.h +++ b/src/dialog/TxInfoDialog.h @@ -28,11 +28,13 @@ signals: void resendTranscation(const QString &txid); private: + void copyTxID(); void copyTxKey(); void createTxProof(); void setData(TransactionInfo *tx); void updateData(); void adjustHeight(QTextEdit *textEdit, qreal docHeight); + void viewOnBlockExplorer(); QScopedPointer ui; QSharedPointer m_ctx; diff --git a/src/dialog/TxInfoDialog.ui b/src/dialog/TxInfoDialog.ui index 02b18c82..22405d92 100644 --- a/src/dialog/TxInfoDialog.ui +++ b/src/dialog/TxInfoDialog.ui @@ -7,7 +7,7 @@ 0 0 929 - 631 + 715
@@ -19,7 +19,7 @@ Transaction ID: - + @@ -30,6 +30,19 @@ + + + + + 0 + 0 + + + + + + +
@@ -205,17 +218,24 @@ + + + + Copy Tx ID + + + - Copy Transaction Key + Copy Tx Secret Key - Create Transaction Proof + Create Tx Proof diff --git a/src/dialog/TxProofDialog.cpp b/src/dialog/TxProofDialog.cpp index 9299c4d5..a2d2a6ac 100644 --- a/src/dialog/TxProofDialog.cpp +++ b/src/dialog/TxProofDialog.cpp @@ -11,7 +11,7 @@ #include "utils/Utils.h" TxProofDialog::TxProofDialog(QWidget *parent, QSharedPointer ctx, TransactionInfo *txInfo) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxProofDialog) , m_ctx(std::move(ctx)) { @@ -88,13 +88,13 @@ void TxProofDialog::selectOutProof() { m_mode = Mode::OutProof; this->resetFrames(); - if (m_OutDestinations.empty()) { - this->showWarning("This transaction did not spend any outputs owned by this wallet. Creating an OutProof is not possible."); + if (m_txKey.isEmpty()) { + this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible."); return; } - if (m_txKey.isEmpty()) { - this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible."); + if (m_OutDestinations.empty()) { + this->showWarning("This transaction did not send funds to any known addresses. Creating an OutProof is not possible."); return; } @@ -107,6 +107,11 @@ void TxProofDialog::selectInProof() { m_mode = Mode::InProof; this->resetFrames(); + if (m_direction == TransactionInfo::Direction_Out) { + this->showWarning("Can't create InProofs for outgoing transactions."); + return; + } + if (m_InDestinations.empty()) { this->showWarning("Your wallet did not receive any outputs in this transaction."); return; diff --git a/src/dialog/TxProofDialog.h b/src/dialog/TxProofDialog.h index ba47a57f..7c1a84f5 100644 --- a/src/dialog/TxProofDialog.h +++ b/src/dialog/TxProofDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/TransactionInfo.h" namespace Ui { class TxProofDialog; } -class TxProofDialog : public QDialog +class TxProofDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/UpdateDialog.cpp b/src/dialog/UpdateDialog.cpp index af673d85..5ed8fa42 100644 --- a/src/dialog/UpdateDialog.cpp +++ b/src/dialog/UpdateDialog.cpp @@ -117,6 +117,11 @@ void UpdateDialog::onInstallUpdate() { ui->btn_installUpdate->hide(); this->setStatus("Unzipping archive..."); +#ifdef Q_OS_MACOS + this->installUpdateMac(); + return; +#endif + zip_error_t err; zip_error_init(&err); @@ -172,14 +177,7 @@ void UpdateDialog::onInstallUpdate() { zip_fclose(zf); zip_close(zip_archive); - QString applicationPath = qgetenv("APPIMAGE"); - if (!applicationPath.isEmpty()) { - applicationPath = QFileInfo(applicationPath).absoluteDir().path(); - } else { - applicationPath = QCoreApplication::applicationDirPath(); - } - - QDir applicationDir(applicationPath); + QDir applicationDir(Utils::applicationPath()); QString filePath = applicationDir.filePath(name); m_updatePath = filePath; @@ -206,6 +204,41 @@ void UpdateDialog::onInstallUpdate() { ui->btn_restart->show(); } +void UpdateDialog::installUpdateMac() { + QString appPath = Utils::applicationPath(); + QDir appDir(appPath); + if (appPath.endsWith("Contents/MacOS")) { + appDir.cd("../../.."); + } + QString appName = QString("feather-%1").arg(m_version); + QString zipName = QString("%1.zip").arg(appName); + QString fPath = appDir.filePath(zipName); + + QFile file(fPath); + qDebug() << "Writing zip file to " << fPath; + if (!file.open(QIODevice::WriteOnly)) + { + this->onInstallError(QString("Error: Could not write to application path: %1").arg(fPath)); + return; + } + + if (static_cast(file.write(&m_updateZipArchive[0], m_updateZipArchive.size())) != m_updateZipArchive.size()) { + this->onInstallError("Error: Unable to write file"); + return; + } + + QProcess unzip; + unzip.start("/usr/bin/unzip", {"-n", fPath, "-d", appDir.path()}); + unzip.waitForFinished(); + + m_updatePath = QString("%1.app/Contents/MacOS/feather").arg(appDir.filePath(appName)); + + file.remove(); + + this->setStatus("Installation successful. Do you want to restart Feather now?"); + ui->btn_restart->show(); +} + void UpdateDialog::onInstallError(const QString &errMsg) { this->setStatus(errMsg); } diff --git a/src/dialog/UpdateDialog.h b/src/dialog/UpdateDialog.h index 27396665..b64332c8 100644 --- a/src/dialog/UpdateDialog.h +++ b/src/dialog/UpdateDialog.h @@ -33,6 +33,7 @@ signals: private: void setStatus(const QString &msg, bool success = false); + void installUpdateMac(); QScopedPointer ui; diff --git a/src/dialog/VerifyProofDialog.cpp b/src/dialog/VerifyProofDialog.cpp index d696f8f6..937b6d9b 100644 --- a/src/dialog/VerifyProofDialog.cpp +++ b/src/dialog/VerifyProofDialog.cpp @@ -11,7 +11,7 @@ #include "utils/Utils.h" VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::VerifyProofDialog) , m_wallet(wallet) { @@ -50,6 +50,9 @@ VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) } }); + connect(m_wallet, &Wallet::transactionProofVerified, this, &VerifyProofDialog::onTxProofVerified); + connect(m_wallet, &Wallet::spendProofVerified, this, &VerifyProofDialog::onSpendProofVerified); + ui->input_formattedProof->setFont(ModelUtils::getMonospaceFont()); } @@ -69,31 +72,15 @@ void VerifyProofDialog::checkProof() { void VerifyProofDialog::checkTxProof(const QString &txId, const QString &address, const QString &message, const QString &signature) { - TxProofResult r = m_wallet->checkTxProof(txId, address, message, signature); - - if (!r.success) { - this->proofStatus(false, m_wallet->errorString()); - return; - } - - if (!r.good) { - this->proofStatus(false, "Proof is invalid"); - return; - } - - this->proofStatus(true, QString("Proof is valid.\n\nThis address received %1 XMR, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations))); + ui->btn_verifyFormattedProof->setEnabled(false); + ui->btn_verify->setEnabled(false); + m_wallet->checkTxProofAsync(txId, address, message, signature); } void VerifyProofDialog::checkSpendProof(const QString &txId, const QString &message, const QString &signature) { - auto r = m_wallet->checkSpendProof(txId, message, signature); - - if (!r.first) { - this->proofStatus(false, m_wallet->errorString()); - return; - } - - r.second ? this->proofStatus(true, "Proof is valid") - : this->proofStatus(false, "Proof is invalid"); + ui->btn_verifyFormattedProof->setEnabled(false); + ui->btn_verify->setEnabled(false); + m_wallet->checkSpendProofAsync(txId, message, signature); } void VerifyProofDialog::checkOutProof() { @@ -163,11 +150,37 @@ void VerifyProofDialog::proofStatus(bool success, const QString &message) { ui->frame_status->show(); ui->label_icon->setPixmap(success ? m_success : m_failure); ui->label_status->setText(message); + ui->btn_verifyFormattedProof->setEnabled(true); } else { + ui->btn_verify->setEnabled(true); success ? QMessageBox::information(this, "Information", message) : QMessageBox::warning(this, "Warning", message); } } +void VerifyProofDialog::onTxProofVerified(TxProofResult r) { + if (!r.success) { + this->proofStatus(false, m_wallet->errorString()); + return; + } + + if (!r.good) { + this->proofStatus(false, "Proof is invalid"); + return; + } + + this->proofStatus(true, QString("Proof is valid.\n\nThis address received %1 XMR, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations))); +} + +void VerifyProofDialog::onSpendProofVerified(QPair r) { + if (!r.first) { + this->proofStatus(false, m_wallet->errorString()); + return; + } + + r.second ? this->proofStatus(true, "Proof is valid") + : this->proofStatus(false, "Proof is invalid"); +} + VerifyProofDialog::~VerifyProofDialog() = default; \ No newline at end of file diff --git a/src/dialog/VerifyProofDialog.h b/src/dialog/VerifyProofDialog.h index 8c538eb6..ca2f641a 100644 --- a/src/dialog/VerifyProofDialog.h +++ b/src/dialog/VerifyProofDialog.h @@ -7,13 +7,14 @@ #include #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class VerifyProofDialog; } -class VerifyProofDialog : public QDialog +class VerifyProofDialog : public WindowModalDialog { Q_OBJECT @@ -31,6 +32,8 @@ private: void checkInProof(); void checkFormattedProof(); void proofStatus(bool success, const QString &message); + void onTxProofVerified(TxProofResult result); + void onSpendProofVerified(QPair result); QScopedPointer ui; Wallet *m_wallet; diff --git a/src/dialog/ViewOnlyDialog.cpp b/src/dialog/ViewOnlyDialog.cpp index a031a1a2..d1251cc5 100644 --- a/src/dialog/ViewOnlyDialog.cpp +++ b/src/dialog/ViewOnlyDialog.cpp @@ -9,7 +9,7 @@ #include ViewOnlyDialog::ViewOnlyDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::ViewOnlyDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/ViewOnlyDialog.h b/src/dialog/ViewOnlyDialog.h index 3cb99429..8f137a8f 100644 --- a/src/dialog/ViewOnlyDialog.h +++ b/src/dialog/ViewOnlyDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class ViewOnlyDialog; } -class ViewOnlyDialog : public QDialog +class ViewOnlyDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/WalletCacheDebugDialog.cpp b/src/dialog/WalletCacheDebugDialog.cpp index f2547ee2..2805c1ca 100644 --- a/src/dialog/WalletCacheDebugDialog.cpp +++ b/src/dialog/WalletCacheDebugDialog.cpp @@ -9,7 +9,7 @@ #include "model/ModelUtils.h" WalletCacheDebugDialog::WalletCacheDebugDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::WalletCacheDebugDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/WalletCacheDebugDialog.h b/src/dialog/WalletCacheDebugDialog.h index 0b8f0768..5778814c 100644 --- a/src/dialog/WalletCacheDebugDialog.h +++ b/src/dialog/WalletCacheDebugDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class WalletCacheDebugDialog; } -class WalletCacheDebugDialog : public QDialog +class WalletCacheDebugDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/WalletInfoDialog.cpp b/src/dialog/WalletInfoDialog.cpp index fac1766b..bcf8f77d 100644 --- a/src/dialog/WalletInfoDialog.cpp +++ b/src/dialog/WalletInfoDialog.cpp @@ -7,7 +7,7 @@ #include WalletInfoDialog::WalletInfoDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::WalletInfoDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/WalletInfoDialog.h b/src/dialog/WalletInfoDialog.h index 9ab4c25d..1c77e748 100644 --- a/src/dialog/WalletInfoDialog.h +++ b/src/dialog/WalletInfoDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class WalletInfoDialog; } -class WalletInfoDialog : public QDialog +class WalletInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/libwalletqt/Coins.cpp b/src/libwalletqt/Coins.cpp index ac49d272..5ba518b4 100644 --- a/src/libwalletqt/Coins.cpp +++ b/src/libwalletqt/Coins.cpp @@ -71,15 +71,15 @@ quint64 Coins::count() const return m_tinfo.count(); } -void Coins::freeze(int index) const +void Coins::freeze(QString &publicKey) const { - m_pimpl->setFrozen(index); + m_pimpl->setFrozen(publicKey.toStdString()); emit coinFrozen(); } -void Coins::thaw(int index) const +void Coins::thaw(QString &publicKey) const { - m_pimpl->thaw(index); + m_pimpl->thaw(publicKey.toStdString()); emit coinThawed(); } @@ -96,9 +96,9 @@ QVector Coins::coins_from_txid(const QString &txid) return coins; } -void Coins::setDescription(int index, quint32 accountIndex, const QString &description) +void Coins::setDescription(const QString &publicKey, quint32 accountIndex, const QString &description) { - m_pimpl->setDescription(index, description.toStdString()); + m_pimpl->setDescription(publicKey.toStdString(), description.toStdString()); this->refresh(accountIndex); emit descriptionChanged(); } diff --git a/src/libwalletqt/Coins.h b/src/libwalletqt/Coins.h index 1813cb05..cda1363d 100644 --- a/src/libwalletqt/Coins.h +++ b/src/libwalletqt/Coins.h @@ -28,10 +28,10 @@ public: Q_INVOKABLE CoinsInfo * coin(int index); Q_INVOKABLE void refresh(quint32 accountIndex); Q_INVOKABLE void refreshUnlocked(); - Q_INVOKABLE void freeze(int index) const; - Q_INVOKABLE void thaw(int index) const; + Q_INVOKABLE void freeze(QString &publicKey) const; + Q_INVOKABLE void thaw(QString &publicKey) const; Q_INVOKABLE QVector coins_from_txid(const QString &txid); - Q_INVOKABLE void setDescription(int index, quint32 accountIndex, const QString &description); + Q_INVOKABLE void setDescription(const QString &publicKey, quint32 accountIndex, const QString &description); quint64 count() const; diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index 754bf4cc..247b0142 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -11,11 +11,6 @@ #include #include "PendingTransactionInfo.h" - -//namespace Monero { -//class PendingTransaction; -//} - class PendingTransaction : public QObject { Q_OBJECT diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp index 50a76f06..deacae13 100644 --- a/src/libwalletqt/TransactionHistory.cpp +++ b/src/libwalletqt/TransactionHistory.cpp @@ -6,6 +6,8 @@ #include "utils/Utils.h" #include "utils/AppData.h" #include "utils/config.h" +#include "constants.h" +#include "WalletManager.h" bool TransactionHistory::transaction(int index, std::function callback) { @@ -147,13 +149,16 @@ TransactionHistory::TransactionHistory(Monero::TransactionHistory *pimpl, QObjec } bool TransactionHistory::writeCSV(const QString &path) { - auto data = QString(""); + QString data; QReadLocker locker(&m_lock); - for (const auto &tx : m_pimpl->getAll()) { - if (tx->subaddrAccount() != 0) { // only account 0 - continue; - } + auto transactions = m_pimpl->getAll(); + + std::sort(transactions.begin(), transactions.end(), [](const Monero::TransactionInfo *info1, const Monero::TransactionInfo *info2){ + return info1->blockHeight() < info2->blockHeight(); + }); + + for (const auto &tx : transactions) { TransactionInfo info(tx, this); // collect column data @@ -166,47 +171,46 @@ bool TransactionHistory::writeCSV(const QString &path) { const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd")); double fiat_price = usd_price * amount; - if(preferredFiatSymbol != "USD") + if (preferredFiatSymbol != "USD") fiat_price = appData()->prices.convert("USD", preferredFiatSymbol, fiat_price); double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0; - if(fiat_price != 0) - fiatAmount = QString("%1 %2").arg(QString::number(fiat_rounded)).arg(preferredFiatSymbol); + if (usd_price != 0) + fiatAmount = QString::number(fiat_rounded); + else + fiatAmount = "\"?\""; - // collect some more column data - quint64 atomicAmount = info.atomicAmount(); - quint32 subaddrAccount = info.subaddrAccount(); - QString fee = info.fee(); QString direction = QString(""); - TransactionInfo::Direction _direction = info.direction(); - if(_direction == TransactionInfo::Direction_In) + if (info.direction() == TransactionInfo::Direction_In) direction = QString("in"); - else if(_direction == TransactionInfo::Direction_Out) + else if (info.direction() == TransactionInfo::Direction_Out) direction = QString("out"); else continue; // skip TransactionInfo::Direction_Both - QString label = info.label(); - label.remove(QChar('"')); // reserved - quint64 blockHeight = info.blockHeight(); - QString date = info.date() + " " + info.time(); - uint epoch = timeStamp.toTime_t(); QString displayAmount = info.displayAmount(); QString paymentId = info.paymentId(); - if(paymentId == "0000000000000000") + if (paymentId == "0000000000000000") { paymentId = ""; + } + + QString date = QString("%1T%2Z").arg(info.date(), info.time()); // ISO 8601 + + QString balanceDelta = WalletManager::displayAmount(info.balanceDelta()); + if (info.direction() == TransactionInfo::Direction_Out) { + balanceDelta = "-" + balanceDelta; + } // format and write - QString line = QString("%1,%2,%3,%4,%5,\"%6\",%7,%8,%9,\"%10\",%11,%12\n") - .arg(QString::number(blockHeight), QString::number(epoch), date) - .arg(direction, QString::number(amount), fiatAmount, QString::number(atomicAmount)) - .arg(info.fee(), info.hash(), label, QString::number(subaddrAccount)) - .arg(paymentId); + QString line = QString("\n%1,%2,\"%3\",%4,\"%5\",%6,%7,%8,\"%9\",\"%10\",\"%11\",%12,\"%13\"") + .arg(QString::number(info.blockHeight()), QString::number(timeStamp.toTime_t()), date, + QString::number(info.subaddrAccount()), direction, balanceDelta, info.displayAmount(), + info.fee(), info.hash(), info.description(), paymentId, fiatAmount, preferredFiatSymbol); data += line; } - if(data.isEmpty()) + if (data.isEmpty()) return false; - data = QString("blockHeight,epoch,date,direction,amount,fiat,atomicAmount,fee,txid,label,subaddrAccount,paymentId\n%1").arg(data); + data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data); return Utils::fileWrite(path, data); } diff --git a/src/libwalletqt/TransactionInfo.cpp b/src/libwalletqt/TransactionInfo.cpp index f7ad540e..2a6bce4e 100644 --- a/src/libwalletqt/TransactionInfo.cpp +++ b/src/libwalletqt/TransactionInfo.cpp @@ -28,7 +28,13 @@ bool TransactionInfo::isCoinbase() const quint64 TransactionInfo::balanceDelta() const { - return m_amount + m_fee; + if (m_direction == Direction_In) { + return m_amount; + } + else if (m_direction == Direction_Out) { + return m_amount + m_fee; + } + return m_amount; } double TransactionInfo::amount() const diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index f0dd9f38..8cfefe33 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -826,11 +826,19 @@ bool Wallet::refresh(bool historyAndSubaddresses /* = true */) } } -void Wallet::commitTransactionAsync(PendingTransaction *t) +void Wallet::commitTransactionAsync(PendingTransaction *t, const QString &description) { - m_scheduler.run([this, t] { + m_scheduler.run([this, t, description] { auto txIdList = t->txid(); // retrieve before commit - emit transactionCommitted(t->commit(), t, txIdList); + bool success = t->commit(); + + if (success && !description.isEmpty()) { + for (const auto &txid : txIdList) { + this->setUserNote(txid, description); + } + } + + emit transactionCommitted(success, t, txIdList); }); } @@ -1016,6 +1024,14 @@ TxProofResult Wallet::checkTxProof(const QString &txid, const QString &address, return {success, good, received, in_pool, confirmations}; } +void Wallet::checkTxProofAsync(const QString &txid, const QString &address, const QString &message, + const QString &signature) { + m_scheduler.run([this, txid, address, message, signature] { + auto result = this->checkTxProof(txid, address, message, signature); + emit transactionProofVerified(result); + }); +} + Q_INVOKABLE TxProof Wallet::getSpendProof(const QString &txid, const QString &message) const { std::string result = m_walletImpl->getSpendProof(txid.toStdString(), message.toStdString()); @@ -1036,6 +1052,13 @@ Q_INVOKABLE QPair Wallet::checkSpendProof(const QString &txid, const return {success, good}; } +void Wallet::checkSpendProofAsync(const QString &txid, const QString &message, const QString &signature) { + m_scheduler.run([this, txid, message, signature] { + auto result = this->checkSpendProof(txid, message, signature); + emit spendProofVerified(result); + }); +} + QString Wallet::signMessage(const QString &message, bool filename, const QString &address) const { if (filename) { @@ -1150,6 +1173,14 @@ QVariantMap Wallet::parse_uri_to_object(const QString &uri) const return result; } +QString Wallet::make_uri(const QString &address, quint64 &amount, const QString &description, + const QString &recipient) const +{ + std::string error; + std::string uri = m_walletImpl->make_uri(address.toStdString(), "", amount, description.toStdString(), recipient.toStdString(), error); + return QString::fromStdString(uri); +} + bool Wallet::rescanSpent() { QMutexLocker locker(&m_asyncMutex); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 1e419450..5bf5bc23 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -61,6 +61,7 @@ class Coins; class CoinsModel; struct TxProofResult { + TxProofResult() {} TxProofResult(bool success, bool good, uint64_t received, bool in_pool, uint64_t confirmations) : success(success), good(good), received(received), in_pool(in_pool), confirmations(confirmations){} @@ -328,7 +329,7 @@ public: bool submitTxFile(const QString &fileName) const; //! asynchronous transaction commit - void commitTransactionAsync(PendingTransaction * t); + void commitTransactionAsync(PendingTransaction * t, const QString &description=""); //! deletes transaction and frees memory void disposeTransaction(PendingTransaction * t); @@ -390,6 +391,8 @@ public: bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector &unknown_parameters, QString &error) const; QVariantMap parse_uri_to_object(const QString &uri) const; + QString make_uri(const QString &address, quint64 &amount, const QString &description, const QString &recipient) const; + //! Namespace your cacheAttribute keys to avoid collisions bool setCacheAttribute(const QString &key, const QString &val); QString getCacheAttribute(const QString &key) const; @@ -404,9 +407,11 @@ public: // void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback); //QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature); TxProofResult checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature); + void checkTxProofAsync(const QString &txid, const QString &address, const QString &message, const QString &signature); TxProof getSpendProof(const QString &txid, const QString &message) const; // void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback); QPair checkSpendProof(const QString &txid, const QString &message, const QString &signature) const; + void checkSpendProofAsync(const QString &txid, const QString &message, const QString &signature); // Rescan spent outputs bool rescanSpent(); @@ -473,6 +478,8 @@ signals: void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const; void deviceShowAddressShowed(); + void transactionProofVerified(TxProofResult result); + void spendProofVerified(QPair result); // emitted when transaction is created async void transactionCreated(PendingTransaction * transaction, QVector address); diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 9c47dd85..bb1ef153 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -297,14 +297,20 @@ bool WalletManager::isDaemonLocal(const QString &daemon_address) const return daemon_address.isEmpty() ? false : Monero::Utils::isAddressLocal(daemon_address.toStdString()); } -QString WalletManager::resolveOpenAlias(const QString &address) const +QString WalletManager::resolveOpenAlias(const QString &address, bool &dnssecValid) const { - bool dnssec_valid = false; - std::string res = m_pimpl->resolveOpenAlias(address.toStdString(), dnssec_valid); - res = std::string(dnssec_valid ? "true" : "false") + "|" + res; + std::string res = m_pimpl->resolveOpenAlias(address.toStdString(), dnssecValid); return QString::fromStdString(res); } +void WalletManager::resolveOpenAliasAsync(const QString &alias) { + m_scheduler.run([this, alias] { + bool dnssecValid; + QString address = this->resolveOpenAlias(alias, dnssecValid); + emit openAliasResolved(alias, address, dnssecValid); + }); +} + void WalletManager::setLogLevel(int logLevel) { Monero::WalletManagerFactory::setLogLevel(logLevel); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index b9086c5d..47e6176b 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -142,7 +142,8 @@ public: Q_INVOKABLE qint64 addi(qint64 x, qint64 y) const { return x + y; } Q_INVOKABLE qint64 subi(qint64 x, qint64 y) const { return x - y; } - Q_INVOKABLE QString resolveOpenAlias(const QString &address) const; + QString resolveOpenAlias(const QString &address, bool &dnssecValid) const; + void resolveOpenAliasAsync(const QString &address); // clear/rename wallet cache Q_INVOKABLE static bool clearWalletCache(const QString &fileName); @@ -162,6 +163,7 @@ signals: void deviceError(const QString &message); void miningStatus(bool isMining) const; void proxyAddressChanged() const; + void openAliasResolved(const QString &alias, const QString &address, bool dnssecValid); public slots: private: diff --git a/src/main.cpp b/src/main.cpp index 68501959..c748615e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -207,6 +207,8 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { qInstallMessageHandler(Utils::applicationLogHandler); qRegisterMetaType>(); + qRegisterMetaType("TxProofResult"); + qRegisterMetaType>(); WindowManager windowManager; diff --git a/src/model/CoinsModel.cpp b/src/model/CoinsModel.cpp index a5a409dc..7110c394 100644 --- a/src/model/CoinsModel.cpp +++ b/src/model/CoinsModel.cpp @@ -179,9 +179,17 @@ bool CoinsModel::setData(const QModelIndex &index, const QVariant &value, int ro if (index.isValid() && role == Qt::EditRole) { const int row = index.row(); + QString pubkey; + bool found = m_coins->coin(index.row(), [this, &pubkey](const CoinsInfo &cInfo) { + pubkey = cInfo.pubKey(); + }); + if (!found) { + return false; + } + switch (index.column()) { case Label: - m_coins->setDescription(row, m_currentSubaddressAccount, value.toString()); + m_coins->setDescription(pubkey, m_currentSubaddressAccount, value.toString()); emit descriptionChanged(); break; default: diff --git a/src/model/HistoryView.cpp b/src/model/HistoryView.cpp index be1da45b..8b1ce165 100644 --- a/src/model/HistoryView.cpp +++ b/src/model/HistoryView.cpp @@ -82,11 +82,16 @@ TransactionInfo* HistoryView::currentEntry() } void HistoryView::setSearchMode(bool mode) { + if (!m_inSearchMode) { + m_showTxidColumn = !header()->isSectionHidden(TransactionHistoryModel::TxID); + } + m_inSearchMode = mode; if (mode) { header()->showSection(TransactionHistoryModel::TxID); - } else { + } + else if (!m_showTxidColumn) { header()->hideSection(TransactionHistoryModel::TxID); } } diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp index 2265a3e9..c7425b0f 100644 --- a/src/model/TransactionHistoryModel.cpp +++ b/src/model/TransactionHistoryModel.cpp @@ -158,7 +158,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI if (role == Qt::UserRole) { return tInfo.balanceDelta(); } - QString amount = QString::number(tInfo.balanceDelta() / constants::cdiv, 'f', this->amountPrecision); + QString amount = QString::number(tInfo.balanceDelta() / constants::cdiv, 'f', config()->get(Config::amountPrecision).toInt()); amount = (tInfo.direction() == TransactionInfo::Direction_Out) ? "-" + amount : "+" + amount; return amount; } @@ -168,23 +168,30 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI case Column::FiatAmount: { double usd_price = appData()->txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd")); - if (usd_price == 0.0) - return QVariant("?"); + if (usd_price == 0.0) { + return QString("?"); + } double usd_amount = usd_price * (tInfo.balanceDelta() / constants::cdiv); - if(this->preferredFiatSymbol != "USD") - usd_amount = appData()->prices.convert("USD", this->preferredFiatSymbol, usd_amount); + + QString preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); + if (preferredFiatCurrency != "USD") { + usd_amount = appData()->prices.convert("USD", preferredFiatCurrency, usd_amount); + } if (role == Qt::UserRole) { return usd_amount; } + if (usd_amount == 0.0) { + return QString("?"); + } double fiat_rounded = ceil(Utils::roundSignificant(usd_amount, 3) * 100.0) / 100.0; - return QString("%1").arg(Utils::amountToCurrencyString(fiat_rounded, this->preferredFiatSymbol)); + return QString("%1").arg(Utils::amountToCurrencyString(fiat_rounded, preferredFiatCurrency)); } default: { qCritical() << "Unimplemented role"; - return QVariant(); + return {}; } } } diff --git a/src/model/TransactionHistoryModel.h b/src/model/TransactionHistoryModel.h index 4df7dbd4..d61da796 100644 --- a/src/model/TransactionHistoryModel.h +++ b/src/model/TransactionHistoryModel.h @@ -35,9 +35,6 @@ public: TransactionHistory * transactionHistory() const; TransactionInfo* entryFromIndex(const QModelIndex& index) const; - QString preferredFiatSymbol = "USD"; - int amountPrecision = 4; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; diff --git a/src/utils/AppData.cpp b/src/utils/AppData.cpp index 7db6e7b3..09c0ddd6 100644 --- a/src/utils/AppData.cpp +++ b/src/utils/AppData.cpp @@ -11,17 +11,13 @@ AppData::AppData(QObject *parent) this->initRestoreHeights(); auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey(); - this->txFiatHistory = new TxFiatHistory(genesis_timestamp, Config::defaultConfigDir().path()); + this->txFiatHistory = new TxFiatHistory(genesis_timestamp, Config::defaultConfigDir().path(), this); connect(&websocketNotifier()->websocketClient, &WebsocketClient::connectionEstablished, this->txFiatHistory, &TxFiatHistory::onUpdateDatabase); connect(this->txFiatHistory, &TxFiatHistory::requestYear, [](int year){ QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8(); websocketNotifier()->websocketClient.sendMsg(data); }); - connect(this->txFiatHistory, &TxFiatHistory::requestYearMonth, [](int year, int month){ - QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8(); - websocketNotifier()->websocketClient.sendMsg(data); - }); connect(websocketNotifier(), &WebsocketNotifier::CryptoRatesReceived, &this->prices, &Prices::cryptoPricesReceived); connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, &this->prices, &Prices::fiatPricesReceived); diff --git a/src/utils/AppData.h b/src/utils/AppData.h index a5415f36..2560b1ba 100644 --- a/src/utils/AppData.h +++ b/src/utils/AppData.h @@ -9,7 +9,7 @@ #include #include "prices.h" -#include "txfiathistory.h" +#include "TxFiatHistory.h" #include "RestoreHeightLookup.h" class AppData : public QObject { diff --git a/src/utils/TxFiatHistory.cpp b/src/utils/TxFiatHistory.cpp new file mode 100644 index 00000000..a573463b --- /dev/null +++ b/src/utils/TxFiatHistory.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "TxFiatHistory.h" +#include "utils/Utils.h" + +TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) + : QObject(parent) + , m_genesis_timestamp(genesis_timestamp) + , m_databasePath(QString("%1/fiatHistory.db").arg(configDirectory)) +{ + this->loadDatabase(); +} + +double TxFiatHistory::get(int timestamp) { + QDateTime ts; + ts.setTime_t(timestamp); + auto key = ts.toString("yyyyMMdd"); + return this->get(key); // USD +} + +double TxFiatHistory::get(const QString &date) { + if (m_database.contains(date)) { + return m_database[date]; // USD + } + return 0.0; +} + +void TxFiatHistory::loadDatabase() { + if (!Utils::fileExists(m_databasePath)) { + return; + } + + m_database.clear(); + + QString contents = Utils::barrayToString(Utils::fileOpen(m_databasePath)); + for (auto &line: contents.split("\n")) { + line = line.trimmed(); + if (line.isEmpty()) { + continue; + } + QStringList spl = line.split(":"); + if (spl.length() == 2) { + m_database[spl.at(0)] = spl.at(1).toDouble(); + } + } +} + +void TxFiatHistory::writeDatabase() { + QString data; + for (const auto &line: m_database.toStdMap()) { + data += QString("%1:%2\n").arg(line.first).arg(QString::number(line.second)); + } + Utils::fileWrite(m_databasePath, data); +} + +void TxFiatHistory::onUpdateDatabase() { + // update local txFiatHistory database + if (m_initialized) { + return; + } + + QDateTime genesis; + genesis.setSecsSinceEpoch(m_genesis_timestamp); + QDate genesis_date = genesis.date(); + + QDate now = QDate::currentDate(); + + QSet missingYears; + for (QDate date = genesis_date; date <= now;) { + + if (!m_database.contains(this->dateToKey(date))) { + qInfo() << "TxFiatHistory: Can't find value for date: " << this->dateToKey(date); + missingYears << date.year(); + date.setDate(date.year()+1, 1, 1); + continue; + } + + date = date.addDays(1); + } + + for (const int year : missingYears) { + emit requestYear(year); + } + + m_initialized = true; +} + +QString TxFiatHistory::dateToKey(const QDate &date) { + return date.toString("yyyyMMdd"); +} + +void TxFiatHistory::onWSData(const QJsonObject &data) { + foreach(const QString &key, data.keys()) { + QJsonValue value = data.value(key); + m_database[key] = value.toDouble(); + } + + this->writeDatabase(); +} diff --git a/src/utils/txfiathistory.h b/src/utils/TxFiatHistory.h similarity index 88% rename from src/utils/txfiathistory.h rename to src/utils/TxFiatHistory.h index cbb7d80d..87cd14fd 100644 --- a/src/utils/txfiathistory.h +++ b/src/utils/TxFiatHistory.h @@ -4,6 +4,10 @@ #ifndef FEATHER_TXFIATHISTORY_H #define FEATHER_TXFIATHISTORY_H +#include +#include +#include + class TxFiatHistory : public QObject { Q_OBJECT @@ -18,14 +22,14 @@ public slots: signals: void requestYear(int year); - void requestYearMonth(int year, int month); private: void loadDatabase(); void writeDatabase(); + QString dateToKey(const QDate &date); + int m_genesis_timestamp; QString m_databasePath; - QString m_configDirectory; bool m_initialized = false; QMap m_database; }; diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index f754c0fb..9be92c5f 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -126,6 +126,16 @@ QString defaultWalletDir() { #endif } +QString applicationPath() { + QString applicationPath = qgetenv("APPIMAGE"); + if (!applicationPath.isEmpty()) { + applicationPath = QFileInfo(applicationPath).absoluteDir().path(); + } else { + applicationPath = QCoreApplication::applicationDirPath(); + } + return applicationPath; +} + bool validateJSON(const QByteArray &blob) { QJsonDocument doc = QJsonDocument::fromJson(blob); QString jsonString = doc.toJson(QJsonDocument::Indented); diff --git a/src/utils/Utils.h b/src/utils/Utils.h index 2df870ff..ea20f7ac 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -23,6 +23,7 @@ namespace Utils bool dirExists(const QString &path); QString defaultWalletDir(); + QString applicationPath(); bool validateJSON(const QByteArray &blob); bool readJsonFile(QIODevice &device, QSettings::SettingsMap &map); diff --git a/src/utils/WebsocketClient.cpp b/src/utils/WebsocketClient.cpp index 5855f95d..7087a174 100644 --- a/src/utils/WebsocketClient.cpp +++ b/src/utils/WebsocketClient.cpp @@ -9,76 +9,59 @@ WebsocketClient::WebsocketClient(QObject *parent) : QObject(parent) { - connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &WebsocketClient::onbinaryMessageReceived); connect(&webSocket, &QWebSocket::connected, this, &WebsocketClient::onConnected); - connect(&webSocket, &QWebSocket::disconnected, this, &WebsocketClient::closed); + connect(&webSocket, &QWebSocket::disconnected, this, &WebsocketClient::onDisconnected); connect(&webSocket, QOverload::of(&QWebSocket::error), this, &WebsocketClient::onError); - connect(&m_connectionTimer, &QTimer::timeout, this, &WebsocketClient::checkConnection); + + connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &WebsocketClient::onbinaryMessageReceived); // Keep websocket connection alive connect(&m_pingTimer, &QTimer::timeout, [this]{ - if (webSocket.state() == QAbstractSocket::ConnectedState) + if (webSocket.state() == QAbstractSocket::ConnectedState) { webSocket.ping(); + } }); m_pingTimer.setInterval(30 * 1000); m_pingTimer.start(); } void WebsocketClient::sendMsg(const QByteArray &data) { - if (webSocket.state() == QAbstractSocket::ConnectedState) + if (webSocket.state() == QAbstractSocket::ConnectedState) { webSocket.sendBinaryMessage(data); -} - -void WebsocketClient::onToggleConnect(bool connect) { - m_connect = connect; - if (m_connect) - checkConnection(); + } } void WebsocketClient::start() { // connect & reconnect on errors/close -#ifdef QT_DEBUG qDebug() << "WebSocket connect:" << m_url.url(); -#endif - if (m_connect) + auto state = webSocket.state(); + if (state != QAbstractSocket::ConnectedState && state != QAbstractSocket::ConnectingState) { webSocket.open(m_url); - - if (!m_connectionTimer.isActive()) { - m_connectionTimer.start(2000); - } -} - -void WebsocketClient::checkConnection() { - if (!m_connect) - return; - - if (webSocket.state() == QAbstractSocket::UnconnectedState) { -#ifdef QT_DEBUG - qDebug() << "WebSocket reconnect"; -#endif - this->start(); } } void WebsocketClient::onConnected() { -#ifdef QT_DEBUG qDebug() << "WebSocket connected"; -#endif emit connectionEstablished(); } +void WebsocketClient::onDisconnected() { + qDebug() << "WebSocket disconnected"; + QTimer::singleShot(1000, [this]{this->start();}); +} + void WebsocketClient::onError(QAbstractSocket::SocketError error) { qCritical() << "WebSocket error: " << error; auto state = webSocket.state(); - if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState) + if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState) { webSocket.abort(); + } } void WebsocketClient::onbinaryMessageReceived(const QByteArray &message) { -#ifdef QT_DEBUG qDebug() << "WebSocket received:" << message; -#endif + if (!Utils::validateJSON(message)) { qCritical() << "Could not interpret WebSocket message as JSON"; return; diff --git a/src/utils/WebsocketClient.h b/src/utils/WebsocketClient.h index c9e098db..f9aaefc9 100644 --- a/src/utils/WebsocketClient.h +++ b/src/utils/WebsocketClient.h @@ -20,24 +20,18 @@ public: QWebSocket webSocket; -public slots: - void onToggleConnect(bool connect); - signals: - void closed(); void connectionEstablished(); void WSMessage(QJsonObject message); private slots: void onConnected(); + void onDisconnected(); void onbinaryMessageReceived(const QByteArray &message); - void checkConnection(); void onError(QAbstractSocket::SocketError error); private: - bool m_connect = false; QUrl m_url = constants::websocketUrl; - QTimer m_connectionTimer; QTimer m_pingTimer; }; diff --git a/src/utils/WebsocketNotifier.h b/src/utils/WebsocketNotifier.h index 96096034..c159e41c 100644 --- a/src/utils/WebsocketNotifier.h +++ b/src/utils/WebsocketNotifier.h @@ -13,7 +13,7 @@ #include "prices.h" #include "widgets/RedditPost.h" #include "widgets/CCSEntry.h" -#include "txfiathistory.h" +#include "TxFiatHistory.h" class WebsocketNotifier : public QObject { Q_OBJECT diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 51f17ad2..f66269eb 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -20,7 +20,6 @@ static const QHash configStrings = { {Config::firstRun, {QS("firstRun"), true}}, {Config::warnOnStagenet,{QS("warnOnStagenet"), true}}, {Config::warnOnTestnet,{QS("warnOnTestnet"), true}}, - {Config::warnOnAlpha,{QS("warnOnAlpha"), true}}, {Config::homeWidget,{QS("homeWidget"), "ccs"}}, {Config::donateBeg,{QS("donateBeg"), 1}}, @@ -49,8 +48,13 @@ static const QHash configStrings = { {Config::showSearchbar,{QS("showSearchbar"), true}}, // Mining + {Config::miningMode,{QS("miningMode"), Config::MiningMode::Pool}}, {Config::xmrigPath,{QS("xmrigPath"), ""}}, + {Config::xmrigElevated,{QS("xmrigElevated"), false}}, + {Config::xmrigThreads,{QS("xmrigThreads"), 1}}, {Config::xmrigPool,{QS("xmrigPool"), "pool.xmr.pt:9000"}}, + {Config::xmrigNetworkTLS,{QS("xmrigNetworkTLS"), true}}, + {Config::xmrigNetworkTor,{QS("xmrigNetworkTor"), false}}, {Config::pools,{QS("pools"), {}}}, // Settings diff --git a/src/utils/config.h b/src/utils/config.h index 1dce5303..679b6913 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -53,8 +53,13 @@ public: showSearchbar, // Mining + miningMode, xmrigPath, + xmrigElevated, + xmrigThreads, xmrigPool, + xmrigNetworkTLS, + xmrigNetworkTor, pools, // Settings @@ -98,6 +103,11 @@ public: spendable }; + enum MiningMode { + Pool = 0, + Solo + }; + ~Config() override; QVariant get(ConfigKey key); QString getFileName(); diff --git a/src/utils/txfiathistory.cpp b/src/utils/txfiathistory.cpp deleted file mode 100644 index 73cd4717..00000000 --- a/src/utils/txfiathistory.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include -#include - -#include "txfiathistory.h" -#include "utils/Utils.h" - -TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) - : QObject(parent) - , m_genesis_timestamp(genesis_timestamp) - , m_configDirectory(configDirectory) -{ - m_databasePath = QString("%1/fiatHistory.db").arg(configDirectory); - this->loadDatabase(); -} - -double TxFiatHistory::get(int timestamp) { - QDateTime ts; - ts.setTime_t(timestamp); - auto key = ts.toString("yyyyMMdd"); - return this->get(key); // USD -} - -double TxFiatHistory::get(const QString &date) { - if(m_database.contains(date)) - return m_database[date]; // USD - return 0.0; -} - -void TxFiatHistory::loadDatabase() { - if(!Utils::fileExists(m_databasePath)) - return; - - m_database.clear(); - QString contents = Utils::barrayToString(Utils::fileOpen(m_databasePath)); - for(auto &line: contents.split("\n")){ - line = line.trimmed(); - if(line.isEmpty()) continue; - auto spl = line.split(":"); - m_database[spl.at(0)] = spl.at(1).toDouble(); - } -} - -void TxFiatHistory::writeDatabase() { - QString data; - for(const auto &line: m_database.toStdMap()) - data += QString("%1:%2\n").arg(line.first).arg(QString::number(line.second)); - Utils::fileWrite(m_databasePath, data); -} - -void TxFiatHistory::onUpdateDatabase() { - // update local txFiatHistory database - if(m_initialized) return; - - QDateTime genesis; - genesis.setTime_t(m_genesis_timestamp); - auto genesis_date = genesis.date(); - - auto now = QDate::currentDate(); - auto nowKey = now.toString("yyyyMMdd"); - int year = genesis.toString("yyyy").toInt(); - auto yearCurrent = now.year(); - - // if current year is genesis year we'll refresh regardless. - if(yearCurrent == genesis_date.year()) { - emit requestYear(year); - m_initialized = true; - return; - } - - // keep local fiatTxHistory database up to date, loop for missing dates - for(; year != yearCurrent + 1; year += 1){ - for(int month = 1; month != 13; month++) { - if(year == yearCurrent && month == now.month() && now.day() == 1) break; - QDateTime _now; - _now.setDate(QDate(year, month, 1)); - if(_now.toSecsSinceEpoch() < m_genesis_timestamp) continue; - if(_now.toSecsSinceEpoch() > std::time(nullptr) - 86400) continue; - QString key = ""; - - // genesis year we'll only fetch once - if(year == genesis_date.year()){ - key = QString("%1%2%3").arg(year).arg(12).arg("31"); - if(!m_database.contains(key)) - emit requestYear(year); - break; - } - - auto _month = QString::number(month); - if(_month.length() == 1) - _month = QString("0%1").arg(_month); // how2fill - - key = QString("%1%2%3").arg(year).arg(_month).arg("01"); - if(!m_database.contains(key)){ - if(year != yearCurrent) { - emit requestYear(year); - break; - } else - emit requestYearMonth(year, month); - } else if (year == yearCurrent && month == now.month() && !m_database.contains(nowKey)) - emit requestYearMonth(year, month); - } - } - - m_initialized = true; -} - -void TxFiatHistory::onWSData(const QJsonObject &data) { - foreach(const QString &key, data.keys()) { - QJsonValue value = data.value(key); - m_database[key] = value.toDouble(); - } - - this->writeDatabase(); -} diff --git a/src/utils/wsclient.cpp b/src/utils/wsclient.cpp deleted file mode 100644 index d9977f3c..00000000 --- a/src/utils/wsclient.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include -#include -#include "wsclient.h" -#include "utils/Utils.h" - -WSClient::WSClient(QUrl url, QObject *parent) - : QObject(parent) - , m_url(std::move(url)) -{ - connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &WSClient::onbinaryMessageReceived); - connect(&webSocket, &QWebSocket::connected, this, &WSClient::onConnected); - connect(&webSocket, &QWebSocket::disconnected, this, &WSClient::closed); - connect(&webSocket, QOverload::of(&QWebSocket::error), this, &WSClient::onError); - connect(&m_connectionTimer, &QTimer::timeout, this, &WSClient::checkConnection); - - // Keep websocket connection alive - connect(&m_pingTimer, &QTimer::timeout, [this]{ - if (webSocket.state() == QAbstractSocket::ConnectedState) - webSocket.ping(); - }); - m_pingTimer.setInterval(30 * 1000); - m_pingTimer.start(); -} - -void WSClient::sendMsg(const QByteArray &data) { - if (webSocket.state() == QAbstractSocket::ConnectedState) - webSocket.sendBinaryMessage(data); -} - -void WSClient::onToggleConnect(bool connect) { - m_connect = connect; - if (m_connect) - checkConnection(); -} - -void WSClient::start() { - // connect & reconnect on errors/close -#ifdef QT_DEBUG - qDebug() << "WebSocket connect:" << m_url.url(); -#endif - - if (m_connect) - webSocket.open(m_url); - - if (!m_connectionTimer.isActive()) { - m_connectionTimer.start(2000); - } -} - -void WSClient::checkConnection() { - if (!m_connect) - return; - - if (webSocket.state() == QAbstractSocket::UnconnectedState) { -#ifdef QT_DEBUG - qDebug() << "WebSocket reconnect"; -#endif - this->start(); - } -} - -void WSClient::onConnected() { -#ifdef QT_DEBUG - qDebug() << "WebSocket connected"; -#endif - emit connectionEstablished(); -} - -void WSClient::onError(QAbstractSocket::SocketError error) { - qCritical() << "WebSocket error: " << error; - auto state = webSocket.state(); - if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState) - webSocket.abort(); -} - -void WSClient::onbinaryMessageReceived(const QByteArray &message) { -#ifdef QT_DEBUG - qDebug() << "WebSocket received:" << message; -#endif - if (!Utils::validateJSON(message)) { - qCritical() << "Could not interpret WebSocket message as JSON"; - return; - } - - QJsonDocument doc = QJsonDocument::fromJson(message); - QJsonObject object = doc.object(); - if(!object.contains("cmd") || !object.contains("data")) { - qCritical() << "Invalid WebSocket message received"; - return; - } - - emit WSMessage(object); -} diff --git a/src/utils/wsclient.h b/src/utils/wsclient.h deleted file mode 100644 index 29a7955a..00000000 --- a/src/utils/wsclient.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#ifndef FEATHER_WSCLIENT_H -#define FEATHER_WSCLIENT_H - -#include -#include -#include - -class WSClient : public QObject -{ - Q_OBJECT - -public: - explicit WSClient(QUrl url, QObject *parent = nullptr); - void start(); - void sendMsg(const QByteArray &data); - QWebSocket webSocket; - -public slots: - void onToggleConnect(bool connect); - -signals: - void closed(); - void connectionEstablished(); - void WSMessage(QJsonObject message); - -private slots: - void onConnected(); - void onbinaryMessageReceived(const QByteArray &message); - void checkConnection(); - void onError(QAbstractSocket::SocketError error); - -private: - bool m_connect = false; - QUrl m_url; - QTimer m_connectionTimer; - QTimer m_pingTimer; -}; - -#endif // FEATHER_WSCLIENT_H \ No newline at end of file diff --git a/src/utils/xmrig.cpp b/src/utils/xmrig.cpp index 2f8b613b..c80597d3 100644 --- a/src/utils/xmrig.cpp +++ b/src/utils/xmrig.cpp @@ -13,49 +13,58 @@ XmRig::XmRig(const QString &configDir, QObject *parent) : QObject(parent) { - this->rigDir = QDir(configDir).filePath("xmrig"); - m_process.setProcessChannelMode(QProcess::MergedChannels); connect(&m_process, &QProcess::readyReadStandardOutput, this, &XmRig::handleProcessOutput); connect(&m_process, &QProcess::errorOccurred, this, &XmRig::handleProcessError); - connect(&m_process, &QProcess::stateChanged, this, &XmRig::stateChanged); + connect(&m_process, &QProcess::stateChanged, this, &XmRig::onStateChanged); } void XmRig::stop() { - if(m_process.state() == QProcess::Running) { + qDebug() << m_process.processId(); + if (m_process.state() == QProcess::Running) { #if defined(Q_OS_WIN) m_process.kill(); // https://doc.qt.io/qt-5/qprocess.html#terminate -#else - m_process.terminate(); +#elif defined(Q_OS_LINUX) + if (m_elevated) { + m_killProcess.start("pkexec", QStringList() << "kill" << QString::number(m_process.processId())); + return; + } #endif + m_process.terminate(); } } void XmRig::start(const QString &path, int threads, const QString &address, const QString &username, - const QString &password, bool tor, bool tls) + const QString &password, bool tor, bool tls, bool elevated) { + m_elevated = elevated; + auto state = m_process.state(); if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) { emit error("Can't start XMRig, already running or starting"); return; } - if(path.isEmpty()) { + if (path.isEmpty()) { emit error("XmRig->Start path parameter missing."); return; } - if(!Utils::fileExists(path)) { + if (!Utils::fileExists(path)) { emit error(QString("Path to XMRig binary invalid; file does not exist: %1").arg(path)); return; } QStringList arguments; + if (m_elevated) { + arguments << path; + } arguments << "-o" << address; arguments << "-a" << "rx/0"; arguments << "-u" << username; - if(!password.isEmpty()) + if (!password.isEmpty()) { arguments << "-p" << password; + } arguments << "--no-color"; arguments << "-t" << QString::number(threads); if (tor) { @@ -67,20 +76,30 @@ void XmRig::start(const QString &path, int threads, const QString &address, cons } arguments << "-x" << QString("%1:%2").arg(host, port); } - - if(tls) + if (tls) { arguments << "--tls"; + } arguments << "--donate-level" << "1"; QString cmd = QString("%1 %2").arg(path, arguments.join(" ")); emit output(cmd.toUtf8()); - m_process.start(path, arguments); + + if (m_elevated) { + m_process.start("pkexec", arguments); + } else { + m_process.start(path, arguments); + } } -void XmRig::stateChanged(QProcess::ProcessState state) { - if(state == QProcess::ProcessState::Running) +void XmRig::onStateChanged(QProcess::ProcessState state) { + emit stateChanged(state); + + if (state == QProcess::ProcessState::Running) { emit output("XMRig started"); - else if (state == QProcess::ProcessState::NotRunning) + } + + else if (state == QProcess::ProcessState::NotRunning) { emit output("XMRig stopped"); + } } void XmRig::handleProcessOutput() { diff --git a/src/utils/xmrig.h b/src/utils/xmrig.h index 2d89abc5..6b48c8f3 100644 --- a/src/utils/xmrig.h +++ b/src/utils/xmrig.h @@ -21,24 +21,24 @@ Q_OBJECT public: explicit XmRig(const QString &configDir, QObject *parent = nullptr); - void start(const QString &path, int threads, const QString &address, const QString &username, const QString &password, bool tor = false, bool tls = true); + void start(const QString &path, int threads, const QString &address, const QString &username, const QString &password, bool tor = false, bool tls = true, bool elevated = false); void stop(); - QString rigDir; - QString rigPath; - signals: void error(const QString &msg); void output(const QByteArray &data); void hashrate(const QString &rate); + void stateChanged(QProcess::ProcessState state); private slots: - void stateChanged(QProcess::ProcessState); + void onStateChanged(QProcess::ProcessState); void handleProcessOutput(); void handleProcessError(QProcess::ProcessError error); private: ChildProcess m_process; + QProcess m_killProcess; + bool m_elevated; }; #endif //FEATHER_XMRIG_H diff --git a/src/widgets/PayToEdit.cpp b/src/widgets/PayToEdit.cpp index 1a7fc813..740f5a48 100644 --- a/src/widgets/PayToEdit.cpp +++ b/src/widgets/PayToEdit.cpp @@ -170,8 +170,9 @@ void PayToEdit::parseAsMultiline(const QStringList &lines) { m_outputs.clear(); m_total = 0; - int i = 0; + int i = -1; for (auto &line : lines) { + i++; PartialTxOutput output = this->parseAddressAndAmount(line); if (output.address.isEmpty() && output.amount == 0) { m_errors.append(PayToLineError(line, "Expected two comma-separated values: (address, amount)", i, true)); @@ -186,6 +187,5 @@ void PayToEdit::parseAsMultiline(const QStringList &lines) { m_outputs.append(output); m_total += output.amount; - i += 1; } } \ No newline at end of file diff --git a/src/widgets/QrCodeWidget.cpp b/src/widgets/QrCodeWidget.cpp index e82902b3..ffc17af6 100644 --- a/src/widgets/QrCodeWidget.cpp +++ b/src/widgets/QrCodeWidget.cpp @@ -57,4 +57,12 @@ void QrCodeWidget::paintEvent(QPaintEvent *event) { } } } +} + +bool QrCodeWidget::hasHeightForWidth() const { + return true; +} + +int QrCodeWidget::heightForWidth(int w) const { + return w; } \ No newline at end of file diff --git a/src/widgets/QrCodeWidget.h b/src/widgets/QrCodeWidget.h index 855936e3..f86c81dd 100644 --- a/src/widgets/QrCodeWidget.h +++ b/src/widgets/QrCodeWidget.h @@ -18,6 +18,8 @@ public: protected: void paintEvent(QPaintEvent *event) override; + int heightForWidth(int w) const override; + bool hasHeightForWidth() const override; private: QrCode *m_qrcode = nullptr; diff --git a/src/widgets/XMRigWidget.cpp b/src/widgets/XMRigWidget.cpp index 39cc22bd..7aa6e700 100644 --- a/src/widgets/XMRigWidget.cpp +++ b/src/widgets/XMRigWidget.cpp @@ -24,52 +24,54 @@ XMRigWidget::XMRigWidget(QSharedPointer ctx, QWidget *parent) { ui->setupUi(this); - QPixmap p(":assets/images/xmrig.svg"); - ui->lbl_logo->setPixmap(p.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - + connect(m_XMRig, &XmRig::stateChanged, this, &XMRigWidget::onXMRigStateChanged); connect(m_XMRig, &XmRig::output, this, &XMRigWidget::onProcessOutput); connect(m_XMRig, &XmRig::error, this, &XMRigWidget::onProcessError); connect(m_XMRig, &XmRig::hashrate, this, &XMRigWidget::onHashrate); - // table - ui->tableView->setModel(this->m_model); + // [Downloads] tab + ui->tableView->setModel(m_model); m_contextMenu->addAction(icons()->icon("network.png"), "Download file", this, &XMRigWidget::linkClicked); connect(ui->tableView, &QHeaderView::customContextMenuRequested, this, &XMRigWidget::showContextMenu); connect(ui->tableView, &QTableView::doubleClicked, this, &XMRigWidget::linkClicked); - // threads + // [Settings] tab + ui->poolFrame->show(); + ui->soloFrame->hide(); + + // XMRig executable + connect(ui->btn_browse, &QPushButton::clicked, this, &XMRigWidget::onBrowseClicked); + ui->lineEdit_path->setText(config()->get(Config::xmrigPath).toString()); + + // Run as admin/root + bool elevated = config()->get(Config::xmrigElevated).toBool(); + if (elevated) { + ui->radio_elevateYes->setChecked(true); + } else { + ui->radio_elevateNo->setChecked(true); + } + connect(ui->radio_elevateYes, &QRadioButton::toggled, this, &XMRigWidget::onXMRigElevationChanged); +#if defined(Q_OS_WIN) + ui->radio_elevateYes->setToolTip("Not supported on Windows, yet."); + ui->radio_elevateYes->setEnabled(false); + ui->radio_elevateNo->setChecked(true); +#endif + + // CPU threads ui->threadSlider->setMinimum(1); - int threads = QThread::idealThreadCount(); - m_threads = threads / 2; - ui->threadSlider->setMaximum(threads); - ui->threadSlider->setValue(m_threads); - ui->label_threads->setText(QString("CPU threads: %1").arg(m_threads)); + ui->threadSlider->setMaximum(QThread::idealThreadCount()); + + int threads = config()->get(Config::xmrigThreads).toInt(); + ui->threadSlider->setValue(threads); + ui->label_threads->setText(QString("CPU threads: %1").arg(threads)); + connect(ui->threadSlider, &QSlider::valueChanged, this, &XMRigWidget::onThreadsValueChanged); - // buttons - connect(ui->btn_start, &QPushButton::clicked, this, &XMRigWidget::onStartClicked); - connect(ui->btn_stop, &QPushButton::clicked, this, &XMRigWidget::onStopClicked); - connect(ui->btn_browse, &QPushButton::clicked, this, &XMRigWidget::onBrowseClicked); - connect(ui->btn_clear, &QPushButton::clicked, this, &XMRigWidget::onClearClicked); + // Mining mode + connect(ui->combo_miningMode, QOverload::of(&QComboBox::currentIndexChanged), this, &XMRigWidget::onMiningModeChanged); + ui->combo_miningMode->setCurrentIndex(config()->get(Config::miningMode).toInt()); - // defaults - ui->btn_stop->setEnabled(false); - ui->check_autoscroll->setChecked(true); - ui->relayTor->setChecked(false); - ui->check_tls->setChecked(true); - ui->label_status->setTextInteractionFlags(Qt::TextSelectableByMouse); - ui->label_status->hide(); - ui->soloFrame->hide(); - ui->poolFrame->hide(); - - // XMRig binary - auto path = config()->get(Config::xmrigPath).toString(); - if(!path.isEmpty()) { - ui->lineEdit_path->setText(path); - } - - // pools - ui->poolFrame->show(); + // Pool/node address this->updatePools(); connect(ui->combo_pools, &QComboBox::currentTextChanged, this, &XMRigWidget::onPoolChanged); @@ -87,41 +89,45 @@ XMRigWidget::XMRigWidget(QSharedPointer ctx, QWidget *parent) this->updatePools(); }); - // info - ui->console->appendPlainText(QString("Detected %1 CPU threads.").arg(threads)); - if(!path.isEmpty() && !Utils::fileExists(path)) - ui->console->appendPlainText("Invalid path to XMRig binary detected. Please reconfigure on the Settings tab."); - else - ui->console->appendPlainText(QString("XMRig path set to %1").arg(path)); + // Network settings + connect(ui->check_tls, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTLSToggled); + connect(ui->relayTor, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTorToggled); + ui->check_tls->setChecked(config()->get(Config::xmrigNetworkTLS).toBool()); + ui->relayTor->setChecked(config()->get(Config::xmrigNetworkTor).toBool()); - ui->console->appendPlainText("Ready to mine."); - - // username/password - connect(ui->lineEdit_password, &QLineEdit::editingFinished, [=]() { - m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); - m_ctx->storeWallet(); - }); - connect(ui->lineEdit_address, &QLineEdit::editingFinished, [=]() { - m_ctx->wallet->setCacheAttribute("feather.xmrig_username", ui->lineEdit_address->text()); - m_ctx->storeWallet(); - }); - - // checkbox connects - connect(ui->check_solo, &QCheckBox::stateChanged, this, &XMRigWidget::onSoloChecked); - - // Xmrig username + // Receiving address auto username = m_ctx->wallet->getCacheAttribute("feather.xmrig_username"); - if(!username.isEmpty()) + if (!username.isEmpty()) { ui->lineEdit_address->setText(username); + } + connect(ui->lineEdit_address, &QLineEdit::textChanged, [=]() { + m_ctx->wallet->setCacheAttribute("feather.xmrig_username", ui->lineEdit_address->text()); + }); + connect(ui->btn_fillPrimaryAddress, &QPushButton::clicked, this, &XMRigWidget::onUsePrimaryAddressClicked); - // Xmrig passwd + // Password auto password = m_ctx->wallet->getCacheAttribute("feather.xmrig_password"); - if(!password.isEmpty()) { + if (!password.isEmpty()) { ui->lineEdit_password->setText(password); } else { ui->lineEdit_password->setText("featherwallet"); m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); } + connect(ui->lineEdit_password, &QLineEdit::textChanged, [=]() { + m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); + }); + + // [Status] tab + connect(ui->btn_start, &QPushButton::clicked, this, &XMRigWidget::onStartClicked); + connect(ui->btn_stop, &QPushButton::clicked, this, &XMRigWidget::onStopClicked); + connect(ui->btn_clear, &QPushButton::clicked, this, &XMRigWidget::onClearClicked); + + ui->btn_stop->setEnabled(false); + ui->check_autoscroll->setChecked(true); + ui->label_status->setTextInteractionFlags(Qt::TextSelectableByMouse); + ui->label_status->hide(); + + this->printConsoleInfo(); } bool XMRigWidget::isMining() { @@ -130,14 +136,11 @@ bool XMRigWidget::isMining() { void XMRigWidget::onWalletClosed() { this->onStopClicked(); - this->onClearClicked(); - ui->lineEdit_password->setText(""); - ui->lineEdit_address->setText(""); } void XMRigWidget::onThreadsValueChanged(int threads) { - m_threads = threads; - ui->label_threads->setText(QString("CPU threads: %1").arg(m_threads)); + config()->set(Config::xmrigThreads, threads); + ui->label_threads->setText(QString("CPU threads: %1").arg(threads)); } void XMRigWidget::onPoolChanged(const QString &pool) { @@ -146,10 +149,15 @@ void XMRigWidget::onPoolChanged(const QString &pool) { } } +void XMRigWidget::onXMRigElevationChanged(bool elevated) { + config()->set(Config::xmrigElevated, elevated); +} + void XMRigWidget::onBrowseClicked() { - QString fileName = QFileDialog::getOpenFileName( - this, "Path to XMRig executable", QDir::homePath()); - if (fileName.isEmpty()) return; + QString fileName = QFileDialog::getOpenFileName(this, "Path to XMRig executable", QDir::homePath()); + if (fileName.isEmpty()) { + return; + } config()->set(Config::xmrigPath, fileName); ui->lineEdit_path->setText(fileName); } @@ -158,47 +166,50 @@ void XMRigWidget::onClearClicked() { ui->console->clear(); } +void XMRigWidget::onUsePrimaryAddressClicked() { + ui->lineEdit_address->setText(m_ctx->wallet->address(0, 0)); +} + void XMRigWidget::onStartClicked() { - QString xmrigPath; - bool solo = ui->check_solo->isChecked(); - xmrigPath = config()->get(Config::xmrigPath).toString(); + QString xmrigPath = config()->get(Config::xmrigPath).toString(); + if (!this->checkXMRigPath()) { + return; + } + + QString address = [this](){ + if (ui->combo_miningMode->currentIndex() == Config::MiningMode::Pool) { + return config()->get(Config::xmrigPool).toString(); + } else { + return ui->lineEdit_solo->text().trimmed(); + } + }(); + if (address.isEmpty()) { + ui->console->appendPlainText("No pool or node address set. Please configure on the Settings tab."); + return; + } // username is receiving address usually auto username = m_ctx->wallet->getCacheAttribute("feather.xmrig_username"); auto password = m_ctx->wallet->getCacheAttribute("feather.xmrig_password"); - if(username.isEmpty()) { - QString err = "Please specify a receiving address on the Settings screen"; - ui->console->appendPlainText(err); - QMessageBox::warning(this, "Error", err); + if (username.isEmpty()) { + ui->console->appendPlainText("Please specify a receiving address on the Settings screen."); return; } - QString address; - if(solo) - address = ui->lineEdit_solo->text().trimmed(); - else - address = config()->get(Config::xmrigPool).toString(); - - if(address.contains("cryptonote.social") && !username.contains(".")) { + if (address.contains("cryptonote.social") && !username.contains(".")) { // cryptonote social requires ., we'll just grab a few chars from primary addy username = QString("%1.%2").arg(username, m_ctx->wallet->address(0, 0).mid(0, 6)); } - m_XMRig->start(xmrigPath, m_threads, address, username, password, ui->relayTor->isChecked(), ui->check_tls->isChecked()); - ui->btn_start->setEnabled(false); - ui->btn_stop->setEnabled(true); - m_isMining = true; - emit miningStarted(); + int threads = ui->threadSlider->value(); + + m_XMRig->start(xmrigPath, threads, address, username, password, ui->relayTor->isChecked(), ui->check_tls->isChecked(), + ui->radio_elevateYes->isChecked()); } void XMRigWidget::onStopClicked() { m_XMRig->stop(); - ui->btn_start->setEnabled(true); - ui->btn_stop->setEnabled(false); - ui->label_status->hide(); - m_isMining = false; - emit miningEnded(); } void XMRigWidget::onProcessOutput(const QByteArray &data) { @@ -216,9 +227,7 @@ void XMRigWidget::onProcessError(const QString &msg) { ui->console->appendPlainText("\n" + msg); ui->btn_start->setEnabled(true); ui->btn_stop->setEnabled(false); - ui->label_status->hide(); - m_isMining = false; - emit miningEnded(); + this->setMiningStopped(); } void XMRigWidget::onHashrate(const QString &hashrate) { @@ -307,15 +316,78 @@ void XMRigWidget::updatePools() { } } -void XMRigWidget::onSoloChecked(int state) { - if(state == 2) { - ui->poolFrame->hide(); - ui->soloFrame->show(); - ui->check_tls->setChecked(false); +void XMRigWidget::printConsoleInfo() { + ui->console->appendPlainText(QString("Detected %1 CPU threads.").arg(QThread::idealThreadCount())); + if (this->checkXMRigPath()) { + QString path = config()->get(Config::xmrigPath).toString(); + ui->console->appendPlainText(QString("XMRig path set to %1").arg(path)); } - else { +} + +void XMRigWidget::onMiningModeChanged(int mode) { + config()->set(Config::miningMode, mode); + + if (mode == Config::MiningMode::Pool) { ui->poolFrame->show(); ui->soloFrame->hide(); + ui->label_poolNodeAddress->setText("Pool address:"); + ui->check_tls->setChecked(true); + } else { // Solo mining + ui->poolFrame->hide(); + ui->soloFrame->show(); + ui->label_poolNodeAddress->setText("Node address:"); + ui->check_tls->setChecked(false); + } +} + +void XMRigWidget::onNetworkTLSToggled(bool checked) { + config()->set(Config::xmrigNetworkTLS, checked); +} + +void XMRigWidget::onNetworkTorToggled(bool checked) { + config()->set(Config::xmrigNetworkTor, checked); +} + +void XMRigWidget::onXMRigStateChanged(QProcess::ProcessState state) { + if (state == QProcess::ProcessState::Starting) { + ui->btn_start->setEnabled(false); + ui->btn_stop->setEnabled(false); + this->setMiningStarted(); + } + else if (state == QProcess::ProcessState::Running) { + ui->btn_start->setEnabled(false); + ui->btn_stop->setEnabled(true); + this->setMiningStarted(); + } + else if (state == QProcess::ProcessState::NotRunning) { + ui->btn_start->setEnabled(true); // todo + ui->btn_stop->setEnabled(false); + ui->label_status->hide(); + this->setMiningStopped(); + } +} + +void XMRigWidget::setMiningStopped() { + m_isMining = false; + emit miningEnded(); +} + +void XMRigWidget::setMiningStarted() { + m_isMining = true; + emit miningStarted(); +} + +bool XMRigWidget::checkXMRigPath() { + QString path = config()->get(Config::xmrigPath).toString(); + + if (path.isEmpty()) { + ui->console->appendPlainText("No XMRig executable is set. Please configure on the Settings tab."); + return false; + } else if (!Utils::fileExists(path)) { + ui->console->appendPlainText("Invalid path to XMRig executable detected. Please reconfigure on the Settings tab."); + return false; + } else { + return true; } } diff --git a/src/widgets/XMRigWidget.h b/src/widgets/XMRigWidget.h index d4b9fffc..bbe349f6 100644 --- a/src/widgets/XMRigWidget.h +++ b/src/widgets/XMRigWidget.h @@ -32,17 +32,22 @@ public slots: void onStartClicked(); void onStopClicked(); void onClearClicked(); + void onUsePrimaryAddressClicked(); void onDownloads(const QJsonObject &data); void linkClicked(); void onProcessError(const QString &msg); void onProcessOutput(const QByteArray &msg); void onHashrate(const QString &hashrate); - void onSoloChecked(int state); + void onMiningModeChanged(int mode); + void onNetworkTLSToggled(bool checked); + void onNetworkTorToggled(bool checked); + void onXMRigStateChanged(QProcess::ProcessState state); private slots: void onBrowseClicked(); void onThreadsValueChanged(int date); void onPoolChanged(const QString &pool); + void onXMRigElevationChanged(bool elevated); signals: void miningStarted(); @@ -51,15 +56,18 @@ signals: private: void showContextMenu(const QPoint &pos); void updatePools(); + void printConsoleInfo(); + void setMiningStopped(); + void setMiningStarted(); + bool checkXMRigPath(); QScopedPointer ui; QSharedPointer m_ctx; - XmRig * m_XMRig; + XmRig *m_XMRig; QStandardItemModel *m_model; QMenu *m_contextMenu; bool m_isMining = false; - int m_threads; QStringList m_urls; QStringList m_defaultPools{"pool.xmr.pt:9000", "pool.supportxmr.com:9000", "mine.xmrpool.net:443", "xmrpool.eu:9999", "xmr-eu1.nanopool.org:14433", "pool.minexmr.com:6666", "us-west.minexmr.com:6666", "monerohash.com:9999", "cryptonote.social:5555", "cryptonote.social:5556"}; }; diff --git a/src/widgets/XMRigWidget.ui b/src/widgets/XMRigWidget.ui index 8b1cb9f1..512a0ca3 100644 --- a/src/widgets/XMRigWidget.ui +++ b/src/widgets/XMRigWidget.ui @@ -33,7 +33,7 @@ - Mining + Status @@ -130,247 +130,16 @@ - - - - - - - - Threads: - - - - - - - - - Qt::Horizontal - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - TLS - - - - - - - Tor - - - - - - - Solo mine - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - - - Pool - - - - - - - - 0 - 0 - - - - - - - - Configure - - - - - - - Qt::Horizontal - - - - 414 - 20 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - - - Node address - - - - - - - 127.0.0.1:18081 - - - - - - - Qt::Horizontal - - - - 173 - 20 - - - - - - - - - - + - Receiving address + XMRig executable: - - - - - - Password (optional) - - - - - - - - - - XMRig executable - - - - @@ -388,50 +157,272 @@ + + + + Run as admin/root: + + + + + + + + + Yes + + + + + + + No + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + CPU threads: + + + + + + + Qt::Horizontal + + + + + + + Mining mode: + + + + + + + + + + Pool mining + + + + + Solo mining + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Pool/node address: + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Configure + + + + + + + Qt::Horizontal + + + + 414 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + + + 127.0.0.1:18081 + + + + + + + Qt::Horizontal + + + + 173 + 20 + + + + + + + + + + + + + Network settings: + + + + + + + + + Secure connection (TLS) + + + + + + + Connect via Tor + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Receiving address: + + + + + + + + + + + + Use primary address + + + + + + + + + Password (optional): + + + + + + - - - - Qt::Horizontal - - - QSizePolicy::Maximum - - - - 24 - 20 - - - - - - - - - - logoimg - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - -