-Subproject commit 9a6dad7cf4958f795b41517badf93f1c6c80b313
+Subproject commit f7705c2c6740699a3fa47895473f79c006624559
#include "utils/Updater.h"
#include "utils/WebsocketNotifier.h"
+//#include "misc_log_ex.h"
+
MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
ui->setupUi(this);
qDebug() << "Platform tag: " << this->getPlatformTag();
+// MCWARNING("feather", "Platform tag: " << this->getPlatformTag().toStdString());
// Ensure the destructor is called after closeEvent()
setAttribute(Qt::WA_DeleteOnClose);
connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
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);
this->onTorConnectionStateChanged(torManager()->torConnected);
this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}");
#endif
-#if defined(Q_OS_MACOS)
- this->patchStylesheetMac();
-#endif
-
this->statusBar()->setFixedHeight(30);
m_statusLabelStatus = new QLabel("Idle", this);
m_statusBtnConnectionStatusIndicator = new StatusBarButton(icons()->icon("status_disconnected.svg"), "Connection status", this);
connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, [this](){
- this->onShowSettingsPage(2);
+ this->onShowSettingsPage(SettingsNew::Pages::NETWORK);
});
this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator);
+ this->onConnectionStatusChanged(Wallet::ConnectionStatus_Disconnected);
m_statusAccountSwitcher = new StatusBarButton(icons()->icon("change_account.png"), "Account switcher", this);
connect(m_statusAccountSwitcher, &StatusBarButton::clicked, this, &MainWindow::showAccountSwitcherDialog);
connect(m_statusBtnSeed, &StatusBarButton::clicked, this, &MainWindow::showSeedDialog);
this->statusBar()->addPermanentWidget(m_statusBtnSeed);
- m_statusBtnTor = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Tor settings", this);
- connect(m_statusBtnTor, &StatusBarButton::clicked, this, &MainWindow::menuTorClicked);
- this->statusBar()->addPermanentWidget(m_statusBtnTor);
+ m_statusBtnProxySettings = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Proxy settings", this);
+ connect(m_statusBtnProxySettings, &StatusBarButton::clicked, this, &MainWindow::menuProxySettingsClicked);
+ this->statusBar()->addPermanentWidget(m_statusBtnProxySettings);
+ this->onProxySettingsChanged();
m_statusBtnHwDevice = new StatusBarButton(this->hardwareDevicePairedIcon(), this->getHardwareDevice(), this);
connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked);
connect(m_ctx.get(), &AppContext::keysCorrupted, this, &MainWindow::onKeysCorrupted);
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
- 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::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded);
}
m_ctx->nodes->connectToNode();
m_updateBytes.start(250);
- this->addToRecentlyOpened(m_ctx->wallet->cachePath());
+ if (config()->get(Config::writeRecentlyOpenedWallets).toBool()) {
+ this->addToRecentlyOpened(m_ctx->wallet->cachePath());
+ }
}
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
#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() {
this->updateNetStats();
this->setStatusText("Synchronized");
// Update connection info in status bar.
QIcon icon;
- switch(status){
- case Wallet::ConnectionStatus_Disconnected:
- icon = icons()->icon("status_disconnected.svg");
- this->setStatusText("Disconnected");
- break;
- case Wallet::ConnectionStatus_Connecting:
- icon = icons()->icon("status_lagging.svg");
- this->setStatusText("Connecting to node");
- break;
- case Wallet::ConnectionStatus_WrongVersion:
- icon = icons()->icon("status_disconnected.svg");
- this->setStatusText("Incompatible node");
- break;
- case Wallet::ConnectionStatus_Synchronizing:
- icon = icons()->icon("status_waiting.svg");
- break;
- case Wallet::ConnectionStatus_Synchronized:
- icon = icons()->icon("status_connected.svg");
- break;
- default:
- icon = icons()->icon("status_disconnected.svg");
- break;
+ if (config()->get(Config::offlineMode).toBool()) {
+ icon = icons()->icon("status_offline.svg");
+ this->setStatusText("Offline");
+ } else {
+ switch(status){
+ case Wallet::ConnectionStatus_Disconnected:
+ icon = icons()->icon("status_disconnected.svg");
+ this->setStatusText("Disconnected");
+ break;
+ case Wallet::ConnectionStatus_Connecting:
+ icon = icons()->icon("status_lagging.svg");
+ this->setStatusText("Connecting to node");
+ break;
+ case Wallet::ConnectionStatus_WrongVersion:
+ icon = icons()->icon("status_disconnected.svg");
+ this->setStatusText("Incompatible node");
+ break;
+ case Wallet::ConnectionStatus_Synchronizing:
+ icon = icons()->icon("status_waiting.svg");
+ break;
+ case Wallet::ConnectionStatus_Synchronized:
+ icon = icons()->icon("status_connected.svg");
+ break;
+ default:
+ icon = icons()->icon("status_disconnected.svg");
+ break;
+ }
}
m_statusBtnConnectionStatusIndicator->setIcon(icon);
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() {
QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
}
this->close();
}
+void MainWindow::menuProxySettingsClicked() {
+ this->menuSettingsClicked(true);
+}
+
void MainWindow::menuAboutClicked() {
AboutDialog dialog{this};
dialog.exec();
}
-void MainWindow::menuSettingsClicked() {
- Settings settings{m_ctx, this};
- 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::menuSettingsClicked(bool showProxyTab) {
+ m_windowManager->showSettings(m_ctx, this, showProxyTab);
}
void MainWindow::menuSignVerifyClicked() {
}
void MainWindow::skinChanged(const QString &skinName) {
- m_windowManager->changeSkin(skinName);
ColorScheme::updateFromWidget(this);
this->updateWidgetIcons();
-
-#if defined(Q_OS_MACOS)
- this->patchStylesheetMac();
-#endif
}
void MainWindow::updateWidgetIcons() {
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() {
m_sendWidget->fill(constants::donationAddress, "Donation to the Feather development team");
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
}
-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() {
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;
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) {
+ if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
+ return;
+ }
+
if (connected)
- m_statusBtnTor->setIcon(icons()->icon("tor_logo.png"));
+ m_statusBtnProxySettings->setIcon(icons()->icon("tor_logo.png"));
else
- m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png"));
+ m_statusBtnProxySettings->setIcon(icons()->icon("tor_logo_disabled.png"));
}
void MainWindow::showUpdateNotification() {
+ if (config()->get(Config::hideUpdateNotifications).toBool()) {
+ return;
+ }
+
QString versionDisplay{m_updater->version};
versionDisplay.replace("beta", "Beta");
QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay);
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() {
m_userLastActive = QDateTime::currentSecsSinceEpoch();
}
#include "components.h"
#include "CalcWindow.h"
#include "SettingsDialog.h"
+#include "SettingsNewDialog.h"
#include "dialog/AboutDialog.h"
#include "dialog/AccountSwitcherDialog.h"
void showOrHide();
void bringToFront();
+public slots:
+ void onPreferredFiatCurrencyChanged();
+ void onHideUpdateNotifications(bool hidden);
+
signals:
void closed();
+protected:
+ void changeEvent(QEvent* event) override;
+
private slots:
// TODO: use a consistent naming convention for slots
// Menu
void menuOpenClicked();
void menuNewRestoreClicked();
void menuQuitClicked();
- void menuSettingsClicked();
+ void menuSettingsClicked(bool showProxytab = false);
void menuAboutClicked();
void menuSignVerifyClicked();
void menuVerifyTxProof();
void menuWalletCloseClicked();
- void menuTorClicked();
+ void menuProxySettingsClicked();
void menuToggleTabVisible(const QString &key);
void menuClearHistoryClicked();
void onExportHistoryCSV(bool checked);
void tryStoreWallet();
void onWebsocketStatusChanged(bool enabled);
void showUpdateNotification();
+ void onProxySettingsChanged();
private:
friend WindowManager;
void saveGeo();
void restoreGeo();
void showDebugInfo();
- void showNodeExhaustedMessage();
- void showWSNodeExhaustedMessage();
void createUnsignedTxDialog(UnsignedTransaction *tx);
void updatePasswordIcon();
void updateNetStats();
void updateRecentlyOpenedMenu();
void updateWidgetIcons();
bool verifyPassword(bool sensitive = true);
- void patchStylesheetMac();
void fillSendTab(const QString &address, const QString &description);
void userActivity();
void checkUserActivity();
StatusBarButton *m_statusBtnPassword;
StatusBarButton *m_statusBtnPreferences;
StatusBarButton *m_statusBtnSeed;
- StatusBarButton *m_statusBtnTor;
+ StatusBarButton *m_statusBtnProxySettings;
StatusBarButton *m_statusBtnHwDevice;
QSignalMapper *m_tabShowHideSignalMapper;
}
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::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
}
--- /dev/null
+// 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 ¤cy : 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;
\ No newline at end of file
--- /dev/null
+// 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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SettingsNew</class>
+ <widget class="QDialog" name="SettingsNew">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>841</width>
+ <height>454</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QListWidget" name="selector">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_23"/>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="page_appearance">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Appearance</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_2">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Theme:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="comboBox_theme"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>Amount precision:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="comboBox_amountPrecision"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Date format:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="comboBox_dateFormat"/>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Time format:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="comboBox_timeFormat"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>Balance display:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="comboBox_balanceDisplay">
+ <item>
+ <property name="text">
+ <string>Total balance</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Spendable balance (+ unconfirmed balance)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Spendable balance</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_17">
+ <property name="text">
+ <string>Fiat currency:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QComboBox" name="comboBox_fiatCurrency"/>
+ </item>
+ <item row="6" column="0">
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_network">
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Network</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget_network">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="Node">
+ <attribute name="title">
+ <string>Node</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <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="NodeWidget" name="nodeWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_proxy">
+ <attribute name="title">
+ <string>Proxy</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <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>
+ <widget class="QWidget" name="tab_websocket">
+ <attribute name="title">
+ <string>Websocket</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <item>
+ <widget class="QLabel" name="label_35">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string><html><head/><body><p>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.</p><p>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.</p><p>If you disable this connection some functionality will be unavailable.</p></body></html></string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_11">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Maximum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_enableWebsocket">
+ <property name="text">
+ <string>Enable websocket connection</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_10">
+ <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>
+ <widget class="QWidget" name="tab_offline">
+ <attribute name="title">
+ <string>Offline</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <item>
+ <widget class="QCheckBox" name="checkBox_offlineMode">
+ <property name="text">
+ <string>Disable all network connections (offline mode)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_storage">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Storage</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget_2">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_5">
+ <attribute name="title">
+ <string>Paths</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>This application uses the following paths:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_22">
+ <property name="text">
+ <string>Wallet directory:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <item>
+ <widget class="QLineEdit" name="lineEdit_defaultWalletDir">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_browseDefaultWalletDir">
+ <property name="text">
+ <string>Change</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_openWalletDir">
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_23">
+ <property name="text">
+ <string>Config directory:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLineEdit" name="lineEdit_configDir">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_openConfigDir">
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_24">
+ <property name="text">
+ <string>Application directory:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="lineEdit_applicationDir">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_portableMode">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <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="QLabel" name="label_10">
+ <property name="text">
+ <string>Portable mode is enabled.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Logging</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="checkBox_writeStackTraceToDisk">
+ <property name="text">
+ <string>Write stack trace to file on crash (Linux-only)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_enableLogging">
+ <property name="text">
+ <string>Write log files to disk</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label_9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Log level:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox_logLevel">
+ <item>
+ <property name="text">
+ <string>Warning</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Info</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Debug</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Trace</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QPushButton" name="btn_openLogFile">
+ <property name="text">
+ <string>Open log file</string>
+ </property>
+ </widget>
+ </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>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_6">
+ <attribute name="title">
+ <string>Misc</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <item>
+ <widget class="QCheckBox" name="checkBox_writeRecentlyOpened">
+ <property name="text">
+ <string>Write recently opened wallets to config file</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_display">
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Display</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_3">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="checkBox_hideBalance">
+ <property name="text">
+ <string>Hide balance</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_hideUpdateNotifications">
+ <property name="text">
+ <string>Hide update notifications</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_hideTransactionNotifications">
+ <property name="text">
+ <string>Hide transaction notifications</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_warnOnExternalLink">
+ <property name="text">
+ <string>Warn before opening external link</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="checkBox_lockOnInactivity">
+ <property name="text">
+ <string>Lock wallet after inactivity of</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_lockOnInactivity">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>120</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_25">
+ <property name="text">
+ <string>minutes</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_lockOnMinimize">
+ <property name="text">
+ <string>Lock wallet after minimizing the window</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_memory">
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Memory</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_4">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <item>
+ <widget class="QCheckBox" name="checkBox_17">
+ <property name="text">
+ <string>Encrypt private spendkey on lock</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>263</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_transactions">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Transactions</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="checkBox_multibroadcast">
+ <property name="text">
+ <string>Multibroadcast outgoing transactions</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btn_multibroadcast">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_alwaysOpenAdvancedTxDialog">
+ <property name="text">
+ <string>Always open advanced transaction dialog</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_requirePasswordToSpend">
+ <property name="text">
+ <string>Require password to spend</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_misc">
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string><html><head/><body><p><span style=" font-weight:600;">Misc</span></p></body></html></string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_7">
+ <attribute name="title">
+ <string>Links</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Block explorer:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox_blockExplorer">
+ <item>
+ <property name="text">
+ <string>exploremonero.com</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>xmrchain.net</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>melo.tools</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>moneroblocks.info</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>blockchair.info</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>127.0.0.1:31312</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_18">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Reddit frontend:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox_redditFrontend">
+ <item>
+ <property name="text">
+ <string>old.reddit.com</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>old.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>reddit.com</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>teddit.net</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_19">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>LocalMonero frontend:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox_localMoneroFrontend"/>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="closeButton">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>NodeWidget</class>
+ <extends>QWidget</extends>
+ <header>widgets/NodeWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>NetworkProxyWidget</class>
+ <extends>QWidget</extends>
+ <header>widgets/NetworkProxyWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>closeButton</sender>
+ <signal>accepted()</signal>
+ <receiver>SettingsNew</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>closeButton</sender>
+ <signal>rejected()</signal>
+ <receiver>SettingsNew</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
m_tray->show();
this->initSkins();
+ this->patchMacStylesheet();
this->showCrashLogs();
}
}
+// ######################## 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 ########################
void WindowManager::tryOpenWallet(const QString &path, const QString &password) {
m_initialNetworkConfigured = true;
appData();
- this->initTor();
- this->initWS();
+ this->onProxySettingsChanged();
}
}
-void WindowManager::initTor() {
+void WindowManager::onProxySettingsChanged() {
+ if (Utils::isTorsocks()) {
+ return;
+ }
+
+ // Will kill the process if necessary
torManager()->init();
torManager()->start();
- this->onTorSettingsChanged();
-}
+ QNetworkProxy proxy{QNetworkProxy::NoProxy};
+ if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
+ QString host = config()->get(Config::socks5Host).toString();
+ quint16 port = config()->get(Config::socks5Port).toString().toUShort();
-void WindowManager::onTorSettingsChanged() {
- if (Utils::isTorsocks()) {
- return;
- }
+ if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
+ host = torManager()->featherTorHost;
+ port = torManager()->featherTorPort;
+ }
- // use local tor -> bundled tor
- QString host = config()->get(Config::socks5Host).toString();
- quint16 port = config()->get(Config::socks5Port).toString().toUShort();
- if (!torManager()->isLocalTor()) {
- host = torManager()->featherTorHost;
- port = torManager()->featherTorPort;
+ proxy = QNetworkProxy{QNetworkProxy::Socks5Proxy, host, port};
+ getNetworkSocks5()->setProxy(proxy);
}
- QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port};
- getNetworkTor()->setProxy(proxy);
+ 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 torSettingsChanged();
+ emit proxySettingsChanged();
}
void WindowManager::onWebsocketStatusChanged(bool 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 ########################
-WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) const {
+WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) {
auto *wizard = new WalletWizard;
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::createWallet, this, &WindowManager::tryCreateWallet);
connect(wizard, &WalletWizard::createWalletFromKeys, this, &WindowManager::tryCreateWalletFromKeys);
return data;
}
-void WindowManager::changeSkin(const QString &skinName) {
+void WindowManager::onChangeTheme(const QString &skinName) {
if (!m_skins.contains(skinName)) {
qWarning() << QString("No such skin %1").arg(skinName);
return;
}
config()->set(Config::skin, skinName);
+
qApp->setStyleSheet(m_skins[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
+}
\ No newline at end of file
void close();
void closeWindow(MainWindow *window);
void showWizard(WalletWizard::Page startPage);
- void changeSkin(const QString &skinName);
void restartApplication(const QString &binaryFilename);
void raise();
+ void showSettings(QSharedPointer<AppContext> ctx, QWidget *parent, bool showProxyTab = false);
+
EventFilter *eventFilter;
signals:
- void torSettingsChanged();
+ void proxySettingsChanged();
void websocketStatusChanged(bool enabled);
+ void updateBalance();
+ void offlineMode(bool offline);
public slots:
- void onTorSettingsChanged();
+ void onProxySettingsChanged();
void onWebsocketStatusChanged(bool enabled);
void tryOpenWallet(const QString &path, const QString &password);
void onDeviceButtonPressed();
void onDeviceError(const QString &errorMessage);
void onWalletPassphraseNeeded(bool on_device);
+ void onChangeTheme(const QString &themeName);
private:
void tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset, const QString &subaddressLookahead);
bool autoOpenWallet();
void initWizard();
- WalletWizard* createWizard(WalletWizard::Page startPage) const;
+ WalletWizard* createWizard(WalletWizard::Page startPage);
void handleWalletError(const QString &message);
void displayWalletErrorMessage(const QString &message);
- void initTor();
- void initWS();
void initSkins();
QString loadStylesheet(const QString &resource);
+ void patchMacStylesheet();
+
void buildTrayMenu();
void startupWarning();
void showWarningMessageBox(const QString &title, const QString &message);
#include "LocalMoneroApi.h"
-LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network, const QString &baseUrl)
+#include "utils/config.h"
+
+LocalMoneroApi::LocalMoneroApi(QObject *parent, UtilsNetworking *network)
: QObject(parent)
, m_network(network)
- , m_baseUrl(baseUrl)
{
}
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);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::COUNTRY_CODES));
}
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);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::CURRENCIES));
}
void LocalMoneroApi::paymentMethods(const QString &countryCode) {
QString url;
if (countryCode.isEmpty()) {
- url = QString("%1/payment_methods").arg(m_baseUrl);
+ url = QString("%1/payment_methods").arg(this->getBaseUrl());
} 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);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::PAYMENT_METHODS));
}
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);
connect(reply, &QNetworkReply::finished, std::bind(&LocalMoneroApi::onResponse, this, reply, Endpoint::ACCOUNT_INFO));
}
QString LocalMoneroApi::getBuySellUrl(bool buy, const QString ¤cyCode, const QString &countryCode,
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())
url += QString("/%1").arg(countryCode);
else if (countryCode.isEmpty() && !paymentMethod.isEmpty())
query.addQueryItem("page", QString::number(page));
url += "?" + query.toString();
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";
}
\ No newline at end of file
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 currencies();
private:
QString getBuySellUrl(bool buy, const QString ¤cyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
+ QString getBaseUrl();
UtilsNetworking *m_network;
- QString m_baseUrl;
};
AppContext::AppContext(Wallet *wallet)
: wallet(wallet)
- , nodes(new Nodes(this, this))
+ , nodes(new Nodes(this))
, 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::moneyReceived, this, &AppContext::onMoneyReceived);
connect(this->wallet, &Wallet::deviceError, this, &AppContext::onDeviceError);
connect(this->wallet, &Wallet::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest);
connect(this->wallet, &Wallet::deviceButtonPressed, this, &AppContext::onDeviceButtonPressed);
- connect(this->wallet, &Wallet::connectionStatusChanged, [this]{
- this->nodes->autoConnect();
- });
connect(this->wallet, &Wallet::currentSubaddressAccountChanged, [this]{
this->updateBalance();
});
connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError);
+ nodes->setContext(this);
+
// Store the wallet every 2 minutes
m_storeTimer.start(2 * 60 * 1000);
connect(&m_storeTimer, &QTimer::timeout, [this](){
emit selectedInputsChanged(selectedInputs);
}
-void AppContext::onTorSettingsChanged() {
+void AppContext::onProxySettingsChanged() {
if (Utils::isTorsocks()) {
return;
}
this->nodes->connectToNode();
-
- auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
- qDebug() << "Changed privacyLevel to " << privacyLevel;
}
void AppContext::stopTimers() {
void onDeviceButtonPressed();
void onDeviceError(const QString &message);
- void onTorSettingsChanged(); // should not be here
+ void onProxySettingsChanged(); // should not be here
private slots:
void onMoneySpent(const QString &txId, quint64 amount);
<file>assets/images/camera_dark.png</file>
<file>assets/images/camera_white.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/clock2.png</file>
<file>assets/images/clock3.png</file>
<file>assets/images/eye_blind.png</file>
<file>assets/images/feather.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/hd_32px.png</file>
<file>assets/images/history.png</file>
+ <file>assets/images/i2p.png</file>
<file>assets/images/info.png</file>
<file>assets/images/info2.svg</file>
+ <file>assets/images/interface_32px.png</file>
<file>assets/images/key.png</file>
<file>assets/images/ledger.png</file>
<file>assets/images/ledger_unpaired.png</file>
<file>assets/images/microphone.png</file>
<file>assets/images/mining.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/person.svg</file>
<file>assets/images/preferences.png</file>
<file>assets/images/securityLevelSafestWhite.png</file>
<file>assets/images/securityLevelStandardWhite.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/status_connected_fork.png</file>
<file>assets/images/status_connected.png</file>
<file>assets/images/status_lagging_fork.png</file>
<file>assets/images/status_lagging.png</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.svg</file>
<file>assets/images/tab_addresses.png</file>
<file>assets/images/unpaid.png</file>
<file>assets/images/update.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.svg</file>
<file>assets/images/zoom.png</file>
--- /dev/null
+<?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>
"rino4muoj3zk223twblr53hwt4ukqemjqw4fbiwdcrqqevcrclpuzyyd.onion:18081",
"majesticrepik35vnngouksfl7jiwf6sj7s2doj3bvdffq27tgqoeayd.onion:18089"
],
+ "i2p": [
+ "ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p:18081",
+ "t7lce3j7mwxt32h755u3njjp3k6og3defgueo3iecgsexxnkoezouobc.b32.i2p:18089"
+ ],
"clearnet": [
"node.community.rino.io:18081",
"node.sethforprivacy.com:18089",
#include <QLabel>
#include <QPlainTextEdit>
#include <QLineEdit>
+#include <QListWidget>
class DoublePixmapLabel : public QLabel
{
// donation constants
const QString donationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f";
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
<property name="text">
<string>TextLabel</string>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item row="13" column="1">
#include "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"
-TorInfoDialog::TorInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
+TorInfoDialog::TorInfoDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::TorInfoDialog)
- , m_ctx(std::move(ctx))
{
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);
- }
-
- initConnectionSettings();
- initPrivacyLevel();
- onConnectionStatusChanged(torManager()->torConnected);
+ ui->logs->setPlainText(torManager()->torLogs);
- 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);
+ this->onStatusChanged(torManager()->errorMsg);
+ this->onConnectionStatusChanged(torManager()->torConnected);
+ connect(torManager(), &TorManager::statusChanged, this, &TorInfoDialog::onStatusChanged);
connect(torManager(), &TorManager::connectionStateChanged, this, &TorInfoDialog::onConnectionStatusChanged);
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();
}
}
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->label_testConnectionStatus->setText("Connected");
- } else {
- ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
- ui->label_testConnectionStatus->setText("Disconnected");
- }
-}
-
-void TorInfoDialog::onApplySettings() {
- config()->set(Config::socks5Host, ui->line_host->text());
- config()->set(Config::socks5Port, ui->line_port->text());
-
- int id = ui->btnGroup_privacyLevel->checkedId();
- config()->set(Config::torPrivacyLevel, id);
-
- 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();
+ ui->icon_connectionStatus->setPixmap(QPixmap(":/assets/images/status_disconnected.png").scaledToWidth(16, Qt::SmoothTransformation));
+ ui->label_testConnectionStatus->setText("Disconnected");
}
-
- 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();
+void TorInfoDialog::onStatusChanged(const QString &msg) {
+ ui->message->setText(msg);
- 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);
+ if (msg.isEmpty()) {
+ ui->message->hide();
}
}
#include <QDialog>
-#include "appcontext.h"
-
namespace Ui {
class TorInfoDialog;
}
Q_OBJECT
public:
- explicit TorInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
+ explicit TorInfoDialog(QWidget *parent = nullptr);
~TorInfoDialog() override;
public slots:
private slots:
void onConnectionStatusChanged(bool connected);
- void onApplySettings();
- void onSettingsChanged();
- void onStopTor();
- void onShowInitSyncConfigDialog();
-
-signals:
- void torSettingsChanged();
+ void onStatusChanged(const QString &msg = "");
private:
- void initConnectionSettings();
- void initPrivacyLevel();
-
QScopedPointer<Ui::TorInfoDialog> ui;
- QSharedPointer<AppContext> m_ctx;
};
<rect>
<x>0</x>
<y>0</y>
- <width>703</width>
- <height>804</height>
+ <width>708</width>
+ <height>496</height>
</rect>
</property>
<property name="windowTitle">
<string>Tor settings</string>
</property>
<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>
<widget class="QGroupBox" name="groupBox">
<property name="title">
</property>
</widget>
</item>
- <item>
- <widget class="QLabel" name="label_changes">
- <property name="text">
- <string>(changes not applied)</string>
- </property>
- </widget>
- </item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
</property>
<item>
<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">
<string>Logs</string>
</property>
<number>0</number>
</property>
<property name="topMargin">
- <number>0</number>
+ <number>7</number>
</property>
<property name="rightMargin">
<number>0</number>
</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>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
- <set>QDialogButtonBox::Apply|QDialogButtonBox::Close</set>
+ <set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
ui->setupUi(this);
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_Close, &QPushButton::clicked, this, &TxBroadcastDialog::reject);
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->sendRawTransaction(tx);
}
}
this->accept();
+
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.");
}
}
void UpdateDialog::checkForUpdates() {
- ui->label_header->setText("Checking for updates...");
- ui->label_body->setText("...");
+ ui->label_header->setText("Checking for updates");
+ 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();
}
void UpdateDialog::noUpdateAvailable() {
+ m_waitingTimer.stop();
this->setStatus("Feather is up-to-date.", true);
}
void UpdateDialog::updateAvailable() {
+ m_waitingTimer.stop();
ui->frame->show();
ui->btn_installUpdate->hide();
ui->btn_restart->hide();
}
void UpdateDialog::onUpdateCheckFailed(const QString &errorMsg) {
+ m_waitingTimer.stop();
this->setStatus(QString("Failed to check for updates: %1").arg(errorMsg), false);
}
ui->btn_download->hide();
ui->progressBar->show();
- UtilsNetworking network{getNetworkTor()};
+ UtilsNetworking network{this};
m_reply = network.get(m_updater->downloadUrl);
connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress);
#include <QDialog>
#include <QNetworkReply>
+#include <QTimer>
#include "utils/Updater.h"
QSharedPointer<Updater> m_updater;
QString m_downloadUrl;
-
QString m_updatePath;
std::string m_updateZipArchive;
+ QTimer m_waitingTimer;
+
QNetworkReply *m_reply = nullptr;
};
#include "constants.h"
#include "MainWindow.h"
#include "utils/EventFilter.h"
+#include "utils/os/Prestium.h"
#include "WindowManager.h"
#include "config.h"
std::cout << keyStream.str();
// Write stack trace to disk
- QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
- std::ofstream out(crashLogPath.toStdString());
- out << keyStream.str();
- out.close();
+ if (config()->get(Config::writeStackTraceToDisk).toBool()) {
+ QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
+ std::ofstream out(crashLogPath.toStdString());
+ out << keyStream.str();
+ out.close();
+ }
// Make a last ditch attempt to restart the application
QProcess::startDetached(qApp->arguments()[0], qApp->arguments());
QCommandLineOption useLocalTorOption(QStringList() << "use-local-tor", "Use system wide installed Tor instead of the bundled.");
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");
parser.addOption(quietModeOption);
bool logLevelFromEnv;
int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv);
- if (!logLevelFromEnv) {
- logLevel = 0;
+ if (logLevelFromEnv) {
+ config()->set(Config::logLevel, logLevel);
+ } else {
+ logLevel = config()->get(Config::logLevel).toInt();
}
- config()->set(Config::logLevel, logLevel);
if (parser.isSet("quiet") || config()->get(Config::disableLogging).toBool()) {
qWarning() << "Logging is disabled";
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);
}
if (!QDir().mkpath(walletDir))
qCritical() << "Unable to create dir: " << walletDir;
- // Setup Tor config
- if (parser.isSet("tor-host"))
- config()->set(Config::socks5Host, parser.value("tor-host"));
- if (parser.isSet("tor-port"))
- config()->set(Config::socks5Port, parser.value("tor-port"));
+ // Prestium initial config
+ if (config()->get(Config::firstRun).toBool() && Prestium::detect()) {
+ config()->set(Config::proxy, Config::Proxy::i2p);
+ config()->set(Config::socks5Port, 4448);
+ }
+
if (parser.isSet("use-local-tor"))
config()->set(Config::useLocalTor, true);
{
switch(section) {
case Votes:
- return QString("🡅");
+ return QString(" 🡅 ");
case Title:
return QString("Title");
case Status:
- return QString("Status");
+ return QString(" Status ");
case Bounty:
- return QString("Bounty");
+ return QString(" Bounty ");
default:
return QVariant();
}
NodeModel::NodeModel(int nodeSource, QObject *parent)
: QAbstractTableModel(parent)
, m_nodeSource(nodeSource)
- , m_offline(icons()->icon("expired_icon.png"))
- , m_online(icons()->icon("confirmed_icon.png"))
+ , m_offline(icons()->icon("status_offline.svg"))
+ , m_online(icons()->icon("status_connected.svg"))
{
}
else if (role == Qt::DecorationRole) {
switch (index.column()) {
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();
}
default: {
else if (role == Qt::ToolTipRole) {
switch (index.column()) {
case NodeModel::URL: {
+ if (node.isConnecting)
+ return QString("Feather is connecting to this node.");
if (node.isActive)
return QString("Feather is connected to this node.");
}
case NodeModel::URL:
return QString("Node");
case NodeModel::Height:
- return QString("Height");
+ return QString("Height ");
default:
return QVariant();
}
case Author:
return QString("Author");
case Comments:
- return QString("Comments");
+ return QString(" Comments ");
default:
return QVariant();
}
#include <QCoreApplication>
#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* getNetworkTor()
+QNetworkAccessManager* getNetworkSocks5()
{
- if (!g_networkManagerTor) {
- g_networkManagerTor = new QNetworkAccessManager(QCoreApplication::instance());
+ if (!g_networkManagerSocks5) {
+ g_networkManagerSocks5 = new QNetworkAccessManager(QCoreApplication::instance());
QNetworkProxy proxy;
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName("127.0.0.1");
proxy.setPort(9050);
- g_networkManagerTor->setProxy(proxy);
+ g_networkManagerSocks5->setProxy(proxy);
}
- return g_networkManagerTor;
+ return g_networkManagerSocks5;
}
QNetworkAccessManager* getNetworkClearnet()
return g_networkManagerClearnet;
}
-//void setTorProxy(const QNetworkProxy &proxy)
-//{
-// QNetworkAccessManager *network = getNetworkTor();
-// network->setProxy(proxy);
-//}
\ No newline at end of file
+
+QNetworkAccessManager* getNetwork(const QString &address)
+{
+ 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();
+}
\ No newline at end of file
#include <QNetworkAccessManager>
-QNetworkAccessManager* getNetworkTor();
+QNetworkAccessManager* getNetworkSocks5();
QNetworkAccessManager* getNetworkClearnet();
-//void setTorProxy(const QNetworkProxy &proxy);
+QNetworkAccessManager* getNetwork(const QString &address = "");
#endif //FEATHER_NETWORKMANAGER_H
if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) {
m_process.kill();
}
+
+ featherTorPort = config()->get(Config::torManagedPort).toString().toUShort();
}
void TorManager::stop() {
auto state = m_process.state();
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;
}
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;
}
m_restarts += 1;
if (m_restarts > 4) {
- this->errorMsg = "Tor failed to start: maximum retries exceeded";
+ this->setErrorMessage("Tor failed to start: maximum retries exceeded");
return;
}
this->setConnectionState(code == 0);
}
+ else if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
+ this->setConnectionState(false);
+ }
+
else if (m_localTor) {
QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort();
void TorManager::stateChanged(QProcess::ProcessState state) {
if (state == QProcess::ProcessState::Running) {
+ this->setErrorMessage("");
qWarning() << "Tor started, awaiting bootstrap";
}
else if (state == QProcess::ProcessState::NotRunning) {
if (error == QProcess::ProcessError::Crashed)
qWarning() << "Tor crashed or killed";
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;
}
}
return false;
#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
if (config()->get(Config::useLocalTor).toBool()) {
return false;
if (!unpacked) {
// Don't try to start a Tor daemon if unpacking failed
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 SemanticVersion::fromString(output);
}
+void TorManager::setErrorMessage(const QString &msg) {
+ this->errorMsg = msg;
+ emit statusChanged(msg);
+}
+
TorManager* TorManager::instance()
{
if (!m_instance) {
signals:
void connectionStateChanged(bool connected);
- void startupFailure(QString reason);
+ void statusChanged(QString reason);
void logsUpdated();
private slots:
private:
bool shouldStartTorDaemon();
void setConnectionState(bool connected);
+ void setErrorMessage(const QString &msg);
static QPointer<TorManager> m_instance;
#include "Updater.h"
#include <common/util.h>
+#undef config
#include <openpgp/hash.h>
+#include "utils/config.h"
#include "config-feather.h"
-#include "constants.h"
#include "Utils.h"
#include "utils/AsyncTask.h"
#include "utils/networking.h"
}
void Updater::checkForUpdates() {
- UtilsNetworking network{getNetworkTor()};
- QNetworkReply *reply = network.getJson("https://featherwallet.org/updates.json");
+ UtilsNetworking network{this};
+ 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));
}
// 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);
connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onSignedHashesReceived, this, reply, platformTag, newVersion));
this->state = Updater::State::UPDATE_AVAILABLE;
this->version = version;
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->signer = signer;
this->platformTag = platformTag;
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(
const QByteArray &armoredSignedHashes,
const QString &binaryFilename,
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);
QString getPlatformTag();
+ QString getWebsiteUrl();
private:
std::vector<openpgp::public_key_block> m_maintainers;
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);
linkWarning.setWindowTitle("External link warning");
linkWarning.setText(body);
font.setPointSize(font.pointSize() + delta);
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");
+}
}
QString getAccountName();
QFont relativeFont(int delta);
+ bool isLocalUrl(const QUrl &url);
+
template<typename QEnum>
QString QtEnumToString (QEnum value) {
return QString::fromStdString(std::string(QMetaEnum::fromType<QEnum>().valueToKey(value)));
#include <QCoreApplication>
#include "utils/Utils.h"
+#include "utils/config.h"
+
WebsocketClient::WebsocketClient(QObject *parent)
: QObject(parent)
{
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();
}
return;
}
+ if (config()->get(Config::offlineMode).toBool()) {
+ return;
+ }
+
+ if (config()->get(Config::disableWebsocket).toBool()) {
+ return;
+ }
+
// connect & reconnect on errors/close
- qDebug() << "WebSocket connect:" << m_url.url();
auto state = webSocket.state();
if (state != QAbstractSocket::ConnectedState && state != QAbstractSocket::ConnectingState) {
+ qDebug() << "WebSocket connect:" << m_url.url();
webSocket.open(m_url);
}
}
}
void WebsocketClient::nextWebsocketUrl() {
- m_url = constants::websocketUrls[m_websocketUrlIndex];
- m_websocketUrlIndex = (m_websocketUrlIndex+1)%constants::websocketUrls.length();
+ Config::Proxy networkType = this->networkType();
+ 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() {
#include <QTimer>
#include <QPointer>
#include "constants.h"
+#include "utils/config.h"
class WebsocketClient : public QObject {
Q_OBJECT
void restart();
void stop();
void sendMsg(const QByteArray &data);
+ void nextWebsocketUrl();
QWebSocket webSocket;
void onStateChanged(QAbstractSocket::SocketState state);
void onbinaryMessageReceived(const QByteArray &message);
void onError(QAbstractSocket::SocketError error);
- void nextWebsocketUrl();
void onConnectionTimeout();
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;
QTimer m_pingTimer;
QTimer m_connectionTimeout;
{Config::balanceDisplay, {QS("balanceDisplay"), Config::BalanceDisplay::spendablePlusUnconfirmed}},
{Config::inactivityLockEnabled, {QS("inactivityLockEnabled"), false}},
{Config::inactivityLockTimeout, {QS("inactivityLockTimeout"), 10}},
+ {Config::lockOnMinimize, {QS("lockOnMinimize"), false}},
{Config::disableWebsocket, {QS("disableWebsocket"), false}},
{Config::offlineMode, {QS("offlineMode"), false}},
{Config::multiBroadcast, {QS("multiBroadcast"), true}},
{Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}},
{Config::hideBalance, {QS("hideBalance"), false}},
- {Config::hideNotifications, {QS("hideNotifications"), false}},
- {Config::disableLogging, {QS("disableLogging"), false}},
+ {Config::hideNotifications, {QS("hideNotifications"), 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::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}},
{Config::cryptoSymbols, {QS("cryptoSymbols"), QStringList{"BTC", "ETH", "LTC", "XMR", "ZEC"}}},
// Tor
+ {Config::proxy, {QS("proxy"), Config::Proxy::Tor}},
{Config::torPrivacyLevel, {QS("torPrivacyLevel"), 1}},
+ {Config::torOnlyAllowOnion, {QS("torOnlyAllowOnion"), false}},
{Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}},
{Config::socks5Port, {QS("socks5Port"), "9050"}},
{Config::socks5User, {QS("socks5User"), ""}}, // Unused
{Config::socks5Pass, {QS("socks5Pass"), ""}}, // Unused
+ {Config::torManagedPort, {QS("torManagedPort"), "19450"}},
{Config::useLocalTor, {QS("useLocalTor"), false}},
{Config::initSyncThreshold, {QS("initSyncThreshold"), 360}}
};
firstRun,
warnOnStagenet,
warnOnTestnet,
- logLevel,
homeWidget,
donateBeg,
// Settings
lastSettingsPage,
- preferredFiatCurrency,
+
+ // Appearance
skin,
amountPrecision,
dateFormat,
timeFormat,
balanceDisplay,
- inactivityLockEnabled,
- inactivityLockTimeout,
+ preferredFiatCurrency,
+
+ // Network -> Proxy
+ proxy,
+ socks5Host,
+ socks5Port,
+ socks5User,
+ socks5Pass,
+ useLocalTor, // Prevents Feather from starting bundled Tor daemon
+ 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,
- multiBroadcast,
- warnOnExternalLink,
- hideBalance,
+ // Storage -> Logging
+ writeStackTraceToDisk,
disableLogging,
+ logLevel,
+
+ // Storage -> Misc
+ writeRecentlyOpenedWallets,
+
+ // Display
+ hideBalance,
+ hideUpdateNotifications,
hideNotifications,
+ warnOnExternalLink,
+ inactivityLockEnabled,
+ inactivityLockTimeout,
+ lockOnMinimize,
+
+ // Transactions
+ multiBroadcast,
+ // Misc
blockExplorer,
redditFrontend,
localMoneroFrontend,
- bountiesFrontend,
+ bountiesFrontend, // unused
fiatSymbols,
cryptoSymbols,
-
- // Tor
- torPrivacyLevel,
- socks5Host,
- socks5Port,
- socks5User,
- socks5Pass,
- useLocalTor, // Prevents Feather from starting bundled Tor daemon
- initSyncThreshold
};
enum PrivacyLevel {
Solo
};
+ enum Proxy {
+ None = 0,
+ Tor,
+ i2p,
+ socks5
+ };
+
~Config() override;
QVariant get(ConfigKey key);
QString getFileName();
#include "daemonrpc.h"
-DaemonRpc::DaemonRpc(QObject *parent, QNetworkAccessManager *network, QString daemonAddress)
+DaemonRpc::DaemonRpc(QObject *parent, QString daemonAddress)
: QObject(parent)
- , m_network(new UtilsNetworking(network, this))
+ , m_network(new UtilsNetworking(this))
, m_daemonAddress(std::move(daemonAddress))
{
}
QString url = QString("%1/send_raw_transaction").arg(m_daemonAddress);
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) {
QString url = QString("%1/get_transactions").arg(m_daemonAddress);
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) {
- const auto ok = reply->error() == QNetworkReply::NoError;
- const auto err = reply->errorString();
+ if (!reply) {
+ emit ApiResponse(DaemonResponse(false, endpoint, "Offline mode"));
+ return;
+ }
+
+ bool ok = reply->error() == QNetworkReply::NoError;
+ QString err = reply->errorString();
QByteArray data = reply->readAll();
reply->deleteLater();
void DaemonRpc::setDaemonAddress(const QString &daemonAddress) {
m_daemonAddress = daemonAddress;
}
-
-void DaemonRpc::setNetwork(QNetworkAccessManager *network) {
- m_network = new UtilsNetworking(network, this);
-}
\ No newline at end of file
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 getTransactions(const QStringList &txs_hashes, bool decode_as_json = false, bool prune = false);
void setDaemonAddress(const QString &daemonAddress);
- void setNetwork(QNetworkAccessManager *network);
signals:
void ApiResponse(DaemonResponse resp);
#include "utils/Utils.h"
#include "utils/networking.h"
+#include "utils/NetworkManager.h"
#include "config.h"
-UtilsNetworking::UtilsNetworking(QNetworkAccessManager *networkAccessManager, QObject *parent)
- : QObject(parent)
- , m_networkAccessManager(networkAccessManager) {}
+UtilsNetworking::UtilsNetworking(QObject *parent)
+ : QObject(parent) {}
void UtilsNetworking::setUserAgent(const QString &userAgent) {
this->m_userAgent = userAgent;
return nullptr;
}
+ m_networkAccessManager = getNetwork(url);
+
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", m_userAgent.toUtf8());
return nullptr;
}
+ m_networkAccessManager = getNetwork(url);
+
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", m_userAgent.toUtf8());
return nullptr;
}
+ m_networkAccessManager = getNetwork(url);
+
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", m_userAgent.toUtf8());
Q_OBJECT
public:
- explicit UtilsNetworking(QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr);
+ explicit UtilsNetworking(QObject *parent = nullptr);
QNetworkReply* get(const QString &url);
QNetworkReply* getJson(const QString &url);
void setUserAgent(const QString &userAgent);
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;
};
obj[networkTypeStr] = netTypeObj;
}
-Nodes::Nodes(AppContext *ctx, QObject *parent)
+Nodes::Nodes(QObject *parent)
: QObject(parent)
, modelWebsocket(new NodeModel(NodeSource::websocket, this))
, modelCustom(new NodeModel(NodeSource::custom, this))
- , m_ctx(ctx)
, m_connection(FeatherNode())
{
+ // TODO: This class is in desperate need of refactoring
+
this->loadConfig();
- connect(m_ctx, &AppContext::walletRefreshed, this, &Nodes::onWalletRefreshed);
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() {
QStringList customNodes = m_nodes.getNodes(constants::networkType, NodeList::custom);
for (const auto &node : customNodes) {
for (const auto &node : nodes_json[netKey].toObject()["clearnet"].toArray()) {
nodes_list.append(node);
}
+ for (const auto &node : nodes_json[netKey].toObject()["i2p"].toArray()) {
+ nodes_list.append(node);
+ }
for (auto node: nodes_list) {
FeatherNode wsNode(node.toString());
void Nodes::connectToNode() {
// auto connect
- m_wsExhaustedWarningEmitted = false;
- m_customExhaustedWarningEmitted = false;
this->autoConnect(true);
}
void Nodes::connectToNode(const FeatherNode &node) {
- if (!node.isValid())
+ if (!m_ctx) {
return;
+ }
+
+ if (!node.isValid()) {
+ return;
+ }
if (config()->get(Config::offlineMode).toBool()) {
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;
+ }
+ }
+
+ qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.toAddress(), node.custom ? "custom" : "ws");
- if (!node.url.userName().isEmpty() && !node.url.password().isEmpty())
+ if (!node.url.userName().isEmpty() && !node.url.password().isEmpty()) {
m_ctx->wallet->setDaemonLogin(node.url.userName(), node.url.password());
+ }
- // Don't use SSL over Tor
- m_ctx->wallet->setUseSSL(!node.isOnion());
+ // Don't use SSL over Tor/i2p
+ m_ctx->wallet->setUseSSL(!node.isAnonymityNetwork());
QString proxyAddress;
- if (useTorProxy(node)) {
- if (!torManager()->isLocalTor()) {
+ if (useSocks5Proxy(node)) {
+ if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
proxyAddress = QString("%1:%2").arg(torManager()->featherTorHost, QString::number(torManager()->featherTorPort));
} else {
proxyAddress = QString("%1:%2").arg(config()->get(Config::socks5Host).toString(),
}
void Nodes::autoConnect(bool forceReconnect) {
+ if (!m_ctx) {
+ return;
+ }
+
// this function is responsible for automatically connecting to a daemon.
if (m_ctx->wallet == nullptr || !m_enableAutoconnect) {
return;
bool wsMode = (this->source() == NodeSource::websocket);
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).";
return;
}
m_recentFailures << m_connection.toAddress();
}
- // try a connect
+ // try connect
FeatherNode node = this->pickEligibleNode();
this->connectToNode(node);
return;
m_connection.isActive = true;
// reset node exhaustion state
- m_wsExhaustedWarningEmitted = false;
- m_customExhaustedWarningEmitted = false;
m_recentFailures.clear();
}
}
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)
if (m_connection.isLocal())
return;
}
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();
if (privacyLevel == Config::allTor) {
return true;
}
if (privacyLevel == Config::allTorExceptInitSync) {
- if (m_ctx->refreshed)
+ if (m_ctx && m_ctx->refreshed) {
return true;
+ }
if (appData()->heights.contains(constants::networkType)) {
int initSyncThreshold = config()->get(Config::initSyncThreshold).toInt();
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 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()) {
return false;
}
return true;
}
- return this->useOnionNodes();
+ if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor) {
+ return this->useOnionNodes();
+ }
+
+ if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
+ return true;
+ }
+
+ return false;
}
void Nodes::updateModels() {
}
void Nodes::exhausted() {
- if (this->source() == NodeSource::websocket)
- 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;
+ // Do nothing
}
QList<FeatherNode> Nodes::nodes() {
QList<FeatherNode> Nodes::websocketNodes() {
bool onionNode = this->useOnionNodes();
+ bool i2pNode = this->useI2PNodes();
QList<FeatherNode> nodes;
for (const auto &node : m_websocketNodes) {
continue;
}
+ if (i2pNode && !node.isI2P()) {
+ continue;
+ }
+
if (!onionNode && node.isOnion()) {
continue;
}
+ if (!i2pNode && node.isI2P()) {
+ continue;
+ }
+
nodes.push_back(node);
}
return nodes;
}
-void Nodes::onTorSettingsChanged() {
- this->autoConnect(true);
-}
-
FeatherNode Nodes::connection() {
return m_connection;
}
#include <QRegularExpression>
#include <QApplication>
#include <QtNetwork>
-#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "model/NodeModel.h"
return url.host().endsWith(".onion");
}
+ bool isI2P() const {
+ return url.host().endsWith(".i2p");
+ }
+
+ bool isAnonymityNetwork() const {
+ return isOnion() || isI2P();
+ };
+
QString toAddress() const {
return QString("%1:%2").arg(url.host(), QString::number(url.port()));
}
Q_OBJECT
public:
- explicit Nodes(AppContext *ctx, QObject *parent = nullptr);
+ explicit Nodes(QObject *parent = nullptr);
+ void setContext(AppContext *ctx);
void loadConfig();
NodeSource source();
void setCustomNodes(const QList<FeatherNode>& nodes);
void autoConnect(bool forceReconnect = false);
- void onTorSettingsChanged();
-
-signals:
- void WSNodeExhausted();
- void nodeExhausted();
-
private slots:
void onWalletRefreshed();
private:
- AppContext *m_ctx;
+ AppContext *m_ctx = nullptr;
QJsonObject m_configJson;
NodeList m_nodes;
FeatherNode m_connection; // current active connection, if any
bool m_wsNodesReceived = false;
- bool m_wsExhaustedWarningEmitted = true;
- bool m_customExhaustedWarningEmitted = true;
bool m_enableAutoconnect = true;
FeatherNode pickEligibleNode();
bool useOnionNodes();
- bool useTorProxy(const FeatherNode &node);
+ bool useI2PNodes();
+ bool useSocks5Proxy(const FeatherNode &node);
void updateModels();
void resetLocalState();
void exhausted();
- void WSNodeExhaustedWarning();
- void nodeExhaustedWarning();
int modeHeight(const QList<FeatherNode> &nodes);
};
--- /dev/null
+// 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");
+}
\ No newline at end of file
--- /dev/null
+// 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
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_model = new LocalMoneroModel(this);
--- /dev/null
+// 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;
--- /dev/null
+// 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
--- /dev/null
+<?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>
#include <QDesktopServices>
#include <QInputDialog>
#include <QMenu>
-#include <QMessageBox>
#include <QTableWidget>
#include "model/NodeModel.h"
{
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);
- ui->nodeBtnGroup->setId(ui->radioButton_custom, NodeSource::custom);
-
- connect(ui->nodeBtnGroup, &QButtonGroup::idClicked, [this](int id){
- config()->set(Config::nodeSource, id);
- emit nodeSourceChanged(static_cast<NodeSource>(id));
+ connect(ui->checkBox_websocketList, &QCheckBox::stateChanged, [this](int id){
+ bool custom = (id == 0);
+ ui->stackedWidget->setCurrentIndex(custom);
+ ui->frame_addCustomNodes->setVisible(custom);
+ config()->set(Config::nodeSource, custom);
+ emit nodeSourceChanged(static_cast<NodeSource>(custom));
});
m_contextActionRemove = new QAction("Remove", this);
- m_contextActionConnect = new QAction(icons()->icon("connect.svg"), "Connect to node", this);
- m_contextActionOpenStatusURL = new QAction(icons()->icon("network.png"), "Visit status page", this);
- m_contextActionCopy = new QAction(icons()->icon("copy.png"), "Copy", this);
+ m_contextActionConnect = new QAction("Connect to node", this);
+ m_contextActionOpenStatusURL = new QAction("Visit status page", this);
+ m_contextActionCopy = new QAction("Copy", this);
connect(m_contextActionConnect, &QAction::triggered, this, &NodeWidget::onContextConnect);
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
connect(m_contextActionOpenStatusURL, &QAction::triggered, this, &NodeWidget::onContextStatusURL);
connect(m_contextActionCopy, &QAction::triggered, this, &NodeWidget::onContextNodeCopy);
connect(m_contextActionRemove, &QAction::triggered, this, &NodeWidget::onContextCustomNodeRemove);
- ui->wsView->setContextMenuPolicy(Qt::CustomContextMenu);
- ui->customView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->wsView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu);
- connect(ui->customView, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu);
+ ui->treeView_websocket->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->treeView_custom->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->treeView_websocket, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowWSContextMenu);
+ connect(ui->treeView_custom, &QTreeView::customContextMenuRequested, this, &NodeWidget::onShowCustomContextMenu);
+
+ connect(ui->treeView_websocket, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
+ connect(ui->treeView_custom, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
- connect(ui->customView, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
- connect(ui->wsView, &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();
}
void NodeWidget::onShowWSContextMenu(const QPoint &pos) {
- m_activeView = ui->wsView;
+ m_activeView = ui->treeView_websocket;
FeatherNode node = this->selectedNode();
if (node.toAddress().isEmpty()) return;
}
void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
- m_activeView = ui->customView;
+ m_activeView = ui->treeView_custom;
FeatherNode node = this->selectedNode();
if (node.toAddress().isEmpty()) return;
void NodeWidget::onWebsocketStatusChanged() {
bool disabled = config()->get(Config::disableWebsocket).toBool() || config()->get(Config::offlineMode).toBool();
- QString labelText = disabled ? "From cached list" : "From websocket (recommended)";
- ui->radioButton_websocket->setText(labelText);
- ui->wsView->setColumnHidden(1, disabled);
+ ui->treeView_websocket->setColumnHidden(1, disabled);
}
void NodeWidget::showContextMenu(const QPoint &pos, const FeatherNode &node) {
QMenu menu(this);
- if (!node.isActive) {
+ if (m_canConnect && !node.isActive) {
menu.addAction(m_contextActionConnect);
}
menu.addAction(m_contextActionOpenStatusURL);
menu.addAction(m_contextActionCopy);
- if (m_activeView == ui->customView)
+ if (m_activeView == ui->treeView_custom) {
menu.addAction(m_contextActionRemove);
+ }
menu.exec(m_activeView->viewport()->mapToGlobal(pos));
}
void NodeWidget::onContextConnect() {
QObject *obj = sender();
- if (obj == ui->customView)
- m_activeView = ui->customView;
+ if (obj == ui->treeView_custom)
+ m_activeView = ui->treeView_custom;
else
- m_activeView = ui->wsView;
+ m_activeView = ui->treeView_websocket;
FeatherNode node = this->selectedNode();
if (!node.toAddress().isEmpty())
if (!index.isValid()) return FeatherNode();
FeatherNode node;
- if (m_activeView == ui->customView) {
+ if (m_activeView == ui->treeView_custom) {
node = m_customModel->node(index.row());
} else {
node = m_wsModel->node(index.row());
}
void NodeWidget::onContextCustomNodeRemove() {
- QModelIndex index = ui->customView->currentIndex();
- if (!index.isValid()) return;
+ QModelIndex index = ui->treeView_custom->currentIndex();
+ if (!index.isValid()) {
+ return;
+ }
FeatherNode node = m_customModel->node(index.row());
- auto nodes = m_ctx->nodes->customNodes();
+ auto nodes = m_nodes->customNodes();
QMutableListIterator<FeatherNode> i(nodes);
while (i.hasNext())
if (i.next() == node)
i.remove();
- m_ctx->nodes->setCustomNodes(nodes);
+ m_nodes->setCustomNodes(nodes);
}
void NodeWidget::onCustomAddClicked(){
- auto currentNodes = m_ctx->nodes->customNodes();
+ auto currentNodes = m_nodes->customNodes();
QString currentNodesText;
for (auto &entry: currentNodes) {
}
bool ok;
- QString text = QInputDialog::getMultiLineText(this, "Add custom node(s).", "E.g: user:password@127.0.0.1:18081", currentNodesText, &ok);
- if (!ok || text.isEmpty())
+ 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()) {
return;
+ }
QList<FeatherNode> nodesList;
auto newNodesList = text.split("\n");
for (auto &newNodeText: newNodesList) {
newNodeText = newNodeText.replace("\r", "").trimmed();
- if (newNodeText.isEmpty())
+ if (newNodeText.isEmpty()) {
continue;
+ }
auto node = FeatherNode(newNodeText);
node.custom = true;
nodesList.append(node);
}
- m_ctx->nodes->setCustomNodes(nodesList);
+ m_nodes->setCustomNodes(nodesList);
}
-void NodeWidget::setupUI(QSharedPointer<AppContext> ctx) {
- m_ctx = ctx;
-
- auto nodeSource = m_ctx->nodes->source();
+void NodeWidget::setupUI(Nodes *nodes) {
+ m_nodes = nodes;
+
+ auto nodeSource = m_nodes->source();
+ ui->checkBox_websocketList->setChecked(nodeSource == NodeSource::websocket);
- if(nodeSource == NodeSource::websocket){
- ui->radioButton_websocket->setChecked(true);
- } else if(nodeSource == NodeSource::custom) {
- ui->radioButton_custom->setChecked(true);
- }
+ this->setWSModel(m_nodes->modelWebsocket);
+ this->setCustomModel(m_nodes->modelCustom);
+}
- this->setWSModel(m_ctx->nodes->modelWebsocket);
- this->setCustomModel(m_ctx->nodes->modelCustom);
+void NodeWidget::setCanConnect(bool canConnect) {
+ m_canConnect = canConnect;
}
void NodeWidget::setWSModel(NodeModel *model) {
m_wsModel = model;
- ui->wsView->setModel(m_wsModel);
- ui->wsView->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
- ui->wsView->header()->setSectionResizeMode(NodeModel::Height, QHeaderView::ResizeToContents);
+ ui->treeView_websocket->setModel(m_wsModel);
+ ui->treeView_websocket->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
+ ui->treeView_websocket->header()->setSectionResizeMode(NodeModel::Height, QHeaderView::ResizeToContents);
}
void NodeWidget::setCustomModel(NodeModel *model) {
m_customModel = model;
- ui->customView->setModel(m_customModel);
- ui->customView->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
+ ui->treeView_custom->setModel(m_customModel);
+ ui->treeView_custom->header()->setSectionResizeMode(NodeModel::URL, QHeaderView::Stretch);
}
NodeModel* NodeWidget::model() {
~NodeWidget();
void setWSModel(NodeModel *model);
void setCustomModel(NodeModel *model);
- void setupUI(QSharedPointer<AppContext> ctx);
+ void setupUI(Nodes *nodes);
+ void setCanConnect(bool canConnect);
NodeModel* model();
public slots:
FeatherNode selectedNode();
QScopedPointer<Ui::NodeWidget> ui;
- QSharedPointer<AppContext> m_ctx;
+ Nodes *m_nodes;
NodeModel *m_customModel;
NodeModel *m_wsModel;
+ bool m_canConnect = true;
QTreeView *m_activeView;
<rect>
<x>0</x>
<y>0</y>
- <width>604</width>
- <height>271</height>
+ <width>724</width>
+ <height>451</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>6</number>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>0</number>
</property>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <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>
<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">
- <bool>false</bool>
- </property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
- </widget>
- </item>
- </layout>
+ <widget class="QTreeView" name="treeView_websocket">
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
</item>
+ </layout>
+ </widget>
+ <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>
- <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>
- </item>
- <item>
- <widget class="QTreeView" name="customView">
- <property name="rootIsDecorated">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="btn_add_custom">
- <property name="text">
- <string>Add custom node(s)</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QTreeView" name="treeView_custom">
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ </widget>
</item>
</layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <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>
- <layout class="QHBoxLayout" name="horizontalLayout_9">
- <property name="bottomMargin">
- <number>0</number>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
</property>
- </layout>
+ <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">
+ <string>Add custom node(s)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</item>
</widget>
<resources/>
<connections/>
- <buttongroups>
- <buttongroup name="nodeBtnGroup"/>
- </buttongroups>
</ui>
#include <QFileDialog>
+#include "SettingsNewDialog.h"
+
PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent)
: QWizardPage(parent)
, ui(new Ui::PageMenu)
ui->setupUi(this);
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();
- 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() {
int nextId() const override;
signals:
- void enableDarkMode(bool enable);
+ void showSettings();
private:
Ui::PageMenu *ui;
</spacer>
</item>
<item>
- <widget class="QCheckBox" name="check_darkMode">
- <property name="focusPolicy">
- <enum>Qt::NoFocus</enum>
- </property>
- <property name="text">
- <string>Dark mode</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="btn_openSettings">
+ <property name="text">
+ <string>Settings</string>
+ </property>
+ </widget>
+ </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>
<layout class="QHBoxLayout" name="horizontalLayout">
#include <QtConcurrent/QtConcurrent>
#include "constants.h"
+#include "utils/os/Prestium.h"
#include "Utils.h"
#include "WalletWizard.h"
}
int PageNetwork::nextId() const {
- return WalletWizard::Page_NetworkTor;
+ if (Prestium::detect()) {
+ return WalletWizard::Page_NetworkWebsocket;
+ }
+
+ return WalletWizard::Page_NetworkProxy;
}
bool PageNetwork::validatePage() {
<item>
<widget class="QLabel" name="label_5">
<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 name="wordWrap">
<bool>true</bool>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
- <string>Custom node:</string>
+ <string>Node:</string>
</property>
</widget>
</item>
--- /dev/null
+// 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;
+}
\ No newline at end of file
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
-#ifndef FEATHER_PAGENETWORKTOR_H
-#define FEATHER_PAGENETWORKTOR_H
+#ifndef FEATHER_PageNetworkProxy_H
+#define FEATHER_PageNetworkProxy_H
#include <QWizardPage>
#include "appcontext.h"
namespace Ui {
- class PageNetworkTor;
+ class PageNetworkProxy;
}
-class PageNetworkTor : public QWizardPage
+class PageNetworkProxy : public QWizardPage
{
Q_OBJECT
public:
- explicit PageNetworkTor(QWidget *parent = nullptr);
+ explicit PageNetworkProxy(QWidget *parent = nullptr);
void initializePage() override;
bool validatePage() override;
int nextId() const override;
void initialNetworkConfigured();
private:
- Ui::PageNetworkTor *ui;
+ Ui::PageNetworkProxy *ui;
};
-#endif //FEATHER_PAGENETWORKTOR_H
+#endif //FEATHER_PageNetworkProxy_H
--- /dev/null
+<?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>
+++ /dev/null
-// 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;
-}
\ No newline at end of file
+++ /dev/null
-<?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>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
- <string><html><head/><body><p>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.</p><p>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.</p><p>If you opt to disable this connection some wallet functionality will be disabled. You can re-enable it at any time.</p></body></html></string>
+ <string><html><head/><body><p>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.</p><p>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.</p><p>If you disable this connection some wallet functionality will be unavailable. You can re-enable it at any time.</p></body></html></string>
</property>
<property name="wordWrap">
<bool>true</bool>
#include "PageSetSeedPassphrase.h"
#include "PageSetSubaddressLookahead.h"
#include "PageHardwareDevice.h"
-#include "PageNetworkTor.h"
+#include "PageNetworkProxy.h"
#include "PageNetworkWebsocket.h"
#include "constants.h"
+#include "SettingsNewDialog.h"
#include <QLineEdit>
#include <QVBoxLayout>
m_walletKeysFilesModel->refresh();
auto networkPage = new PageNetwork(this);
- auto networkTorPage = new PageNetworkTor(this);
+ auto networkProxyPage = new PageNetworkProxy(this);
auto networkWebsocketPage = new PageNetworkWebsocket(this);
auto menuPage = new PageMenu(&m_wizardFields, m_walletKeysFilesModel, this);
auto openWalletPage = new PageOpenWallet(m_walletKeysFilesModel, this);
setPage(Page_CreateWalletSeed, createWalletSeed);
setPage(Page_SetPasswordPage, walletSetPasswordPage);
setPage(Page_Network, networkPage);
- setPage(Page_NetworkTor, networkTorPage);
+ setPage(Page_NetworkProxy, networkProxyPage);
setPage(Page_NetworkWebsocket, networkWebsocketPage);
setPage(Page_WalletRestoreSeed, new PageWalletRestoreSeed(&m_wizardFields, this));
setPage(Page_WalletRestoreKeys, new PageWalletRestoreKeys(&m_wizardFields, this));
emit initialNetworkConfigured();
});
- connect(menuPage, &PageMenu::enableDarkMode, [this](bool enable){
- if (enable)
- emit skinChanged("QDarkStyle");
- else
- emit skinChanged("Native");
- });
+ connect(menuPage, &PageMenu::showSettings, this, &WalletWizard::showSettings);
connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet);
Page_WalletRestoreKeys,
Page_SetRestoreHeight,
Page_HardwareDevice,
- Page_NetworkTor,
+ Page_NetworkProxy,
Page_NetworkWebsocket
};
signals:
void initialNetworkConfigured();
- void skinChanged(const QString &skin);
+ void showSettings();
void openWallet(QString path, QString password);
void defaultWalletDirChanged(QString walletDir);