settings rework, proxy support

This commit is contained in:
tobtoht 2023-02-11 18:11:21 +01:00
parent 076903c586
commit f3c8e116c0
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
77 changed files with 3301 additions and 1149 deletions

2
monero

@ -1 +1 @@
Subproject commit 9a6dad7cf4958f795b41517badf93f1c6c80b313 Subproject commit f7705c2c6740699a3fa47895473f79c006624559

View file

@ -37,6 +37,8 @@
#include "utils/Updater.h" #include "utils/Updater.h"
#include "utils/WebsocketNotifier.h" #include "utils/WebsocketNotifier.h"
//#include "misc_log_ex.h"
MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent) MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, ui(new Ui::MainWindow) , ui(new Ui::MainWindow)
@ -46,6 +48,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
ui->setupUi(this); ui->setupUi(this);
qDebug() << "Platform tag: " << this->getPlatformTag(); qDebug() << "Platform tag: " << this->getPlatformTag();
// MCWARNING("feather", "Platform tag: " << this->getPlatformTag().toStdString());
// Ensure the destructor is called after closeEvent() // Ensure the destructor is called after closeEvent()
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
@ -78,7 +81,19 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged); connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
this->onWebsocketStatusChanged(!config()->get(Config::disableWebsocket).toBool()); this->onWebsocketStatusChanged(!config()->get(Config::disableWebsocket).toBool());
connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged); connect(m_windowManager, &WindowManager::proxySettingsChanged, [this]{
m_ctx->onProxySettingsChanged();
this->onProxySettingsChanged();
});
connect(m_windowManager, &WindowManager::updateBalance, m_ctx.data(), &AppContext::updateBalance);
connect(m_windowManager, &WindowManager::offlineMode, [this](bool offline){
if (!m_ctx->wallet) {
return;
}
m_ctx->wallet->setOffline(offline);
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Disconnected);
});
connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged);
this->onTorConnectionStateChanged(torManager()->torConnected); this->onTorConnectionStateChanged(torManager()->torConnected);
@ -113,10 +128,6 @@ void MainWindow::initStatusBar() {
this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}"); this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}");
#endif #endif
#if defined(Q_OS_MACOS)
this->patchStylesheetMac();
#endif
this->statusBar()->setFixedHeight(30); this->statusBar()->setFixedHeight(30);
m_statusLabelStatus = new QLabel("Idle", this); m_statusLabelStatus = new QLabel("Idle", this);
@ -143,9 +154,10 @@ void MainWindow::initStatusBar() {
m_statusBtnConnectionStatusIndicator = new StatusBarButton(icons()->icon("status_disconnected.svg"), "Connection status", this); m_statusBtnConnectionStatusIndicator = new StatusBarButton(icons()->icon("status_disconnected.svg"), "Connection status", this);
connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, [this](){ connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, [this](){
this->onShowSettingsPage(2); this->onShowSettingsPage(SettingsNew::Pages::NETWORK);
}); });
this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator); this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator);
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Disconnected);
m_statusAccountSwitcher = new StatusBarButton(icons()->icon("change_account.png"), "Account switcher", this); m_statusAccountSwitcher = new StatusBarButton(icons()->icon("change_account.png"), "Account switcher", this);
connect(m_statusAccountSwitcher, &StatusBarButton::clicked, this, &MainWindow::showAccountSwitcherDialog); connect(m_statusAccountSwitcher, &StatusBarButton::clicked, this, &MainWindow::showAccountSwitcherDialog);
@ -163,9 +175,10 @@ void MainWindow::initStatusBar() {
connect(m_statusBtnSeed, &StatusBarButton::clicked, this, &MainWindow::showSeedDialog); connect(m_statusBtnSeed, &StatusBarButton::clicked, this, &MainWindow::showSeedDialog);
this->statusBar()->addPermanentWidget(m_statusBtnSeed); this->statusBar()->addPermanentWidget(m_statusBtnSeed);
m_statusBtnTor = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Tor settings", this); m_statusBtnProxySettings = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Proxy settings", this);
connect(m_statusBtnTor, &StatusBarButton::clicked, this, &MainWindow::menuTorClicked); connect(m_statusBtnProxySettings, &StatusBarButton::clicked, this, &MainWindow::menuProxySettingsClicked);
this->statusBar()->addPermanentWidget(m_statusBtnTor); this->statusBar()->addPermanentWidget(m_statusBtnProxySettings);
this->onProxySettingsChanged();
m_statusBtnHwDevice = new StatusBarButton(this->hardwareDevicePairedIcon(), this->getHardwareDevice(), this); m_statusBtnHwDevice = new StatusBarButton(this->hardwareDevicePairedIcon(), this->getHardwareDevice(), this);
connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked); connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked);
@ -417,12 +430,12 @@ void MainWindow::initWalletContext() {
connect(m_ctx.get(), &AppContext::keysCorrupted, this, &MainWindow::onKeysCorrupted); connect(m_ctx.get(), &AppContext::keysCorrupted, this, &MainWindow::onKeysCorrupted);
connect(m_ctx.get(), &AppContext::selectedInputsChanged, this, &MainWindow::onSelectedInputsChanged); connect(m_ctx.get(), &AppContext::selectedInputsChanged, this, &MainWindow::onSelectedInputsChanged);
// Nodes
connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage);
connect(m_ctx->nodes, &Nodes::WSNodeExhausted, this, &MainWindow::showWSNodeExhaustedMessage);
// Wallet // Wallet
connect(m_ctx->wallet, &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged); connect(m_ctx->wallet, &Wallet::connectionStatusChanged, [this](int status){
// Order is important, first inform UI about a potential disconnect, then reconnect
this->onConnectionStatusChanged(status);
this->m_ctx->nodes->autoConnect();
});
connect(m_ctx->wallet, &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle); connect(m_ctx->wallet, &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle);
connect(m_ctx->wallet, &Wallet::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded); connect(m_ctx->wallet, &Wallet::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded);
} }
@ -521,7 +534,9 @@ void MainWindow::onWalletOpened() {
m_ctx->nodes->connectToNode(); m_ctx->nodes->connectToNode();
m_updateBytes.start(250); m_updateBytes.start(250);
if (config()->get(Config::writeRecentlyOpenedWallets).toBool()) {
this->addToRecentlyOpened(m_ctx->wallet->cachePath()); this->addToRecentlyOpened(m_ctx->wallet->cachePath());
}
} }
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) { void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
@ -593,6 +608,24 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
#endif #endif
} }
void MainWindow::onProxySettingsChanged() {
int proxy = config()->get(Config::proxy).toInt();
if (proxy == Config::Proxy::Tor) {
this->onTorConnectionStateChanged(torManager()->torConnected);
m_statusBtnProxySettings->show();
return;
}
if (proxy == Config::Proxy::i2p) {
m_statusBtnProxySettings->setIcon(icons()->icon("i2p.png"));
m_statusBtnProxySettings->show();
return;
}
m_statusBtnProxySettings->hide();
}
void MainWindow::onSynchronized() { void MainWindow::onSynchronized() {
this->updateNetStats(); this->updateNetStats();
this->setStatusText("Synchronized"); this->setStatusText("Synchronized");
@ -617,6 +650,10 @@ void MainWindow::onConnectionStatusChanged(int status)
// Update connection info in status bar. // Update connection info in status bar.
QIcon icon; QIcon icon;
if (config()->get(Config::offlineMode).toBool()) {
icon = icons()->icon("status_offline.svg");
this->setStatusText("Offline");
} else {
switch(status){ switch(status){
case Wallet::ConnectionStatus_Disconnected: case Wallet::ConnectionStatus_Disconnected:
icon = icons()->icon("status_disconnected.svg"); icon = icons()->icon("status_disconnected.svg");
@ -640,6 +677,7 @@ void MainWindow::onConnectionStatusChanged(int status)
icon = icons()->icon("status_disconnected.svg"); icon = icons()->icon("status_disconnected.svg");
break; break;
} }
}
m_statusBtnConnectionStatusIndicator->setIcon(icon); m_statusBtnConnectionStatusIndicator->setIcon(icon);
} }
@ -826,13 +864,6 @@ void MainWindow::showViewOnlyDialog() {
dialog.exec(); dialog.exec();
} }
void MainWindow::menuTorClicked() {
auto *dialog = new TorInfoDialog(m_ctx, this);
connect(dialog, &TorInfoDialog::torSettingsChanged, m_windowManager, &WindowManager::onTorSettingsChanged);
dialog->exec();
dialog->deleteLater();
}
void MainWindow::menuHwDeviceClicked() { void MainWindow::menuHwDeviceClicked() {
QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice())); QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
} }
@ -854,21 +885,17 @@ void MainWindow::menuWalletCloseClicked() {
this->close(); this->close();
} }
void MainWindow::menuProxySettingsClicked() {
this->menuSettingsClicked(true);
}
void MainWindow::menuAboutClicked() { void MainWindow::menuAboutClicked() {
AboutDialog dialog{this}; AboutDialog dialog{this};
dialog.exec(); dialog.exec();
} }
void MainWindow::menuSettingsClicked() { void MainWindow::menuSettingsClicked(bool showProxyTab) {
Settings settings{m_ctx, this}; m_windowManager->showSettings(m_ctx, this, showProxyTab);
for (const auto &widget: m_tickerWidgets) {
connect(&settings, &Settings::preferredFiatCurrencyChanged, widget, &TickerWidgetBase::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);
connect(&settings, &Settings::websocketStatusChanged, m_windowManager, &WindowManager::onWebsocketStatusChanged);
settings.exec();
} }
void MainWindow::menuSignVerifyClicked() { void MainWindow::menuSignVerifyClicked() {
@ -887,13 +914,8 @@ void MainWindow::onShowSettingsPage(int page) {
} }
void MainWindow::skinChanged(const QString &skinName) { void MainWindow::skinChanged(const QString &skinName) {
m_windowManager->changeSkin(skinName);
ColorScheme::updateFromWidget(this); ColorScheme::updateFromWidget(this);
this->updateWidgetIcons(); this->updateWidgetIcons();
#if defined(Q_OS_MACOS)
this->patchStylesheetMac();
#endif
} }
void MainWindow::updateWidgetIcons() { void MainWindow::updateWidgetIcons() {
@ -949,6 +971,17 @@ void MainWindow::closeEvent(QCloseEvent *event) {
event->accept(); event->accept();
} }
void MainWindow::changeEvent(QEvent* event)
{
if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) {
if (config()->get(Config::lockOnMinimize).toBool()) {
this->lockWallet();
}
} else {
QMainWindow::changeEvent(event);
}
}
void MainWindow::donateButtonClicked() { void MainWindow::donateButtonClicked() {
m_sendWidget->fill(constants::donationAddress, "Donation to the Feather development team"); m_sendWidget->fill(constants::donationAddress, "Donation to the Feather development team");
ui->tabWidget->setCurrentIndex(Tabs::SEND); ui->tabWidget->setCurrentIndex(Tabs::SEND);
@ -1075,22 +1108,6 @@ void MainWindow::showAddressChecker() {
} }
} }
void MainWindow::showNodeExhaustedMessage() {
// Spawning dialogs inside a lambda can cause system freezes on linux so we have to do it this way ¯\_(ツ)_/¯
auto msg = "Feather is in 'custom node connection mode' but could not "
"find an eligible node to connect to. Please go to Settings->Node "
"and enter a node manually.";
QMessageBox::warning(this, "Could not connect to a node", msg);
}
void MainWindow::showWSNodeExhaustedMessage() {
auto msg = "Feather is in 'automatic node connection mode' but the "
"websocket server returned no available nodes. Please go to Settings->Node "
"and enter a node manually.";
QMessageBox::warning(this, "Could not connect to a node", msg);
}
void MainWindow::exportKeyImages() { void MainWindow::exportKeyImages() {
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)"); 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.isEmpty()) return;
@ -1343,14 +1360,39 @@ void MainWindow::bringToFront() {
activateWindow(); activateWindow();
} }
void MainWindow::onPreferredFiatCurrencyChanged() {
for (const auto &widget : m_tickerWidgets) {
widget->updateDisplay();
}
m_balanceTickerWidget->updateDisplay();
m_sendWidget->onPreferredFiatCurrencyChanged();
}
void MainWindow::onHideUpdateNotifications(bool hidden) {
if (hidden) {
m_statusUpdateAvailable->hide();
}
else if (m_updater->state == Updater::State::UPDATE_AVAILABLE) {
m_statusUpdateAvailable->show();
}
}
void MainWindow::onTorConnectionStateChanged(bool connected) { void MainWindow::onTorConnectionStateChanged(bool connected) {
if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
return;
}
if (connected) if (connected)
m_statusBtnTor->setIcon(icons()->icon("tor_logo.png")); m_statusBtnProxySettings->setIcon(icons()->icon("tor_logo.png"));
else else
m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png")); m_statusBtnProxySettings->setIcon(icons()->icon("tor_logo_disabled.png"));
} }
void MainWindow::showUpdateNotification() { void MainWindow::showUpdateNotification() {
if (config()->get(Config::hideUpdateNotifications).toBool()) {
return;
}
QString versionDisplay{m_updater->version}; QString versionDisplay{m_updater->version};
versionDisplay.replace("beta", "Beta"); versionDisplay.replace("beta", "Beta");
QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay); QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay);
@ -1602,14 +1644,6 @@ bool MainWindow::verifyPassword(bool sensitive) {
return true; 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::userActivity() { void MainWindow::userActivity() {
m_userLastActive = QDateTime::currentSecsSinceEpoch(); m_userLastActive = QDateTime::currentSecsSinceEpoch();
} }

View file

@ -12,6 +12,7 @@
#include "components.h" #include "components.h"
#include "CalcWindow.h" #include "CalcWindow.h"
#include "SettingsDialog.h" #include "SettingsDialog.h"
#include "SettingsNewDialog.h"
#include "dialog/AboutDialog.h" #include "dialog/AboutDialog.h"
#include "dialog/AccountSwitcherDialog.h" #include "dialog/AccountSwitcherDialog.h"
@ -102,21 +103,28 @@ public:
void showOrHide(); void showOrHide();
void bringToFront(); void bringToFront();
public slots:
void onPreferredFiatCurrencyChanged();
void onHideUpdateNotifications(bool hidden);
signals: signals:
void closed(); void closed();
protected:
void changeEvent(QEvent* event) override;
private slots: private slots:
// TODO: use a consistent naming convention for slots // TODO: use a consistent naming convention for slots
// Menu // Menu
void menuOpenClicked(); void menuOpenClicked();
void menuNewRestoreClicked(); void menuNewRestoreClicked();
void menuQuitClicked(); void menuQuitClicked();
void menuSettingsClicked(); void menuSettingsClicked(bool showProxytab = false);
void menuAboutClicked(); void menuAboutClicked();
void menuSignVerifyClicked(); void menuSignVerifyClicked();
void menuVerifyTxProof(); void menuVerifyTxProof();
void menuWalletCloseClicked(); void menuWalletCloseClicked();
void menuTorClicked(); void menuProxySettingsClicked();
void menuToggleTabVisible(const QString &key); void menuToggleTabVisible(const QString &key);
void menuClearHistoryClicked(); void menuClearHistoryClicked();
void onExportHistoryCSV(bool checked); void onExportHistoryCSV(bool checked);
@ -184,6 +192,7 @@ private slots:
void tryStoreWallet(); void tryStoreWallet();
void onWebsocketStatusChanged(bool enabled); void onWebsocketStatusChanged(bool enabled);
void showUpdateNotification(); void showUpdateNotification();
void onProxySettingsChanged();
private: private:
friend WindowManager; friend WindowManager;
@ -199,8 +208,6 @@ private:
void saveGeo(); void saveGeo();
void restoreGeo(); void restoreGeo();
void showDebugInfo(); void showDebugInfo();
void showNodeExhaustedMessage();
void showWSNodeExhaustedMessage();
void createUnsignedTxDialog(UnsignedTransaction *tx); void createUnsignedTxDialog(UnsignedTransaction *tx);
void updatePasswordIcon(); void updatePasswordIcon();
void updateNetStats(); void updateNetStats();
@ -217,7 +224,6 @@ private:
void updateRecentlyOpenedMenu(); void updateRecentlyOpenedMenu();
void updateWidgetIcons(); void updateWidgetIcons();
bool verifyPassword(bool sensitive = true); bool verifyPassword(bool sensitive = true);
void patchStylesheetMac();
void fillSendTab(const QString &address, const QString &description); void fillSendTab(const QString &address, const QString &description);
void userActivity(); void userActivity();
void checkUserActivity(); void checkUserActivity();
@ -264,7 +270,7 @@ private:
StatusBarButton *m_statusBtnPassword; StatusBarButton *m_statusBtnPassword;
StatusBarButton *m_statusBtnPreferences; StatusBarButton *m_statusBtnPreferences;
StatusBarButton *m_statusBtnSeed; StatusBarButton *m_statusBtnSeed;
StatusBarButton *m_statusBtnTor; StatusBarButton *m_statusBtnProxySettings;
StatusBarButton *m_statusBtnHwDevice; StatusBarButton *m_statusBtnHwDevice;
QSignalMapper *m_tabShowHideSignalMapper; QSignalMapper *m_tabShowHideSignalMapper;

View file

@ -160,7 +160,7 @@ void Settings::setupPrivacyTab() {
} }
void Settings::setupNodeTab() { void Settings::setupNodeTab() {
ui->nodeWidget->setupUI(m_ctx); // ui->nodeWidget->setupUI(m_ctx);
connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged); connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged);
connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode)); connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
} }

390
src/SettingsNewDialog.cpp Normal file
View file

@ -0,0 +1,390 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "SettingsNewDialog.h"
#include "ui_SettingsNewDialog.h"
#include <QCloseEvent>
#include <QDesktopServices>
#include <QFileDialog>
#include <QMessageBox>
#include "utils/Icons.h"
#include "utils/WebsocketNotifier.h"
#include "widgets/NetworkProxyWidget.h"
SettingsNew::SettingsNew(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent)
, ui(new Ui::SettingsNew)
, m_ctx(std::move(ctx))
{
ui->setupUi(this);
this->setupAppearanceTab();
this->setupNetworkTab();
this->setupStorageTab();
this->setupDisplayTab();
this->setupMemoryTab();
this->setupTransactionsTab();
this->setupMiscTab();
connect(ui->selector, &QListWidget::currentItemChanged, [this](QListWidgetItem *current, QListWidgetItem *previous){
ui->stackedWidget->setCurrentIndex(current->type());
});
ui->selector->setSelectionMode(QAbstractItemView::SingleSelection);
ui->selector->setSelectionBehavior(QAbstractItemView::SelectRows);
// ui->selector->setCurrentRow(config()->get(Config::lastSettingsPage).toInt());
new QListWidgetItem(icons()->icon("interface_32px.png"), "Appearance", ui->selector, Pages::APPEARANCE);
new QListWidgetItem(icons()->icon("nw_32px.png"), "Network", ui->selector, Pages::NETWORK);
new QListWidgetItem(icons()->icon("hd_32px.png"), "Storage", ui->selector, Pages::STORAGE);
new QListWidgetItem(icons()->icon("vrdp_32px.png"), "Display", ui->selector, Pages::DISPLAY);
// new QListWidgetItem(icons()->icon("chipset_32px.png"), "Memory", ui->selector, Pages::MEMORY);
new QListWidgetItem(icons()->icon("file_manager_32px.png"), "Transactions", ui->selector, Pages::TRANSACTIONS);
new QListWidgetItem(icons()->icon("settings_disabled_32px.png"), "Misc", ui->selector, Pages::MISC);
ui->selector->setFixedWidth(ui->selector->sizeHintForColumn(0) + ui->selector->frameWidth() + 5);
// for (int i = 0; i < ui->selector->count(); ++i) {
// QListWidgetItem *item = ui->selector->item(i);
// item->setSizeHint(QSize(item->sizeHint().width(), ui->selector->iconSize().height() + 8));
// }
connect(ui->closeButton, &QDialogButtonBox::accepted, [this]{
// Save Proxy settings
bool isProxySettingChanged = ui->proxyWidget->isProxySettingsChanged();
ui->proxyWidget->setProxySettings();
if (isProxySettingChanged) {
emit proxySettingsChanged();
}
config()->set(Config::lastSettingsPage, ui->selector->currentRow());
this->close();
});
this->setSelection(config()->get(Config::lastSettingsPage).toInt());
this->adjustSize();
}
void SettingsNew::setupAppearanceTab() {
// [Theme]
this->setupThemeComboBox();
auto settingsTheme = config()->get(Config::skin).toString();
if (m_themes.contains(settingsTheme)) {
ui->comboBox_theme->setCurrentIndex(m_themes.indexOf(settingsTheme));
}
connect(ui->comboBox_theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
emit skinChanged(m_themes.at(pos));
});
// [Amount precision]
for (int i = 0; i <= 12; i++) {
ui->comboBox_amountPrecision->addItem(QString::number(i));
}
ui->comboBox_amountPrecision->setCurrentIndex(config()->get(Config::amountPrecision).toInt());
connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::amountPrecision, pos);
emit updateBalance();
});
// [Date format]
QDateTime now = QDateTime::currentDateTime();
for (const auto & format : m_dateFormats) {
ui->comboBox_dateFormat->addItem(now.toString(format));
}
QString dateFormatSetting = config()->get(Config::dateFormat).toString();
if (m_dateFormats.contains(dateFormatSetting)) {
ui->comboBox_dateFormat->setCurrentIndex(m_dateFormats.indexOf(dateFormatSetting));
}
connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::dateFormat, m_dateFormats.at(pos));
});
// [Time format]
for (const auto & format : m_timeFormats) {
ui->comboBox_timeFormat->addItem(now.toString(format));
}
QString timeFormatSetting = config()->get(Config::timeFormat).toString();
if (m_timeFormats.contains(timeFormatSetting)) {
ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting));
}
connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::timeFormat, m_timeFormats.at(pos));
});
// [Balance display]
ui->comboBox_balanceDisplay->setCurrentIndex(config()->get(Config::balanceDisplay).toInt());
connect(ui->comboBox_balanceDisplay, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::balanceDisplay, pos);
emit updateBalance();
});
// [Preferred fiat currency]
QStringList availableFiatCurrencies = appData()->prices.rates.keys();
for (const auto &currency : availableFiatCurrencies) {
ui->comboBox_fiatCurrency->addItem(currency);
}
QStringList fiatCurrencies;
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() && fiatCurrencies.contains(preferredFiatCurrency)) {
ui->comboBox_fiatCurrency->setCurrentText(preferredFiatCurrency);
}
connect(ui->comboBox_fiatCurrency, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
QString selection = ui->comboBox_fiatCurrency->itemText(index);
config()->set(Config::preferredFiatCurrency, selection);
emit preferredFiatCurrencyChanged(selection);
});
}
void SettingsNew::setupNetworkTab() {
// Node
if (m_ctx) {
ui->nodeWidget->setupUI(m_ctx->nodes);
connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged);
connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
} else {
m_nodes = new Nodes(this);
ui->nodeWidget->setupUI(m_nodes);
ui->nodeWidget->setCanConnect(false);
}
// Proxy
connect(ui->proxyWidget, &NetworkProxyWidget::proxySettingsChanged, this, &SettingsNew::onProxySettingsChanged);
// Websocket
// [Obtain third-party data]
ui->checkBox_enableWebsocket->setChecked(!config()->get(Config::disableWebsocket).toBool());
connect(ui->checkBox_enableWebsocket, &QCheckBox::toggled, [this](bool checked){
config()->set(Config::disableWebsocket, !checked);
this->enableWebsocket(checked);
});
// Overview
ui->checkBox_offlineMode->setChecked(config()->get(Config::offlineMode).toBool());
connect(ui->checkBox_offlineMode, &QCheckBox::toggled, [this](bool checked){
config()->set(Config::offlineMode, checked);
emit offlineMode(checked);
this->enableWebsocket(!checked);
});
}
void SettingsNew::setupStorageTab() {
// Paths
ui->lineEdit_defaultWalletDir->setText(config()->get(Config::walletDirectory).toString());
ui->lineEdit_configDir->setText(Config::defaultConfigDir().path());
ui->lineEdit_applicationDir->setText(Utils::applicationPath());
bool portableMode = Utils::isPortableMode();
if (portableMode) {
ui->btn_browseDefaultWalletDir->setDisabled(true);
ui->btn_browseDefaultWalletDir->setToolTip("Portable Mode enabled");
}
connect(ui->btn_browseDefaultWalletDir, &QPushButton::clicked, [this]{
QString walletDirOld = config()->get(Config::walletDirectory).toString();
QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", walletDirOld, QFileDialog::ShowDirsOnly);
if (walletDir.isEmpty())
return;
config()->set(Config::walletDirectory, walletDir);
ui->lineEdit_defaultWalletDir->setText(walletDir);
});
connect(ui->btn_openWalletDir, &QPushButton::clicked, []{
QDesktopServices::openUrl(QUrl::fromLocalFile(config()->get(Config::walletDirectory).toString()));
});
connect(ui->btn_openConfigDir, &QPushButton::clicked, []{
QDesktopServices::openUrl(QUrl::fromLocalFile(Config::defaultConfigDir().path()));
});
ui->frame_portableMode->setVisible(portableMode);
// Logging
// [Write log files to disk]
ui->checkBox_enableLogging->setChecked(!config()->get(Config::disableLogging).toBool());
connect(ui->checkBox_enableLogging, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::disableLogging, !toggled);
WalletManager::instance()->setLogLevel(toggled ? config()->get(Config::logLevel).toInt() : -1);
});
// [Log level]
ui->comboBox_logLevel->setCurrentIndex(config()->get(Config::logLevel).toInt());
connect(ui->comboBox_logLevel, &QComboBox::currentIndexChanged, [](int index){
config()->set(Config::logLevel, index);
if (!config()->get(Config::disableLogging).toBool()) {
WalletManager::instance()->setLogLevel(index);
}
});
// [Write stack trace to disk on crash]
ui->checkBox_writeStackTraceToDisk->setChecked(config()->get(Config::writeStackTraceToDisk).toBool());
connect(ui->checkBox_writeStackTraceToDisk, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::writeStackTraceToDisk, toggled);
});
// [Open log file]
connect(ui->btn_openLogFile, &QPushButton::clicked, []{
QDesktopServices::openUrl(QUrl::fromLocalFile(Config::defaultConfigDir().path() + "/libwallet.log"));
});
// Misc
// [Save recently opened wallet to config file]
ui->checkBox_writeRecentlyOpened->setChecked(config()->get(Config::writeRecentlyOpenedWallets).toBool());
connect(ui->checkBox_writeRecentlyOpened, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::writeRecentlyOpenedWallets, toggled);
if (!toggled) {
config()->set(Config::recentlyOpenedWallets, {});
}
});
}
void SettingsNew::setupDisplayTab() {
// [Hide balance]
ui->checkBox_hideBalance->setChecked(config()->get(Config::hideBalance).toBool());
connect(ui->checkBox_hideBalance, &QCheckBox::toggled, [this](bool toggled){
config()->set(Config::hideBalance, toggled);
emit updateBalance();
});
// [Hide update notifications]
ui->checkBox_hideUpdateNotifications->setChecked(config()->get(Config::hideUpdateNotifications).toBool());
connect(ui->checkBox_hideUpdateNotifications, &QCheckBox::toggled, [this](bool toggled){
config()->set(Config::hideUpdateNotifications, toggled);
emit hideUpdateNotifications(toggled);
});
// [Hide transaction notifications]
ui->checkBox_hideTransactionNotifications->setChecked(config()->get(Config::hideNotifications).toBool());
connect(ui->checkBox_hideTransactionNotifications, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::hideNotifications, toggled);
});
// [Warn before opening external link]
ui->checkBox_warnOnExternalLink->setChecked(config()->get(Config::warnOnExternalLink).toBool());
connect(ui->checkBox_warnOnExternalLink, &QCheckBox::clicked, this, [this]{
bool state = ui->checkBox_warnOnExternalLink->isChecked();
config()->set(Config::warnOnExternalLink, state);
});
// [Lock wallet on inactivity]
ui->checkBox_lockOnInactivity->setChecked(config()->get(Config::inactivityLockEnabled).toBool());
ui->spinBox_lockOnInactivity->setValue(config()->get(Config::inactivityLockTimeout).toInt());
connect(ui->checkBox_lockOnInactivity, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::inactivityLockEnabled, toggled);
});
connect(ui->spinBox_lockOnInactivity, QOverload<int>::of(&QSpinBox::valueChanged), [](int value){
config()->set(Config::inactivityLockTimeout, value);
});
// [Lock wallet on minimize]
ui->checkBox_lockOnMinimize->setChecked(config()->get(Config::lockOnMinimize).toBool());
connect(ui->checkBox_lockOnMinimize, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::lockOnMinimize, toggled);
});
}
void SettingsNew::setupMemoryTab() {
// Nothing here, yet
}
void SettingsNew::setupTransactionsTab() {
// [Multibroadcast outgoing transactions]
ui->checkBox_multibroadcast->setChecked(config()->get(Config::multiBroadcast).toBool());
connect(ui->checkBox_multibroadcast, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::multiBroadcast, toggled);
});
connect(ui->btn_multibroadcast, &QPushButton::clicked, [this]{
QMessageBox::information(this, "Multibroadcasting", "Multibroadcasting relays outgoing transactions to all nodes in your selected node list. This may improve transaction relay speed and reduces the chance of your transaction failing.");
});
// Hide unimplemented settings
ui->checkBox_alwaysOpenAdvancedTxDialog->hide();
ui->checkBox_requirePasswordToSpend->hide();
}
void SettingsNew::setupMiscTab() {
// [Block explorer]
ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(config()->get(Config::blockExplorer).toString()));
connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString blockExplorer = ui->comboBox_blockExplorer->currentText();
config()->set(Config::blockExplorer, blockExplorer);
emit blockExplorerChanged(blockExplorer);
});
// [Reddit frontend]
ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(config()->get(Config::redditFrontend).toString()));
connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString redditFrontend = ui->comboBox_redditFrontend->currentText();
config()->set(Config::redditFrontend, redditFrontend);
});
// [LocalMonero frontend]
ui->comboBox_localMoneroFrontend->addItem("localmonero.co", "https://localmonero.co");
ui->comboBox_localMoneroFrontend->addItem("localmonero.co/nojs", "https://localmonero.co/nojs");
ui->comboBox_localMoneroFrontend->addItem("nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion",
"http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion");
ui->comboBox_localMoneroFrontend->addItem("yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p",
"http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p");
ui->comboBox_localMoneroFrontend->setCurrentIndex(ui->comboBox_localMoneroFrontend->findData(config()->get(Config::localMoneroFrontend).toString()));
connect(ui->comboBox_localMoneroFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString localMoneroFrontend = ui->comboBox_localMoneroFrontend->currentData().toString();
config()->set(Config::localMoneroFrontend, localMoneroFrontend);
});
}
void SettingsNew::onProxySettingsChanged() {
ui->closeButton->addButton(QDialogButtonBox::Apply);
connect(ui->closeButton->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, [this](){
ui->proxyWidget->setProxySettings();
emit proxySettingsChanged();
ui->closeButton->removeButton(ui->closeButton->button(QDialogButtonBox::Apply));
});
}
void SettingsNew::showNetworkProxyTab() {
this->setSelection(SettingsNew::Pages::NETWORK);
ui->tabWidget_network->setCurrentIndex(1);
}
void SettingsNew::setupThemeComboBox() {
#if defined(Q_OS_WIN)
m_themes.removeOne("Breeze/Dark");
m_themes.removeOne("Breeze/Light");
#elif defined(Q_OS_MAC)
m_themes.removeOne("QDarkStyle");
#endif
ui->comboBox_theme->insertItems(0, m_themes);
}
void SettingsNew::setSelection(int index) {
// You'd really think there is a better way
QListWidgetItem *item = ui->selector->item(index);
QModelIndex idx = ui->selector->indexFromItem(item);
ui->selector->setCurrentRow(index);
ui->selector->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
void SettingsNew::enableWebsocket(bool enabled) {
if (enabled && !config()->get(Config::offlineMode).toBool() && !config()->get(Config::disableWebsocket).toBool()) {
websocketNotifier()->websocketClient.restart();
} else {
websocketNotifier()->websocketClient.stop();
}
ui->nodeWidget->onWebsocketStatusChanged();
emit websocketStatusChanged(enabled);
}
SettingsNew::~SettingsNew() = default;

78
src/SettingsNewDialog.h Normal file
View file

@ -0,0 +1,78 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_SETTINGSNEWDIALOG_H
#define FEATHER_SETTINGSNEWDIALOG_H
#include <QAbstractButton>
#include <QDialog>
#include <QSettings>
#include "appcontext.h"
#include "widgets/NodeWidget.h"
namespace Ui {
class SettingsNew;
}
class SettingsNew : public QDialog
{
Q_OBJECT
public:
explicit SettingsNew(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
~SettingsNew() override;
void showNetworkProxyTab();
enum Pages {
APPEARANCE = 0,
NETWORK,
STORAGE,
DISPLAY,
MEMORY,
TRANSACTIONS,
MISC
};
signals:
void preferredFiatCurrencyChanged(QString currency);
void skinChanged(QString skinName);
void blockExplorerChanged(QString blockExplorer);
void hideUpdateNotifications(bool hidden);
void websocketStatusChanged(bool enabled);
void proxySettingsChanged();
void updateBalance();
void offlineMode(bool offline);
public slots:
// void checkboxExternalLinkWarn();
// void fiatCurrencySelected(int index);
private slots:
void onProxySettingsChanged();
private:
void setupAppearanceTab();
void setupNetworkTab();
void setupStorageTab();
void setupDisplayTab();
void setupMemoryTab();
void setupTransactionsTab();
void setupMiscTab();
void setupThemeComboBox();
void setSelection(int index);
void enableWebsocket(bool enabled);
QScopedPointer<Ui::SettingsNew> ui;
QSharedPointer<AppContext> m_ctx;
Nodes *m_nodes = nullptr;
QStringList m_themes{"Native", "QDarkStyle", "Breeze/Dark", "Breeze/Light"};
QStringList m_dateFormats{"yyyy-MM-dd", "MM-dd-yyyy", "dd-MM-yyyy"};
QStringList m_timeFormats{"hh:mm", "hh:mm ap"};
};
#endif //FEATHER_SETTINGSNEWDIALOG_H

1120
src/SettingsNewDialog.ui Normal file

File diff suppressed because it is too large Load diff

View file

@ -38,6 +38,7 @@ WindowManager::WindowManager(EventFilter *eventFilter)
m_tray->show(); m_tray->show();
this->initSkins(); this->initSkins();
this->patchMacStylesheet();
this->showCrashLogs(); this->showCrashLogs();
@ -135,6 +136,34 @@ void WindowManager::raise() {
} }
} }
// ######################## SETTINGS ########################
void WindowManager::showSettings(QSharedPointer<AppContext> ctx, QWidget *parent, bool showProxyTab) {
SettingsNew settings{ctx, parent};
connect(&settings, &SettingsNew::preferredFiatCurrencyChanged, [this]{
for (const auto &window : m_windows) {
window->onPreferredFiatCurrencyChanged();
}
});
connect(&settings, &SettingsNew::skinChanged, this, &WindowManager::onChangeTheme);
connect(&settings, &SettingsNew::updateBalance, this, &WindowManager::updateBalance);
connect(&settings, &SettingsNew::proxySettingsChanged, this, &WindowManager::onProxySettingsChanged);
connect(&settings, &SettingsNew::websocketStatusChanged, this, &WindowManager::onWebsocketStatusChanged);
connect(&settings, &SettingsNew::offlineMode, this, &WindowManager::offlineMode);
connect(&settings, &SettingsNew::hideUpdateNotifications, [this](bool hidden){
for (const auto &window : m_windows) {
window->onHideUpdateNotifications(hidden);
}
});
if (showProxyTab) {
settings.showNetworkProxyTab();
}
settings.exec();
}
// ######################## WALLET OPEN ######################## // ######################## WALLET OPEN ########################
void WindowManager::tryOpenWallet(const QString &path, const QString &password) { void WindowManager::tryOpenWallet(const QString &path, const QString &password) {
@ -544,60 +573,56 @@ void WindowManager::onInitialNetworkConfigured() {
m_initialNetworkConfigured = true; m_initialNetworkConfigured = true;
appData(); appData();
this->initTor(); this->onProxySettingsChanged();
this->initWS();
} }
} }
void WindowManager::initTor() { void WindowManager::onProxySettingsChanged() {
torManager()->init();
torManager()->start();
this->onTorSettingsChanged();
}
void WindowManager::onTorSettingsChanged() {
if (Utils::isTorsocks()) { if (Utils::isTorsocks()) {
return; return;
} }
// use local tor -> bundled tor // Will kill the process if necessary
torManager()->init();
torManager()->start();
QNetworkProxy proxy{QNetworkProxy::NoProxy};
if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
QString host = config()->get(Config::socks5Host).toString(); QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort(); quint16 port = config()->get(Config::socks5Port).toString().toUShort();
if (!torManager()->isLocalTor()) {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
host = torManager()->featherTorHost; host = torManager()->featherTorHost;
port = torManager()->featherTorPort; port = torManager()->featherTorPort;
} }
QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port}; proxy = QNetworkProxy{QNetworkProxy::Socks5Proxy, host, port};
getNetworkTor()->setProxy(proxy); getNetworkSocks5()->setProxy(proxy);
websocketNotifier()->websocketClient.webSocket.setProxy(proxy); }
emit torSettingsChanged(); qWarning() << "Proxy: " << proxy.hostName() << " " << proxy.port();
// Switch websocket to new proxy and update URL
websocketNotifier()->websocketClient.stop();
websocketNotifier()->websocketClient.webSocket.setProxy(proxy);
websocketNotifier()->websocketClient.nextWebsocketUrl();
websocketNotifier()->websocketClient.restart();
emit proxySettingsChanged();
} }
void WindowManager::onWebsocketStatusChanged(bool enabled) { void WindowManager::onWebsocketStatusChanged(bool enabled) {
emit websocketStatusChanged(enabled); emit websocketStatusChanged(enabled);
} }
void WindowManager::initWS() {
if (config()->get(Config::offlineMode).toBool()) {
return;
}
if (config()->get(Config::disableWebsocket).toBool()) {
return;
}
websocketNotifier()->websocketClient.start();
}
// ######################## WIZARD ######################## // ######################## WIZARD ########################
WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) const { WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) {
auto *wizard = new WalletWizard; auto *wizard = new WalletWizard;
connect(wizard, &WalletWizard::initialNetworkConfigured, this, &WindowManager::onInitialNetworkConfigured); connect(wizard, &WalletWizard::initialNetworkConfigured, this, &WindowManager::onInitialNetworkConfigured);
connect(wizard, &WalletWizard::skinChanged, this, &WindowManager::changeSkin); connect(wizard, &WalletWizard::showSettings, [this, wizard]{
this->showSettings(nullptr, wizard);
});
connect(wizard, &WalletWizard::openWallet, this, &WindowManager::tryOpenWallet); connect(wizard, &WalletWizard::openWallet, this, &WindowManager::tryOpenWallet);
connect(wizard, &WalletWizard::createWallet, this, &WindowManager::tryCreateWallet); connect(wizard, &WalletWizard::createWallet, this, &WindowManager::tryCreateWallet);
connect(wizard, &WalletWizard::createWalletFromKeys, this, &WindowManager::tryCreateWalletFromKeys); connect(wizard, &WalletWizard::createWalletFromKeys, this, &WindowManager::tryCreateWalletFromKeys);
@ -667,13 +692,32 @@ QString WindowManager::loadStylesheet(const QString &resource) {
return data; return data;
} }
void WindowManager::changeSkin(const QString &skinName) { void WindowManager::onChangeTheme(const QString &skinName) {
if (!m_skins.contains(skinName)) { if (!m_skins.contains(skinName)) {
qWarning() << QString("No such skin %1").arg(skinName); qWarning() << QString("No such skin %1").arg(skinName);
return; return;
} }
config()->set(Config::skin, skinName); config()->set(Config::skin, skinName);
qApp->setStyleSheet(m_skins[skinName]); qApp->setStyleSheet(m_skins[skinName]);
qDebug() << QString("Skin changed to %1").arg(skinName); qDebug() << QString("Skin changed to %1").arg(skinName);
this->patchMacStylesheet();
for (const auto &window : m_windows) {
window->skinChanged(skinName);
}
}
void WindowManager::patchMacStylesheet() {
#if defined(Q_OS_MACOS)
QString styleSheet = qApp->styleSheet();
auto patch = Utils::fileOpenQRC(":assets/macStylesheet.patch");
auto patch_text = Utils::barrayToString(patch);
styleSheet += patch_text;
qApp->setStyleSheet(styleSheet);
#endif
} }

View file

@ -24,18 +24,21 @@ public:
void close(); void close();
void closeWindow(MainWindow *window); void closeWindow(MainWindow *window);
void showWizard(WalletWizard::Page startPage); void showWizard(WalletWizard::Page startPage);
void changeSkin(const QString &skinName);
void restartApplication(const QString &binaryFilename); void restartApplication(const QString &binaryFilename);
void raise(); void raise();
void showSettings(QSharedPointer<AppContext> ctx, QWidget *parent, bool showProxyTab = false);
EventFilter *eventFilter; EventFilter *eventFilter;
signals: signals:
void torSettingsChanged(); void proxySettingsChanged();
void websocketStatusChanged(bool enabled); void websocketStatusChanged(bool enabled);
void updateBalance();
void offlineMode(bool offline);
public slots: public slots:
void onTorSettingsChanged(); void onProxySettingsChanged();
void onWebsocketStatusChanged(bool enabled); void onWebsocketStatusChanged(bool enabled);
void tryOpenWallet(const QString &path, const QString &password); void tryOpenWallet(const QString &path, const QString &password);
@ -48,6 +51,7 @@ private slots:
void onDeviceButtonPressed(); void onDeviceButtonPressed();
void onDeviceError(const QString &errorMessage); void onDeviceError(const QString &errorMessage);
void onWalletPassphraseNeeded(bool on_device); void onWalletPassphraseNeeded(bool on_device);
void onChangeTheme(const QString &themeName);
private: private:
void tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset, const QString &subaddressLookahead); void tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset, const QString &subaddressLookahead);
@ -57,15 +61,15 @@ private:
bool autoOpenWallet(); bool autoOpenWallet();
void initWizard(); void initWizard();
WalletWizard* createWizard(WalletWizard::Page startPage) const; WalletWizard* createWizard(WalletWizard::Page startPage);
void handleWalletError(const QString &message); void handleWalletError(const QString &message);
void displayWalletErrorMessage(const QString &message); void displayWalletErrorMessage(const QString &message);
void initTor();
void initWS();
void initSkins(); void initSkins();
QString loadStylesheet(const QString &resource); QString loadStylesheet(const QString &resource);
void patchMacStylesheet();
void buildTrayMenu(); void buildTrayMenu();
void startupWarning(); void startupWarning();
void showWarningMessageBox(const QString &title, const QString &message); void showWarningMessageBox(const QString &title, const QString &message);

View file

@ -3,21 +3,22 @@
#include "LocalMoneroApi.h" #include "LocalMoneroApi.h"
LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl) #include "utils/config.h"
LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network)
: QObject(parent) : QObject(parent)
, m_network(network) , m_network(network)
, m_baseUrl(baseUrl)
{ {
} }
void LocalMoneroApi::countryCodes() { void LocalMoneroApi::countryCodes() {
QString url = QString("%1/countrycodes").arg(m_baseUrl); QString url = QString("%1/countrycodes").arg(this->getBaseUrl());
QNetworkReply *reply = m_network->getJson(url); QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::COUNTRY_CODES)); connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::COUNTRY_CODES));
} }
void LocalMoneroApi::currencies() { void LocalMoneroApi::currencies() {
QString url = QString("%1/currencies").arg(m_baseUrl); QString url = QString("%1/currencies").arg(this->getBaseUrl());
QNetworkReply *reply = m_network->getJson(url); QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::CURRENCIES)); connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::CURRENCIES));
} }
@ -25,9 +26,9 @@ void LocalMoneroApi::currencies() {
void LocalMoneroApi::paymentMethods(const QString &countryCode) { void LocalMoneroApi::paymentMethods(const QString &countryCode) {
QString url; QString url;
if (countryCode.isEmpty()) { if (countryCode.isEmpty()) {
url = QString("%1/payment_methods").arg(m_baseUrl); url = QString("%1/payment_methods").arg(this->getBaseUrl());
} else { } else {
url = QString("%1/payment_methods/%2").arg(m_baseUrl, countryCode); url = QString("%1/payment_methods/%2").arg(this->getBaseUrl(), countryCode);
} }
QNetworkReply *reply = m_network->getJson(url); QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::PAYMENT_METHODS)); connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::PAYMENT_METHODS));
@ -50,7 +51,7 @@ void LocalMoneroApi::sellMoneroOnline(const QString &currencyCode, const QString
} }
void LocalMoneroApi::accountInfo(const QString &username) { void LocalMoneroApi::accountInfo(const QString &username) {
QString url = QString("%1/account_info/%2").arg(m_baseUrl, username); QString url = QString("%1/account_info/%2").arg(this->getBaseUrl(), username);
QNetworkReply *reply = m_network->getJson(url); QNetworkReply *reply = m_network->getJson(url);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::ACCOUNT_INFO)); connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::ACCOUNT_INFO));
} }
@ -88,7 +89,7 @@ void LocalMoneroApi::onResponse(QNetworkReply *reply, LocalMoneroApi::Endpoint e
QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode, QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode,
const QString &paymentMethod, const QString &amount, int page) const QString &paymentMethod, const QString &amount, int page)
{ {
QString url = QString("%1/%2-monero-online/%3").arg(m_baseUrl, buy ? "buy" : "sell", currencyCode); QString url = QString("%1/%2-monero-online/%3").arg(this->getBaseUrl(), buy ? "buy" : "sell", currencyCode);
if (!countryCode.isEmpty() && paymentMethod.isEmpty()) if (!countryCode.isEmpty() && paymentMethod.isEmpty())
url += QString("/%1").arg(countryCode); url += QString("/%1").arg(countryCode);
else if (countryCode.isEmpty() && !paymentMethod.isEmpty()) else if (countryCode.isEmpty() && !paymentMethod.isEmpty())
@ -104,3 +105,15 @@ QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, con
url += "?" + query.toString(); url += "?" + query.toString();
return url; return url;
} }
QString LocalMoneroApi::getBaseUrl() {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
return "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion/api/v1";
}
if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
return "http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p/api/v1";
}
return "https://agoradesk.com/api/v1";
}

View file

@ -27,7 +27,7 @@ public:
QJsonObject obj; QJsonObject obj;
}; };
explicit LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl = "https://agoradesk.com/api/v1"); explicit LocalMoneroApi(QObject *parent, UtilsNetworking *network);
void countryCodes(); void countryCodes();
void currencies(); void currencies();
@ -44,9 +44,9 @@ private slots:
private: private:
QString getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0); QString getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
QString getBaseUrl();
UtilsNetworking *m_network; UtilsNetworking *m_network;
QString m_baseUrl;
}; };

View file

@ -22,9 +22,9 @@
AppContext::AppContext(Wallet *wallet) AppContext::AppContext(Wallet *wallet)
: wallet(wallet) : wallet(wallet)
, nodes(new Nodes(this, this)) , nodes(new Nodes(this))
, networkType(constants::networkType) , networkType(constants::networkType)
, m_rpc(new DaemonRpc{this, getNetworkTor(), ""}) , m_rpc(new DaemonRpc{this, ""})
{ {
connect(this->wallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent); connect(this->wallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent);
connect(this->wallet, &Wallet::moneyReceived, this, &AppContext::onMoneyReceived); connect(this->wallet, &Wallet::moneyReceived, this, &AppContext::onMoneyReceived);
@ -38,15 +38,14 @@ AppContext::AppContext(Wallet *wallet)
connect(this->wallet, &Wallet::deviceError, this, &AppContext::onDeviceError); connect(this->wallet, &Wallet::deviceError, this, &AppContext::onDeviceError);
connect(this->wallet, &Wallet::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest); connect(this->wallet, &Wallet::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest);
connect(this->wallet, &Wallet::deviceButtonPressed, this, &AppContext::onDeviceButtonPressed); connect(this->wallet, &Wallet::deviceButtonPressed, this, &AppContext::onDeviceButtonPressed);
connect(this->wallet, &Wallet::connectionStatusChanged, [this]{
this->nodes->autoConnect();
});
connect(this->wallet, &Wallet::currentSubaddressAccountChanged, [this]{ connect(this->wallet, &Wallet::currentSubaddressAccountChanged, [this]{
this->updateBalance(); this->updateBalance();
}); });
connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError); connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError);
nodes->setContext(this);
// Store the wallet every 2 minutes // Store the wallet every 2 minutes
m_storeTimer.start(2 * 60 * 1000); m_storeTimer.start(2 * 60 * 1000);
connect(&m_storeTimer, &QTimer::timeout, [this](){ connect(&m_storeTimer, &QTimer::timeout, [this](){
@ -188,15 +187,12 @@ void AppContext::setSelectedInputs(const QStringList &selectedInputs) {
emit selectedInputsChanged(selectedInputs); emit selectedInputsChanged(selectedInputs);
} }
void AppContext::onTorSettingsChanged() { void AppContext::onProxySettingsChanged() {
if (Utils::isTorsocks()) { if (Utils::isTorsocks()) {
return; return;
} }
this->nodes->connectToNode(); this->nodes->connectToNode();
auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
qDebug() << "Changed privacyLevel to " << privacyLevel;
} }
void AppContext::stopTimers() { void AppContext::stopTimers() {

View file

@ -62,7 +62,7 @@ public slots:
void onDeviceButtonPressed(); void onDeviceButtonPressed();
void onDeviceError(const QString &message); void onDeviceError(const QString &message);
void onTorSettingsChanged(); // should not be here void onProxySettingsChanged(); // should not be here
private slots: private slots:
void onMoneySpent(const QString &txId, quint64 amount); void onMoneySpent(const QString &txId, quint64 amount);

View file

@ -20,6 +20,7 @@
<file>assets/images/camera_dark.png</file> <file>assets/images/camera_dark.png</file>
<file>assets/images/camera_white.png</file> <file>assets/images/camera_white.png</file>
<file>assets/images/change_account.png</file> <file>assets/images/change_account.png</file>
<file>assets/images/chipset_32px.png</file>
<file>assets/images/clock1.png</file> <file>assets/images/clock1.png</file>
<file>assets/images/clock2.png</file> <file>assets/images/clock2.png</file>
<file>assets/images/clock3.png</file> <file>assets/images/clock3.png</file>
@ -44,10 +45,14 @@
<file>assets/images/eye_blind.png</file> <file>assets/images/eye_blind.png</file>
<file>assets/images/feather.png</file> <file>assets/images/feather.png</file>
<file>assets/images/file.png</file> <file>assets/images/file.png</file>
<file>assets/images/file_manager_32px.png</file>
<file>assets/images/gnome-calc.png</file> <file>assets/images/gnome-calc.png</file>
<file>assets/images/hd_32px.png</file>
<file>assets/images/history.png</file> <file>assets/images/history.png</file>
<file>assets/images/i2p.png</file>
<file>assets/images/info.png</file> <file>assets/images/info.png</file>
<file>assets/images/info2.svg</file> <file>assets/images/info2.svg</file>
<file>assets/images/interface_32px.png</file>
<file>assets/images/key.png</file> <file>assets/images/key.png</file>
<file>assets/images/ledger.png</file> <file>assets/images/ledger.png</file>
<file>assets/images/ledger_unpaired.png</file> <file>assets/images/ledger_unpaired.png</file>
@ -65,6 +70,7 @@
<file>assets/images/microphone.png</file> <file>assets/images/microphone.png</file>
<file>assets/images/mining.png</file> <file>assets/images/mining.png</file>
<file>assets/images/network.png</file> <file>assets/images/network.png</file>
<file>assets/images/nw_32px.png</file>
<file>assets/images/offline_tx.png</file> <file>assets/images/offline_tx.png</file>
<file>assets/images/person.svg</file> <file>assets/images/person.svg</file>
<file>assets/images/preferences.png</file> <file>assets/images/preferences.png</file>
@ -81,6 +87,7 @@
<file>assets/images/securityLevelSafestWhite.png</file> <file>assets/images/securityLevelSafestWhite.png</file>
<file>assets/images/securityLevelStandardWhite.png</file> <file>assets/images/securityLevelStandardWhite.png</file>
<file>assets/images/seed.png</file> <file>assets/images/seed.png</file>
<file>assets/images/settings_disabled_32px.png</file>
<file>assets/images/speaker.png</file> <file>assets/images/speaker.png</file>
<file>assets/images/status_connected_fork.png</file> <file>assets/images/status_connected_fork.png</file>
<file>assets/images/status_connected.png</file> <file>assets/images/status_connected.png</file>
@ -93,6 +100,7 @@
<file>assets/images/status_lagging_fork.png</file> <file>assets/images/status_lagging_fork.png</file>
<file>assets/images/status_lagging.png</file> <file>assets/images/status_lagging.png</file>
<file>assets/images/status_lagging.svg</file> <file>assets/images/status_lagging.svg</file>
<file>assets/images/status_offline.svg</file>
<file>assets/images/status_waiting.png</file> <file>assets/images/status_waiting.png</file>
<file>assets/images/status_waiting.svg</file> <file>assets/images/status_waiting.svg</file>
<file>assets/images/tab_addresses.png</file> <file>assets/images/tab_addresses.png</file>
@ -117,6 +125,7 @@
<file>assets/images/unpaid.png</file> <file>assets/images/unpaid.png</file>
<file>assets/images/update.png</file> <file>assets/images/update.png</file>
<file>assets/images/warning.png</file> <file>assets/images/warning.png</file>
<file>assets/images/vrdp_32px.png</file>
<file>assets/images/xmrig.ico</file> <file>assets/images/xmrig.ico</file>
<file>assets/images/xmrig.svg</file> <file>assets/images/xmrig.svg</file>
<file>assets/images/zoom.png</file> <file>assets/images/zoom.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/assets/images/i2p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

View file

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0"
id="svg7854"
height="512"
width="512"
viewBox="9 9 30 30">
<defs
id="defs7856">
<linearGradient
id="linearGradient860">
<stop
id="stop856"
offset="0"
style="stop-color:#aaaaaa;stop-opacity:1" />
<stop
id="stop858"
offset="1"
style="stop-color:#999999;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient7577">
<stop
id="stop7579"
offset="0"
style="stop-color:#000000;stop-opacity:0.3137255;" />
<stop
id="stop7581"
offset="1"
style="stop-color:#ffffff;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient5167">
<stop
style="stop-color:#999999;stop-opacity:1"
offset="0"
id="stop5169" />
<stop
style="stop-color:#666666;stop-opacity:1"
offset="1"
id="stop5171" />
</linearGradient>
<linearGradient
id="linearGradient5184">
<stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop5186" />
<stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop5188" />
</linearGradient>
<linearGradient
gradientTransform="matrix(2.1074616,0,0,2.1078593,-9.43551,-10.006786)"
y2="17.024479"
x2="16.657505"
y1="10.883683"
x1="15.011773"
gradientUnits="userSpaceOnUse"
id="linearGradient8317"
xlink:href="#linearGradient7577" />
<radialGradient
gradientTransform="matrix(1.897257,0,0,1.897615,-6.10046,-6.6146433)"
r="7.5896134"
fy="20.410854"
fx="15.865708"
cy="20.410854"
cx="15.865708"
gradientUnits="userSpaceOnUse"
id="radialGradient8319"
xlink:href="#linearGradient5167" />
<radialGradient
r="5.96875"
fy="11.308558"
fx="14.05685"
cy="11.308558"
cx="14.05685"
gradientTransform="matrix(-4.2002315,0.5953403,0.2958442,2.0989386,75.31118,-18.732928)"
gradientUnits="userSpaceOnUse"
id="radialGradient8321"
xlink:href="#linearGradient5184" />
<linearGradient
gradientTransform="matrix(1.7591324,0,0,1.7580929,-3.90899,-4.3562887)"
y2="26.431587"
x2="13.458839"
y1="2.0178134"
x1="8.9317284"
gradientUnits="userSpaceOnUse"
id="linearGradient8323"
xlink:href="#linearGradient860" />
</defs>
<metadata
id="metadata7859">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>Lapo Calamandrei</dc:title>
</cc:Agent>
</dc:creator>
<dc:source />
<cc:license
rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
<dc:title></dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>record</rdf:li>
<rdf:li>media</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:contributor>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
<cc:requires
rdf:resource="http://web.resource.org/cc/SourceCode" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1">
<ellipse
ry="14.997972"
rx="14.995141"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.4;fill:url(#linearGradient8317);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.11079514;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path7691"
cx="24.00086"
cy="24.002029" />
<ellipse
ry="13.502028"
rx="13.49948"
cy="24.002029"
cx="24.000866"
id="path7968"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#radialGradient8319);fill-opacity:1;fill-rule:nonzero;stroke:#555555;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
<path
id="path7970"
d="M 25.3861,13.485003 C 20.31979,12.724926 15.45183,15.857848 14,20.764516 c 1.18871,3.18039 3.90811,5.70993 7.46677,6.47724 5.29459,1.141602 10.50115,-2.027543 12.01505,-7.143895 -1.18869,-3.180413 -3.90812,-5.709952 -7.46675,-6.477239 -0.217,-0.04678 -0.41248,-0.103152 -0.62897,-0.135619 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.404;fill:url(#radialGradient8321);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.09465754;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
<ellipse
ry="12.509292"
rx="12.516688"
cy="24.009293"
cx="24.000891"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.54494413;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient8323);stroke-width:1.00000215;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path7972" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

View file

@ -15,6 +15,10 @@
"rino4muoj3zk223twblr53hwt4ukqemjqw4fbiwdcrqqevcrclpuzyyd.onion:18081", "rino4muoj3zk223twblr53hwt4ukqemjqw4fbiwdcrqqevcrclpuzyyd.onion:18081",
"majesticrepik35vnngouksfl7jiwf6sj7s2doj3bvdffq27tgqoeayd.onion:18089" "majesticrepik35vnngouksfl7jiwf6sj7s2doj3bvdffq27tgqoeayd.onion:18089"
], ],
"i2p": [
"ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p:18081",
"t7lce3j7mwxt32h755u3njjp3k6og3defgueo3iecgsexxnkoezouobc.b32.i2p:18089"
],
"clearnet": [ "clearnet": [
"node.community.rino.io:18081", "node.community.rino.io:18081",
"node.sethforprivacy.com:18089", "node.sethforprivacy.com:18089",

View file

@ -10,6 +10,7 @@
#include <QLabel> #include <QLabel>
#include <QPlainTextEdit> #include <QPlainTextEdit>
#include <QLineEdit> #include <QLineEdit>
#include <QListWidget>
class DoublePixmapLabel : public QLabel class DoublePixmapLabel : public QLabel
{ {

View file

@ -24,15 +24,6 @@ namespace constants
// donation constants // donation constants
const QString donationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f"; const QString donationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f";
const int donationBoundary = 25; const int donationBoundary = 25;
// websocket constants
const QVector<QUrl> websocketUrls = {
QUrl(QStringLiteral("wss://ws.featherwallet.org/ws")),
QUrl(QStringLiteral("wss://ws.featherwallet.net/ws"))
};
// website constants
const QString websiteUrl = "https://featherwallet.org";
} }
#endif //FEATHER_CONSTANTS_H #endif //FEATHER_CONSTANTS_H

View file

@ -212,6 +212,9 @@
<property name="text"> <property name="text">
<string>TextLabel</string> <string>TextLabel</string>
</property> </property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget> </widget>
</item> </item>
<item row="13" column="1"> <item row="13" column="1">

View file

@ -4,64 +4,23 @@
#include "TorInfoDialog.h" #include "TorInfoDialog.h"
#include "ui_TorInfoDialog.h" #include "ui_TorInfoDialog.h"
#include <QDesktopServices>
#include <QInputDialog>
#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpressionValidator>
#include "utils/ColorScheme.h"
#include "utils/Icons.h"
#include "utils/os/tails.h"
#include "utils/TorManager.h" #include "utils/TorManager.h"
TorInfoDialog::TorInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent) TorInfoDialog::TorInfoDialog(QWidget *parent)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::TorInfoDialog) , ui(new Ui::TorInfoDialog)
, m_ctx(std::move(ctx))
{ {
ui->setupUi(this); ui->setupUi(this);
if (!torManager()->torConnected && !torManager()->errorMsg.isEmpty()) {
ui->message->setText(torManager()->errorMsg);
} else {
ui->message->hide();
}
if (torManager()->isLocalTor()) {
ui->frame_logs->setHidden(true);
} else {
ui->logs->setPlainText(torManager()->torLogs); ui->logs->setPlainText(torManager()->torLogs);
}
initConnectionSettings(); this->onStatusChanged(torManager()->errorMsg);
initPrivacyLevel(); this->onConnectionStatusChanged(torManager()->torConnected);
onConnectionStatusChanged(torManager()->torConnected);
auto *portValidator = new QRegularExpressionValidator{QRegularExpression("[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]")};
ui->line_port->setValidator(portValidator);
connect(torManager(), &TorManager::statusChanged, this, &TorInfoDialog::onStatusChanged);
connect(torManager(), &TorManager::connectionStateChanged, this, &TorInfoDialog::onConnectionStatusChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &TorInfoDialog::onConnectionStatusChanged);
connect(torManager(), &TorManager::logsUpdated, this, &TorInfoDialog::onLogsUpdated); connect(torManager(), &TorManager::logsUpdated, this, &TorInfoDialog::onLogsUpdated);
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &TorInfoDialog::onApplySettings);
connect(ui->line_host, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
connect(ui->line_port, &QLineEdit::textEdited, this, &TorInfoDialog::onSettingsChanged);
connect(ui->check_useLocalTor, &QCheckBox::stateChanged, this, &TorInfoDialog::onSettingsChanged);
connect(ui->btnGroup_privacyLevel, &QButtonGroup::idToggled, this, &TorInfoDialog::onSettingsChanged);
ui->label_changes->hide();
ui->btn_configureInitSync->setIcon(icons()->icon("preferences.svg"));
connect(ui->btn_configureInitSync, &QPushButton::clicked, this, &TorInfoDialog::onShowInitSyncConfigDialog);
#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
ui->check_useLocalTor->setChecked(true);
ui->check_useLocalTor->setEnabled(false);
ui->check_useLocalTor->setToolTip("Feather was bundled without Tor");
#endif
this->adjustSize(); this->adjustSize();
} }
@ -70,104 +29,25 @@ void TorInfoDialog::onLogsUpdated() {
} }
void TorInfoDialog::onConnectionStatusChanged(bool connected) { void TorInfoDialog::onConnectionStatusChanged(bool connected) {
if (connected) { if (!torManager()->isStarted()) {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_offline.svg").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Not running");
}
else if (connected) {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_connected.png").scaledToWidth(16, Qt::SmoothTransformation)); ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_connected.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Connected"); ui->label_testConnectionStatus->setText("Connected");
} else { }
else {
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation)); ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Disconnected"); ui->label_testConnectionStatus->setText("Disconnected");
} }
} }
void TorInfoDialog::onApplySettings() { void TorInfoDialog::onStatusChanged(const QString &msg) {
config()->set(Config::socks5Host, ui->line_host->text()); ui->message->setText(msg);
config()->set(Config::socks5Port, ui->line_port->text());
int id = ui->btnGroup_privacyLevel->checkedId(); if (msg.isEmpty()) {
config()->set(Config::torPrivacyLevel, id); ui->message->hide();
ui->label_changes->hide();
bool useLocalTor = ui->check_useLocalTor->isChecked();
if (config()->get(Config::useLocalTor).toBool() && useLocalTor && torManager()->isStarted()) {
QMessageBox::warning(this, "Warning", "Feather is running the bundled Tor daemon, "
"but the option to never start a bundled Tor daemon was selected. "
"A restart is required to apply the setting.");
}
config()->set(Config::useLocalTor, useLocalTor);
ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_lagging.png").scaledToWidth(16, Qt::SmoothTransformation));
ui->label_testConnectionStatus->setText("Connecting");
emit torSettingsChanged();
}
void TorInfoDialog::onSettingsChanged() {
ui->label_changes->show();
}
void TorInfoDialog::initConnectionSettings() {
bool localTor = torManager()->isLocalTor();
ui->label_connectionSettingsMessage->setVisible(!localTor);
ui->frame_connectionSettings->setVisible(localTor);
ui->line_host->setText(config()->get(Config::socks5Host).toString());
ui->line_port->setText(config()->get(Config::socks5Port).toString());
ui->check_useLocalTor->setChecked(config()->get(Config::useLocalTor).toBool());
}
void TorInfoDialog::initPrivacyLevel() {
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptNode, Config::allTorExceptNode);
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptInitSync, Config::allTorExceptInitSync);
ui->btnGroup_privacyLevel->setId(ui->radio_allTor, Config::allTor);
int privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
auto button = ui->btnGroup_privacyLevel->button(privacyLevel);
if (button) {
button->setChecked(true);
}
if (m_ctx->nodes->connection().isLocal()) {
ui->label_notice->setText("You are connected to a local node. Traffic to node is not routed over Tor.");
}
else if (Utils::isTorsocks()) {
ui->label_notice->setText("Feather was started with torsocks, all traffic is routed over Tor");
}
else if (WhonixOS::detect()) {
ui->label_notice->setText("Feather is running on Whonix, all traffic is routed over Tor");
}
else if (TailsOS::detect()) {
ui->label_notice->setText("Feather is running on Tails, all traffic is routed over Tor");
}
else {
ui->frame_notice->hide();
}
bool dark = ColorScheme::darkScheme;
QPixmap iconNoTor(dark ? ":/assets/images/securityLevelStandardWhite.png" : ":/assets/images/securityLevelStandard.png");
QPixmap iconNoSync(dark ? ":/assets/images/securityLevelSaferWhite.png" : ":/assets/images/securityLevelSafer.png");
QPixmap iconAllTor(dark ? ":/assets/images/securityLevelSafestWhite.png" : ":/assets/images/securityLevelSafest.png");
ui->icon_noTor->setPixmap(iconNoTor.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_noSync->setPixmap(iconNoSync.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_allTor->setPixmap(iconAllTor.scaledToHeight(16, Qt::SmoothTransformation));
}
void TorInfoDialog::onStopTor() {
torManager()->stop();
}
void TorInfoDialog::onShowInitSyncConfigDialog() {
int threshold = config()->get(Config::initSyncThreshold).toInt();
bool ok;
int newThreshold = QInputDialog::getInt(this, "Sync threshold",
"Synchronize over clearnet if wallet is behind more than x blocks: ",
threshold, 0, 10000, 10, &ok);
if (ok) {
config()->set(Config::initSyncThreshold, newThreshold);
} }
} }

View file

@ -6,8 +6,6 @@
#include <QDialog> #include <QDialog>
#include "appcontext.h"
namespace Ui { namespace Ui {
class TorInfoDialog; class TorInfoDialog;
} }
@ -17,7 +15,7 @@ class TorInfoDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit TorInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr); explicit TorInfoDialog(QWidget *parent = nullptr);
~TorInfoDialog() override; ~TorInfoDialog() override;
public slots: public slots:
@ -25,20 +23,10 @@ public slots:
private slots: private slots:
void onConnectionStatusChanged(bool connected); void onConnectionStatusChanged(bool connected);
void onApplySettings(); void onStatusChanged(const QString &msg = "");
void onSettingsChanged();
void onStopTor();
void onShowInitSyncConfigDialog();
signals:
void torSettingsChanged();
private: private:
void initConnectionSettings();
void initPrivacyLevel();
QScopedPointer<Ui::TorInfoDialog> ui; QScopedPointer<Ui::TorInfoDialog> ui;
QSharedPointer<AppContext> m_ctx;
}; };

View file

@ -6,224 +6,14 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>703</width> <width>708</width>
<height>804</height> <height>496</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Tor settings</string> <string>Tor settings</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="group_connectionSettings">
<property name="title">
<string>Connection settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="frame_connectionSettings">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_host">
<property name="text">
<string>127.0.0.1</string>
</property>
<property name="placeholderText">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_port">
<property name="text">
<string>9050</string>
</property>
<property name="placeholderText">
<string>9050</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_connectionSettingsMessage">
<property name="text">
<string>Tor daemon is managed by Feather.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_useLocalTor">
<property name="text">
<string>Never start bundled Tor (requires local Tor daemon)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_privacyLevel">
<property name="title">
<string>Privacy Level</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="icon_noTor">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTorExceptNode">
<property name="text">
<string>Route all traffic over Tor, except traffic to node</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="icon_noSync">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTorExceptInitSync">
<property name="text">
<string>Route all traffic over Tor, except initial wallet synchronization</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_configureInitSync">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="icon_allTor">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTor">
<property name="text">
<string>Route all traffic over Tor</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame_notice">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_notice">
<property name="text">
<string>notice</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
@ -252,13 +42,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="label_changes">
<property name="text">
<string>(changes not applied)</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
@ -310,6 +93,12 @@
</property> </property>
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title"> <property name="title">
<string>Logs</string> <string>Logs</string>
</property> </property>
@ -318,7 +107,7 @@
<number>0</number> <number>0</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>0</number> <number>7</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>0</number> <number>0</number>
@ -345,26 +134,13 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close</set> <set>QDialogButtonBox::Close</set>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -16,7 +16,7 @@ TxBroadcastDialog::TxBroadcastDialog(QWidget *parent, QSharedPointer<AppContext>
ui->setupUi(this); ui->setupUi(this);
auto node = m_ctx->nodes->connection(); auto node = m_ctx->nodes->connection();
m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress()); m_rpc = new DaemonRpc(this, node.toAddress());
connect(ui->btn_Broadcast, &QPushButton::clicked, this, &TxBroadcastDialog::broadcastTx); connect(ui->btn_Broadcast, &QPushButton::clicked, this, &TxBroadcastDialog::broadcastTx);
connect(ui->btn_Close, &QPushButton::clicked, this, &TxBroadcastDialog::reject); connect(ui->btn_Close, &QPushButton::clicked, this, &TxBroadcastDialog::reject);
@ -35,12 +35,6 @@ void TxBroadcastDialog::broadcastTx() {
FeatherNode node = ui->radio_useCustom->isChecked() ? FeatherNode(ui->customNode->text()) : m_ctx->nodes->connection(); FeatherNode node = ui->radio_useCustom->isChecked() ? FeatherNode(ui->customNode->text()) : m_ctx->nodes->connection();
if (node.isLocal()) {
m_rpc->setNetwork(getNetworkClearnet());
} else {
m_rpc->setNetwork(getNetworkTor());
}
m_rpc->setDaemonAddress(node.toURL()); m_rpc->setDaemonAddress(node.toURL());
m_rpc->sendRawTransaction(tx); m_rpc->sendRawTransaction(tx);
} }
@ -56,6 +50,7 @@ void TxBroadcastDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) {
} }
this->accept(); this->accept();
QMessageBox::information(this, "Transaction broadcast", "Transaction submitted successfully.\n\n" QMessageBox::information(this, "Transaction broadcast", "Transaction submitted successfully.\n\n"
"If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab."); "If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab.");
} }

View file

@ -51,16 +51,22 @@ UpdateDialog::UpdateDialog(QWidget *parent, QSharedPointer<Updater> updater)
} }
void UpdateDialog::checkForUpdates() { void UpdateDialog::checkForUpdates() {
ui->label_header->setText("Checking for updates..."); ui->label_header->setText("Checking for updates");
ui->label_body->setText("..."); ui->label_body->setText("..");
connect(&m_waitingTimer, &QTimer::timeout, [this]{
ui->label_body->setText(ui->label_body->text() + ".");
});
m_waitingTimer.start(500);
m_updater->checkForUpdates(); m_updater->checkForUpdates();
} }
void UpdateDialog::noUpdateAvailable() { void UpdateDialog::noUpdateAvailable() {
m_waitingTimer.stop();
this->setStatus("Feather is up-to-date.", true); this->setStatus("Feather is up-to-date.", true);
} }
void UpdateDialog::updateAvailable() { void UpdateDialog::updateAvailable() {
m_waitingTimer.stop();
ui->frame->show(); ui->frame->show();
ui->btn_installUpdate->hide(); ui->btn_installUpdate->hide();
ui->btn_restart->hide(); ui->btn_restart->hide();
@ -70,6 +76,7 @@ void UpdateDialog::updateAvailable() {
} }
void UpdateDialog::onUpdateCheckFailed(const QString &errorMsg) { void UpdateDialog::onUpdateCheckFailed(const QString &errorMsg) {
m_waitingTimer.stop();
this->setStatus(QString("Failed to check for updates: %1").arg(errorMsg), false); this->setStatus(QString("Failed to check for updates: %1").arg(errorMsg), false);
} }
@ -78,7 +85,7 @@ void UpdateDialog::onDownloadClicked() {
ui->btn_download->hide(); ui->btn_download->hide();
ui->progressBar->show(); ui->progressBar->show();
UtilsNetworking network{getNetworkTor()}; UtilsNetworking network{this};
m_reply = network.get(m_updater->downloadUrl); m_reply = network.get(m_updater->downloadUrl);
connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress); connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress);

View file

@ -6,6 +6,7 @@
#include <QDialog> #include <QDialog>
#include <QNetworkReply> #include <QNetworkReply>
#include <QTimer>
#include "utils/Updater.h" #include "utils/Updater.h"
@ -45,11 +46,12 @@ private:
QSharedPointer<Updater> m_updater; QSharedPointer<Updater> m_updater;
QString m_downloadUrl; QString m_downloadUrl;
QString m_updatePath; QString m_updatePath;
std::string m_updateZipArchive; std::string m_updateZipArchive;
QTimer m_waitingTimer;
QNetworkReply *m_reply = nullptr; QNetworkReply *m_reply = nullptr;
}; };

View file

@ -11,6 +11,7 @@
#include "constants.h" #include "constants.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "utils/EventFilter.h" #include "utils/EventFilter.h"
#include "utils/os/Prestium.h"
#include "WindowManager.h" #include "WindowManager.h"
#include "config.h" #include "config.h"
@ -40,10 +41,12 @@ void signal_handler(int signum) {
std::cout << keyStream.str(); std::cout << keyStream.str();
// Write stack trace to disk // Write stack trace to disk
if (config()->get(Config::writeStackTraceToDisk).toBool()) {
QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"}; QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
std::ofstream out(crashLogPath.toStdString()); std::ofstream out(crashLogPath.toStdString());
out << keyStream.str(); out << keyStream.str();
out.close(); out.close();
}
// Make a last ditch attempt to restart the application // Make a last ditch attempt to restart the application
QProcess::startDetached(qApp->arguments()[0], qApp->arguments()); QProcess::startDetached(qApp->arguments()[0], qApp->arguments());
@ -91,12 +94,6 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
QCommandLineOption useLocalTorOption(QStringList() << "use-local-tor", "Use system wide installed Tor instead of the bundled."); QCommandLineOption useLocalTorOption(QStringList() << "use-local-tor", "Use system wide installed Tor instead of the bundled.");
parser.addOption(useLocalTorOption); parser.addOption(useLocalTorOption);
QCommandLineOption torHostOption(QStringList() << "tor-host", "Address of running Tor instance.", "torHost");
parser.addOption(torHostOption);
QCommandLineOption torPortOption(QStringList() << "tor-port", "Port of running Tor instance.", "torPort");
parser.addOption(torPortOption);
QCommandLineOption quietModeOption(QStringList() << "quiet", "Limit console output"); QCommandLineOption quietModeOption(QStringList() << "quiet", "Limit console output");
parser.addOption(quietModeOption); parser.addOption(quietModeOption);
@ -164,16 +161,17 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
bool logLevelFromEnv; bool logLevelFromEnv;
int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv); int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv);
if (!logLevelFromEnv) { if (logLevelFromEnv) {
logLevel = 0;
}
config()->set(Config::logLevel, logLevel); config()->set(Config::logLevel, logLevel);
} else {
logLevel = config()->get(Config::logLevel).toInt();
}
if (parser.isSet("quiet") || config()->get(Config::disableLogging).toBool()) { if (parser.isSet("quiet") || config()->get(Config::disableLogging).toBool()) {
qWarning() << "Logging is disabled"; qWarning() << "Logging is disabled";
WalletManager::instance()->setLogLevel(-1); WalletManager::instance()->setLogLevel(-1);
} }
else if (logLevelFromEnv && logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max) { else if (logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max) {
Monero::WalletManagerFactory::setLogLevel(logLevel); Monero::WalletManagerFactory::setLogLevel(logLevel);
} }
@ -186,11 +184,12 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
if (!QDir().mkpath(walletDir)) if (!QDir().mkpath(walletDir))
qCritical() << "Unable to create dir: " << walletDir; qCritical() << "Unable to create dir: " << walletDir;
// Setup Tor config // Prestium initial config
if (parser.isSet("tor-host")) if (config()->get(Config::firstRun).toBool() && Prestium::detect()) {
config()->set(Config::socks5Host, parser.value("tor-host")); config()->set(Config::proxy, Config::Proxy::i2p);
if (parser.isSet("tor-port")) config()->set(Config::socks5Port, 4448);
config()->set(Config::socks5Port, parser.value("tor-port")); }
if (parser.isSet("use-local-tor")) if (parser.isSet("use-local-tor"))
config()->set(Config::useLocalTor, true); config()->set(Config::useLocalTor, true);

View file

@ -90,13 +90,13 @@ QVariant BountiesModel::headerData(int section, Qt::Orientation orientation, int
{ {
switch(section) { switch(section) {
case Votes: case Votes:
return QString("🡅"); return QString(" 🡅 ");
case Title: case Title:
return QString("Title"); return QString("Title");
case Status: case Status:
return QString("Status"); return QString(" Status ");
case Bounty: case Bounty:
return QString("Bounty"); return QString(" Bounty ");
default: default:
return QVariant(); return QVariant();
} }

View file

@ -9,8 +9,8 @@
NodeModel::NodeModel(int nodeSource, QObject *parent) NodeModel::NodeModel(int nodeSource, QObject *parent)
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
, m_nodeSource(nodeSource) , m_nodeSource(nodeSource)
, m_offline(icons()->icon("expired_icon.png")) , m_offline(icons()->icon("status_offline.svg"))
, m_online(icons()->icon("confirmed_icon.png")) , m_online(icons()->icon("status_connected.svg"))
{ {
} }
@ -60,8 +60,9 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
else if (role == Qt::DecorationRole) { else if (role == Qt::DecorationRole) {
switch (index.column()) { switch (index.column()) {
case NodeModel::URL: { case NodeModel::URL: {
if(m_nodeSource == NodeSource::websocket) if (m_nodeSource == NodeSource::websocket && !config()->get(Config::offlineMode).toBool()) {
return QVariant(node.online ? m_online : m_offline); return QVariant(node.online ? m_online : m_offline);
}
return QVariant(); return QVariant();
} }
default: { default: {
@ -72,6 +73,8 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
else if (role == Qt::ToolTipRole) { else if (role == Qt::ToolTipRole) {
switch (index.column()) { switch (index.column()) {
case NodeModel::URL: { case NodeModel::URL: {
if (node.isConnecting)
return QString("Feather is connecting to this node.");
if (node.isActive) if (node.isActive)
return QString("Feather is connected to this node."); return QString("Feather is connected to this node.");
} }
@ -95,7 +98,7 @@ QVariant NodeModel::headerData(int section, Qt::Orientation orientation, int rol
case NodeModel::URL: case NodeModel::URL:
return QString("Node"); return QString("Node");
case NodeModel::Height: case NodeModel::Height:
return QString("Height"); return QString("Height ");
default: default:
return QVariant(); return QVariant();
} }

View file

@ -86,7 +86,7 @@ QVariant RedditModel::headerData(int section, Qt::Orientation orientation, int r
case Author: case Author:
return QString("Author"); return QString("Author");
case Comments: case Comments:
return QString("Comments"); return QString(" Comments ");
default: default:
return QVariant(); return QVariant();
} }

View file

@ -5,21 +5,26 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QRegularExpression>
#include <QUrl>
QNetworkAccessManager *g_networkManagerTor = nullptr; #include "utils/config.h"
#include "utils/Utils.h"
QNetworkAccessManager *g_networkManagerSocks5 = nullptr;
QNetworkAccessManager *g_networkManagerClearnet = nullptr; QNetworkAccessManager *g_networkManagerClearnet = nullptr;
QNetworkAccessManager* getNetworkTor() QNetworkAccessManager* getNetworkSocks5()
{ {
if (!g_networkManagerTor) { if (!g_networkManagerSocks5) {
g_networkManagerTor = new QNetworkAccessManager(QCoreApplication::instance()); g_networkManagerSocks5 = new QNetworkAccessManager(QCoreApplication::instance());
QNetworkProxy proxy; QNetworkProxy proxy;
proxy.setType(QNetworkProxy::Socks5Proxy); proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName("127.0.0.1"); proxy.setHostName("127.0.0.1");
proxy.setPort(9050); proxy.setPort(9050);
g_networkManagerTor->setProxy(proxy); g_networkManagerSocks5->setProxy(proxy);
} }
return g_networkManagerTor; return g_networkManagerSocks5;
} }
QNetworkAccessManager* getNetworkClearnet() QNetworkAccessManager* getNetworkClearnet()
@ -30,8 +35,17 @@ QNetworkAccessManager* getNetworkClearnet()
return g_networkManagerClearnet; return g_networkManagerClearnet;
} }
//void setTorProxy(const QNetworkProxy &proxy)
//{ QNetworkAccessManager* getNetwork(const QString &address)
// QNetworkAccessManager *network = getNetworkTor(); {
// network->setProxy(proxy); if (config()->get(Config::proxy).toInt() == Config::Proxy::None) {
//} return getNetworkClearnet();
}
// Ignore proxy rules for local addresses
if (!address.isEmpty() && Utils::isLocalUrl(QUrl(address))) {
return getNetworkClearnet();
}
return getNetworkSocks5();
}

View file

@ -6,9 +6,9 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
QNetworkAccessManager* getNetworkTor(); QNetworkAccessManager* getNetworkSocks5();
QNetworkAccessManager* getNetworkClearnet(); QNetworkAccessManager* getNetworkClearnet();
//void setTorProxy(const QNetworkProxy &proxy); QNetworkAccessManager* getNetwork(const QString &address = "");
#endif //FEATHER_NETWORKMANAGER_H #endif //FEATHER_NETWORKMANAGER_H

View file

@ -42,6 +42,8 @@ void TorManager::init() {
if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) { if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) {
m_process.kill(); m_process.kill();
} }
featherTorPort = config()->get(Config::torManagedPort).toString().toUShort();
} }
void TorManager::stop() { void TorManager::stop() {
@ -58,12 +60,12 @@ void TorManager::start() {
auto state = m_process.state(); auto state = m_process.state();
if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) { if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) {
this->errorMsg = "Can't start Tor, already running or starting"; this->setErrorMessage("Can't start Tor, already running or starting");
return; return;
} }
if (Utils::portOpen(featherTorHost, featherTorPort)) { if (Utils::portOpen(featherTorHost, featherTorPort)) {
this->errorMsg = QString("Unable to start Tor on %1:%2. Port already in use.").arg(featherTorHost, QString::number(featherTorPort)); this->setErrorMessage(QString("Unable to start Tor on %1:%2. Port already in use.").arg(featherTorHost, QString::number(featherTorPort)));
return; return;
} }
@ -71,7 +73,7 @@ void TorManager::start() {
m_restarts += 1; m_restarts += 1;
if (m_restarts > 4) { if (m_restarts > 4) {
this->errorMsg = "Tor failed to start: maximum retries exceeded"; this->setErrorMessage("Tor failed to start: maximum retries exceeded");
return; return;
} }
@ -107,6 +109,10 @@ void TorManager::checkConnection() {
this->setConnectionState(code == 0); this->setConnectionState(code == 0);
} }
else if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
this->setConnectionState(false);
}
else if (m_localTor) { else if (m_localTor) {
QString host = config()->get(Config::socks5Host).toString(); QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort(); quint16 port = config()->get(Config::socks5Port).toString().toUShort();
@ -125,6 +131,7 @@ void TorManager::setConnectionState(bool connected) {
void TorManager::stateChanged(QProcess::ProcessState state) { void TorManager::stateChanged(QProcess::ProcessState state) {
if (state == QProcess::ProcessState::Running) { if (state == QProcess::ProcessState::Running) {
this->setErrorMessage("");
qWarning() << "Tor started, awaiting bootstrap"; qWarning() << "Tor started, awaiting bootstrap";
} }
else if (state == QProcess::ProcessState::NotRunning) { else if (state == QProcess::ProcessState::NotRunning) {
@ -155,7 +162,7 @@ void TorManager::handleProcessError(QProcess::ProcessError error) {
if (error == QProcess::ProcessError::Crashed) if (error == QProcess::ProcessError::Crashed)
qWarning() << "Tor crashed or killed"; qWarning() << "Tor crashed or killed";
else if (error == QProcess::ProcessError::FailedToStart) { else if (error == QProcess::ProcessError::FailedToStart) {
this->errorMsg = "Tor binary failed to start: " + this->torPath; this->setErrorMessage("Tor binary failed to start: " + this->torPath);
this->m_stopRetries = true; this->m_stopRetries = true;
} }
} }
@ -236,6 +243,11 @@ bool TorManager::shouldStartTorDaemon() {
return false; return false;
#endif #endif
// Don't start a Tor daemon if our proxy config isn't set to Tor
if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
return false;
}
// Don't start a Tor daemon if --use-local-tor is specified // Don't start a Tor daemon if --use-local-tor is specified
if (config()->get(Config::useLocalTor).toBool()) { if (config()->get(Config::useLocalTor).toBool()) {
return false; return false;
@ -250,7 +262,7 @@ bool TorManager::shouldStartTorDaemon() {
if (!unpacked) { if (!unpacked) {
// Don't try to start a Tor daemon if unpacking failed // Don't try to start a Tor daemon if unpacking failed
qWarning() << "Error unpacking embedded Tor. Assuming --use-local-tor"; qWarning() << "Error unpacking embedded Tor. Assuming --use-local-tor";
this->errorMsg = "Error unpacking embedded Tor. Assuming --use-local-tor"; this->setErrorMessage("Error unpacking embedded Tor. Assuming --use-local-tor");
return false; return false;
} }
@ -280,6 +292,11 @@ SemanticVersion TorManager::getVersion(const QString &fileName) {
return SemanticVersion::fromString(output); return SemanticVersion::fromString(output);
} }
void TorManager::setErrorMessage(const QString &msg) {
this->errorMsg = msg;
emit statusChanged(msg);
}
TorManager* TorManager::instance() TorManager* TorManager::instance()
{ {
if (!m_instance) { if (!m_instance) {

View file

@ -43,7 +43,7 @@ public:
signals: signals:
void connectionStateChanged(bool connected); void connectionStateChanged(bool connected);
void startupFailure(QString reason); void statusChanged(QString reason);
void logsUpdated(); void logsUpdated();
private slots: private slots:
@ -55,6 +55,7 @@ private slots:
private: private:
bool shouldStartTorDaemon(); bool shouldStartTorDaemon();
void setConnectionState(bool connected); void setConnectionState(bool connected);
void setErrorMessage(const QString &msg);
static QPointer<TorManager> m_instance; static QPointer<TorManager> m_instance;

View file

@ -4,10 +4,11 @@
#include "Updater.h" #include "Updater.h"
#include <common/util.h> #include <common/util.h>
#undef config
#include <openpgp/hash.h> #include <openpgp/hash.h>
#include "utils/config.h"
#include "config-feather.h" #include "config-feather.h"
#include "constants.h"
#include "Utils.h" #include "Utils.h"
#include "utils/AsyncTask.h" #include "utils/AsyncTask.h"
#include "utils/networking.h" #include "utils/networking.h"
@ -22,8 +23,12 @@ Updater::Updater(QObject *parent) :
} }
void Updater::checkForUpdates() { void Updater::checkForUpdates() {
UtilsNetworking network{getNetworkTor()}; UtilsNetworking network{this};
QNetworkReply *reply = network.getJson("https://featherwallet.org/updates.json"); QNetworkReply *reply = network.getJson(QString("%1/updates.json").arg(this->getWebsiteUrl()));
if (!reply) {
emit updateCheckFailed("Can't check for websites: offline mode enabled");
return;
}
connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onUpdateCheckResponse, this, reply)); connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onUpdateCheckResponse, this, reply));
} }
@ -77,9 +82,10 @@ void Updater::wsUpdatesReceived(const QJsonObject &updates) {
// Hooray! New update available // Hooray! New update available
QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(constants::websiteUrl, newVersion); QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(this->getWebsiteUrl(), newVersion);
qDebug() << hashesUrl;
UtilsNetworking network{getNetworkTor()}; UtilsNetworking network{this};
QNetworkReply *reply = network.get(hashesUrl); QNetworkReply *reply = network.get(hashesUrl);
connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onSignedHashesReceived, this, reply, platformTag, newVersion)); connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onSignedHashesReceived, this, reply, platformTag, newVersion));
@ -115,7 +121,7 @@ void Updater::onSignedHashesReceived(QNetworkReply *reply, const QString &platfo
this->state = Updater::State::UPDATE_AVAILABLE; this->state = Updater::State::UPDATE_AVAILABLE;
this->version = version; this->version = version;
this->binaryFilename = binaryFilename; this->binaryFilename = binaryFilename;
this->downloadUrl = QString("https://featherwallet.org/files/releases/%1/%2").arg(platformTag, binaryFilename); this->downloadUrl = QString("%1/files/releases/%2/%3").arg(this->getWebsiteUrl(), platformTag, binaryFilename);
this->hash = hash; this->hash = hash;
this->signer = signer; this->signer = signer;
this->platformTag = platformTag; this->platformTag = platformTag;
@ -154,6 +160,18 @@ QString Updater::getPlatformTag() {
return ""; return "";
} }
QString Updater::getWebsiteUrl() {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
return "http://featherdvtpi7ckdbkb2yxjfwx3oyvr3xjz3oo4rszylfzjdg6pbm3id.onion";
}
else if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
return "http://rwzulgcql2y3n6os2jhmhg6un2m33rylazfnzhf56likav47aylq.b32.i2p";
}
else {
return "https://featherwallet.org";
}
}
QByteArray Updater::verifyParseSignedHashes( QByteArray Updater::verifyParseSignedHashes(
const QByteArray &armoredSignedHashes, const QByteArray &armoredSignedHashes,
const QString &binaryFilename, const QString &binaryFilename,

View file

@ -50,6 +50,7 @@ private:
QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const; QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const;
void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version); void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version);
QString getPlatformTag(); QString getPlatformTag();
QString getWebsiteUrl();
private: private:
std::vector<openpgp::public_key_block> m_maintainers; std::vector<openpgp::public_key_block> m_maintainers;

View file

@ -464,9 +464,6 @@ void externalLinkWarning(QWidget *parent, const QString &url){
QString body = QString("You are about to open the following link:\n\n%1").arg(url); QString body = QString("You are about to open the following link:\n\n%1").arg(url);
if (!(TailsOS::detect() || WhonixOS::detect()))
body += "\n\nYou will NOT be using Tor.";
QMessageBox linkWarning(parent); QMessageBox linkWarning(parent);
linkWarning.setWindowTitle("External link warning"); linkWarning.setWindowTitle("External link warning");
linkWarning.setText(body); linkWarning.setText(body);
@ -574,4 +571,9 @@ QFont relativeFont(int delta) {
font.setPointSize(font.pointSize() + delta); font.setPointSize(font.pointSize() + delta);
return font; return font;
} }
bool isLocalUrl(const QUrl &url) {
QRegularExpression localNetwork(R"((^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.))");
return (localNetwork.match(url.host()).hasMatch() || url.host() == "localhost");
}
} }

View file

@ -66,6 +66,8 @@ namespace Utils
QString getAccountName(); QString getAccountName();
QFont relativeFont(int delta); QFont relativeFont(int delta);
bool isLocalUrl(const QUrl &url);
template<typename QEnum> template<typename QEnum>
QString QtEnumToString (QEnum value) { QString QtEnumToString (QEnum value) {
return QString::fromStdString(std::string(QMetaEnum::fromType<QEnum>().valueToKey(value))); return QString::fromStdString(std::string(QMetaEnum::fromType<QEnum>().valueToKey(value)));

View file

@ -6,6 +6,8 @@
#include <QCoreApplication> #include <QCoreApplication>
#include "utils/Utils.h" #include "utils/Utils.h"
#include "utils/config.h"
WebsocketClient::WebsocketClient(QObject *parent) WebsocketClient::WebsocketClient(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
@ -27,7 +29,7 @@ WebsocketClient::WebsocketClient(QObject *parent)
connect(&m_connectionTimeout, &QTimer::timeout, this, &WebsocketClient::onConnectionTimeout); connect(&m_connectionTimeout, &QTimer::timeout, this, &WebsocketClient::onConnectionTimeout);
m_websocketUrlIndex = QRandomGenerator::global()->bounded(constants::websocketUrls.length()); m_websocketUrlIndex = QRandomGenerator::global()->bounded(m_websocketUrls[this->networkType()].length());
this->nextWebsocketUrl(); this->nextWebsocketUrl();
} }
@ -42,10 +44,18 @@ void WebsocketClient::start() {
return; return;
} }
if (config()->get(Config::offlineMode).toBool()) {
return;
}
if (config()->get(Config::disableWebsocket).toBool()) {
return;
}
// connect & reconnect on errors/close // connect & reconnect on errors/close
qDebug() << "WebSocket connect:" << m_url.url();
auto state = webSocket.state(); auto state = webSocket.state();
if (state != QAbstractSocket::ConnectedState && state != QAbstractSocket::ConnectingState) { if (state != QAbstractSocket::ConnectedState && state != QAbstractSocket::ConnectingState) {
qDebug() << "WebSocket connect:" << m_url.url();
webSocket.open(m_url); webSocket.open(m_url);
} }
} }
@ -90,8 +100,22 @@ void WebsocketClient::onError(QAbstractSocket::SocketError error) {
} }
void WebsocketClient::nextWebsocketUrl() { void WebsocketClient::nextWebsocketUrl() {
m_url = constants::websocketUrls[m_websocketUrlIndex]; Config::Proxy networkType = this->networkType();
m_websocketUrlIndex = (m_websocketUrlIndex+1)%constants::websocketUrls.length(); m_websocketUrlIndex = (m_websocketUrlIndex+1)%m_websocketUrls[networkType].length();
m_url = m_websocketUrls[networkType][m_websocketUrlIndex];
}
Config::Proxy WebsocketClient::networkType() {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
// Websocket performance with onion services is abysmal, connect to clearnet server unless instructed otherwise
return Config::Proxy::Tor;
}
else if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
return Config::Proxy::i2p;
}
else {
return Config::Proxy::None;
}
} }
void WebsocketClient::onConnectionTimeout() { void WebsocketClient::onConnectionTimeout() {

View file

@ -9,6 +9,7 @@
#include <QTimer> #include <QTimer>
#include <QPointer> #include <QPointer>
#include "constants.h" #include "constants.h"
#include "utils/config.h"
class WebsocketClient : public QObject { class WebsocketClient : public QObject {
Q_OBJECT Q_OBJECT
@ -20,6 +21,7 @@ public:
void restart(); void restart();
void stop(); void stop();
void sendMsg(const QByteArray &data); void sendMsg(const QByteArray &data);
void nextWebsocketUrl();
QWebSocket webSocket; QWebSocket webSocket;
@ -33,10 +35,25 @@ private slots:
void onStateChanged(QAbstractSocket::SocketState state); void onStateChanged(QAbstractSocket::SocketState state);
void onbinaryMessageReceived(const QByteArray &message); void onbinaryMessageReceived(const QByteArray &message);
void onError(QAbstractSocket::SocketError error); void onError(QAbstractSocket::SocketError error);
void nextWebsocketUrl();
void onConnectionTimeout(); void onConnectionTimeout();
private: private:
Config::Proxy networkType();
const QHash<Config::Proxy, QVector<QUrl>> m_websocketUrls = {
{Config::Proxy::None, {
QUrl(QStringLiteral("wss://ws.featherwallet.org/ws")),
QUrl(QStringLiteral("wss://ws.featherwallet.net/ws"))
}},
{Config::Proxy::Tor, {
QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws")),
QUrl(QStringLiteral("ws://an5ecwgzyujqe7jverkp42d22zhvjes2mrhvol6tpqcgfkzwseqrafqd.onion/ws"))
}},
{Config::Proxy::i2p, {
QUrl(QStringLiteral("ws://hk5smvnkifjcm5346bs6cmnczwbiupr4jyiw3gz5z7ybaigp72fa.b32.i2p/ws")),
QUrl(QStringLiteral("ws://tr7iahturgfii64txw7cjhrfunmpg35w2lmmqmsa6i4jxwi7vplq.b32.i2p/ws"))
}}
};
QUrl m_url; QUrl m_url;
QTimer m_pingTimer; QTimer m_pingTimer;
QTimer m_connectionTimeout; QTimer m_connectionTimeout;

View file

@ -68,6 +68,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::balanceDisplay, {QS("balanceDisplay"), Config::BalanceDisplay::spendablePlusUnconfirmed}}, {Config::balanceDisplay, {QS("balanceDisplay"), Config::BalanceDisplay::spendablePlusUnconfirmed}},
{Config::inactivityLockEnabled, {QS("inactivityLockEnabled"), false}}, {Config::inactivityLockEnabled, {QS("inactivityLockEnabled"), false}},
{Config::inactivityLockTimeout, {QS("inactivityLockTimeout"), 10}}, {Config::inactivityLockTimeout, {QS("inactivityLockTimeout"), 10}},
{Config::lockOnMinimize, {QS("lockOnMinimize"), false}},
{Config::disableWebsocket, {QS("disableWebsocket"), false}}, {Config::disableWebsocket, {QS("disableWebsocket"), false}},
{Config::offlineMode, {QS("offlineMode"), false}}, {Config::offlineMode, {QS("offlineMode"), false}},
@ -75,7 +76,10 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}}, {Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}},
{Config::hideBalance, {QS("hideBalance"), false}}, {Config::hideBalance, {QS("hideBalance"), false}},
{Config::hideNotifications, {QS("hideNotifications"), false}}, {Config::hideNotifications, {QS("hideNotifications"), false}},
{Config::disableLogging, {QS("disableLogging"), false}}, {Config::hideUpdateNotifications, {QS("hideUpdateNotifications"), false}},
{Config::disableLogging, {QS("disableLogging"), true}},
{Config::writeStackTraceToDisk, {QS("writeStackTraceToDisk"), true}},
{Config::writeRecentlyOpenedWallets, {QS("writeRecentlyOpenedWallets"), true}},
{Config::blockExplorer,{QS("blockExplorer"), "exploremonero.com"}}, {Config::blockExplorer,{QS("blockExplorer"), "exploremonero.com"}},
{Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}}, {Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}},
@ -86,11 +90,14 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::cryptoSymbols, {QS("cryptoSymbols"), QStringList{"BTC", "ETH", "LTC", "XMR", "ZEC"}}}, {Config::cryptoSymbols, {QS("cryptoSymbols"), QStringList{"BTC", "ETH", "LTC", "XMR", "ZEC"}}},
// Tor // Tor
{Config::proxy, {QS("proxy"), Config::Proxy::Tor}},
{Config::torPrivacyLevel, {QS("torPrivacyLevel"), 1}}, {Config::torPrivacyLevel, {QS("torPrivacyLevel"), 1}},
{Config::torOnlyAllowOnion, {QS("torOnlyAllowOnion"), false}},
{Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}}, {Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}},
{Config::socks5Port, {QS("socks5Port"), "9050"}}, {Config::socks5Port, {QS("socks5Port"), "9050"}},
{Config::socks5User, {QS("socks5User"), ""}}, // Unused {Config::socks5User, {QS("socks5User"), ""}}, // Unused
{Config::socks5Pass, {QS("socks5Pass"), ""}}, // Unused {Config::socks5Pass, {QS("socks5Pass"), ""}}, // Unused
{Config::torManagedPort, {QS("torManagedPort"), "19450"}},
{Config::useLocalTor, {QS("useLocalTor"), false}}, {Config::useLocalTor, {QS("useLocalTor"), false}},
{Config::initSyncThreshold, {QS("initSyncThreshold"), 360}} {Config::initSyncThreshold, {QS("initSyncThreshold"), 360}}
}; };

View file

@ -24,7 +24,6 @@ public:
firstRun, firstRun,
warnOnStagenet, warnOnStagenet,
warnOnTestnet, warnOnTestnet,
logLevel,
homeWidget, homeWidget,
donateBeg, donateBeg,
@ -64,39 +63,61 @@ public:
// Settings // Settings
lastSettingsPage, lastSettingsPage,
preferredFiatCurrency,
// Appearance
skin, skin,
amountPrecision, amountPrecision,
dateFormat, dateFormat,
timeFormat, timeFormat,
balanceDisplay, balanceDisplay,
inactivityLockEnabled, preferredFiatCurrency,
inactivityLockTimeout,
disableWebsocket,
offlineMode,
multiBroadcast, // Network -> Proxy
warnOnExternalLink, proxy,
hideBalance,
disableLogging,
hideNotifications,
blockExplorer,
redditFrontend,
localMoneroFrontend,
bountiesFrontend,
fiatSymbols,
cryptoSymbols,
// Tor
torPrivacyLevel,
socks5Host, socks5Host,
socks5Port, socks5Port,
socks5User, socks5User,
socks5Pass, socks5Pass,
useLocalTor, // Prevents Feather from starting bundled Tor daemon useLocalTor, // Prevents Feather from starting bundled Tor daemon
initSyncThreshold torOnlyAllowOnion,
torPrivacyLevel, // Tor node network traffic strategy
torManagedPort, // Port for managed Tor daemon
initSyncThreshold, // Switch to Tor after initial sync threshold blocks
// Network -> Websocket
disableWebsocket,
// Network -> Offline
offlineMode,
// Storage -> Logging
writeStackTraceToDisk,
disableLogging,
logLevel,
// Storage -> Misc
writeRecentlyOpenedWallets,
// Display
hideBalance,
hideUpdateNotifications,
hideNotifications,
warnOnExternalLink,
inactivityLockEnabled,
inactivityLockTimeout,
lockOnMinimize,
// Transactions
multiBroadcast,
// Misc
blockExplorer,
redditFrontend,
localMoneroFrontend,
bountiesFrontend, // unused
fiatSymbols,
cryptoSymbols,
}; };
enum PrivacyLevel { enum PrivacyLevel {
@ -116,6 +137,13 @@ public:
Solo Solo
}; };
enum Proxy {
None = 0,
Tor,
i2p,
socks5
};
~Config() override; ~Config() override;
QVariant get(ConfigKey key); QVariant get(ConfigKey key);
QString getFileName(); QString getFileName();

View file

@ -3,9 +3,9 @@
#include "daemonrpc.h" #include "daemonrpc.h"
DaemonRpc::DaemonRpc(QObject *parent, QNetworkAccessManager *network, QString daemonAddress) DaemonRpc::DaemonRpc(QObject *parent, QString daemonAddress)
: QObject(parent) : QObject(parent)
, m_network(new UtilsNetworking(network, this)) , m_network(new UtilsNetworking(this))
, m_daemonAddress(std::move(daemonAddress)) , m_daemonAddress(std::move(daemonAddress))
{ {
} }
@ -18,7 +18,9 @@ void DaemonRpc::sendRawTransaction(const QString &tx_as_hex, bool do_not_relay,
QString url = QString("%1/send_raw_transaction").arg(m_daemonAddress); QString url = QString("%1/send_raw_transaction").arg(m_daemonAddress);
QNetworkReply *reply = m_network->postJson(url, req); QNetworkReply *reply = m_network->postJson(url, req);
connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::SEND_RAW_TRANSACTION)); connect(reply, &QNetworkReply::finished, [this, reply]{
onResponse(reply, Endpoint::SEND_RAW_TRANSACTION);
});
} }
void DaemonRpc::getTransactions(const QStringList &txs_hashes, bool decode_as_json, bool prune) { void DaemonRpc::getTransactions(const QStringList &txs_hashes, bool decode_as_json, bool prune) {
@ -29,12 +31,19 @@ void DaemonRpc::getTransactions(const QStringList &txs_hashes, bool decode_as_js
QString url = QString("%1/get_transactions").arg(m_daemonAddress); QString url = QString("%1/get_transactions").arg(m_daemonAddress);
QNetworkReply *reply = m_network->postJson(url, req); QNetworkReply *reply = m_network->postJson(url, req);
connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::GET_TRANSACTIONS)); connect(reply, &QNetworkReply::finished, [this, reply]{
onResponse(reply, Endpoint::GET_TRANSACTIONS);
});
} }
void DaemonRpc::onResponse(QNetworkReply *reply, Endpoint endpoint) { void DaemonRpc::onResponse(QNetworkReply *reply, Endpoint endpoint) {
const auto ok = reply->error() == QNetworkReply::NoError; if (!reply) {
const auto err = reply->errorString(); emit ApiResponse(DaemonResponse(false, endpoint, "Offline mode"));
return;
}
bool ok = reply->error() == QNetworkReply::NoError;
QString err = reply->errorString();
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
reply->deleteLater(); reply->deleteLater();
@ -93,7 +102,3 @@ QString DaemonRpc::onSendRawTransactionFailed(const QJsonObject &obj) {
void DaemonRpc::setDaemonAddress(const QString &daemonAddress) { void DaemonRpc::setDaemonAddress(const QString &daemonAddress) {
m_daemonAddress = daemonAddress; m_daemonAddress = daemonAddress;
} }
void DaemonRpc::setNetwork(QNetworkAccessManager *network) {
m_network = new UtilsNetworking(network, this);
}

View file

@ -27,13 +27,12 @@ public:
QJsonObject obj; QJsonObject obj;
}; };
explicit DaemonRpc(QObject *parent, QNetworkAccessManager *network, QString daemonAddress); explicit DaemonRpc(QObject *parent, QString daemonAddress);
void sendRawTransaction(const QString &tx_as_hex, bool do_not_relay = false, bool do_sanity_checks = true); void sendRawTransaction(const QString &tx_as_hex, bool do_not_relay = false, bool do_sanity_checks = true);
void getTransactions(const QStringList &txs_hashes, bool decode_as_json = false, bool prune = false); void getTransactions(const QStringList &txs_hashes, bool decode_as_json = false, bool prune = false);
void setDaemonAddress(const QString &daemonAddress); void setDaemonAddress(const QString &daemonAddress);
void setNetwork(QNetworkAccessManager *network);
signals: signals:
void ApiResponse(DaemonResponse resp); void ApiResponse(DaemonResponse resp);

View file

@ -6,11 +6,11 @@
#include "utils/Utils.h" #include "utils/Utils.h"
#include "utils/networking.h" #include "utils/networking.h"
#include "utils/NetworkManager.h"
#include "config.h" #include "config.h"
UtilsNetworking::UtilsNetworking(QNetworkAccessManager *networkAccessManager, QObject *parent) UtilsNetworking::UtilsNetworking(QObject *parent)
: QObject(parent) : QObject(parent) {}
, m_networkAccessManager(networkAccessManager) {}
void UtilsNetworking::setUserAgent(const QString &userAgent) { void UtilsNetworking::setUserAgent(const QString &userAgent) {
this->m_userAgent = userAgent; this->m_userAgent = userAgent;
@ -21,6 +21,8 @@ QNetworkReply* UtilsNetworking::get(const QString &url) {
return nullptr; return nullptr;
} }
m_networkAccessManager = getNetwork(url);
QNetworkRequest request; QNetworkRequest request;
request.setUrl(QUrl(url)); request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", m_userAgent.toUtf8()); request.setRawHeader("User-Agent", m_userAgent.toUtf8());
@ -33,6 +35,8 @@ QNetworkReply* UtilsNetworking::getJson(const QString &url) {
return nullptr; return nullptr;
} }
m_networkAccessManager = getNetwork(url);
QNetworkRequest request; QNetworkRequest request;
request.setUrl(QUrl(url)); request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", m_userAgent.toUtf8()); request.setRawHeader("User-Agent", m_userAgent.toUtf8());
@ -46,6 +50,8 @@ QNetworkReply* UtilsNetworking::postJson(const QString &url, const QJsonObject &
return nullptr; return nullptr;
} }
m_networkAccessManager = getNetwork(url);
QNetworkRequest request; QNetworkRequest request;
request.setUrl(QUrl(url)); request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", m_userAgent.toUtf8()); request.setRawHeader("User-Agent", m_userAgent.toUtf8());

View file

@ -16,7 +16,7 @@ class UtilsNetworking : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit UtilsNetworking(QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr); explicit UtilsNetworking(QObject *parent = nullptr);
QNetworkReply* get(const QString &url); QNetworkReply* get(const QString &url);
QNetworkReply* getJson(const QString &url); QNetworkReply* getJson(const QString &url);
@ -24,7 +24,7 @@ public:
void setUserAgent(const QString &userAgent); void setUserAgent(const QString &userAgent);
private: private:
QString m_userAgent = "Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0"; QString m_userAgent = "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0";
QNetworkAccessManager *m_networkAccessManager; QNetworkAccessManager *m_networkAccessManager;
}; };

View file

@ -94,18 +94,23 @@ void NodeList::ensureStructure(QJsonObject &obj, NetworkType::Type networkType)
obj[networkTypeStr] = netTypeObj; obj[networkTypeStr] = netTypeObj;
} }
Nodes::Nodes(AppContext *ctx, QObject *parent) Nodes::Nodes(QObject *parent)
: QObject(parent) : QObject(parent)
, modelWebsocket(new NodeModel(NodeSource::websocket, this)) , modelWebsocket(new NodeModel(NodeSource::websocket, this))
, modelCustom(new NodeModel(NodeSource::custom, this)) , modelCustom(new NodeModel(NodeSource::custom, this))
, m_ctx(ctx)
, m_connection(FeatherNode()) , m_connection(FeatherNode())
{ {
// TODO: This class is in desperate need of refactoring
this->loadConfig(); this->loadConfig();
connect(m_ctx, &AppContext::walletRefreshed, this, &Nodes::onWalletRefreshed);
connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this, &Nodes::onWSNodesReceived); connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this, &Nodes::onWSNodesReceived);
} }
void Nodes::setContext(AppContext *ctx) {
m_ctx = ctx;
connect(m_ctx, &AppContext::walletRefreshed, this, &Nodes::onWalletRefreshed);
}
void Nodes::loadConfig() { void Nodes::loadConfig() {
QStringList customNodes = m_nodes.getNodes(constants::networkType, NodeList::custom); QStringList customNodes = m_nodes.getNodes(constants::networkType, NodeList::custom);
for (const auto &node : customNodes) { for (const auto &node : customNodes) {
@ -165,6 +170,9 @@ void Nodes::loadConfig() {
for (const auto &node : nodes_json[netKey].toObject()["clearnet"].toArray()) { for (const auto &node : nodes_json[netKey].toObject()["clearnet"].toArray()) {
nodes_list.append(node); nodes_list.append(node);
} }
for (const auto &node : nodes_json[netKey].toObject()["i2p"].toArray()) {
nodes_list.append(node);
}
for (auto node: nodes_list) { for (auto node: nodes_list) {
FeatherNode wsNode(node.toString()); FeatherNode wsNode(node.toString());
@ -183,30 +191,40 @@ void Nodes::loadConfig() {
void Nodes::connectToNode() { void Nodes::connectToNode() {
// auto connect // auto connect
m_wsExhaustedWarningEmitted = false;
m_customExhaustedWarningEmitted = false;
this->autoConnect(true); this->autoConnect(true);
} }
void Nodes::connectToNode(const FeatherNode &node) { void Nodes::connectToNode(const FeatherNode &node) {
if (!node.isValid()) if (!m_ctx) {
return; return;
}
if (!node.isValid()) {
return;
}
if (config()->get(Config::offlineMode).toBool()) { if (config()->get(Config::offlineMode).toBool()) {
return; return;
} }
qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.toAddress()).arg(node.custom ? "custom" : "ws"); if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) {
if (!node.isOnion()) {
return;
}
}
if (!node.url.userName().isEmpty() && !node.url.password().isEmpty()) qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.toAddress(), node.custom ? "custom" : "ws");
if (!node.url.userName().isEmpty() && !node.url.password().isEmpty()) {
m_ctx->wallet->setDaemonLogin(node.url.userName(), node.url.password()); m_ctx->wallet->setDaemonLogin(node.url.userName(), node.url.password());
}
// Don't use SSL over Tor // Don't use SSL over Tor/i2p
m_ctx->wallet->setUseSSL(!node.isOnion()); m_ctx->wallet->setUseSSL(!node.isAnonymityNetwork());
QString proxyAddress; QString proxyAddress;
if (useTorProxy(node)) { if (useSocks5Proxy(node)) {
if (!torManager()->isLocalTor()) { if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
proxyAddress = QString("%1:%2").arg(torManager()->featherTorHost, QString::number(torManager()->featherTorPort)); proxyAddress = QString("%1:%2").arg(torManager()->featherTorHost, QString::number(torManager()->featherTorPort));
} else { } else {
proxyAddress = QString("%1:%2").arg(config()->get(Config::socks5Host).toString(), proxyAddress = QString("%1:%2").arg(config()->get(Config::socks5Host).toString(),
@ -225,6 +243,10 @@ void Nodes::connectToNode(const FeatherNode &node) {
} }
void Nodes::autoConnect(bool forceReconnect) { void Nodes::autoConnect(bool forceReconnect) {
if (!m_ctx) {
return;
}
// this function is responsible for automatically connecting to a daemon. // this function is responsible for automatically connecting to a daemon.
if (m_ctx->wallet == nullptr || !m_enableAutoconnect) { if (m_ctx->wallet == nullptr || !m_enableAutoconnect) {
return; return;
@ -234,7 +256,7 @@ void Nodes::autoConnect(bool forceReconnect) {
bool wsMode = (this->source() == NodeSource::websocket); bool wsMode = (this->source() == NodeSource::websocket);
if (wsMode && !m_wsNodesReceived && websocketNodes().count() == 0) { if (wsMode && !m_wsNodesReceived && websocketNodes().count() == 0) {
// this situation should rarely occur due to the usage of the websocket node cache on startup. // this situation should rarely onneccur due to the usage of the websocket node cache on startup.
qInfo() << "Feather is in websocket connection mode but was not able to receive any nodes (yet)."; qInfo() << "Feather is in websocket connection mode but was not able to receive any nodes (yet).";
return; return;
} }
@ -244,7 +266,7 @@ void Nodes::autoConnect(bool forceReconnect) {
m_recentFailures << m_connection.toAddress(); m_recentFailures << m_connection.toAddress();
} }
// try a connect // try connect
FeatherNode node = this->pickEligibleNode(); FeatherNode node = this->pickEligibleNode();
this->connectToNode(node); this->connectToNode(node);
return; return;
@ -257,8 +279,6 @@ void Nodes::autoConnect(bool forceReconnect) {
m_connection.isActive = true; m_connection.isActive = true;
// reset node exhaustion state // reset node exhaustion state
m_wsExhaustedWarningEmitted = false;
m_customExhaustedWarningEmitted = false;
m_recentFailures.clear(); m_recentFailures.clear();
} }
@ -374,7 +394,7 @@ void Nodes::setCustomNodes(const QList<FeatherNode> &nodes) {
} }
void Nodes::onWalletRefreshed() { void Nodes::onWalletRefreshed() {
if (config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptInitSync) { if (config()->get(Config::proxy) == Config::Proxy::Tor && config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptInitSync) {
// Don't reconnect if we're connected to a local node (traffic will not be routed through Tor) // Don't reconnect if we're connected to a local node (traffic will not be routed through Tor)
if (m_connection.isLocal()) if (m_connection.isLocal())
return; return;
@ -388,20 +408,29 @@ void Nodes::onWalletRefreshed() {
} }
bool Nodes::useOnionNodes() { bool Nodes::useOnionNodes() {
if (config()->get(Config::proxy) != Config::Proxy::Tor) {
return false;
}
if (config()->get(Config::torOnlyAllowOnion).toBool()) {
return true;
}
auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt(); auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
if (privacyLevel == Config::allTor) { if (privacyLevel == Config::allTor) {
return true; return true;
} }
if (privacyLevel == Config::allTorExceptInitSync) { if (privacyLevel == Config::allTorExceptInitSync) {
if (m_ctx->refreshed) if (m_ctx && m_ctx->refreshed) {
return true; return true;
}
if (appData()->heights.contains(constants::networkType)) { if (appData()->heights.contains(constants::networkType)) {
int initSyncThreshold = config()->get(Config::initSyncThreshold).toInt(); int initSyncThreshold = config()->get(Config::initSyncThreshold).toInt();
int networkHeight = appData()->heights[constants::networkType]; int networkHeight = appData()->heights[constants::networkType];
if (m_ctx->wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) { if (m_ctx && m_ctx->wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) {
return true; return true;
} }
} }
@ -410,7 +439,15 @@ bool Nodes::useOnionNodes() {
return false; return false;
} }
bool Nodes::useTorProxy(const FeatherNode &node) { bool Nodes::useI2PNodes() {
if (config()->get(Config::proxy) == Config::Proxy::i2p) {
return true;
}
return false;
}
bool Nodes::useSocks5Proxy(const FeatherNode &node) {
if (node.isLocal()) { if (node.isLocal()) {
return false; return false;
} }
@ -423,7 +460,15 @@ bool Nodes::useTorProxy(const FeatherNode &node) {
return true; return true;
} }
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor) {
return this->useOnionNodes(); return this->useOnionNodes();
}
if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
return true;
}
return false;
} }
void Nodes::updateModels() { void Nodes::updateModels() {
@ -450,28 +495,7 @@ void Nodes::resetLocalState() {
} }
void Nodes::exhausted() { void Nodes::exhausted() {
if (this->source() == NodeSource::websocket) // Do nothing
this->WSNodeExhaustedWarning();
else
this->nodeExhaustedWarning();
}
void Nodes::nodeExhaustedWarning(){
if (m_customExhaustedWarningEmitted)
return;
emit nodeExhausted();
qWarning() << "Could not find an eligible custom node to connect to.";
m_customExhaustedWarningEmitted = true;
}
void Nodes::WSNodeExhaustedWarning() {
if (m_wsExhaustedWarningEmitted)
return;
emit WSNodeExhausted();
qWarning() << "Could not find an eligible websocket node to connect to.";
m_wsExhaustedWarningEmitted = true;
} }
QList<FeatherNode> Nodes::nodes() { QList<FeatherNode> Nodes::nodes() {
@ -485,6 +509,7 @@ QList<FeatherNode> Nodes::customNodes() {
QList<FeatherNode> Nodes::websocketNodes() { QList<FeatherNode> Nodes::websocketNodes() {
bool onionNode = this->useOnionNodes(); bool onionNode = this->useOnionNodes();
bool i2pNode = this->useI2PNodes();
QList<FeatherNode> nodes; QList<FeatherNode> nodes;
for (const auto &node : m_websocketNodes) { for (const auto &node : m_websocketNodes) {
@ -492,20 +517,24 @@ QList<FeatherNode> Nodes::websocketNodes() {
continue; continue;
} }
if (i2pNode && !node.isI2P()) {
continue;
}
if (!onionNode && node.isOnion()) { if (!onionNode && node.isOnion()) {
continue; continue;
} }
if (!i2pNode && node.isI2P()) {
continue;
}
nodes.push_back(node); nodes.push_back(node);
} }
return nodes; return nodes;
} }
void Nodes::onTorSettingsChanged() {
this->autoConnect(true);
}
FeatherNode Nodes::connection() { FeatherNode Nodes::connection() {
return m_connection; return m_connection;
} }

View file

@ -8,7 +8,6 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QApplication> #include <QApplication>
#include <QtNetwork> #include <QtNetwork>
#include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include "model/NodeModel.h" #include "model/NodeModel.h"
@ -83,6 +82,14 @@ struct FeatherNode {
return url.host().endsWith(".onion"); return url.host().endsWith(".onion");
} }
bool isI2P() const {
return url.host().endsWith(".i2p");
}
bool isAnonymityNetwork() const {
return isOnion() || isI2P();
};
QString toAddress() const { QString toAddress() const {
return QString("%1:%2").arg(url.host(), QString::number(url.port())); return QString("%1:%2").arg(url.host(), QString::number(url.port()));
} }
@ -111,7 +118,8 @@ class Nodes : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit Nodes(AppContext *ctx, QObject *parent = nullptr); explicit Nodes(QObject *parent = nullptr);
void setContext(AppContext *ctx);
void loadConfig(); void loadConfig();
NodeSource source(); NodeSource source();
@ -132,17 +140,11 @@ public slots:
void setCustomNodes(const QList<FeatherNode>& nodes); void setCustomNodes(const QList<FeatherNode>& nodes);
void autoConnect(bool forceReconnect = false); void autoConnect(bool forceReconnect = false);
void onTorSettingsChanged();
signals:
void WSNodeExhausted();
void nodeExhausted();
private slots: private slots:
void onWalletRefreshed(); void onWalletRefreshed();
private: private:
AppContext *m_ctx; AppContext *m_ctx = nullptr;
QJsonObject m_configJson; QJsonObject m_configJson;
NodeList m_nodes; NodeList m_nodes;
@ -155,20 +157,17 @@ private:
FeatherNode m_connection; // current active connection, if any FeatherNode m_connection; // current active connection, if any
bool m_wsNodesReceived = false; bool m_wsNodesReceived = false;
bool m_wsExhaustedWarningEmitted = true;
bool m_customExhaustedWarningEmitted = true;
bool m_enableAutoconnect = true; bool m_enableAutoconnect = true;
FeatherNode pickEligibleNode(); FeatherNode pickEligibleNode();
bool useOnionNodes(); bool useOnionNodes();
bool useTorProxy(const FeatherNode &node); bool useI2PNodes();
bool useSocks5Proxy(const FeatherNode &node);
void updateModels(); void updateModels();
void resetLocalState(); void resetLocalState();
void exhausted(); void exhausted();
void WSNodeExhaustedWarning();
void nodeExhaustedWarning();
int modeHeight(const QList<FeatherNode> &nodes); int modeHeight(const QList<FeatherNode> &nodes);
}; };

22
src/utils/os/Prestium.cpp Normal file
View file

@ -0,0 +1,22 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "Prestium.h"
#include <QString>
#include <QSysInfo>
#include "utils/Utils.h"
bool Prestium::detect()
{
// Temporary detect
if (Utils::fileExists("/etc/hostname")) {
QByteArray data = Utils::fileOpen("/etc/hostname");
if (data == "prestium\n") {
return true;
}
}
return QSysInfo::prettyProductName().contains("Prestium");
}

13
src/utils/os/Prestium.h Normal file
View file

@ -0,0 +1,13 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_PRESTIUM_H
#define FEATHER_PRESTIUM_H
class Prestium {
public:
static bool detect();
};
#endif //FEATHER_PRESTIUM_H

View file

@ -25,7 +25,7 @@ LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, QSharedPointer<AppContext>
ui->combo_currency->addItem(config()->get(Config::preferredFiatCurrency).toString()); ui->combo_currency->addItem(config()->get(Config::preferredFiatCurrency).toString());
m_network = new UtilsNetworking(getNetworkTor(), this); m_network = new UtilsNetworking(this);
m_api = new LocalMoneroApi(this, m_network); m_api = new LocalMoneroApi(this, m_network);
m_model = new LocalMoneroModel(this); m_model = new LocalMoneroModel(this);

View file

@ -0,0 +1,126 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "NetworkProxyWidget.h"
#include "ui_NetworkProxyWidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QWidget>
#include "utils/config.h"
#include "utils/os/Prestium.h"
NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::NetworkProxyWidget)
, m_torInfoDialog(new TorInfoDialog(this))
{
ui->setupUi(this);
ui->comboBox_proxy->setCurrentIndex(config()->get(Config::proxy).toInt());
connect(ui->comboBox_proxy, &QComboBox::currentIndexChanged, [this](int index){
this->onProxySettingsChanged();
ui->frame_proxy->setVisible(index != Config::Proxy::None);
ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText()));
ui->frame_tor->setVisible(index == Config::Proxy::Tor);
this->updatePort();
});
int proxy = config()->get(Config::proxy).toInt();
ui->frame_proxy->setVisible(proxy != Config::Proxy::None);
ui->frame_tor->setVisible(proxy == Config::Proxy::Tor);
ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText()));
// [Host]
connect(ui->line_host, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Port]
auto *portValidator = new QRegularExpressionValidator{QRegularExpression("[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]")};
ui->line_port->setValidator(portValidator);
ui->line_port->setText(config()->get(Config::socks5Port).toString());
connect(ui->line_port, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Tor settings]
// [Let Feather start and manage a Tor daemon]
#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
ui->checkBox_torManaged->setChecked(false);
ui->checkBox_torManaged->setEnabled(false);
ui->checkBox_torManaged->setToolTip("Feather was bundled without Tor");
#else
ui->checkBox_torManaged->setChecked(!config()->get(Config::useLocalTor).toBool());
connect(ui->checkBox_torManaged, &QCheckBox::toggled, [this](bool toggled){
this->updatePort();
this->onProxySettingsChanged();
if (!m_disableTorLogs) {
ui->frame_torShowLogs->setVisible(toggled);
}
});
#endif
// [Only allow connections to onion services]
ui->checkBox_torOnlyAllowOnion->setChecked(config()->get(Config::torOnlyAllowOnion).toBool());
connect(ui->checkBox_torOnlyAllowOnion, &QCheckBox::toggled, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Node traffic]
ui->comboBox_torNodeTraffic->setCurrentIndex(config()->get(Config::torPrivacyLevel).toInt());
connect(ui->comboBox_torNodeTraffic, &QComboBox::currentIndexChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Show Tor logs]
ui->frame_torShowLogs->setVisible(!config()->get(Config::useLocalTor).toBool());
#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
ui->frame_torShowLogs->setVisible(false);
#endif
connect(ui->btn_torShowLogs, &QPushButton::clicked, [this]{
m_torInfoDialog->show();
});
ui->frame_notice->hide();
}
void NetworkProxyWidget::onProxySettingsChanged() {
if (!m_proxySettingsChanged) {
emit proxySettingsChanged();
}
m_proxySettingsChanged = true;
}
void NetworkProxyWidget::updatePort() {
int socks5port;
switch (ui->comboBox_proxy->currentIndex()) {
case Config::Proxy::Tor: {
socks5port = 9050;
break;
}
case Config::Proxy::i2p: {
if (Prestium::detect()) {
socks5port = 4448;
} else {
socks5port = 4447;
}
break;
}
default: {
socks5port = 9050;
}
}
ui->line_port->setText(QString::number(socks5port));
}
void NetworkProxyWidget::setProxySettings() {
config()->set(Config::proxy, ui->comboBox_proxy->currentIndex());
config()->set(Config::socks5Host, ui->line_host->text());
config()->set(Config::socks5Port, ui->line_port->text());
config()->set(Config::useLocalTor, !ui->checkBox_torManaged->isChecked());
config()->set(Config::torOnlyAllowOnion, ui->checkBox_torOnlyAllowOnion->isChecked());
config()->set(Config::torPrivacyLevel, ui->comboBox_torNodeTraffic->currentIndex());
m_proxySettingsChanged = false;
}
void NetworkProxyWidget::setDisableTorLogs() {
m_disableTorLogs = true;
ui->frame_torShowLogs->hide();
}
NetworkProxyWidget::~NetworkProxyWidget() = default;

View file

@ -0,0 +1,45 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_NETWORKPROXYWIDGET_H
#define FEATHER_NETWORKPROXYWIDGET_H
#include <QWidget>
#include <QTextEdit>
#include "dialog/TorInfoDialog.h"
namespace Ui {
class NetworkProxyWidget;
}
class NetworkProxyWidget : public QWidget
{
Q_OBJECT
public:
explicit NetworkProxyWidget(QWidget *parent = nullptr);
~NetworkProxyWidget() override;
void setProxySettings();
bool isProxySettingsChanged() {
return m_proxySettingsChanged;
};
void setDisableTorLogs();
signals:
void proxySettingsChanged();
private:
void onProxySettingsChanged();
void updatePort();
QScopedPointer<Ui::NetworkProxyWidget> ui;
TorInfoDialog *m_torInfoDialog;
bool m_disableTorLogs = false;
bool m_proxySettingsChanged = false;
};
#endif //FEATHER_NETWORKPROXYWIDGET_H

View file

@ -0,0 +1,293 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NetworkProxyWidget</class>
<widget class="QWidget" name="NetworkProxyWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>649</width>
<height>382</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_21">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Proxy:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_proxy">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Tor</string>
</property>
</item>
<item>
<property name="text">
<string>i2p</string>
</property>
</item>
<item>
<property name="text">
<string>socks5</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame_proxy">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_proxySettings">
<property name="title">
<string>Tor settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="label_36">
<property name="text">
<string>Socks5 Host:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_host">
<property name="text">
<string>127.0.0.1</string>
</property>
<property name="placeholderText">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_37">
<property name="text">
<string>Socks5 Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="line_port">
<property name="text">
<string>9050</string>
</property>
<property name="placeholderText">
<string>9050</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame_tor">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_22">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="checkBox_torManaged">
<property name="text">
<string>Let Feather start and manage a Tor daemon</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_torShowLogs">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_torShowLogs">
<property name="text">
<string>Show status</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBox_torOnlyAllowOnion">
<property name="text">
<string>Only allow connections to onion services</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_32">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Node traffic:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_torNodeTraffic">
<item>
<property name="text">
<string>Never over Tor</string>
</property>
</item>
<item>
<property name="text">
<string>Switch to Tor after initial synchronization</string>
</property>
</item>
<item>
<property name="text">
<string>Always over Tor</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_notice">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_notice">
<property name="text">
<string>Feather is connected to a local node. Proxy settings ignored for node traffic.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -8,7 +8,6 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QInputDialog> #include <QInputDialog>
#include <QMenu> #include <QMenu>
#include <QMessageBox>
#include <QTableWidget> #include <QTableWidget>
#include "model/NodeModel.h" #include "model/NodeModel.h"
@ -20,39 +19,43 @@ NodeWidget::NodeWidget(QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->btn_add_custom, &QPushButton::clicked, this, &NodeWidget::onCustomAddClicked); connect(ui->btn_addCustomNodes, &QPushButton::clicked, this, &NodeWidget::onCustomAddClicked);
ui->nodeBtnGroup->setId(ui->radioButton_websocket, NodeSource::websocket); connect(ui->checkBox_websocketList, &QCheckBox::stateChanged, [this](int id){
ui->nodeBtnGroup->setId(ui->radioButton_custom, NodeSource::custom); bool custom = (id == 0);
ui->stackedWidget->setCurrentIndex(custom);
connect(ui->nodeBtnGroup, &QButtonGroup::idClicked, [this](int id){ ui->frame_addCustomNodes->setVisible(custom);
config()->set(Config::nodeSource, id); config()->set(Config::nodeSource, custom);
emit nodeSourceChanged(static_cast<NodeSource>(id)); emit nodeSourceChanged(static_cast<NodeSource>(custom));
}); });
m_contextActionRemove = new QAction("Remove", this); m_contextActionRemove = new QAction("Remove", this);
m_contextActionConnect = new QAction(icons()->icon("connect.svg"), "Connect to node", this); m_contextActionConnect = new QAction("Connect to node", this);
m_contextActionOpenStatusURL = new QAction(icons()->icon("network.png"), "Visit status page", this); m_contextActionOpenStatusURL = new QAction("Visit status page", this);
m_contextActionCopy = new QAction(icons()->icon("copy.png"), "Copy", this); m_contextActionCopy = new QAction("Copy", this);
connect(m_contextActionConnect, &QAction::triggered, this, &NodeWidget::onContextConnect); connect(m_contextActionConnect, &QAction::triggered, this, &NodeWidget::onContextConnect);
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove); connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextStatusURL); connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextStatusURL);
connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextNodeCopy); connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextNodeCopy);
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove); connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
ui->wsView->setContextMenuPolicy(Qt::CustomContextMenu); ui->treeView_websocket->setContextMenuPolicy(Qt::CustomContextMenu);
ui->customView->setContextMenuPolicy(Qt::CustomContextMenu); ui->treeView_custom->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->wsView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu); connect(ui->treeView_websocket, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu);
connect(ui->customView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu); connect(ui->treeView_custom, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu);
connect(ui->customView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect); connect(ui->treeView_websocket, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
connect(ui->wsView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect); connect(ui->treeView_custom, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
int index = config()->get(Config::nodeSource).toInt();
ui->stackedWidget->setCurrentIndex(config()->get(Config::nodeSource).toInt());
ui->frame_addCustomNodes->setVisible(index);
this->onWebsocketStatusChanged(); this->onWebsocketStatusChanged();
} }
void NodeWidget::onShowWSContextMenu(const QPoint &pos) { void NodeWidget::onShowWSContextMenu(const QPoint &pos) {
m_activeView = ui->wsView; m_activeView = ui->treeView_websocket;
FeatherNode node = this->selectedNode(); FeatherNode node = this->selectedNode();
if (node.toAddress().isEmpty()) return; if (node.toAddress().isEmpty()) return;
@ -60,7 +63,7 @@ void NodeWidget::onShowWSContextMenu(const QPoint &pos) {
} }
void NodeWidget::onShowCustomContextMenu(const QPoint &pos) { void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
m_activeView = ui->customView; m_activeView = ui->treeView_custom;
FeatherNode node = this->selectedNode(); FeatherNode node = this->selectedNode();
if (node.toAddress().isEmpty()) return; if (node.toAddress().isEmpty()) return;
@ -69,33 +72,32 @@ void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
void NodeWidget::onWebsocketStatusChanged() { void NodeWidget::onWebsocketStatusChanged() {
bool disabled = config()->get(Config::disableWebsocket).toBool() || config()->get(Config::offlineMode).toBool(); bool disabled = config()->get(Config::disableWebsocket).toBool() || config()->get(Config::offlineMode).toBool();
QString labelText = disabled ? "From cached list" : "From websocket (recommended)"; ui->treeView_websocket->setColumnHidden(1, disabled);
ui->radioButton_websocket->setText(labelText);
ui->wsView->setColumnHidden(1, disabled);
} }
void NodeWidget::showContextMenu(const QPoint &pos, const FeatherNode &node) { void NodeWidget::showContextMenu(const QPoint &pos, const FeatherNode &node) {
QMenu menu(this); QMenu menu(this);
if (!node.isActive) { if (m_canConnect && !node.isActive) {
menu.addAction(m_contextActionConnect); menu.addAction(m_contextActionConnect);
} }
menu.addAction(m_contextActionOpenStatusURL); menu.addAction(m_contextActionOpenStatusURL);
menu.addAction(m_contextActionCopy); menu.addAction(m_contextActionCopy);
if (m_activeView == ui->customView) if (m_activeView == ui->treeView_custom) {
menu.addAction(m_contextActionRemove); menu.addAction(m_contextActionRemove);
}
menu.exec(m_activeView->viewport()->mapToGlobal(pos)); menu.exec(m_activeView->viewport()->mapToGlobal(pos));
} }
void NodeWidget::onContextConnect() { void NodeWidget::onContextConnect() {
QObject *obj = sender(); QObject *obj = sender();
if (obj == ui->customView) if (obj == ui->treeView_custom)
m_activeView = ui->customView; m_activeView = ui->treeView_custom;
else else
m_activeView = ui->wsView; m_activeView = ui->treeView_websocket;
FeatherNode node = this->selectedNode(); FeatherNode node = this->selectedNode();
if (!node.toAddress().isEmpty()) if (!node.toAddress().isEmpty())
@ -118,7 +120,7 @@ FeatherNode NodeWidget::selectedNode() {
if (!index.isValid()) return FeatherNode(); if (!index.isValid()) return FeatherNode();
FeatherNode node; FeatherNode node;
if (m_activeView == ui->customView) { if (m_activeView == ui->treeView_custom) {
node = m_customModel->node(index.row()); node = m_customModel->node(index.row());
} else { } else {
node = m_wsModel->node(index.row()); node = m_wsModel->node(index.row());
@ -127,21 +129,23 @@ FeatherNode NodeWidget::selectedNode() {
} }
void NodeWidget::onContextCustomNodeRemove() { void NodeWidget::onContextCustomNodeRemove() {
QModelIndex index = ui->customView->currentIndex(); QModelIndex index = ui->treeView_custom->currentIndex();
if (!index.isValid()) return; if (!index.isValid()) {
return;
}
FeatherNode node = m_customModel->node(index.row()); FeatherNode node = m_customModel->node(index.row());
auto nodes = m_ctx->nodes->customNodes(); auto nodes = m_nodes->customNodes();
QMutableListIterator<FeatherNode> i(nodes); QMutableListIterator<FeatherNode> i(nodes);
while (i.hasNext()) while (i.hasNext())
if (i.next() == node) if (i.next() == node)
i.remove(); i.remove();
m_ctx->nodes->setCustomNodes(nodes); m_nodes->setCustomNodes(nodes);
} }
void NodeWidget::onCustomAddClicked(){ void NodeWidget::onCustomAddClicked(){
auto currentNodes = m_ctx->nodes->customNodes(); auto currentNodes = m_nodes->customNodes();
QString currentNodesText; QString currentNodesText;
for (auto &entry: currentNodes) { for (auto &entry: currentNodes) {
@ -149,51 +153,52 @@ void NodeWidget::onCustomAddClicked(){
} }
bool ok; bool ok;
QString text = QInputDialog::getMultiLineText(this, "Add custom node(s).", "E.g: user:password@127.0.0.1:18081", currentNodesText, &ok); QString text = QInputDialog::getMultiLineText(this, "Add custom node(s).", "One node per line\nE.g: user:password@127.0.0.1:18081", currentNodesText, &ok);
if (!ok || text.isEmpty()) if (!ok || text.isEmpty()) {
return; return;
}
QList<FeatherNode> nodesList; QList<FeatherNode> nodesList;
auto newNodesList = text.split("\n"); auto newNodesList = text.split("\n");
for (auto &newNodeText: newNodesList) { for (auto &newNodeText: newNodesList) {
newNodeText = newNodeText.replace("\r", "").trimmed(); newNodeText = newNodeText.replace("\r", "").trimmed();
if (newNodeText.isEmpty()) if (newNodeText.isEmpty()) {
continue; continue;
}
auto node = FeatherNode(newNodeText); auto node = FeatherNode(newNodeText);
node.custom = true; node.custom = true;
nodesList.append(node); nodesList.append(node);
} }
m_ctx->nodes->setCustomNodes(nodesList); m_nodes->setCustomNodes(nodesList);
} }
void NodeWidget::setupUI(QSharedPointer<AppContext> ctx) { void NodeWidget::setupUI(Nodes *nodes) {
m_ctx = ctx; m_nodes = nodes;
auto nodeSource = m_ctx->nodes->source(); auto nodeSource = m_nodes->source();
ui->checkBox_websocketList->setChecked(nodeSource == NodeSource::websocket);
if(nodeSource == NodeSource::websocket){ this->setWSModel(m_nodes->modelWebsocket);
ui->radioButton_websocket->setChecked(true); this->setCustomModel(m_nodes->modelCustom);
} else if(nodeSource == NodeSource::custom) { }
ui->radioButton_custom->setChecked(true);
}
this->setWSModel(m_ctx->nodes->modelWebsocket); void NodeWidget::setCanConnect(bool canConnect) {
this->setCustomModel(m_ctx->nodes->modelCustom); m_canConnect = canConnect;
} }
void NodeWidget::setWSModel(NodeModel *model) { void NodeWidget::setWSModel(NodeModel *model) {
m_wsModel = model; m_wsModel = model;
ui->wsView->setModel(m_wsModel); ui->treeView_websocket->setModel(m_wsModel);
ui->wsView->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch); ui->treeView_websocket->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
ui->wsView->header()->setSectionResizeMode(NodeModel::Height, QHeaderView::ResizeToContents); ui->treeView_websocket->header()->setSectionResizeMode(NodeModel::Height, QHeaderView::ResizeToContents);
} }
void NodeWidget::setCustomModel(NodeModel *model) { void NodeWidget::setCustomModel(NodeModel *model) {
m_customModel = model; m_customModel = model;
ui->customView->setModel(m_customModel); ui->treeView_custom->setModel(m_customModel);
ui->customView->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch); ui->treeView_custom->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
} }
NodeModel* NodeWidget::model() { NodeModel* NodeWidget::model() {

View file

@ -26,7 +26,8 @@ public:
~NodeWidget(); ~NodeWidget();
void setWSModel(NodeModel *model); void setWSModel(NodeModel *model);
void setCustomModel(NodeModel *model); void setCustomModel(NodeModel *model);
void setupUI(QSharedPointer<AppContext> ctx); void setupUI(Nodes *nodes);
void setCanConnect(bool canConnect);
NodeModel* model(); NodeModel* model();
public slots: public slots:
@ -50,9 +51,10 @@ private:
FeatherNode selectedNode(); FeatherNode selectedNode();
QScopedPointer<Ui::NodeWidget> ui; QScopedPointer<Ui::NodeWidget> ui;
QSharedPointer<AppContext> m_ctx; Nodes *m_nodes;
NodeModel *m_customModel; NodeModel *m_customModel;
NodeModel *m_wsModel; NodeModel *m_wsModel;
bool m_canConnect = true;
QTreeView *m_activeView; QTreeView *m_activeView;

View file

@ -6,14 +6,14 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>604</width> <width>724</width>
<height>271</height> <height>451</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
</property> </property>
@ -26,30 +26,27 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <widget class="QStackedWidget" name="stackedWidget">
<property name="spacing"> <property name="currentIndex">
<number>6</number> <number>0</number>
</property>
<widget class="QWidget" name="page_websocket">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property> </property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <widget class="QTreeView" name="treeView_websocket">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radioButton_websocket">
<property name="text">
<string>From websocket (recommended)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">nodeBtnGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QTreeView" name="wsView">
<property name="rootIsDecorated"> <property name="rootIsDecorated">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -59,43 +56,87 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="radioButton_custom">
<property name="text">
<string>From custom list</string>
</property>
<attribute name="buttonGroup">
<string notr="true">nodeBtnGroup</string>
</attribute>
</widget> </widget>
</item> <widget class="QWidget" name="page_custom">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<widget class="QTreeView" name="customView"> <widget class="QTreeView" name="treeView_custom">
<property name="rootIsDecorated"> <property name="rootIsDecorated">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</widget>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="btn_add_custom"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="checkBox_websocketList">
<property name="text">
<string>Let Feather manage this list</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QFrame" name="frame_addCustomNodes">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="btn_addCustomNodes">
<property name="text"> <property name="text">
<string>Add custom node(s)</string> <string>Add custom node(s)</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </widget>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
@ -103,7 +144,4 @@
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
<buttongroups>
<buttongroup name="nodeBtnGroup"/>
</buttongroups>
</ui> </ui>

View file

@ -7,6 +7,8 @@
#include <QFileDialog> #include <QFileDialog>
#include "SettingsNewDialog.h"
PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent) PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent)
: QWizardPage(parent) : QWizardPage(parent)
, ui(new Ui::PageMenu) , ui(new Ui::PageMenu)
@ -16,14 +18,9 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
ui->setupUi(this); ui->setupUi(this);
this->setButtonText(QWizard::FinishButton, "Open recent wallet"); this->setButtonText(QWizard::FinishButton, "Open recent wallet");
#if defined(Q_OS_MAC)
ui->check_darkMode->setVisible(false);
#endif
QString settingsSkin = config()->get(Config::skin).toString(); QString settingsSkin = config()->get(Config::skin).toString();
ui->check_darkMode->setChecked(settingsSkin == "QDarkStyle");
connect(ui->check_darkMode, &QCheckBox::toggled, this, &PageMenu::enableDarkMode); connect(ui->btn_openSettings, &QPushButton::clicked, this, &PageMenu::showSettings);
} }
void PageMenu::initializePage() { void PageMenu::initializePage() {

View file

@ -25,7 +25,7 @@ public:
int nextId() const override; int nextId() const override;
signals: signals:
void enableDarkMode(bool enable); void showSettings();
private: private:
Ui::PageMenu *ui; Ui::PageMenu *ui;

View file

@ -88,15 +88,29 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="check_darkMode"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="focusPolicy"> <item>
<enum>Qt::NoFocus</enum> <widget class="QPushButton" name="btn_openSettings">
</property>
<property name="text"> <property name="text">
<string>Dark mode</string> <string>Settings</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>

View file

@ -7,6 +7,7 @@
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
#include "constants.h" #include "constants.h"
#include "utils/os/Prestium.h"
#include "Utils.h" #include "Utils.h"
#include "WalletWizard.h" #include "WalletWizard.h"
@ -55,7 +56,11 @@ PageNetwork::PageNetwork(QWidget *parent)
} }
int PageNetwork::nextId() const { int PageNetwork::nextId() const {
return WalletWizard::Page_NetworkTor; if (Prestium::detect()) {
return WalletWizard::Page_NetworkWebsocket;
}
return WalletWizard::Page_NetworkProxy;
} }
bool PageNetwork::validatePage() { bool PageNetwork::validatePage() {

View file

@ -51,7 +51,7 @@
<item> <item>
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>In most cases you want to let Feather pick one at random. Nodes are hosted by the Feather team and trusted members of the Monero community. </string> <string>In most cases you want to let Feather pick one at random. Nodes are hosted by the developers and trusted members of the Monero community. </string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@ -171,7 +171,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Custom node:</string> <string>Node:</string>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -0,0 +1,43 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "PageNetworkProxy.h"
#include "ui_PageNetworkProxy.h"
#include "WalletWizard.h"
#include <QSysInfo>
PageNetworkProxy::PageNetworkProxy(QWidget *parent)
: QWizardPage(parent)
, ui(new Ui::PageNetworkProxy)
{
ui->setupUi(this);
connect(ui->radio_configureManually, &QRadioButton::toggled, [this](bool checked){
ui->frame_privacyLevel->setVisible(checked);
this->adjustSize();
this->updateGeometry();
});
ui->proxyWidget->setDisableTorLogs();
}
void PageNetworkProxy::initializePage() {
// Fuck you Qt. No squish.
QTimer::singleShot(1, [this]{
ui->frame_privacyLevel->setVisible(false);
});
}
int PageNetworkProxy::nextId() const {
return WalletWizard::Page_NetworkWebsocket;
}
bool PageNetworkProxy::validatePage() {
if (ui->proxyWidget->isProxySettingsChanged()) {
ui->proxyWidget->setProxySettings();
}
emit initialNetworkConfigured();
return true;
}

View file

@ -1,23 +1,23 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project // SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_PAGENETWORKTOR_H #ifndef FEATHER_PageNetworkProxy_H
#define FEATHER_PAGENETWORKTOR_H #define FEATHER_PageNetworkProxy_H
#include <QWizardPage> #include <QWizardPage>
#include "appcontext.h" #include "appcontext.h"
namespace Ui { namespace Ui {
class PageNetworkTor; class PageNetworkProxy;
} }
class PageNetworkTor : public QWizardPage class PageNetworkProxy : public QWizardPage
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit PageNetworkTor(QWidget *parent = nullptr); explicit PageNetworkProxy(QWidget *parent = nullptr);
void initializePage() override; void initializePage() override;
bool validatePage() override; bool validatePage() override;
int nextId() const override; int nextId() const override;
@ -26,7 +26,7 @@ signals:
void initialNetworkConfigured(); void initialNetworkConfigured();
private: private:
Ui::PageNetworkTor *ui; Ui::PageNetworkProxy *ui;
}; };
#endif //FEATHER_PAGENETWORKTOR_H #endif //FEATHER_PageNetworkProxy_H

View file

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PageNetworkProxy</class>
<widget class="QWizardPage" name="PageNetworkProxy">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>450</height>
</rect>
</property>
<property name="windowTitle">
<string>WizardPage</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>How should Feather route its network traffic?</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>By default, Feather routes most traffic over Tor. </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>An exception is made for the initial wallet synchronization after opening a wallet. Synchronization requires a lot of data transfer and is therefore very slow over Tor. </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Connections to local nodes are never routed over Tor.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_useDefaultSettings">
<property name="text">
<string>Use default settings</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_configureManually">
<property name="text">
<string>Change proxy settings</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_privacyLevel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="NetworkProxyWidget" name="proxyWidget" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NetworkProxyWidget</class>
<extends>QWidget</extends>
<header>widgets/NetworkProxyWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -1,56 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "PageNetworkTor.h"
#include "ui_PageNetworkTor.h"
#include "WalletWizard.h"
PageNetworkTor::PageNetworkTor(QWidget *parent)
: QWizardPage(parent)
, ui(new Ui::PageNetworkTor)
{
ui->setupUi(this);
QPixmap iconAllTorExceptNode(":/assets/images/securityLevelStandard.png");
QPixmap iconAllTorExceptInitSync(":/assets/images/securityLevelSafer.png");
QPixmap iconAllTor(":/assets/images/securityLevelSafest.png");
ui->icon_allTorExceptNode->setPixmap(iconAllTorExceptNode.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_allTorExceptInitSync->setPixmap(iconAllTorExceptInitSync.scaledToHeight(16, Qt::SmoothTransformation));
ui->icon_allTor->setPixmap(iconAllTor.scaledToHeight(16, Qt::SmoothTransformation));
connect(ui->radio_configureManually, &QRadioButton::toggled, [this](bool checked){
ui->frame_privacyLevel->setVisible(checked);
this->adjustSize();
this->updateGeometry();
});
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptNode, Config::allTorExceptNode);
ui->btnGroup_privacyLevel->setId(ui->radio_allTorExceptInitSync, Config::allTorExceptInitSync);
ui->btnGroup_privacyLevel->setId(ui->radio_allTor, Config::allTor);
int privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
auto button = ui->btnGroup_privacyLevel->button(privacyLevel);
if (button) {
button->setChecked(true);
}
}
void PageNetworkTor::initializePage() {
// Fuck you Qt. No squish.
QTimer::singleShot(1, [this]{
ui->frame_privacyLevel->setVisible(false);
});
}
int PageNetworkTor::nextId() const {
return WalletWizard::Page_NetworkWebsocket;
}
bool PageNetworkTor::validatePage() {
int id = ui->btnGroup_privacyLevel->checkedId();
config()->set(Config::torPrivacyLevel, id);
emit initialNetworkConfigured();
return true;
}

View file

@ -1,208 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PageNetworkTor</class>
<widget class="QWizardPage" name="PageNetworkTor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>618</width>
<height>438</height>
</rect>
</property>
<property name="windowTitle">
<string>WizardPage</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>How should Feather route its network traffic?</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>By default, Feather routes most traffic over Tor. </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>An exception is made for the initial wallet synchronization after opening a wallet. Synchronization requires a lot of data transfer and is therefore very slow over Tor. </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>On Tails, Whonix, or when Feather is started with Torsocks, all traffic is routed through Tor regardless of application configuration.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Connections to local nodes are never routed over Tor.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_useDefaultSettings">
<property name="text">
<string>Use default settings (recommended)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_configureManually">
<property name="text">
<string>Configure manually</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_privacyLevel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="icon_allTorExceptNode">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTorExceptNode">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Route all traffic over Tor, except traffic to node</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="icon_allTorExceptInitSync">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTorExceptInitSync">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Route all traffic over Tor, except initial wallet sync</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon_allTor">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_allTor">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Route all traffic over Tor</string>
</property>
<attribute name="buttonGroup">
<string notr="true">btnGroup_privacyLevel</string>
</attribute>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="btnGroup_privacyLevel"/>
</buttongroups>
</ui>

View file

@ -30,7 +30,7 @@
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather can connect to an onion service hosted by the Feather developers to obtain pricing information, a curated list of remote nodes, Home feeds, the latest version of Feather Wallet and more.&lt;/p&gt;&lt;p&gt;This service is only used to fetch information and can only be reached over Tor. The wallet does not send information about its state or your transactions to the server. It is not used for any telemetry or crash reports.&lt;/p&gt;&lt;p&gt;If you opt to disable this connection some wallet functionality will be disabled. You can re-enable it at any time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather can connect to a service hosted by the developers to obtain pricing information, a curated list of remote nodes, Home feeds, the latest version of Feather Wallet and more.&lt;/p&gt;&lt;p&gt;This service is only used to fetch information. The wallet does not send information about its state or your transactions to the server. It is not used for any telemetry or crash reports.&lt;/p&gt;&lt;p&gt;If you disable this connection some wallet functionality will be unavailable. You can re-enable it at any time.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View file

@ -16,9 +16,10 @@
#include "PageSetSeedPassphrase.h" #include "PageSetSeedPassphrase.h"
#include "PageSetSubaddressLookahead.h" #include "PageSetSubaddressLookahead.h"
#include "PageHardwareDevice.h" #include "PageHardwareDevice.h"
#include "PageNetworkTor.h" #include "PageNetworkProxy.h"
#include "PageNetworkWebsocket.h" #include "PageNetworkWebsocket.h"
#include "constants.h" #include "constants.h"
#include "SettingsNewDialog.h"
#include <QLineEdit> #include <QLineEdit>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -34,7 +35,7 @@ WalletWizard::WalletWizard(QWidget *parent)
m_walletKeysFilesModel->refresh(); m_walletKeysFilesModel->refresh();
auto networkPage = new PageNetwork(this); auto networkPage = new PageNetwork(this);
auto networkTorPage = new PageNetworkTor(this); auto networkProxyPage = new PageNetworkProxy(this);
auto networkWebsocketPage = new PageNetworkWebsocket(this); auto networkWebsocketPage = new PageNetworkWebsocket(this);
auto menuPage = new PageMenu(&m_wizardFields, m_walletKeysFilesModel, this); auto menuPage = new PageMenu(&m_wizardFields, m_walletKeysFilesModel, this);
auto openWalletPage = new PageOpenWallet(m_walletKeysFilesModel, this); auto openWalletPage = new PageOpenWallet(m_walletKeysFilesModel, this);
@ -49,7 +50,7 @@ WalletWizard::WalletWizard(QWidget *parent)
setPage(Page_CreateWalletSeed, createWalletSeed); setPage(Page_CreateWalletSeed, createWalletSeed);
setPage(Page_SetPasswordPage, walletSetPasswordPage); setPage(Page_SetPasswordPage, walletSetPasswordPage);
setPage(Page_Network, networkPage); setPage(Page_Network, networkPage);
setPage(Page_NetworkTor, networkTorPage); setPage(Page_NetworkProxy, networkProxyPage);
setPage(Page_NetworkWebsocket, networkWebsocketPage); setPage(Page_NetworkWebsocket, networkWebsocketPage);
setPage(Page_WalletRestoreSeed, new PageWalletRestoreSeed(&m_wizardFields, this)); setPage(Page_WalletRestoreSeed, new PageWalletRestoreSeed(&m_wizardFields, this));
setPage(Page_WalletRestoreKeys, new PageWalletRestoreKeys(&m_wizardFields, this)); setPage(Page_WalletRestoreKeys, new PageWalletRestoreKeys(&m_wizardFields, this));
@ -69,12 +70,7 @@ WalletWizard::WalletWizard(QWidget *parent)
emit initialNetworkConfigured(); emit initialNetworkConfigured();
}); });
connect(menuPage, &PageMenu::enableDarkMode, [this](bool enable){ connect(menuPage, &PageMenu::showSettings, this, &WalletWizard::showSettings);
if (enable)
emit skinChanged("QDarkStyle");
else
emit skinChanged("Native");
});
connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet); connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet);

View file

@ -82,7 +82,7 @@ public:
Page_WalletRestoreKeys, Page_WalletRestoreKeys,
Page_SetRestoreHeight, Page_SetRestoreHeight,
Page_HardwareDevice, Page_HardwareDevice,
Page_NetworkTor, Page_NetworkProxy,
Page_NetworkWebsocket Page_NetworkWebsocket
}; };
@ -91,7 +91,7 @@ public:
signals: signals:
void initialNetworkConfigured(); void initialNetworkConfigured();
void skinChanged(const QString &skin); void showSettings();
void openWallet(QString path, QString password); void openWallet(QString path, QString password);
void defaultWalletDirChanged(QString walletDir); void defaultWalletDirChanged(QString walletDir);