From f410cca1d0a2e0f79ce2a188e546e5de80d5a4dd Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Mon, 12 Jan 2026 12:46:10 -0500 Subject: [PATCH 1/1] add docs on (orphaned) docs branch --- docs/LIFECYCLE_WALLET_FUNCTIONS.md | 114 ++ docs/bounty.txt | 140 ++ docs/hist.log | 2703 ++++++++++++++++++++++++++++ docs/issues.txt | 15 + docs/setup_flags.sh | 15 + setup_flags.sh | 15 + 6 files changed, 3002 insertions(+) create mode 100644 docs/LIFECYCLE_WALLET_FUNCTIONS.md create mode 100644 docs/bounty.txt create mode 100644 docs/hist.log create mode 100644 docs/issues.txt create mode 100644 docs/setup_flags.sh create mode 100644 setup_flags.sh diff --git a/docs/LIFECYCLE_WALLET_FUNCTIONS.md b/docs/LIFECYCLE_WALLET_FUNCTIONS.md new file mode 100644 index 00000000..6ccbc806 --- /dev/null +++ b/docs/LIFECYCLE_WALLET_FUNCTIONS.md @@ -0,0 +1,114 @@ +# Wallet Lifecycle Functions + +This document outlines the key functions modified or introduced in the recent feature work regarding sync pausing, skipping, minimization behavior, and application exit. + +## `src/libwalletqt/Wallet.cpp` + +### `syncStatusUpdated(quint64 height, quint64 target)` +**Role:** Signals the UI about synchronization progress. +**Modification:** +- Updated to check for `m_stopHeight` during range syncs. +- Calls `skipToTip()` if the range sync target is reached. +- Emits `syncStatus` signal to update the UI. +- **Note:** Contains a `TODO` regarding the necessity of `updateBalance()`. +- Emits `syncStatus` signal to update the UI. +- **Note:** Contains a `TODO` regarding the necessity of `updateBalance()`. + +### `initAsync(const QString &daemonAddress, bool trustedDaemon, quint64 searchHeight)` +**Role:** Initializes the wallet asynchronously. +**Details:** +- Sets connection status to `Connecting`. +- Attempts to connect to the daemon. +- On success, if sync is not paused, calls `startRefresh()`. + +### `startRefresh()` +**Role:** Enables the background refresh loop. +**Implementation:** +- Sets `m_refreshEnabled = true`. +- Sets `m_refreshNow = true` to force an immediate check in the refresh thread. + +### `pauseRefresh()` +**Role:** Disables the background refresh loop. +**Implementation:** +- Sets `m_refreshEnabled = false`. +- Used by `setSyncPaused`, `skipToTip`, and other methods that need to interrupt scanning. + +### `startRefreshThread()` +**Role:** The main background loop for wallet synchronization. +**Details:** +- Runs continuously on a separate thread. +- Checks `m_refreshEnabled` every 10 seconds (or immediately if `m_refreshNow` is true). +- Calls `m_walletImpl->refresh()` to fetch blocks. +- Emits `refreshed` signals. + +### `setSyncPaused(bool paused)` +**Role:** Pauses or resumes the wallet synchronization process. +**Implementation:** +- Sets `m_syncPaused` flag. +- Calls `pauseRefresh()` or `startRefresh()` on the underlying `wallet2` instance. + +### `skipToTip()` +**Role:** Fast-forwards the wallet to the current network tip. +**Usage:** Used when the user wants to skip scanning old blocks (e.g. data saver mode). +**Details:** +- Gets current blockchain height. +- Updates refresh height. +- Logs the jump ("Head moving from X to Y"). + +### `syncDateRange(const QDate &start, const QDate &end)` +**Role:** Syncs the wallet for a specific date range. +**Workflow:** +- Converts dates to block heights using `RestoreHeightLookup`. +- Sets `m_wallet2` refresh height to start date. +- Sets internal `m_stopHeight`. +- Enables `m_rangeSyncActive`. + +### `fullSync()` +**Role:** Resets the wallet to scan from the original creation height. +**Details:** +- Retrieves `feather.creation_height` from cache. +- Fallback to current height if creation height is missing (with warning). +- Clears `m_rangeSyncActive`. +- Triggers rescan. + +## `src/MainWindow.cpp` + +### `onSyncStatus(quint64 height, quint64 target, bool daemonSync)` +**Role:** Updates the status bar with sync progress. +**Modifications:** +- Added detailed logging (escalated to `qWarning` for visibility). +- Improved tooltip to show Wallet Height vs Network Tip. +- Formats "Blocks remaining" and "Approximate size". + +### `changeEvent(QEvent *event)` +**Role:** Handles window state changes (minimization). +**Work:** +- **Reverted Attempt:** Initial `QWindow::visibilityChanged` hook was unreliable on Wayland. +- **Final Solution:** Uses `QEvent::ActivationChange`. +- **Implementation:** + - Checks `isActiveWindow()`. + - Uses a `QTimer::singleShot` (500ms) to delay the check for `!isExposed()`. + - Minimizes to tray if the window is truly hidden/not exposed. + - Logs events with `qWarning` (previously `qInfo`) for debugging Wayland behavior. + - Handles `lockOnMinimize`. + +### `setPausedSyncStatus()` +**Role:** Updates UI when sync is explicitly paused. +**Details:** +- Sets status text to "Sync paused". +- Improved logging (`qWarning`). + +## `src/WindowManager.cpp` + +### `close()` +**Role:** Handles application shutdown. +**Critical Fix:** +- Reordered shutdown logic to close all windows *before* waiting for the global thread pool. +- Prevents deadlocks where background threads (refreshing wallets) would hang waiting for the thread pool while the UI was still partially active. +- Added recursion guard `m_closing` to prevent infinite loops during tray exit. + +### `toggleVisibility()` (Concept) +**Role:** Toggles window show/hide state. +**Context:** +- Integrated into the tray icon left-click handler logic within `DataManager` / `WindowManager` context (specifically `QSystemTrayIcon::activated`). +- Replaced the "Context Menu" on left click default behavior. diff --git a/docs/bounty.txt b/docs/bounty.txt new file mode 100644 index 00000000..3d5074a9 --- /dev/null +++ b/docs/bounty.txt @@ -0,0 +1,140 @@ +1.230ɱ | Add a skip sync feature to a Monero wallet +Posted by Anonymous +· Apr 23 +Description + +Background: + +A "skip sync" feature would allow users to save time and mobile data, if they know they haven't received Monero since the last time they opened their wallet. + +In some places, KYC-free mobile data is expensive (e.g. $10 for 1 GB) or limited (e.g. prepaid plans max out at 5 GB/month). In addition, public wifi may be unreliable or difficult to find. + +Not only would skip sync save 500 MB+ if the user didn't open the wallet for some weeks, it would also save 30+ minutes of waiting to sync, which is convenient when paying in a brick-and-mortar store or doing a cash in person trade. + +How it works: + + Disable "auto_refresh" (to avoid automatic or background syncing) + One button named "Skip sync", which runs the "refresh" command with "start_height = the current blockheight" + One button named "Sync dates", which allows you to specify a start date and end date to sync (e.g. April 1 - April 8) + One button named "Full sync (estimated 500 MB)", which syncs from the last sync date until today. + A button to import a TX with "scan_tx" + +Skip sync could be available as an opt-in "Data Saving" option in "Settings" or "Advanced". + +More info: https://codeberg.org/anarkio/ideas/src/branch/main/monero-skip-sync.md + +Unfortunately I can't develop this myself and can't afford to donate. +Voters +plowsofgingeropolouseriswhoeverlovesDigit (2)lau90 +Tags +TBD +Notifications +You’re receiving notifications about activity on this post. +Discussion +Monero Bounties Bot +Monero Bounties Bot +· Apr 23 + +Donate to the address below to fund this bounty +88jqsz71KqbeDqkUtowzJ7Y8nyLPGF47J2XPRwVFj9cM4R5kFLcbNwYRsdaePxhpVAMqYQJNTajkH73dgiTaoyZ1SkujYeX +Your donation will be reflected in the comments. +Payouts will be made once the bounty is complete to the individual(s) who completed the bounty first. +Monero Bounties Bot +Monero Bounties Bot +· Apr 23 · edited + +Bounty increased by 0.05 XMR 💰 +Total Bounty: 0.05 XMR +Monero Bounties Bot +Monero Bounties Bot +· May 23 · edited + +Bounty increased by 0.00000001 XMR 💰 +Total Bounty: 0.05000001 XMR +Monero Bounties Bot +Monero Bounties Bot +· May 23 · edited + +Bounty increased by 0.051 XMR 💰 +Total Bounty: 0.10100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· Jun 23 · edited + +Bounty increased by 0.02 XMR 💰 +Total Bounty: 0.12100001 XMR +Waterdrought +Waterdrought +· May 24 + +ill give it a shot +Monero Bounties Bot +Monero Bounties Bot +· May 24 · edited + +Bounty increased by 0.09 XMR 💰 +Total Bounty: 0.21100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· 5 months ago · edited + +Bounty increased by 0.21 XMR 💰 +Total Bounty: 0.42100001 XMR +Cyrix126 +Cyrix126 +· 4 months ago + +link of the codeberg is dead. +Anyway isn't it an issue for decoy selection which needs the latest block height ? +Monero Bounties Bot +Monero Bounties Bot +· 4 months ago · edited + +Bounty increased by 0.08 XMR 💰 +Total Bounty: 0.50100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· 4 months ago · edited + +Bounty increased by 0.1 XMR 💰 +Total Bounty: 0.60100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· 4 months ago · edited + +Bounty increased by 0.4 XMR 💰 +Total Bounty: 1.00100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· 3 months ago · edited + +Bounty increased by 0.1 XMR 💰 +Total Bounty: 1.10100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· 2 months ago · edited + +Bounty increased by 0.11 XMR 💰 +Total Bounty: 1.21100001 XMR +Monero Bounties Bot +Monero Bounties Bot +· 2 months ago · edited + +Bounty increased by 0.019 XMR 💰 +Total Bounty: 1.23000001 XMR +plowsof +plowsof +· 24 days ago + +the total donated seems to be (3dp): 2.440 XMR +2025-12-15 03:10:28 UTC +Trinity +Trinity +· 22 days ago + +Is this still needed? If so, could you reupload the information? +mr_overquald +mr_overquald +· 14 hours ago + +I'm implementing this in the Feather wallet. I got 'Skip to Tip' and 'Date Range' logic functional in a local build. Working on some edge cases and documenting user impacts. Planning to submit a PR within a week. -gg diff --git a/docs/hist.log b/docs/hist.log new file mode 100644 index 00000000..df769d32 --- /dev/null +++ b/docs/hist.log @@ -0,0 +1,2703 @@ +commit 65c45af6be791fda8edc3978a06eacb50794d3ba +Author: gg +Date: Mon Jan 12 10:52:44 2026 -0500 + + Fix minimize to tray. Add setting: toggle focus on tray left-click. + +diff --git a/setup_flags.sh b/setup_flags.sh +deleted file mode 100644 +index 9112cf76..00000000 +--- a/setup_flags.sh ++++ /dev/null +@@ -1,15 +0,0 @@ +-#!/bin/bash +- +-# Configure CMake with custom flags +-# - FEATHER_VERSION_DEBUG_BUILD=ON : Use git describe for version string +-# - CHECK_UPDATES=OFF : Disable built-in update checker +-# - USE_DEVICE_TREZOR=OFF : Disable Trezor hardware wallet support +-# - WITH_SCANNER=OFF : Disable webcam QR scanner support +- +-cmake \ +- -DCMAKE_BUILD_TYPE=Debug \ +- -DFEATHER_VERSION_DEBUG_BUILD=ON \ +- -DCHECK_UPDATES=OFF \ +- -DUSE_DEVICE_TREZOR=OFF \ +- -DWITH_SCANNER=OFF \ +- .. +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index fbcbc010..e0645c4a 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -1523,31 +1523,6 @@ void MainWindow::closeEvent(QCloseEvent *event) { + void MainWindow::showEvent(QShowEvent *event) + { + QMainWindow::showEvent(event); +- qDebug() << "MainWindow::showEvent. WindowHandle:" << this->windowHandle(); +- if (auto *window = this->windowHandle()) { +- if (!m_visibilityConnection) { +- m_visibilityConnection = connect(window, &QWindow::visibilityChanged, this, [this](QWindow::Visibility visibility){ +- qDebug() << "Visibility changed:" << visibility << " WindowState:" << this->windowHandle()->windowState(); +- if (visibility == QWindow::Minimized || this->windowHandle()->windowState() & Qt::WindowMinimized) { +- if (conf()->get(Config::lockOnMinimize).toBool()) { +- this->lockWallet(); +- } +- +- bool showTray = conf()->get(Config::showTrayIcon).toBool(); +- bool minimizeToTray = conf()->get(Config::minimizeToTray).toBool(); +- +- qInfo() << "Visibility: Minimized. Tray=" << showTray << " MinToTray=" << minimizeToTray; +- +- if (showTray && minimizeToTray) { +- this->hide(); +- } +- } +- }); +- qDebug() << "Connected to visibilityChanged signal:" << (bool)m_visibilityConnection; +- } +- } else { +- qDebug() << "MainWindow::showEvent: No window handle available!"; +- } + } + + void MainWindow::changeEvent(QEvent* event) +@@ -1573,21 +1548,22 @@ void MainWindow::changeEvent(QEvent* event) + } + } + } else if (event->type() == QEvent::ActivationChange) { +- auto winHandleState = this->windowHandle() ? this->windowHandle()->windowState() : Qt::WindowNoState; +- qInfo() << "changeEvent: ActivationChange. State:" << this->windowState() << " isActive:" << this->isActiveWindow() << " WinHandleState:" << winHandleState; +- if (this->windowHandle() && (this->windowHandle()->windowState() & Qt::WindowMinimized || this->isMinimized())) { +- qInfo() << "changeEvent: ActivationChange -> detected Minimized state"; +- if (conf()->get(Config::lockOnMinimize).toBool()) { +- this->lockWallet(); +- } +- +- bool showTray = conf()->get(Config::showTrayIcon).toBool(); +- bool minimizeToTray = conf()->get(Config::minimizeToTray).toBool(); ++ qInfo() << "changeEvent: ActivationChange. Active:" << this->isActiveWindow(); ++ QTimer::singleShot(500, this, [this](){ ++ auto handle = this->windowHandle(); ++ if (handle && !handle->isExposed()) { ++ qInfo() << "ActivationChange (delayed): Window not exposed -> Hiding to tray"; ++ if (conf()->get(Config::lockOnMinimize).toBool()) { ++ this->lockWallet(); ++ } + +- if (showTray && minimizeToTray) { +- this->hide(); ++ bool showTray = conf()->get(Config::showTrayIcon).toBool(); ++ bool minimizeToTray = conf()->get(Config::minimizeToTray).toBool(); ++ if (showTray && minimizeToTray) { ++ this->hide(); ++ } + } +- } ++ }); + } + } + +diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp +index 982fef24..9dabad09 100644 +--- a/src/SettingsDialog.cpp ++++ b/src/SettingsDialog.cpp +@@ -314,6 +314,7 @@ void Settings::setupDisplayTab() { + connect(ui->checkBox_showTrayIcon, &QCheckBox::toggled, [this](bool toggled) { + conf()->set(Config::showTrayIcon, toggled); + ui->checkBox_minimizeToTray->setEnabled(toggled); ++ ui->checkBox_trayLeftClickToggles->setEnabled(toggled); + emit showTrayIcon(toggled); + }); + +@@ -323,6 +324,13 @@ void Settings::setupDisplayTab() { + connect(ui->checkBox_minimizeToTray, &QCheckBox::toggled, [this](bool toggled) { + conf()->set(Config::minimizeToTray, toggled); + }); ++ ++ // [Left click system tray icon to toggle focus] ++ ui->checkBox_trayLeftClickToggles->setEnabled(ui->checkBox_showTrayIcon->isChecked()); ++ ui->checkBox_trayLeftClickToggles->setChecked(conf()->get(Config::trayLeftClickToggles).toBool()); ++ connect(ui->checkBox_trayLeftClickToggles, &QCheckBox::toggled, [this](bool toggled) { ++ conf()->set(Config::trayLeftClickToggles, toggled); ++ }); + } + + void Settings::setupMemoryTab() { +diff --git a/src/SettingsDialog.ui b/src/SettingsDialog.ui +index ccaad425..f4f01107 100644 +--- a/src/SettingsDialog.ui ++++ b/src/SettingsDialog.ui +@@ -838,6 +838,36 @@ + + + ++ ++ ++ ++ ++ ++ Qt::Orientation::Horizontal ++ ++ ++ QSizePolicy::Policy::Fixed ++ ++ ++ ++ 30 ++ 20 ++ ++ ++ ++ ++ ++ ++ ++ false ++ ++ ++ Left click system tray icon to toggle focus ++ ++ ++ ++ ++ + + + +diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp +index 48daf008..d3f72c48 100644 +--- a/src/WindowManager.cpp ++++ b/src/WindowManager.cpp +@@ -7,7 +7,6 @@ + #include + #include + #include +-#include + + #include "Application.h" + #include "constants.h" +@@ -48,6 +47,24 @@ WindowManager::WindowManager(QObject *parent) + this->buildTrayMenu(); + m_tray->setVisible(conf()->get(Config::showTrayIcon).toBool()); + ++ connect(m_tray, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) { ++ if (reason == QSystemTrayIcon::Trigger) { ++ if (conf()->get(Config::trayLeftClickToggles).toBool()) { ++ for (const auto &window : m_windows) { ++ if (window->isVisible()) { ++ window->hide(); ++ } else { ++ window->show(); ++ window->raise(); ++ window->activateWindow(); ++ } ++ } ++ } else { ++ m_tray->contextMenu()->popup(QCursor::pos()); ++ } ++ } ++ }); ++ + this->initSkins(); + this->patchMacStylesheet(); + +@@ -95,6 +112,12 @@ void WindowManager::quitAfterLastWindow() { + void WindowManager::close() { + qDebug() << Q_FUNC_INFO << QThread::currentThreadId(); + ++ if (m_closing) { ++ return; ++ } ++ m_closing = true; ++ ++ + // Stop all threads before application shutdown to avoid QThreadStorage warnings + if (m_cleanupThread && m_cleanupThread->isRunning()) { + m_cleanupThread->quit(); +@@ -102,6 +125,13 @@ void WindowManager::close() { + qDebug() << "WindowManager: cleanup thread stopped in close()"; + } + ++ // Close all windows first to ensure they cancel their tasks/connections ++ // Iterate over a copy because close() modifies m_windows ++ auto windows = m_windows; ++ for (const auto &window: windows) { ++ window->close(); ++ } ++ + // Stop Tor manager threads + torManager()->stop(); + +@@ -112,10 +142,6 @@ void WindowManager::close() { + std::_Exit(1); // Fast exit without cleanup - threads may still hold resources + } + +- for (const auto &window: m_windows) { +- window->close(); +- } +- + if (m_splashDialog) { + m_splashDialog->deleteLater(); + } +@@ -656,14 +682,7 @@ void WindowManager::buildTrayMenu() { + + for (const auto &window : m_windows) { + QString name = window->walletName(); +- QString displayName = name; +- if (name.length() > 20) { +- displayName = name.left(17) + "..."; +- } +- +- QMenu *submenu = menu->addMenu(displayName); +- submenu->setToolTip(name); +- submenu->menuAction()->setToolTip(name); ++ QMenu *submenu = menu->addMenu(name); + submenu->addAction("Show/Hide", window, &MainWindow::showOrHide); + submenu->addAction("Close", window, &MainWindow::close); + } +diff --git a/src/WindowManager.h b/src/WindowManager.h +index d0f3e460..54690e2e 100644 +--- a/src/WindowManager.h ++++ b/src/WindowManager.h +@@ -113,6 +113,7 @@ private: + bool m_openWalletTriedOnce = false; + bool m_openingWallet = false; + bool m_initialNetworkConfigured = false; ++ bool m_closing = false; + + QThread *m_cleanupThread; + }; +diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp +index df7179cb..cfde59f5 100644 +--- a/src/libwalletqt/Wallet.cpp ++++ b/src/libwalletqt/Wallet.cpp +@@ -561,15 +561,6 @@ quint64 Wallet::daemonBlockChainTargetHeight() const { + return m_daemonBlockChainTargetHeight; + } + +-void Wallet::syncStatusUpdated(quint64 height, quint64 target) { +- if (height >= (target - 1)) { +- // TODO: is this needed? +- this->updateBalance(); +- } +- +- emit syncStatus(height, target, false); +-} +- + void Wallet::setSyncPaused(bool paused) { + m_syncPaused = paused; + if (paused) { +diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h +index 4341efe2..d09604e4 100644 +--- a/src/libwalletqt/Wallet.h ++++ b/src/libwalletqt/Wallet.h +@@ -458,7 +458,6 @@ signals: + void connectionStatusChanged(int status) const; + void currentSubaddressAccountChanged() const; + +- + void syncStatus(quint64 height, quint64 target, bool daemonSync = false); + + void balanceUpdated(quint64 balance, quint64 spendable); +diff --git a/src/utils/config.cpp b/src/utils/config.cpp +index f9453d5c..9da3d876 100644 +--- a/src/utils/config.cpp ++++ b/src/utils/config.cpp +@@ -75,6 +75,7 @@ static const QHash configStrings = { + {Config::lockOnMinimize, {QS("lockOnMinimize"), false}}, + {Config::showTrayIcon, {QS("showTrayIcon"), true}}, + {Config::minimizeToTray, {QS("minimizeToTray"), false}}, ++ {Config::trayLeftClickToggles, {QS("trayLeftClickToggles"), false}}, + {Config::disableWebsocket, {QS("disableWebsocket"), false}}, + {Config::disableAutoRefresh, {QS("disableAutoRefresh"), false}}, + {Config::offlineMode, {QS("offlineMode"), false}}, +diff --git a/src/utils/config.h b/src/utils/config.h +index ffe9a6e6..085c1331 100644 +--- a/src/utils/config.h ++++ b/src/utils/config.h +@@ -109,6 +109,7 @@ public: + lockOnMinimize, + showTrayIcon, + minimizeToTray, ++ trayLeftClickToggles, + + // Transactions + multiBroadcast, + +commit 71dfea7db08073684d5bd93d843e5903c0a9c529 +Author: gg +Date: Mon Jan 12 10:38:30 2026 -0500 + + p log minimize event# Please enter the commit message for your changes. Lines starting + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index a67be3d4..fbcbc010 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -216,6 +216,7 @@ void MainWindow::initStatusBar() { + ui->menuTools->addAction(scanTxAction); + + connect(pauseSyncAction, &QAction::toggled, this, [this](bool checked) { ++ qInfo() << "Pause Sync toggled. Checked =" << checked; + conf()->set(Config::syncPaused, checked); + if (m_wallet) { + if (checked) { +@@ -864,6 +865,7 @@ void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) { + } + + void MainWindow::setPausedSyncStatus() { ++ qInfo() << "setPausedSyncStatus called. Sync paused:" << conf()->get(Config::syncPaused).toBool(); + QString tooltip; + QString status = Utils::getPausedSyncStatus(m_wallet, m_nodes, &tooltip); + this->setStatusText(status); +@@ -981,6 +983,7 @@ void MainWindow::onMultiBroadcast(const QMap &txHexMap) { + } + + void MainWindow::onSyncStatus(quint64 height, quint64 target, bool daemonSync) { ++ qInfo() << "onSyncStatus: Height" << height << "Target" << target << "DaemonSync" << daemonSync; + if (height >= (target - 1) && target > 0) { + this->updateNetStats(); + this->setStatusText(QString("Synchronized (%1)").arg(QLocale().toString(height))); +@@ -1517,20 +1520,79 @@ void MainWindow::closeEvent(QCloseEvent *event) { + event->accept(); + } + ++void MainWindow::showEvent(QShowEvent *event) ++{ ++ QMainWindow::showEvent(event); ++ qDebug() << "MainWindow::showEvent. WindowHandle:" << this->windowHandle(); ++ if (auto *window = this->windowHandle()) { ++ if (!m_visibilityConnection) { ++ m_visibilityConnection = connect(window, &QWindow::visibilityChanged, this, [this](QWindow::Visibility visibility){ ++ qDebug() << "Visibility changed:" << visibility << " WindowState:" << this->windowHandle()->windowState(); ++ if (visibility == QWindow::Minimized || this->windowHandle()->windowState() & Qt::WindowMinimized) { ++ if (conf()->get(Config::lockOnMinimize).toBool()) { ++ this->lockWallet(); ++ } ++ ++ bool showTray = conf()->get(Config::showTrayIcon).toBool(); ++ bool minimizeToTray = conf()->get(Config::minimizeToTray).toBool(); ++ ++ qInfo() << "Visibility: Minimized. Tray=" << showTray << " MinToTray=" << minimizeToTray; ++ ++ if (showTray && minimizeToTray) { ++ this->hide(); ++ } ++ } ++ }); ++ qDebug() << "Connected to visibilityChanged signal:" << (bool)m_visibilityConnection; ++ } ++ } else { ++ qDebug() << "MainWindow::showEvent: No window handle available!"; ++ } ++} ++ + void MainWindow::changeEvent(QEvent* event) + { +- if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) { +- if (conf()->get(Config::lockOnMinimize).toBool()) { +- this->lockWallet(); ++ QMainWindow::changeEvent(event); ++ ++// In changeEvent: ++ if (event->type() == QEvent::WindowStateChange) { ++ qInfo() << "changeEvent: WindowStateChange. State:" << this->windowState() << " isMinimized:" << this->isMinimized(); ++ if (this->isMinimized()) { ++ // ... existing logic ... ++ if (conf()->get(Config::lockOnMinimize).toBool()) { ++ this->lockWallet(); ++ } ++ ++ bool showTray = conf()->get(Config::showTrayIcon).toBool(); ++ bool minimizeToTray = conf()->get(Config::minimizeToTray).toBool(); ++ ++ qInfo() << "WindowStateChange: minimized. Tray=" << showTray << " MinToTray=" << minimizeToTray; ++ ++ if (showTray && minimizeToTray) { ++ this->hide(); ++ } + } +- if (conf()->get(Config::showTrayIcon).toBool() && conf()->get(Config::minimizeToTray).toBool()) { +- this->hide(); ++ } else if (event->type() == QEvent::ActivationChange) { ++ auto winHandleState = this->windowHandle() ? this->windowHandle()->windowState() : Qt::WindowNoState; ++ qInfo() << "changeEvent: ActivationChange. State:" << this->windowState() << " isActive:" << this->isActiveWindow() << " WinHandleState:" << winHandleState; ++ if (this->windowHandle() && (this->windowHandle()->windowState() & Qt::WindowMinimized || this->isMinimized())) { ++ qInfo() << "changeEvent: ActivationChange -> detected Minimized state"; ++ if (conf()->get(Config::lockOnMinimize).toBool()) { ++ this->lockWallet(); ++ } ++ ++ bool showTray = conf()->get(Config::showTrayIcon).toBool(); ++ bool minimizeToTray = conf()->get(Config::minimizeToTray).toBool(); ++ ++ if (showTray && minimizeToTray) { ++ this->hide(); ++ } + } +- } else { +- QMainWindow::changeEvent(event); + } + } + ++// Add logs to sync methods (need to locate them first, assuming onSyncStatus and setPausedSyncStatus) ++ + void MainWindow::showHistoryTab() { + this->raise(); + ui->tabWidget->setCurrentIndex(this->findTab("History")); +@@ -1826,6 +1888,7 @@ QString MainWindow::statusDots() { + } + + void MainWindow::showOrHide() { ++ qDebug() << "showOrHide called. isHidden=" << this->isHidden(); + if (this->isHidden()) + this->bringToFront(); + else +diff --git a/src/MainWindow.h b/src/MainWindow.h +index d465ceba..da45aab2 100644 +--- a/src/MainWindow.h ++++ b/src/MainWindow.h +@@ -6,6 +6,7 @@ + + #include + #include ++#include + + #include "components.h" + #include "SettingsDialog.h" +@@ -97,6 +98,7 @@ signals: + + protected: + void changeEvent(QEvent* event) override; ++ void showEvent(QShowEvent *event) override; + + private slots: + // TODO: use a consistent naming convention for slots +@@ -268,6 +270,8 @@ private: + EventFilter *m_eventFilter = nullptr; + qint64 m_userLastActive = QDateTime::currentSecsSinceEpoch(); + ++ QMetaObject::Connection m_visibilityConnection; ++ + #ifdef CHECK_UPDATES + QSharedPointer m_updater = nullptr; + #endif +diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp +index 69d81198..48daf008 100644 +--- a/src/WindowManager.cpp ++++ b/src/WindowManager.cpp +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + + #include "Application.h" + #include "constants.h" +@@ -655,7 +656,14 @@ void WindowManager::buildTrayMenu() { + + for (const auto &window : m_windows) { + QString name = window->walletName(); +- QMenu *submenu = menu->addMenu(name); ++ QString displayName = name; ++ if (name.length() > 20) { ++ displayName = name.left(17) + "..."; ++ } ++ ++ QMenu *submenu = menu->addMenu(displayName); ++ submenu->setToolTip(name); ++ submenu->menuAction()->setToolTip(name); + submenu->addAction("Show/Hide", window, &MainWindow::showOrHide); + submenu->addAction("Close", window, &MainWindow::close); + } +diff --git a/src/dialog/TxImportDialog.cpp b/src/dialog/TxImportDialog.cpp +index 1473311f..56b2befc 100644 +--- a/src/dialog/TxImportDialog.cpp ++++ b/src/dialog/TxImportDialog.cpp +@@ -18,6 +18,7 @@ TxImportDialog::TxImportDialog(QWidget *parent, Wallet *wallet) + connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport); + + this->adjustSize(); ++ this->layout()->setSizeConstraint(QLayout::SetFixedSize); + } + + void TxImportDialog::onImport() { + +commit 5a385678b311d5cdc2d44476af8c81ae9e2d23e5 +Author: gg +Date: Mon Jan 12 09:40:06 2026 -0500 + + fix sync 1000 block even when paused bug + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index 406a3e55..a67be3d4 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -219,11 +219,11 @@ void MainWindow::initStatusBar() { + conf()->set(Config::syncPaused, checked); + if (m_wallet) { + if (checked) { +- m_wallet->pauseRefresh(); +- ++ m_wallet->setSyncPaused(true); ++ + this->setPausedSyncStatus(); + } else { +- m_wallet->startRefresh(); ++ m_wallet->setSyncPaused(false); + this->setStatusText(tr("Resuming sync...")); + } + } +@@ -816,11 +816,11 @@ void MainWindow::onWalletOpened() { + this->updateTitle(); + m_nodes->allowConnection(); + if (!conf()->get(Config::disableAutoRefresh).toBool()) { +- m_nodes->connectToNode(); + if (conf()->get(Config::syncPaused).toBool()) { +- m_wallet->pauseRefresh(); ++ m_wallet->setSyncPaused(true); + this->setPausedSyncStatus(); + } ++ m_nodes->connectToNode(); + } + m_updateBytes.start(250); + +@@ -1712,7 +1712,7 @@ void MainWindow::onDeviceError(const QString &error, quint64 errorCode) { + } + } + m_statusBtnHwDevice->setIcon(this->hardwareDevicePairedIcon()); +- m_wallet->startRefresh(); ++ m_wallet->setSyncPaused(conf()->get(Config::syncPaused).toBool()); + m_showDeviceError = false; + } + +diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp +index 8f3c8a93..df7179cb 100644 +--- a/src/libwalletqt/Wallet.cpp ++++ b/src/libwalletqt/Wallet.cpp +@@ -431,7 +431,9 @@ void Wallet::initAsync(const QString &daemonAddress, bool trustedDaemon, quint64 + + if (success) { + qDebug() << "init async finished - starting refresh"; +- startRefresh(); ++ if (!m_syncPaused) { ++ startRefresh(); ++ } + } + }); + if (future.first) +@@ -568,6 +570,15 @@ void Wallet::syncStatusUpdated(quint64 height, quint64 target) { + emit syncStatus(height, target, false); + } + ++void Wallet::setSyncPaused(bool paused) { ++ m_syncPaused = paused; ++ if (paused) { ++ pauseRefresh(); ++ } else { ++ startRefresh(); ++ } ++} ++ + void Wallet::skipToTip() { + if (!m_wallet2) + return; +diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h +index 2b345310..4341efe2 100644 +--- a/src/libwalletqt/Wallet.h ++++ b/src/libwalletqt/Wallet.h +@@ -229,6 +229,7 @@ public: + quint64 daemonBlockChainTargetHeight() const; + + void syncStatusUpdated(quint64 height, quint64 target); ++ void setSyncPaused(bool paused); + Q_INVOKABLE void skipToTip(); + Q_INVOKABLE void syncDateRange(const QDate &start, const QDate &end); + void fullSync(); // Rescans from wallet creation height, not genesis block +@@ -534,6 +535,7 @@ private: + + std::atomic m_stopHeight{0}; + std::atomic m_rangeSyncActive{false}; ++ std::atomic m_syncPaused{false}; + }; + + #endif // FEATHER_WALLET_H + +commit dde94b5a4dc18be85edb0cdcf1fb46e47198ab5d +Author: gg +Date: Mon Jan 12 09:34:45 2026 -0500 + + remove Import Tx (duplicate feature?) + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index 88624dfa..406a3e55 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -375,9 +375,8 @@ void MainWindow::initStatusBar() { + + connect(scanTxAction, &QAction::triggered, this, [this](){ + if (m_wallet) { +- QDialog dialog(this); +- dialog.setWindowTitle("Scan Transaction"); +- dialog.setWindowIcon(QIcon(":/assets/images/appicons/64x64.png")); ++ TxImportDialog dialog(this, m_wallet); ++ dialog.exec(); + + auto *layout = new QVBoxLayout(&dialog); + layout->addWidget(new QLabel("Enter transaction ID:")); +diff --git a/src/dialog/TxImportDialog.cpp b/src/dialog/TxImportDialog.cpp +index 56b2befc..1473311f 100644 +--- a/src/dialog/TxImportDialog.cpp ++++ b/src/dialog/TxImportDialog.cpp +@@ -18,7 +18,6 @@ TxImportDialog::TxImportDialog(QWidget *parent, Wallet *wallet) + connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport); + + this->adjustSize(); +- this->layout()->setSizeConstraint(QLayout::SetFixedSize); + } + + void TxImportDialog::onImport() { + +commit 810adb30e39a7f1882be69446a1aa9d15e485e9e +Author: gg +Date: Mon Jan 12 09:33:39 2026 -0500 + + small lint fixes + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index 14ed0109..88624dfa 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -332,11 +332,13 @@ void MainWindow::initStatusBar() { + uint64_t startHeight = lookup->dateToHeight(fromDateEdit->date().startOfDay().toSecsSinceEpoch()); + uint64_t endHeight = lookup->dateToHeight(toDateEdit->date().endOfDay().toSecsSinceEpoch()); + quint64 blocks = (endHeight > startHeight) ? endHeight - startHeight : 0; ++ quint64 size = Utils::estimateSyncDataSize(blocks); + +- this->setStatusText(tr("Syncing range %1 - %2 (~%3 blocks)...") ++ this->setStatusText(tr("Syncing range %1 - %2 (~%3 blocks)\nEst. download size: %4") + .arg(fromDateEdit->date().toString("yyyy-MM-dd")) + .arg(toDateEdit->date().toString("yyyy-MM-dd")) +- .arg(QLocale().toString(blocks))); ++ .arg(QLocale().toString(blocks)) ++ .arg(Utils::formatBytes(size))); + } + }); + +diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp +index f43605e8..8f3c8a93 100644 +--- a/src/libwalletqt/Wallet.cpp ++++ b/src/libwalletqt/Wallet.cpp +@@ -443,7 +443,6 @@ void Wallet::initAsync(const QString &daemonAddress, bool trustedDaemon, quint64 + // #################### Synchronization (Refresh) #################### + + void Wallet::startRefresh() { +- m_refreshEnabled = true; + m_refreshEnabled = true; + m_refreshNow = true; + } + +commit fdae95f564c5a576a20d4de11a984bee0993996e +Author: gg +Date: Mon Jan 12 09:15:47 2026 -0500 + + simplify status bar to block count, not download size/time + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index c09bb1d9..14ed0109 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -332,12 +332,11 @@ void MainWindow::initStatusBar() { + uint64_t startHeight = lookup->dateToHeight(fromDateEdit->date().startOfDay().toSecsSinceEpoch()); + uint64_t endHeight = lookup->dateToHeight(toDateEdit->date().endOfDay().toSecsSinceEpoch()); + quint64 blocks = (endHeight > startHeight) ? endHeight - startHeight : 0; +- quint64 size = Utils::estimateSyncDataSize(blocks); + +- this->setStatusText(QString("Syncing range %1 - %2 (~%3)...") ++ this->setStatusText(tr("Syncing range %1 - %2 (~%3 blocks)...") + .arg(fromDateEdit->date().toString("yyyy-MM-dd")) + .arg(toDateEdit->date().toString("yyyy-MM-dd")) +- .arg(Utils::formatBytes(size))); ++ .arg(QLocale().toString(blocks))); + } + }); + +@@ -353,22 +352,21 @@ void MainWindow::initStatusBar() { + if (daemonHeight > 0) { + blocksBehind = (daemonHeight > walletCreationHeight) ? (daemonHeight - walletCreationHeight) : 0; + quint64 estimatedBytes = Utils::estimateSyncDataSize(blocksBehind); +- estBlocks = QString::number(blocksBehind); ++ estBlocks = QLocale().toString(blocksBehind); + estSize = QString("~%1").arg(Utils::formatBytes(estimatedBytes)); + } + +- QString msg = QString("Full sync will rescan from your restore height.\n\n" +- "Blocks behind: %1\n" +- "Estimated data: %2\n\n" +- "Note: We will not rescan blocks which have cached/hashed/checksummed, " +- "and any discrepancy between block height and daemon height can be understood in these terms.\n\n" +- "Continue?") ++ QString msg = tr("Full sync will rescan from your restore height.\n\n" ++ "Blocks to scan: %1\n" ++ "Estimated data: %2\n\n" ++ "Note: Cached blocks will be skipped.\n\n" ++ "Continue?") + .arg(estBlocks) + .arg(estSize); + +- if (QMessageBox::question(this, "Full Sync", msg) == QMessageBox::Yes) { ++ if (QMessageBox::question(this, tr("Full Sync"), msg) == QMessageBox::Yes) { + m_wallet->fullSync(); +- this->setStatusText(QString("Full sync started (%1)...").arg(estSize)); ++ this->setStatusText(tr("Full sync started (%1 blocks)...").arg(estBlocks)); + } + } + }); + +commit fbcfa7a4e2393d45db50fe3312054ae33534bb71 +Author: gg +Date: Mon Jan 12 09:09:41 2026 -0500 + + more lint fixes; start blocks instead of time + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index f89f889c..c09bb1d9 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -984,25 +984,16 @@ void MainWindow::onMultiBroadcast(const QMap &txHexMap) { + void MainWindow::onSyncStatus(quint64 height, quint64 target, bool daemonSync) { + if (height >= (target - 1) && target > 0) { + this->updateNetStats(); +- this->setStatusText(QString("Synchronized (%1)").arg(height)); ++ this->setStatusText(QString("Synchronized (%1)").arg(QLocale().toString(height))); + } else { +- // Calculate depth + quint64 blocksLeft = (target > height) ? (target - height) : 0; +- // Estimate download size +- QString sizeText; +- if (target > 1) { +- quint64 estimatedBytes = Utils::estimateSyncDataSize(blocksLeft); +- sizeText = Utils::formatBytes(estimatedBytes); +- } +- QString statusMsg = Utils::formatSyncStatus(height, target, daemonSync); +- // Show size estimate only if available +- if (!sizeText.isEmpty()) { +- this->setStatusText(QString("%1 (approx. %2)").arg(statusMsg).arg(sizeText)); +- } else { +- this->setStatusText(statusMsg); +- } ++ QString type = daemonSync ? tr("Blockchain") : tr("Wallet"); ++ QString blocksStr = QLocale().toString(blocksLeft); ++ this->setStatusText(tr("%1 sync: %2 blocks behind").arg(type, blocksStr)); + } +- m_statusLabelStatus->setToolTip(QString("Wallet Height: %1 | Network Tip: %2").arg(height).arg(target)); ++ m_statusLabelStatus->setToolTip(tr("Wallet Height: %1 | Network Tip: %2") ++ .arg(QLocale().toString(height)) ++ .arg(QLocale().toString(target))); + } + + void MainWindow::onConnectionStatusChanged(int status) +diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp +index 23ae865b..375e535a 100644 +--- a/src/utils/Utils.cpp ++++ b/src/utils/Utils.cpp +@@ -734,8 +734,8 @@ quint64 estimateSyncDataSize(quint64 blocks) { + } + + QString formatPausedSyncStatus(quint64 blocks) { +- quint64 size = estimateSyncDataSize(blocks); +- return QObject::tr("[PAUSED] Sync %1 blocks / %2 upon resume").arg(blocks).arg(formatBytes(size)); ++ QString blocksStr = QLocale().toString(blocks); ++ return QObject::tr("[PAUSED] %1 blocks behind").arg(blocksStr); + } + + QString getPausedSyncStatus(Wallet *wallet, Nodes *nodes, QString *tooltip) { + +commit e408843c30f6c0a484ff77c92d37d15b4c072542 +Author: gg +Date: Mon Jan 12 09:01:17 2026 -0500 + + fix remaining small issues + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index 9282db96..f89f889c 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -391,7 +391,7 @@ void MainWindow::initStatusBar() { + connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + layout->addWidget(btnBox); + +- // Compact vertical size, allow horizontal resize ++ // Use fixed size based on content - prevents manual resizing + dialog.layout()->setSizeConstraint(QLayout::SetFixedSize); + + if (dialog.exec() == QDialog::Accepted) { +diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h +index 4b7d3d6b..2b345310 100644 +--- a/src/libwalletqt/Wallet.h ++++ b/src/libwalletqt/Wallet.h +@@ -231,7 +231,7 @@ public: + void syncStatusUpdated(quint64 height, quint64 target); + Q_INVOKABLE void skipToTip(); + Q_INVOKABLE void syncDateRange(const QDate &start, const QDate &end); +- void fullSync(); // from restoreHeight, not genesis - recreate your wallet for that ;P ++ void fullSync(); // Rescans from wallet creation height, not genesis block + + bool importTransaction(const QString &txid); + +diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp +index a3339485..23ae865b 100644 +--- a/src/utils/Utils.cpp ++++ b/src/utils/Utils.cpp +@@ -717,8 +717,14 @@ QString formatSyncTimeEstimate(quint64 blocks) { + quint64 minutes = blocks * 2; + quint64 days = minutes / (60 * 24); + +- QString timeStr = (days > 0) ? QString("~%1 days").arg(days) : QString("~%1 hours").arg(minutes / 60); +- if (days == 0 && minutes < 60) timeStr = "< 1 hour"; ++ QString timeStr; ++ if (days > 0) { ++ timeStr = QObject::tr("~%1 days").arg(days); ++ } else if (minutes >= 60) { ++ timeStr = QObject::tr("~%1 hours").arg(minutes / 60); ++ } else { ++ timeStr = QObject::tr("< 1 hour"); ++ } + return timeStr; + } + +@@ -750,16 +756,16 @@ QString getPausedSyncStatus(Wallet *wallet, Nodes *nodes, QString *tooltip) { + + if (daemonHeight > 0) { + if (tooltip) { +- *tooltip = QString("Wallet Height: %1 | Network Tip: %2").arg(walletHeight).arg(daemonHeight); ++ *tooltip = QObject::tr("Wallet Height: %1 | Network Tip: %2").arg(walletHeight).arg(daemonHeight); + } + quint64 blocksBehind = (daemonHeight > startHeight) ? (daemonHeight - startHeight) : 0; + if (blocksBehind == 0) { +- return "[PAUSED] Sync paused"; ++ return QObject::tr("[PAUSED] Sync paused"); + } + return formatPausedSyncStatus(blocksBehind); + } + +- return "[PAUSED] Sync paused"; ++ return QObject::tr("[PAUSED] Sync paused"); + } + + QString formatRestoreHeight(quint64 height) { + +commit 1cf93f99cc186d0a23ff3a64a5aaaba2af54f223 +Author: gg +Date: Mon Jan 12 08:59:57 2026 -0500 + + fix file close cache bug + +diff --git a/src/model/WalletKeysFilesModel.cpp b/src/model/WalletKeysFilesModel.cpp +index 58bc703c..fc85eaa9 100644 +--- a/src/model/WalletKeysFilesModel.cpp ++++ b/src/model/WalletKeysFilesModel.cpp +@@ -97,18 +97,19 @@ void WalletKeysFilesModel::findWallets() { + + if (Utils::fileExists(basePath + ".address.txt")) { + QFile file(basePath + ".address.txt"); +- if (!file.open(QFile::ReadOnly | QFile::Text)) +- continue; +- const QString _address = QString::fromUtf8(file.readAll()); +- +- if (!_address.isEmpty()) { +- addr = _address; +- if (addr.startsWith("5") || addr.startsWith("7")) +- networkType = NetworkType::STAGENET; +- else if (addr.startsWith("9") || addr.startsWith("B")) +- networkType = NetworkType::TESTNET; ++ if (file.open(QFile::ReadOnly | QFile::Text)) { ++ const QString _address = QString::fromUtf8(file.readAll()); ++ ++ if (!_address.isEmpty()) { ++ addr = _address; ++ if (addr.startsWith("5") || addr.startsWith("7")) ++ networkType = NetworkType::STAGENET; ++ else if (addr.startsWith("9") || addr.startsWith("B")) ++ networkType = NetworkType::TESTNET; ++ } ++ file.close(); + } +- file.close(); ++ // If file can't be opened, just proceed without the cached address + } + + this->addWalletKeysFile(WalletKeysFile(fileInfo, networkType, std::move(addr))); + +commit 8b90b3c9133aacad2d6789fc7e59bbe9ebebc09f +Author: gg +Date: Mon Jan 12 08:58:23 2026 -0500 + + fix p1: full sync bug & mutex + +diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp +index b0e50ec2..69d81198 100644 +--- a/src/WindowManager.cpp ++++ b/src/WindowManager.cpp +@@ -106,7 +106,7 @@ void WindowManager::close() { + + // Wait for all threads in the global thread pool with timeout to prevent indefinite blocking + if (!QThreadPool::globalInstance()->waitForDone(15000)) { +- qCritical() << "WindowManager: Thread pool tasks did not complete within 30s timeout. " ++ qCritical() << "WindowManager: Thread pool tasks did not complete within 15s timeout. " + << "Forcing exit to prevent use-after-free."; + std::_Exit(1); // Fast exit without cleanup - threads may still hold resources + } +diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp +index 4c079f42..f43605e8 100644 +--- a/src/libwalletqt/Wallet.cpp ++++ b/src/libwalletqt/Wallet.cpp +@@ -574,6 +574,7 @@ void Wallet::skipToTip() { + return; + uint64_t tip = m_wallet2->get_blockchain_current_height(); + if (tip > 0) { ++ QMutexLocker locker(&m_asyncMutex); + // Log previous height for debugging + uint64_t prevHeight = m_wallet2->get_refresh_from_block_height(); + qInfo() << "Skip Sync triggered. Head moving from" << prevHeight << "to:" << tip; +@@ -599,10 +600,12 @@ void Wallet::syncDateRange(const QDate &start, const QDate &end) { + if (startHeight >= endHeight) + return; + +- m_stopHeight = endHeight; +- m_rangeSyncActive = true; +- +- m_wallet2->set_refresh_from_block_height(startHeight); ++ { ++ QMutexLocker locker(&m_asyncMutex); ++ m_stopHeight = endHeight; ++ m_rangeSyncActive = true; ++ m_wallet2->set_refresh_from_block_height(startHeight); ++ } + pauseRefresh(); + startRefresh(); + } +@@ -627,13 +630,12 @@ void Wallet::fullSync() { + << ". This may miss transactions if skipToTip() was previously used."; + } + +- m_wallet2->set_refresh_from_block_height(originalHeight); ++ { ++ QMutexLocker locker(&m_asyncMutex); ++ m_wallet2->set_refresh_from_block_height(originalHeight); ++ } + // Trigger rescan + pauseRefresh(); +- +- // Optional: Clear transaction cache to ensure fresh view +- // m_wallet2->clearCache(); +- + startRefresh(); + + qInfo() << "Full Sync triggered. Rescanning from original restore height:" << originalHeight; + +commit 28a2f6d8f96e52ce48431ad287fa39386785f7e8 +Author: gg +Date: Mon Jan 12 08:24:03 2026 -0500 + + resolve build errors & lint warnings + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index d15bf349..9282db96 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -277,14 +277,8 @@ void MainWindow::initStatusBar() { + auto *fromDateEdit = new QDateEdit(QDate::currentDate().addDays(-defaultDays)); + fromDateEdit->setCalendarPopup(true); + fromDateEdit->setDisplayFormat("yyyy-MM-dd"); +- fromDateEdit->setCalendarPopup(true); +- fromDateEdit->setDisplayFormat("yyyy-MM-dd"); + fromDateEdit->setToolTip(tr("Calculated from 'End date' and day span.")); + +- +- toDateEdit->setCalendarPopup(true); +- toDateEdit->setDisplayFormat("yyyy-MM-dd"); +- + auto *infoLabel = new QLabel; + infoLabel->setWordWrap(true); + infoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; }"); +@@ -994,17 +988,19 @@ void MainWindow::onSyncStatus(quint64 height, quint64 target, bool daemonSync) { + } else { + // Calculate depth + quint64 blocksLeft = (target > height) ? (target - height) : 0; +- // Estimate download size in MB: assuming 500 MB for full history, +- // and we are blocksLeft behind out of (target-1) total blocks (since block 0 is genesis) +- double approximateSizeMB = 0.0; ++ // Estimate download size ++ QString sizeText; + if (target > 1) { + quint64 estimatedBytes = Utils::estimateSyncDataSize(blocksLeft); + sizeText = Utils::formatBytes(estimatedBytes); + } + QString statusMsg = Utils::formatSyncStatus(height, target, daemonSync); +- // Shows "# blocks remaining (approx. X MB)" to sync +- // Shows "Wallet sync: # blocks remaining (approx. X MB)" +- this->setStatusText(QString("%1 (approx. %2)").arg(statusMsg).arg(sizeText)); ++ // Show size estimate only if available ++ if (!sizeText.isEmpty()) { ++ this->setStatusText(QString("%1 (approx. %2)").arg(statusMsg).arg(sizeText)); ++ } else { ++ this->setStatusText(statusMsg); ++ } + } + m_statusLabelStatus->setToolTip(QString("Wallet Height: %1 | Network Tip: %2").arg(height).arg(target)); + } +diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp +index 38c4cd97..b0e50ec2 100644 +--- a/src/WindowManager.cpp ++++ b/src/WindowManager.cpp +@@ -104,8 +104,12 @@ void WindowManager::close() { + // Stop Tor manager threads + torManager()->stop(); + +- // Wait for all threads in the global thread pool +- QThreadPool::globalInstance()->waitForDone(); ++ // Wait for all threads in the global thread pool with timeout to prevent indefinite blocking ++ if (!QThreadPool::globalInstance()->waitForDone(15000)) { ++ qCritical() << "WindowManager: Thread pool tasks did not complete within 30s timeout. " ++ << "Forcing exit to prevent use-after-free."; ++ std::_Exit(1); // Fast exit without cleanup - threads may still hold resources ++ } + + for (const auto &window: m_windows) { + window->close(); +diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp +index 85ec7399..4c079f42 100644 +--- a/src/libwalletqt/Wallet.cpp ++++ b/src/libwalletqt/Wallet.cpp +@@ -592,10 +592,9 @@ void Wallet::syncDateRange(const QDate &start, const QDate &end) { + cryptonote::network_type nettype = m_wallet2->nettype(); + QString filename = Utils::getRestoreHeightFilename(static_cast(nettype)); + +- auto lookup = RestoreHeightLookup::fromFile(filename, static_cast(nettype)); ++ std::unique_ptr lookup(RestoreHeightLookup::fromFile(filename, static_cast(nettype))); + uint64_t startHeight = lookup->dateToHeight(start.startOfDay().toSecsSinceEpoch()); + uint64_t endHeight = lookup->dateToHeight(end.startOfDay().toSecsSinceEpoch()); +- delete lookup; + + if (startHeight >= endHeight) + return; +@@ -621,9 +620,11 @@ void Wallet::fullSync() { + if (!storedHeight.isEmpty()) { + originalHeight = storedHeight.toULongLong(); + } else { +- // Fallback (dangerous if skipped, but better than 0) ++ // Fallback: if skipToTip() was used, this may be the current tip, missing all transactions + originalHeight = m_wallet2->get_refresh_from_block_height(); +->>>>>>> 43ee0e4e (Implement Skip Sync and Data Saving features) ++ qWarning() << "fullSync: No stored creation height found (feather.creation_height). " ++ << "Falling back to current refresh height:" << originalHeight ++ << ". This may miss transactions if skipToTip() was previously used."; + } + + m_wallet2->set_refresh_from_block_height(originalHeight); +@@ -672,6 +673,7 @@ bool Wallet::importTransaction(const QString &txid) { + m_wallet2->scan_tx(txids); + qInfo() << "Successfully imported transaction:" << txid; + this->updateBalance(); ++ this->history()->refresh(); + return true; + } catch (const std::exception &e) { + qWarning() << "Failed to import transaction: " << txid << ", error: " << e.what(); +diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h +index 8dee4365..4b7d3d6b 100644 +--- a/src/libwalletqt/Wallet.h ++++ b/src/libwalletqt/Wallet.h +@@ -532,8 +532,8 @@ private: + QTimer *m_storeTimer = nullptr; + std::set m_selectedInputs; + +- uint64_t m_stopHeight = 0; +- bool m_rangeSyncActive = false; ++ std::atomic m_stopHeight{0}; ++ std::atomic m_rangeSyncActive{false}; + }; + + #endif // FEATHER_WALLET_H +diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp +index 55260154..a3339485 100644 +--- a/src/utils/Utils.cpp ++++ b/src/utils/Utils.cpp +@@ -752,6 +752,7 @@ QString getPausedSyncStatus(Wallet *wallet, Nodes *nodes, QString *tooltip) { + if (tooltip) { + *tooltip = QString("Wallet Height: %1 | Network Tip: %2").arg(walletHeight).arg(daemonHeight); + } ++ quint64 blocksBehind = (daemonHeight > startHeight) ? (daemonHeight - startHeight) : 0; + if (blocksBehind == 0) { + return "[PAUSED] Sync paused"; + } + +commit 29c90e409a0e092b3404c72aa4184206b8c6e86b +Author: gg +Date: Sat Jan 10 09:43:07 2026 -0500 + + breaking commit? why? + +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index 7b58f564..d15bf349 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -998,13 +998,8 @@ void MainWindow::onSyncStatus(quint64 height, quint64 target, bool daemonSync) { + // and we are blocksLeft behind out of (target-1) total blocks (since block 0 is genesis) + double approximateSizeMB = 0.0; + if (target > 1) { +- approximateSizeMB = (blocksLeft * 500.0) / (target - 1); +- } +- QString sizeText; +- if (approximateSizeMB < 1) { +- sizeText = QString("%1 KB").arg(QString::number(approximateSizeMB * 1024, 'f', 0)); +- } else { +- sizeText = QString("%1 MB").arg(QString::number(approximateSizeMB, 'f', 1)); ++ quint64 estimatedBytes = Utils::estimateSyncDataSize(blocksLeft); ++ sizeText = Utils::formatBytes(estimatedBytes); + } + QString statusMsg = Utils::formatSyncStatus(height, target, daemonSync); + // Shows "# blocks remaining (approx. X MB)" to sync +diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp +index 0ef06d99..55260154 100644 +--- a/src/utils/Utils.cpp ++++ b/src/utils/Utils.cpp +@@ -723,8 +723,8 @@ QString formatSyncTimeEstimate(quint64 blocks) { + } + + quint64 estimateSyncDataSize(quint64 blocks) { +- // Estimate 1024 bytes per block (1KB) for wallet scanning. +- return blocks * 1024; ++ // Estimate 30KB per block for wallet scanning. ++ return blocks * 30 * 1024; + } + + QString formatPausedSyncStatus(quint64 blocks) { +@@ -752,7 +752,9 @@ QString getPausedSyncStatus(Wallet *wallet, Nodes *nodes, QString *tooltip) { + if (tooltip) { + *tooltip = QString("Wallet Height: %1 | Network Tip: %2").arg(walletHeight).arg(daemonHeight); + } +- quint64 blocksBehind = (daemonHeight > startHeight) ? (daemonHeight - startHeight) : 0; ++ if (blocksBehind == 0) { ++ return "[PAUSED] Sync paused"; ++ } + return formatPausedSyncStatus(blocksBehind); + } + + +commit c7f88c3854c1fea88ee356d7c38d5b6944571ea9 +Author: gg +Date: Sat Jan 10 04:37:11 2026 -0500 + + fix resize bug? + +diff --git a/src/dialog/TxImportDialog.cpp b/src/dialog/TxImportDialog.cpp +index 1473311f..56b2befc 100644 +--- a/src/dialog/TxImportDialog.cpp ++++ b/src/dialog/TxImportDialog.cpp +@@ -18,6 +18,7 @@ TxImportDialog::TxImportDialog(QWidget *parent, Wallet *wallet) + connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport); + + this->adjustSize(); ++ this->layout()->setSizeConstraint(QLayout::SetFixedSize); + } + + void TxImportDialog::onImport() { + +commit 7e93b8fd4083e1e4d0d04fda0569fb6f3d3117b1 +Author: gg +Date: Sat Jan 10 00:13:33 2026 -0500 + + more deprecation warning/Qt6 stuff + +diff --git a/src/plugins/crowdfunding/CCSProgressDelegate.cpp b/src/plugins/crowdfunding/CCSProgressDelegate.cpp +index c915d24d..021b36ea 100644 +--- a/src/plugins/crowdfunding/CCSProgressDelegate.cpp ++++ b/src/plugins/crowdfunding/CCSProgressDelegate.cpp +@@ -23,7 +23,7 @@ void CCSProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o + progressBarOption.state = QStyle::State_Enabled; + progressBarOption.direction = QApplication::layoutDirection(); + progressBarOption.rect = option.rect; +- progressBarOption.fontMetrics = QApplication::fontMetrics(); ++ progressBarOption.fontMetrics = QFontMetrics(qApp->font()); + progressBarOption.minimum = 0; + progressBarOption.maximum = 100; + progressBarOption.textAlignment = Qt::AlignCenter; +diff --git a/src/qrcode/utils/QrCodeUtils.cpp b/src/qrcode/utils/QrCodeUtils.cpp +index e957780f..ee3841eb 100644 +--- a/src/qrcode/utils/QrCodeUtils.cpp ++++ b/src/qrcode/utils/QrCodeUtils.cpp +@@ -3,14 +3,14 @@ + + #include "QrCodeUtils.h" + +-Result QrCodeUtils::ReadBarcode(const QImage& img, const ZXing::DecodeHints& hints) ++Result QrCodeUtils::ReadBarcode(const QImage& img, const ZXing::ReaderOptions& hints) + { + auto ImgFmtFromQImg = [](const QImage& img){ + switch (img.format()) { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: + #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +- return ZXing::ImageFormat::BGRX; ++ return ZXing::ImageFormat::BGRA; + + #else + return ZXing::ImageFormat::XRGB; +@@ -21,7 +21,7 @@ Result QrCodeUtils::ReadBarcode(const QImage& img, const ZXing::DecodeHints& hin + + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888: +- return ZXing::ImageFormat::RGBX; ++ return ZXing::ImageFormat::RGBA; + + case QImage::Format_Grayscale8: + return ZXing::ImageFormat::Lum; +@@ -50,7 +50,7 @@ Result QrCodeUtils::ReadBarcode(const QImage& img, const ZXing::DecodeHints& hin + + + QString QrCodeUtils::scanImage(const QImage &img) { +- const auto hints = ZXing::DecodeHints() ++ const auto hints = ZXing::ReaderOptions() + .setFormats(ZXing::BarcodeFormat::QRCode | ZXing::BarcodeFormat::DataMatrix) + .setTryHarder(true) + .setBinarizer(ZXing::Binarizer::FixedThreshold); +diff --git a/src/qrcode/utils/QrCodeUtils.h b/src/qrcode/utils/QrCodeUtils.h +index 305bc968..b41c7be0 100644 +--- a/src/qrcode/utils/QrCodeUtils.h ++++ b/src/qrcode/utils/QrCodeUtils.h +@@ -27,7 +27,7 @@ private: + class QrCodeUtils { + public: + static QString scanImage(const QImage &img); +- static Result ReadBarcode(const QImage& img, const ZXing::DecodeHints& hints = { }); ++ static Result ReadBarcode(const QImage& img, const ZXing::ReaderOptions& hints = { }); + }; + + #endif //FEATHER_QRCODEUTILS_H +diff --git a/src/utils/WebsocketClient.cpp b/src/utils/WebsocketClient.cpp +index 815666bf..fd0e089a 100644 +--- a/src/utils/WebsocketClient.cpp ++++ b/src/utils/WebsocketClient.cpp +@@ -20,7 +20,7 @@ WebsocketClient::WebsocketClient(QObject *parent) + connect(webSocket, &QWebSocket::stateChanged, this, &WebsocketClient::onStateChanged); + connect(webSocket, &QWebSocket::connected, this, &WebsocketClient::onConnected); + connect(webSocket, &QWebSocket::disconnected, this, &WebsocketClient::onDisconnected); +- connect(webSocket, QOverload::of(&QWebSocket::error), this, &WebsocketClient::onError); ++ connect(webSocket, &QWebSocket::errorOccurred, this, &WebsocketClient::onError); + + connect(webSocket, &QWebSocket::binaryMessageReceived, this, &WebsocketClient::onbinaryMessageReceived); + +diff --git a/src/widgets/NodeWidget.cpp b/src/widgets/NodeWidget.cpp +index b2c94e77..b01e5d23 100644 +--- a/src/widgets/NodeWidget.cpp ++++ b/src/widgets/NodeWidget.cpp +@@ -21,8 +21,8 @@ NodeWidget::NodeWidget(QWidget *parent) + + connect(ui->btn_addCustomNodes, &QPushButton::clicked, this, &NodeWidget::onCustomAddClicked); + +- connect(ui->checkBox_websocketList, &QCheckBox::stateChanged, [this](int id){ +- bool custom = (id == 0); ++ connect(ui->checkBox_websocketList, &QCheckBox::checkStateChanged, [this](Qt::CheckState state){ ++ bool custom = (state == Qt::Unchecked); + ui->stackedWidget->setCurrentIndex(custom); + ui->frame_addCustomNodes->setVisible(custom); + conf()->set(Config::nodeSource, custom); + +commit 8fa89ac3cc8a109e58a7122666807f72c4639955 +Author: gg +Date: Fri Jan 9 17:31:58 2026 -0500 + + more deprecation warnings resolved + +diff --git a/src/model/HistoryView.cpp b/src/model/HistoryView.cpp +index c9820971..eb10219e 100644 +--- a/src/model/HistoryView.cpp ++++ b/src/model/HistoryView.cpp +@@ -115,8 +115,8 @@ void HistoryView::showHeaderMenu(const QPoint& position) + { + const QList actions = m_columnActions->actions(); + for (auto& action : actions) { +- Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); +- if (static_cast(action->data().type()) != QMetaType::Int) { ++ Q_ASSERT(static_cast(action->data().typeId()) == QMetaType::Int); ++ if (static_cast(action->data().typeId()) != QMetaType::Int) { + continue; + } + int columnIndex = action->data().toInt(); +@@ -131,8 +131,8 @@ void HistoryView::toggleColumnVisibility(QAction* action) + // Verify action carries a column index as data. Since QVariant.toInt() + // below will accept anything that's interpretable as int, perform a type + // check here to make sure data actually IS int +- Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); +- if (static_cast(action->data().type()) != QMetaType::Int) { ++ Q_ASSERT(static_cast(action->data().typeId()) == QMetaType::Int); ++ if (static_cast(action->data().typeId()) != QMetaType::Int) { + return; + } + + +commit ed93160b727af3c1ae334cd05572ecd143a7a750 +Author: gg +Date: Fri Jan 9 17:30:45 2026 -0500 + + handle deprecation warnings in build + +diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp +index 13ad493f..38c4cd97 100644 +--- a/src/WindowManager.cpp ++++ b/src/WindowManager.cpp +@@ -789,7 +789,10 @@ QString WindowManager::loadStylesheet(const QString &resource) { + return ""; + } + +- f.open(QFile::ReadOnly | QFile::Text); ++ if (!f.open(QFile::ReadOnly | QFile::Text)) { ++ qWarning() << "Failed to open stylesheet:" << resource; ++ return ""; ++ } + QTextStream ts(&f); + QString data = ts.readAll(); + f.close(); +diff --git a/src/model/AddressBookProxyModel.h b/src/model/AddressBookProxyModel.h +index 4ea5565a..dfcbad23 100644 +--- a/src/model/AddressBookProxyModel.h ++++ b/src/model/AddressBookProxyModel.h +@@ -17,8 +17,9 @@ public: + + public slots: + void setSearchFilter(const QString& searchString){ ++ beginFilterChange(); + m_searchRegExp.setPattern(searchString); +- invalidateFilter(); ++ endFilterChange(); + } + + private: +diff --git a/src/model/SubaddressProxyModel.h b/src/model/SubaddressProxyModel.h +index 38069ed1..d65c68aa 100644 +--- a/src/model/SubaddressProxyModel.h ++++ b/src/model/SubaddressProxyModel.h +@@ -18,9 +18,10 @@ public: + + public slots: + void setSearchFilter(const QString& searchString){ ++ beginFilterChange(); + m_searchRegExp.setPattern(searchString); + m_searchCaseSensitiveRegExp.setPattern(searchString); +- invalidateFilter(); ++ endFilterChange(); + } + + private: +diff --git a/src/model/TransactionHistoryProxyModel.h b/src/model/TransactionHistoryProxyModel.h +index 8217efbe..50e8a72c 100644 +--- a/src/model/TransactionHistoryProxyModel.h ++++ b/src/model/TransactionHistoryProxyModel.h +@@ -19,8 +19,9 @@ public: + + public slots: + void setSearchFilter(const QString& searchString){ ++ beginFilterChange(); + m_searchRegExp.setPattern(searchString); +- invalidateFilter(); ++ endFilterChange(); + } + + private: +diff --git a/src/model/WalletKeysFilesModel.cpp b/src/model/WalletKeysFilesModel.cpp +index 343ad181..58bc703c 100644 +--- a/src/model/WalletKeysFilesModel.cpp ++++ b/src/model/WalletKeysFilesModel.cpp +@@ -97,7 +97,8 @@ void WalletKeysFilesModel::findWallets() { + + if (Utils::fileExists(basePath + ".address.txt")) { + QFile file(basePath + ".address.txt"); +- file.open(QFile::ReadOnly | QFile::Text); ++ if (!file.open(QFile::ReadOnly | QFile::Text)) ++ continue; + const QString _address = QString::fromUtf8(file.readAll()); + + if (!_address.isEmpty()) { + +commit 5410323c245a30f7c87e6f77adad115bed5a7a6a +Author: gg +Date: Fri Jan 9 17:22:23 2026 -0500 + + fixup + +diff --git a/cmake/FindBCUR.cmake b/cmake/FindBCUR.cmake +index 44ce1bbc..671ce4cd 100644 +--- a/cmake/FindBCUR.cmake ++++ b/cmake/FindBCUR.cmake +@@ -1,18 +1,12 @@ +-message(STATUS "FindBCUR: Starting. Initial BCUR_INCLUDE_DIR=${BCUR_INCLUDE_DIR}") + find_path(BCUR_INCLUDE_DIR "bcur/bc-ur.hpp") + find_library(BCUR_LIBRARY bcur) + +-message(STATUS "FindBCUR: After find. BCUR_INCLUDE_DIR=${BCUR_INCLUDE_DIR}") +- + if (NOT BCUR_INCLUDE_DIR OR NOT BCUR_LIBRARY) + MESSAGE(STATUS "Could not find installed BCUR, using vendored library instead") + set(BCUR_VENDORED "ON") +- set(BCUR_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/third-party CACHE PATH "Path to BCUR headers" FORCE) +- set(BCUR_LIBRARY bcur_static CACHE STRING "BCUR library name" FORCE) +- message(STATUS "Using vendored BCUR at ${BCUR_INCLUDE_DIR}") +-else() +- message(STATUS "Found installed BCUR at ${BCUR_INCLUDE_DIR}") ++ set(BCUR_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/third-party) ++ set(BCUR_LIBRARY bcur_static) + endif() + +-message(STATUS "FindBCUR: Final BCUR PATH ${BCUR_INCLUDE_DIR}") +-message(STATUS "FindBCUR: Final BCUR LIBRARY ${BCUR_LIBRARY}") +\ No newline at end of file ++message(STATUS "BCUR PATH ${BCUR_INCLUDE_DIR}") ++message(STATUS "BCUR LIBRARY ${BCUR_LIBRARY}") +\ No newline at end of file +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 3587f5bb..1ff01908 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -309,6 +309,7 @@ if (WITH_SCANNER) + Qt::Multimedia + Qt::MultimediaWidgets + ${ZXING_LIBRARIES} ++ ${BCUR_LIBRARY} + ) + endif() + + +commit d1f3ce76ae028a22e56d718f3dac145fe3189872 +Author: gg +Date: Fri Jan 9 17:04:04 2026 -0500 + + fix + +diff --git a/cmake/FindBCUR.cmake b/cmake/FindBCUR.cmake +index 671ce4cd..44ce1bbc 100644 +--- a/cmake/FindBCUR.cmake ++++ b/cmake/FindBCUR.cmake +@@ -1,12 +1,18 @@ ++message(STATUS "FindBCUR: Starting. Initial BCUR_INCLUDE_DIR=${BCUR_INCLUDE_DIR}") + find_path(BCUR_INCLUDE_DIR "bcur/bc-ur.hpp") + find_library(BCUR_LIBRARY bcur) + ++message(STATUS "FindBCUR: After find. BCUR_INCLUDE_DIR=${BCUR_INCLUDE_DIR}") ++ + if (NOT BCUR_INCLUDE_DIR OR NOT BCUR_LIBRARY) + MESSAGE(STATUS "Could not find installed BCUR, using vendored library instead") + set(BCUR_VENDORED "ON") +- set(BCUR_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/third-party) +- set(BCUR_LIBRARY bcur_static) ++ set(BCUR_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/third-party CACHE PATH "Path to BCUR headers" FORCE) ++ set(BCUR_LIBRARY bcur_static CACHE STRING "BCUR library name" FORCE) ++ message(STATUS "Using vendored BCUR at ${BCUR_INCLUDE_DIR}") ++else() ++ message(STATUS "Found installed BCUR at ${BCUR_INCLUDE_DIR}") + endif() + +-message(STATUS "BCUR PATH ${BCUR_INCLUDE_DIR}") +-message(STATUS "BCUR LIBRARY ${BCUR_LIBRARY}") +\ No newline at end of file ++message(STATUS "FindBCUR: Final BCUR PATH ${BCUR_INCLUDE_DIR}") ++message(STATUS "FindBCUR: Final BCUR LIBRARY ${BCUR_LIBRARY}") +\ No newline at end of file +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index f94d2e38..3587f5bb 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -182,7 +182,7 @@ target_include_directories(feather PUBLIC + ${LIBZIP_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${POLYSEED_INCLUDE_DIR} +- ${BCUR_INCLUDE_DIR} ++ + ) + + target_compile_definitions(feather PRIVATE FEATHER_VERSION="${DETECTED_FEATHER_VERSION}") +@@ -194,6 +194,7 @@ if(WITH_SCANNER) + ${QtMultimedia_INCLUDE_DIRS} + ${QtMultimediaWidgets_INCLUDE_DIRS} + ${ZXING_INCLUDE_DIRS} ++ ${BCUR_INCLUDE_DIR} + ) + endif() + +diff --git a/src/model/SubaddressProxyModel.h b/src/model/SubaddressProxyModel.h +index 833c6f25..38069ed1 100644 +--- a/src/model/SubaddressProxyModel.h ++++ b/src/model/SubaddressProxyModel.h +@@ -20,7 +20,7 @@ public slots: + void setSearchFilter(const QString& searchString){ + m_searchRegExp.setPattern(searchString); + m_searchCaseSensitiveRegExp.setPattern(searchString); +- invalidate(); ++ invalidateFilter(); + } + + private: + +commit b698555ecc125a4e582d05f7062d503349bff1b0 +Author: gg +Date: Wed Jan 7 06:37:00 2026 -0500 + + Implement Skip Sync and Data Saving features + + Logic: + - Add 'Skip to Tip', 'Date Range', and 'Full Sync' engine to libwalletqt + - Implement 'Scan Transaction' functionality for specific TXIDs + + UI: + - Add context menu actions to bottom bar for selective sync + - Display block-depth count in status bar, courtesy of @masflam + + bounty claimed here (as "mr_overquald") + https://bounties.monero.social/posts/79/1-230m-add-a-skip-sync-feature-to-a-monero-wallet + + Co-authored-by: MasFlam + + refactor, cleanup, and format code. + + allow for storing a debug version in the build # + + wip gem + + idk gem, little pruney there + + restore master. Let's go from there again + + Implement Skip Sync and Data Saving features + + Logic: + - Add 'Skip to Tip', 'Date Range', and 'Full Sync' engine to libwalletqt + - Implement 'Scan Transaction' functionality for specific TXIDs + + UI: + - Add context menu actions to bottom bar for selective sync + - Display block-depth count in status bar, courtesy of @masflam + + bounty claimed here (as "mr_overquald") + https://bounties.monero.social/posts/79/1-230m-add-a-skip-sync-feature-to-a-monero-wallet + + Co-authored-by: MasFlam + + allow for storing a debug version in the build # + + idk gem, little pruney there + + allow for storing a debug version in the build num (merge: keep-both) + + fix cmakelists + + wip + + ds updates to Wallet.cpp/Wallet.h + + fix build error + + wip + + wip2 + + super + + getting there + + fix warning/info messages + + bigger scan Tx window + + fix transaction diaglogue sizing + + fix warning/info in build logs + + updae message box stuff + + better + + fix compile error; hopefully persist settings? + + fixing it up + + laughable bug + + better conditional & debug logging + + better? + + tidy estimatedBytes + + use simple QDialog for transaction Scan window + + rename to syncPause + + properly dispose of QThreadStorage disposal + + $ ./build/bin/feather --version + FeatherWallet 2.8.1-79-g16eec531 + QThreadStorage: entry 2 destroyed before end of thread 0x562e3e2b3b90 + QThreadStorage: entry 1 destroyed before end of thread 0x562e3e2b3b90 + + shellcheck stuff? + + wip1 + + wip2 + + more wip + + better + + improvements in status bar + + debug build + + better! + + keep trucking + + better synch status + + trying to get status always updated + + put into helper method + + restore CMakeLists.txt back to master status + + polishing for review + + remove formatting diffs; remove BCUR ref + + refactor bool importTransaction() + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index dd2087ad..09fd8a86 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1,7 +1,34 @@ +-cmake_minimum_required(VERSION 3.18) ++cmake_minimum_required(VERSION 3.16) ++ ++# Configurable options ++option(FEATHER_VERSION_DEBUG_BUILD "Enable git describe version string" OFF) ++ ++if(FEATHER_VERSION_DEBUG_BUILD AND EXISTS "${CMAKE_SOURCE_DIR}/.git") ++ find_package(Git) ++ if(GIT_FOUND) ++ execute_process( ++ COMMAND ${GIT_EXECUTABLE} describe --tags --dirty --always ++ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ++ OUTPUT_VARIABLE GIT_DESCRIBE_VERSION ++ OUTPUT_STRIP_TRAILING_WHITESPACE ++ ) ++ message(STATUS "Feather DEBUG BUILD version (git): ${GIT_DESCRIBE_VERSION}") ++ set(DETECTED_FEATHER_VERSION ${GIT_DESCRIBE_VERSION}) ++ endif() ++endif() ++ ++if(NOT DETECTED_FEATHER_VERSION) ++ set(DETECTED_FEATHER_VERSION "2.8.1") ++endif() ++ ++# Extract standard version format for project() ++string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" PROJECT_VERSION_CLEAN "${DETECTED_FEATHER_VERSION}") ++if(NOT PROJECT_VERSION_CLEAN) ++ set(PROJECT_VERSION_CLEAN "2.8.1") ++endif() + + project(feather +- VERSION "2.8.1" ++ VERSION ${PROJECT_VERSION_CLEAN} + DESCRIPTION "A free Monero desktop wallet" + LANGUAGES CXX C ASM + ) +diff --git a/setup_flags.sh b/setup_flags.sh +new file mode 100644 +index 00000000..9112cf76 +--- /dev/null ++++ b/setup_flags.sh +@@ -0,0 +1,15 @@ ++#!/bin/bash ++ ++# Configure CMake with custom flags ++# - FEATHER_VERSION_DEBUG_BUILD=ON : Use git describe for version string ++# - CHECK_UPDATES=OFF : Disable built-in update checker ++# - USE_DEVICE_TREZOR=OFF : Disable Trezor hardware wallet support ++# - WITH_SCANNER=OFF : Disable webcam QR scanner support ++ ++cmake \ ++ -DCMAKE_BUILD_TYPE=Debug \ ++ -DFEATHER_VERSION_DEBUG_BUILD=ON \ ++ -DCHECK_UPDATES=OFF \ ++ -DUSE_DEVICE_TREZOR=OFF \ ++ -DWITH_SCANNER=OFF \ ++ .. +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 1ad529ca..f94d2e38 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -185,7 +185,7 @@ target_include_directories(feather PUBLIC + ${BCUR_INCLUDE_DIR} + ) + +-target_compile_definitions(feather PRIVATE FEATHER_VERSION="${PROJECT_VERSION}") ++target_compile_definitions(feather PRIVATE FEATHER_VERSION="${DETECTED_FEATHER_VERSION}") + target_compile_definitions(feather PRIVATE FEATHER_TARGET_TRIPLET="${FEATHER_TARGET_TRIPLET}") + target_compile_definitions(feather PRIVATE TOR_VERSION="${TOR_VERSION}") + +@@ -289,7 +289,6 @@ target_link_libraries(feather PRIVATE + ${ICU_LIBRARIES} + ${LIBZIP_LIBRARIES} + ${ZLIB_LIBRARIES} +- ${BCUR_LIBRARY} + ) + + if(CHECK_UPDATES) +diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp +index 6a2d6b80..7b58f564 100644 +--- a/src/MainWindow.cpp ++++ b/src/MainWindow.cpp +@@ -8,6 +8,10 @@ + #include + #include + #include ++#include ++#include ++#include ++#include + + #include "constants.h" + #include "dialog/AddressCheckerIndexDialog.h" +@@ -30,10 +34,12 @@ + #include "libwalletqt/TransactionHistory.h" + #include "model/AddressBookModel.h" + #include "plugins/PluginRegistry.h" ++#include "plugins/Plugin.h" + #include "utils/AppData.h" + #include "utils/AsyncTask.h" + #include "utils/ColorScheme.h" + #include "utils/Icons.h" ++#include "utils/RestoreHeightLookup.h" + #include "utils/TorManager.h" + #include "utils/WebsocketNotifier.h" + +@@ -80,7 +86,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa + + this->onOfflineMode(conf()->get(Config::offlineMode).toBool()); + conf()->set(Config::restartRequired, false); +- ++ + // Websocket notifier + #ifdef CHECK_UPDATES + connect(websocketNotifier(), &WebsocketNotifier::UpdatesReceived, m_updater.data(), &Updater::wsUpdatesReceived); +@@ -135,7 +141,7 @@ void MainWindow::initStatusBar() { + this->statusBar()->setFixedHeight(30); + + m_statusLabelStatus = new QLabel("Idle", this); +- m_statusLabelStatus->setTextInteractionFlags(Qt::TextSelectableByMouse); ++ m_statusLabelStatus->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); + this->statusBar()->addWidget(m_statusLabelStatus); + + m_statusLabelNetStats = new QLabel("", this); +@@ -188,6 +194,221 @@ void MainWindow::initStatusBar() { + connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked); + this->statusBar()->addPermanentWidget(m_statusBtnHwDevice); + m_statusBtnHwDevice->hide(); ++ ++ m_statusLabelStatus->setContextMenuPolicy(Qt::ActionsContextMenu); ++ ++ QAction *pauseSyncAction = new QAction(tr("Pause Sync"), this); ++ pauseSyncAction->setCheckable(true); ++ pauseSyncAction->setChecked(conf()->get(Config::syncPaused).toBool()); ++ m_statusLabelStatus->addAction(pauseSyncAction); ++ ++ QAction *skipSyncAction = new QAction(tr("Skip Sync"), this); ++ m_statusLabelStatus->addAction(skipSyncAction); ++ ++ QAction *syncRangeAction = new QAction(tr("Sync Date Range..."), this); ++ m_statusLabelStatus->addAction(syncRangeAction); ++ ++ QAction *fullSyncAction = new QAction(tr("Full Sync"), this); ++ m_statusLabelStatus->addAction(fullSyncAction); ++ ++ QAction *scanTxAction = new QAction(tr("Scan Transaction"), this); ++ m_statusLabelStatus->addAction(scanTxAction); ++ ui->menuTools->addAction(scanTxAction); ++ ++ connect(pauseSyncAction, &QAction::toggled, this, [this](bool checked) { ++ conf()->set(Config::syncPaused, checked); ++ if (m_wallet) { ++ if (checked) { ++ m_wallet->pauseRefresh(); ++ ++ this->setPausedSyncStatus(); ++ } else { ++ m_wallet->startRefresh(); ++ this->setStatusText(tr("Resuming sync...")); ++ } ++ } ++ }); ++ ++ connect(skipSyncAction, &QAction::triggered, this, [this](){ ++ if (!m_wallet) return; ++ ++ QString msg = tr("Skip sync will set your wallet's restore height to the current network height.\n\n" ++ "Use this if you know you haven't received any transactions since your last sync.\n" ++ "You can always use 'Full Sync' to rescan from the beginning.\n\n" ++ "Continue?"); ++ ++ if (QMessageBox::question(this, tr("Skip Sync"), msg) == QMessageBox::Yes) { ++ m_wallet->skipToTip(); ++ this->setStatusText(tr("Skipped sync to tip.")); ++ } ++ }); ++ ++ connect(syncRangeAction, &QAction::triggered, this, [this](){ ++ if (!m_wallet) return; ++ ++ QDialog dialog(this); ++ dialog.setWindowTitle(tr("Sync Date Range")); ++ dialog.setWindowIcon(QIcon(":/assets/images/appicons/64x64.png")); ++ dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); ++ dialog.setWindowFlags(dialog.windowFlags() | Qt::MSWindowsFixedSizeDialogHint); ++ ++ auto *layout = new QVBoxLayout(&dialog); ++ ++ auto *formLayout = new QFormLayout; ++ formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); ++ ++ auto *toDateEdit = new QDateEdit(QDate::currentDate()); ++ toDateEdit->setCalendarPopup(true); ++ toDateEdit->setDisplayFormat("yyyy-MM-dd"); ++ ++ // Load lookup for accurate block calculations ++ NetworkType::Type nettype = m_wallet->nettype(); ++ QString filename = Utils::getRestoreHeightFilename(nettype); ++ ++ std::unique_ptr lookup(RestoreHeightLookup::fromFile(filename, nettype)); ++ ++ int defaultDays = 7; ++ ++ auto *daysSpinBox = new QSpinBox; ++ daysSpinBox->setRange(1, 3650); // 10 years ++ daysSpinBox->setValue(defaultDays); ++ daysSpinBox->setSuffix(tr(" days")); ++ ++ auto *fromDateEdit = new QDateEdit(QDate::currentDate().addDays(-defaultDays)); ++ fromDateEdit->setCalendarPopup(true); ++ fromDateEdit->setDisplayFormat("yyyy-MM-dd"); ++ fromDateEdit->setCalendarPopup(true); ++ fromDateEdit->setDisplayFormat("yyyy-MM-dd"); ++ fromDateEdit->setToolTip(tr("Calculated from 'End date' and day span.")); ++ ++ ++ toDateEdit->setCalendarPopup(true); ++ toDateEdit->setDisplayFormat("yyyy-MM-dd"); ++ ++ auto *infoLabel = new QLabel; ++ infoLabel->setWordWrap(true); ++ infoLabel->setStyleSheet("QLabel { color: #888; font-size: 11px; }"); ++ ++ formLayout->addRow(tr("Day span:"), daysSpinBox); ++ formLayout->addRow(tr("Start date:"), fromDateEdit); ++ formLayout->addRow(tr("End date:"), toDateEdit); ++ ++ layout->addLayout(formLayout); ++ layout->addWidget(infoLabel); ++ ++ auto updateInfo = [=, &lookup]() { ++ QDate start = fromDateEdit->date(); ++ QDate end = toDateEdit->date(); ++ ++ uint64_t startHeight = lookup->dateToHeight(start.startOfDay().toSecsSinceEpoch()); ++ uint64_t endHeight = lookup->dateToHeight(end.endOfDay().toSecsSinceEpoch()); ++ ++ if (endHeight < startHeight) endHeight = startHeight; ++ quint64 blocks = endHeight - startHeight; ++ quint64 size = Utils::estimateSyncDataSize(blocks); ++ ++ infoLabel->setText(tr("Scanning ~%1 blocks\nEst. download size: %2") ++ .arg(blocks) ++ .arg(Utils::formatBytes(size))); ++ }; ++ ++ auto updateFromDate = [=]() { ++ fromDateEdit->setDate(toDateEdit->date().addDays(-daysSpinBox->value())); ++ updateInfo(); ++ }; ++ ++ connect(fromDateEdit, &QDateEdit::dateChanged, updateInfo); ++ connect(toDateEdit, &QDateEdit::dateChanged, updateFromDate); ++ connect(daysSpinBox, QOverload::of(&QSpinBox::valueChanged), updateFromDate); ++ ++ // Init label ++ updateInfo(); ++ ++ auto *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); ++ connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); ++ connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); ++ layout->addWidget(btnBox); ++ ++ dialog.resize(320, dialog.height()); ++ ++ if (dialog.exec() == QDialog::Accepted) { ++ m_wallet->syncDateRange(fromDateEdit->date(), toDateEdit->date()); ++ ++ // Re-calculate for status text ++ uint64_t startHeight = lookup->dateToHeight(fromDateEdit->date().startOfDay().toSecsSinceEpoch()); ++ uint64_t endHeight = lookup->dateToHeight(toDateEdit->date().endOfDay().toSecsSinceEpoch()); ++ quint64 blocks = (endHeight > startHeight) ? endHeight - startHeight : 0; ++ quint64 size = Utils::estimateSyncDataSize(blocks); ++ ++ this->setStatusText(QString("Syncing range %1 - %2 (~%3)...") ++ .arg(fromDateEdit->date().toString("yyyy-MM-dd")) ++ .arg(toDateEdit->date().toString("yyyy-MM-dd")) ++ .arg(Utils::formatBytes(size))); ++ } ++ }); ++ ++ connect(fullSyncAction, &QAction::triggered, this, [this](){ ++ if (m_wallet) { ++ QString estBlocks = "Unknown (waiting for node)"; ++ QString estSize = "Unknown"; ++ ++ quint64 walletCreationHeight = m_wallet->getWalletCreationHeight(); ++ quint64 daemonHeight = m_wallet->daemonBlockChainHeight(); ++ quint64 blocksBehind = 0; ++ ++ if (daemonHeight > 0) { ++ blocksBehind = (daemonHeight > walletCreationHeight) ? (daemonHeight - walletCreationHeight) : 0; ++ quint64 estimatedBytes = Utils::estimateSyncDataSize(blocksBehind); ++ estBlocks = QString::number(blocksBehind); ++ estSize = QString("~%1").arg(Utils::formatBytes(estimatedBytes)); ++ } ++ ++ QString msg = QString("Full sync will rescan from your restore height.\n\n" ++ "Blocks behind: %1\n" ++ "Estimated data: %2\n\n" ++ "Note: We will not rescan blocks which have cached/hashed/checksummed, " ++ "and any discrepancy between block height and daemon height can be understood in these terms.\n\n" ++ "Continue?") ++ .arg(estBlocks) ++ .arg(estSize); ++ ++ if (QMessageBox::question(this, "Full Sync", msg) == QMessageBox::Yes) { ++ m_wallet->fullSync(); ++ this->setStatusText(QString("Full sync started (%1)...").arg(estSize)); ++ } ++ } ++ }); ++ ++ connect(scanTxAction, &QAction::triggered, this, [this](){ ++ if (m_wallet) { ++ QDialog dialog(this); ++ dialog.setWindowTitle("Scan Transaction"); ++ dialog.setWindowIcon(QIcon(":/assets/images/appicons/64x64.png")); ++ ++ auto *layout = new QVBoxLayout(&dialog); ++ layout->addWidget(new QLabel("Enter transaction ID:")); ++ ++ auto *lineEdit = new QLineEdit; ++ lineEdit->setMinimumWidth(600); ++ layout->addWidget(lineEdit); ++ ++ auto *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); ++ connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); ++ connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); ++ layout->addWidget(btnBox); ++ ++ // Compact vertical size, allow horizontal resize ++ dialog.layout()->setSizeConstraint(QLayout::SetFixedSize); ++ ++ if (dialog.exec() == QDialog::Accepted) { ++ QString txid = lineEdit->text(); ++ if (!txid.isEmpty()) { ++ m_wallet->importTransaction(txid.trimmed()); ++ this->setStatusText("Transaction scanned: " + txid.trimmed().left(8) + "..."); ++ } ++ } ++ } ++ }); + } + + void MainWindow::initPlugins() { +@@ -489,6 +710,17 @@ void MainWindow::initWalletContext() { + this->onConnectionStatusChanged(status); + m_nodes->autoConnect(); + }); ++ ++ connect(m_wallet, &Wallet::heightsRefreshed, this, [this](bool success, quint64 daemonHeight, quint64 targetHeight) { ++ // When paused, we might get success=false because wallet->refresh() is skipped, ++ // preventing strict cache updates. We should attempt to fallback to m_nodes info. ++ if (!success && !conf()->get(Config::syncPaused).toBool()) return; ++ ++ // Update sync estimate if paused ++ if (conf()->get(Config::syncPaused).toBool()) { ++ this->setPausedSyncStatus(); ++ } ++ }); + connect(m_wallet, &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle); + connect(m_wallet, &Wallet::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded); + +@@ -509,7 +741,7 @@ void MainWindow::initWalletContext() { + connect(m_wallet, &Wallet::deviceButtonRequest, this, &MainWindow::onDeviceButtonRequest); + connect(m_wallet, &Wallet::deviceButtonPressed, this, &MainWindow::onDeviceButtonPressed); + connect(m_wallet, &Wallet::deviceError, this, &MainWindow::onDeviceError); +- ++ + connect(m_wallet, &Wallet::multiBroadcast, this, &MainWindow::onMultiBroadcast); + } + +@@ -590,7 +822,13 @@ void MainWindow::onWalletOpened() { + this->updatePasswordIcon(); + this->updateTitle(); + m_nodes->allowConnection(); +- m_nodes->connectToNode(); ++ if (!conf()->get(Config::disableAutoRefresh).toBool()) { ++ m_nodes->connectToNode(); ++ if (conf()->get(Config::syncPaused).toBool()) { ++ m_wallet->pauseRefresh(); ++ this->setPausedSyncStatus(); ++ } ++ } + m_updateBytes.start(250); + + if (conf()->get(Config::writeRecentlyOpenedWallets).toBool()) { +@@ -632,6 +870,14 @@ void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) { + m_statusLabelBalance->setText(balance_str); + } + ++void MainWindow::setPausedSyncStatus() { ++ QString tooltip; ++ QString status = Utils::getPausedSyncStatus(m_wallet, m_nodes, &tooltip); ++ this->setStatusText(status); ++ if (!tooltip.isEmpty()) ++ m_statusLabelStatus->setToolTip(tooltip); ++} ++ + void MainWindow::setStatusText(const QString &text, bool override, int timeout) { + if (override) { + m_statusOverrideActive = true; +@@ -742,11 +988,30 @@ void MainWindow::onMultiBroadcast(const QMap &txHexMap) { + } + + void MainWindow::onSyncStatus(quint64 height, quint64 target, bool daemonSync) { +- if (height >= (target - 1)) { ++ if (height >= (target - 1) && target > 0) { + this->updateNetStats(); ++ this->setStatusText(QString("Synchronized (%1)").arg(height)); ++ } else { ++ // Calculate depth ++ quint64 blocksLeft = (target > height) ? (target - height) : 0; ++ // Estimate download size in MB: assuming 500 MB for full history, ++ // and we are blocksLeft behind out of (target-1) total blocks (since block 0 is genesis) ++ double approximateSizeMB = 0.0; ++ if (target > 1) { ++ approximateSizeMB = (blocksLeft * 500.0) / (target - 1); ++ } ++ QString sizeText; ++ if (approximateSizeMB < 1) { ++ sizeText = QString("%1 KB").arg(QString::number(approximateSizeMB * 1024, 'f', 0)); ++ } else { ++ sizeText = QString("%1 MB").arg(QString::number(approximateSizeMB, 'f', 1)); ++ } ++ QString statusMsg = Utils::formatSyncStatus(height, target, daemonSync); ++ // Shows "# blocks remaining (approx. X MB)" to sync ++ // Shows "Wallet sync: # blocks remaining (approx. X MB)" ++ this->setStatusText(QString("%1 (approx. %2)").arg(statusMsg).arg(sizeText)); + } +- this->setStatusText(Utils::formatSyncStatus(height, target, daemonSync)); +- m_statusLabelStatus->setToolTip(QString("Wallet height: %1").arg(QString::number(height))); ++ m_statusLabelStatus->setToolTip(QString("Wallet Height: %1 | Network Tip: %2").arg(height).arg(target)); + } + + void MainWindow::onConnectionStatusChanged(int status) +@@ -788,6 +1053,12 @@ void MainWindow::onConnectionStatusChanged(int status) + } + } + ++ if (conf()->get(Config::syncPaused).toBool() && !conf()->get(Config::offlineMode).toBool()) { ++ if (status == Wallet::ConnectionStatus_Synchronizing || status == Wallet::ConnectionStatus_Synchronized) { ++ this->setPausedSyncStatus(); ++ } ++ } ++ + m_statusBtnConnectionStatusIndicator->setIcon(icon); + } + +@@ -967,7 +1238,7 @@ void MainWindow::onTransactionCreated(PendingTransaction *tx, const QVectorget(Config::syncPaused).toBool()) { ++ m_statusLabelNetStats->hide(); ++ return; ++ } ++ + m_statusLabelNetStats->show(); + m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_wallet->getBytesReceived()))); + } +@@ -1544,12 +1820,12 @@ void MainWindow::rescanSpent() { + "Make sure you are connected to a trusted node.\n\n" + "Do you want to proceed?"); + warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); +- ++ + auto r = warning.exec(); + if (r == QMessageBox::No) { + return; + } +- ++ + if (!m_wallet->rescanSpent()) { + Utils::showError(this, "Failed to rescan spent outputs", m_wallet->errorString()); + } else { +diff --git a/src/MainWindow.h b/src/MainWindow.h +index c44270d3..d465ceba 100644 +--- a/src/MainWindow.h ++++ b/src/MainWindow.h +@@ -209,6 +209,7 @@ private: + void unlockWallet(const QString &password); + void closeQDialogChildren(QObject *object); + int findTab(const QString &title); ++ void setPausedSyncStatus(); + + QIcon hardwareDevicePairedIcon(); + QIcon hardwareDeviceUnpairedIcon(); +diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp +index 5d6696e9..13ad493f 100644 +--- a/src/WindowManager.cpp ++++ b/src/WindowManager.cpp +@@ -71,9 +71,13 @@ void WindowManager::setEventFilter(EventFilter *ef) { + + WindowManager::~WindowManager() { + qDebug() << "~WindowManager"; +- m_cleanupThread->quit(); +- m_cleanupThread->wait(); +- qDebug() << "WindowManager: cleanup thread done" << QThread::currentThreadId(); ++ if (m_cleanupThread && m_cleanupThread->isRunning()) { ++ m_cleanupThread->quit(); ++ m_cleanupThread->wait(); ++ qDebug() << "WindowManager: cleanup thread done" << QThread::currentThreadId(); ++ } else { ++ qDebug() << "WindowManager: cleanup thread already stopped"; ++ } + } + + // ######################## APPLICATION LIFECYCLE ######################## +@@ -89,6 +93,20 @@ void WindowManager::quitAfterLastWindow() { + + void WindowManager::close() { + qDebug() << Q_FUNC_INFO << QThread::currentThreadId(); ++ ++ // Stop all threads before application shutdown to avoid QThreadStorage warnings ++ if (m_cleanupThread && m_cleanupThread->isRunning()) { ++ m_cleanupThread->quit(); ++ m_cleanupThread->wait(); ++ qDebug() << "WindowManager: cleanup thread stopped in close()"; ++ } ++ ++ // Stop Tor manager threads ++ torManager()->stop(); ++ ++ // Wait for all threads in the global thread pool ++ QThreadPool::globalInstance()->waitForDone(); ++ + for (const auto &window: m_windows) { + window->close(); + } +@@ -106,8 +124,6 @@ void WindowManager::close() { + m_docsDialog->deleteLater(); + } + +- torManager()->stop(); +- + deleteLater(); + + qDebug() << "Calling QApplication::quit()"; +diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp +index c26e40d9..85ec7399 100644 +--- a/src/libwalletqt/Wallet.cpp ++++ b/src/libwalletqt/Wallet.cpp +@@ -14,6 +14,7 @@ + #include "WalletManager.h" + #include "WalletListenerImpl.h" + ++#include "utils/config.h" + #include "config.h" + #include "constants.h" + +@@ -25,6 +26,8 @@ + #include "model/CoinsModel.h" + + #include "utils/ScopeGuard.h" ++#include "utils/RestoreHeightLookup.h" ++#include "utils/Utils.h" + + #include "wallet/wallet2.h" + +@@ -83,6 +86,15 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent) + connect(m_subaddress, &Subaddress::corrupted, [this]{ + emit keysCorrupted(); + }); ++ ++ // Store original creation height if not already present ++ // This protects the restore height from being overwritten by "Skip Sync" or range syncs ++ if (!cacheAttributeExists("feather.creation_height")) { ++ quint64 height = m_wallet2->get_refresh_from_block_height(); ++ if (height > 0) { ++ setCacheAttribute("feather.creation_height", QString::number(height)); ++ } ++ } + } + + // #################### Status #################### +@@ -502,13 +514,26 @@ void Wallet::onHeightsRefreshed(bool success, quint64 daemonHeight, quint64 targ + m_daemonBlockChainHeight = daemonHeight; + m_daemonBlockChainTargetHeight = targetHeight; + ++ if (conf()->get(Config::syncPaused).toBool()) { ++ if (success) { ++ quint64 walletHeight = blockChainHeight(); ++ if (walletHeight < (targetHeight - 1)) { ++ setConnectionStatus(ConnectionStatus_Synchronizing); ++ } else { ++ setConnectionStatus(ConnectionStatus_Synchronized); ++ } ++ } else { ++ setConnectionStatus(ConnectionStatus_Disconnected); ++ } ++ return; ++ } ++ + if (success) { + quint64 walletHeight = blockChainHeight(); + + if (daemonHeight < targetHeight) { + emit syncStatus(daemonHeight, targetHeight, true); +- } +- else { ++ } else { + this->syncStatusUpdated(walletHeight, daemonHeight); + } + +@@ -544,7 +569,120 @@ void Wallet::syncStatusUpdated(quint64 height, quint64 target) { + emit syncStatus(height, target, false); + } + ++void Wallet::skipToTip() { ++ if (!m_wallet2) ++ return; ++ uint64_t tip = m_wallet2->get_blockchain_current_height(); ++ if (tip > 0) { ++ // Log previous height for debugging ++ uint64_t prevHeight = m_wallet2->get_refresh_from_block_height(); ++ qInfo() << "Skip Sync triggered. Head moving from" << prevHeight << "to:" << tip; ++ ++ m_wallet2->set_refresh_from_block_height(tip); ++ pauseRefresh(); ++ startRefresh(); ++ } ++} ++ ++void Wallet::syncDateRange(const QDate &start, const QDate &end) { ++ if (!m_wallet2) ++ return; ++ ++ // Convert dates to heights with internal table lookup ++ cryptonote::network_type nettype = m_wallet2->nettype(); ++ QString filename = Utils::getRestoreHeightFilename(static_cast(nettype)); ++ ++ auto lookup = RestoreHeightLookup::fromFile(filename, static_cast(nettype)); ++ uint64_t startHeight = lookup->dateToHeight(start.startOfDay().toSecsSinceEpoch()); ++ uint64_t endHeight = lookup->dateToHeight(end.startOfDay().toSecsSinceEpoch()); ++ delete lookup; ++ ++ if (startHeight >= endHeight) ++ return; ++ ++ m_stopHeight = endHeight; ++ m_rangeSyncActive = true; ++ ++ m_wallet2->set_refresh_from_block_height(startHeight); ++ pauseRefresh(); ++ startRefresh(); ++} ++ ++void Wallet::fullSync() { ++ if (!m_wallet2) ++ return; ++ ++ // Reset range sync just in case ++ m_rangeSyncActive = false; ++ ++ // Retrieve original creation height from persistent storage ++ uint64_t originalHeight = 0; ++ QString storedHeight = this->getCacheAttribute("feather.creation_height"); ++ if (!storedHeight.isEmpty()) { ++ originalHeight = storedHeight.toULongLong(); ++ } else { ++ // Fallback (dangerous if skipped, but better than 0) ++ originalHeight = m_wallet2->get_refresh_from_block_height(); ++>>>>>>> 43ee0e4e (Implement Skip Sync and Data Saving features) ++ } ++ ++ m_wallet2->set_refresh_from_block_height(originalHeight); ++ // Trigger rescan ++ pauseRefresh(); ++ ++ // Optional: Clear transaction cache to ensure fresh view ++ // m_wallet2->clearCache(); ++ ++ startRefresh(); ++ ++ qInfo() << "Full Sync triggered. Rescanning from original restore height:" << originalHeight; ++} ++ ++void Wallet::syncStatusUpdated(quint64 height, quint64 targetHeight) { ++ if (m_rangeSyncActive && height >= m_stopHeight) { ++ // At end of requested date range, jump to tip ++ m_rangeSyncActive = false; ++ this->skipToTip(); ++ return; ++ } ++ ++ if (height >= (targetHeight - 1)) { ++ this->updateBalance(); ++ } ++ emit syncStatus(height, targetHeight, false); ++} ++ ++bool Wallet::importTransaction(const QString &txid) { ++ if (!m_wallet2 || txid.isEmpty()) ++ return false; ++ ++ // If scanning a specific TX, we shouldn't be constrained by range sync ++ if (m_rangeSyncActive) { ++ m_rangeSyncActive = false; ++ } ++ ++ try { ++ std::unordered_set txids; ++ crypto::hash txid_hash; ++ if (!epee::string_tools::hex_to_pod(txid.toStdString(), txid_hash)) { ++ qWarning() << "Invalid transaction id: " << txid; ++ return false; ++ } ++ txids.insert(txid_hash); ++ m_wallet2->scan_tx(txids); ++ qInfo() << "Successfully imported transaction:" << txid; ++ this->updateBalance(); ++ return true; ++ } catch (const std::exception &e) { ++ qWarning() << "Failed to import transaction: " << txid << ", error: " << e.what(); ++ } ++ return false; ++} ++ + void Wallet::onNewBlock(uint64_t walletHeight) { ++ if (conf()->get(Config::syncPaused).toBool()) { ++ return; ++ } + // Called whenever a new block gets scanned by the wallet + quint64 daemonHeight = m_daemonBlockChainTargetHeight; + +@@ -693,11 +831,6 @@ bool Wallet::importOutputsFromStr(const std::string &outputs) { + return m_walletImpl->importOutputsFromStr(outputs); + } + +-bool Wallet::importTransaction(const QString& txid) { +- std::vector txids = {txid.toStdString()}; +- return m_walletImpl->scanTransactions(txids); +-} +- + // #################### Wallet cache #################### + + void Wallet::store() { +diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h +index 20d21af4..8dee4365 100644 +--- a/src/libwalletqt/Wallet.h ++++ b/src/libwalletqt/Wallet.h +@@ -142,7 +142,7 @@ public: + bool isDeterministic() const; + + QString walletName() const; +- ++ + // ##### Balance ##### + //! returns balance + quint64 balance() const; +@@ -153,7 +153,7 @@ public: + quint64 unlockedBalance() const; + quint64 unlockedBalance(quint32 accountIndex) const; + quint64 unlockedBalanceAll() const; +- ++ + quint64 viewOnlyBalance(quint32 accountIndex) const; + + void updateBalance(); +@@ -229,6 +229,11 @@ public: + quint64 daemonBlockChainTargetHeight() const; + + void syncStatusUpdated(quint64 height, quint64 target); ++ Q_INVOKABLE void skipToTip(); ++ Q_INVOKABLE void syncDateRange(const QDate &start, const QDate &end); ++ void fullSync(); // from restoreHeight, not genesis - recreate your wallet for that ;P ++ ++ bool importTransaction(const QString &txid); + + void refreshModels(); + +@@ -250,25 +255,22 @@ public: + void setForceKeyImageSync(bool enabled); + bool hasUnknownKeyImages() const; + bool keyImageSyncNeeded(quint64 amount, bool sendAll) const; +- ++ + //! export/import key images + bool exportKeyImages(const QString& path, bool all = false); + bool exportKeyImagesToStr(std::string &keyImages, bool all = false); + bool exportKeyImagesForOutputsFromStr(const std::string &outputs, std::string &keyImages); +- ++ + bool importKeyImages(const QString& path); + bool importKeyImagesFromStr(const std::string &keyImages); + + //! export/import outputs + bool exportOutputs(const QString& path, bool all = false); + bool exportOutputsToStr(std::string& outputs, bool all); +- ++ + bool importOutputs(const QString& path); + bool importOutputsFromStr(const std::string &outputs); + +- //! import a transaction +- bool importTransaction(const QString& txid); +- + // ##### Wallet cache ##### + //! saves wallet to the file by given path + //! empty path stores in current location +@@ -342,7 +344,7 @@ public: + //! Sign a transfer from file + UnsignedTransaction * loadTxFile(const QString &fileName); + UnsignedTransaction * loadUnsignedTransactionFromStr(const std::string &data); +- ++ + //! Load an unsigned transaction from a base64 encoded string + UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx); + +@@ -529,6 +531,10 @@ private: + + QTimer *m_storeTimer = nullptr; + std::set m_selectedInputs; ++ ++ uint64_t m_stopHeight = 0; ++ bool m_rangeSyncActive = false; + }; + + #endif // FEATHER_WALLET_H ++ +diff --git a/src/model/SubaddressProxyModel.h b/src/model/SubaddressProxyModel.h +index 38069ed1..833c6f25 100644 +--- a/src/model/SubaddressProxyModel.h ++++ b/src/model/SubaddressProxyModel.h +@@ -20,7 +20,7 @@ public slots: + void setSearchFilter(const QString& searchString){ + m_searchRegExp.setPattern(searchString); + m_searchCaseSensitiveRegExp.setPattern(searchString); +- invalidateFilter(); ++ invalidate(); + } + + private: +diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp +index b468fed3..0ef06d99 100644 +--- a/src/utils/Utils.cpp ++++ b/src/utils/Utils.cpp +@@ -26,6 +26,7 @@ + #include "utils/os/tails.h" + #include "utils/os/whonix.h" + #include "libwalletqt/Wallet.h" ++#include "utils/nodes.h" + #include "WindowManager.h" + + namespace Utils { +@@ -712,6 +713,52 @@ QString formatSyncStatus(quint64 height, quint64 target, bool daemonSync) { + return "Synchronized"; + } + ++QString formatSyncTimeEstimate(quint64 blocks) { ++ quint64 minutes = blocks * 2; ++ quint64 days = minutes / (60 * 24); ++ ++ QString timeStr = (days > 0) ? QString("~%1 days").arg(days) : QString("~%1 hours").arg(minutes / 60); ++ if (days == 0 && minutes < 60) timeStr = "< 1 hour"; ++ return timeStr; ++} ++ ++quint64 estimateSyncDataSize(quint64 blocks) { ++ // Estimate 1024 bytes per block (1KB) for wallet scanning. ++ return blocks * 1024; ++} ++ ++QString formatPausedSyncStatus(quint64 blocks) { ++ quint64 size = estimateSyncDataSize(blocks); ++ return QObject::tr("[PAUSED] Sync %1 blocks / %2 upon resume").arg(blocks).arg(formatBytes(size)); ++} ++ ++QString getPausedSyncStatus(Wallet *wallet, Nodes *nodes, QString *tooltip) { ++ if (!wallet) return QObject::tr("[PAUSED] Sync paused"); ++ ++ quint64 walletHeight = wallet->blockChainHeight(); ++ quint64 creationHeight = wallet->getWalletCreationHeight(); ++ quint64 startHeight = (walletHeight > creationHeight) ? walletHeight : creationHeight; ++ quint64 daemonHeight = wallet->daemonBlockChainTargetHeight(); ++ ++ // If sync is paused or wallet just started, Wallet's internal height might be 0. ++ // If the daemon is connected, use its target_height or height to determine the latest tip. ++ if (daemonHeight == 0 && nodes) { ++ auto connection = nodes->connection(); ++ if (connection.target_height > 0) daemonHeight = connection.target_height; ++ else if (connection.height > 0) daemonHeight = connection.height; ++ } ++ ++ if (daemonHeight > 0) { ++ if (tooltip) { ++ *tooltip = QString("Wallet Height: %1 | Network Tip: %2").arg(walletHeight).arg(daemonHeight); ++ } ++ quint64 blocksBehind = (daemonHeight > startHeight) ? (daemonHeight - startHeight) : 0; ++ return formatPausedSyncStatus(blocksBehind); ++ } ++ ++ return "[PAUSED] Sync paused"; ++} ++ + QString formatRestoreHeight(quint64 height) { + const QDateTime restoreDate = appData()->restoreHeights[constants::networkType]->heightToDate(height); + return QString("%1 (%2)").arg(QString::number(height), restoreDate.toString("yyyy-MM-dd")); +@@ -724,4 +771,13 @@ QString getVersion() { + #endif + return version; + } ++ ++QString getRestoreHeightFilename(NetworkType::Type nettype) { ++ if (nettype == NetworkType::MAINNET) ++ return ":/assets/restore_heights_monero_mainnet.txt"; ++ else if (nettype == NetworkType::TESTNET) ++ return ":/assets/restore_heights_monero_testnet.txt"; ++ else ++ return ":/assets/restore_heights_monero_stagenet.txt"; ++} + } +diff --git a/src/utils/Utils.h b/src/utils/Utils.h +index 22bba27f..2f29bfc3 100644 +--- a/src/utils/Utils.h ++++ b/src/utils/Utils.h +@@ -14,6 +14,8 @@ + #include "networktype.h" + + class SubaddressIndex; ++class Wallet; ++class Nodes; + + namespace Utils + { +@@ -119,9 +121,14 @@ namespace Utils + void clearLayout(QLayout *layout, bool deleteWidgets = true); + + QString formatSyncStatus(quint64 height, quint64 target, bool daemonSync = false); ++ QString formatSyncTimeEstimate(quint64 blocks); ++ quint64 estimateSyncDataSize(quint64 blocks); ++ QString formatPausedSyncStatus(quint64 blocks); ++ QString getPausedSyncStatus(Wallet *wallet, Nodes *nodes, QString *tooltip = nullptr); + QString formatRestoreHeight(quint64 height); + + QString getVersion(); ++ QString getRestoreHeightFilename(NetworkType::Type nettype); + } + + #endif //FEATHER_UTILS_H +diff --git a/src/utils/config.cpp b/src/utils/config.cpp +index b5baa886..f9453d5c 100644 +--- a/src/utils/config.cpp ++++ b/src/utils/config.cpp +@@ -76,7 +76,9 @@ static const QHash configStrings = { + {Config::showTrayIcon, {QS("showTrayIcon"), true}}, + {Config::minimizeToTray, {QS("minimizeToTray"), false}}, + {Config::disableWebsocket, {QS("disableWebsocket"), false}}, ++ {Config::disableAutoRefresh, {QS("disableAutoRefresh"), false}}, + {Config::offlineMode, {QS("offlineMode"), false}}, ++ {Config::syncPaused, {QS("syncPaused"), false}}, + + // Transactions + {Config::multiBroadcast, {QS("multiBroadcast"), true}}, +@@ -242,7 +244,9 @@ QDir Config::defaultConfigDir() { + #elif defined(Q_OS_MACOS) + return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + #else +- return QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/feather"); ++ QDir path(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/feather"); ++ qDebug() << "Config path resolved to: " << path.absolutePath(); ++ return path; + #endif + } + +diff --git a/src/utils/config.h b/src/utils/config.h +index 04d9d759..ffe9a6e6 100644 +--- a/src/utils/config.h ++++ b/src/utils/config.h +@@ -88,6 +88,7 @@ public: + disableWebsocket, + + // Network -> Offline ++ disableAutoRefresh, + offlineMode, + + // Storage -> Logging +@@ -120,7 +121,7 @@ public: + blockExplorers, + blockExplorer, + lastPath, +- ++ + // UR + URmsPerFragment, + URfragmentLength, +@@ -139,6 +140,9 @@ public: + // Tickers + tickers, + tickersShowFiatBalance, ++ ++ // Sync & data saver ++ syncPaused, + }; + + enum PrivacyLevel { +@@ -169,7 +173,7 @@ public: + UnifiedResources = 0, + FileTransfer + }; +- ++ + ~Config() override; + QVariant get(ConfigKey key); + QString getFileName(); diff --git a/docs/issues.txt b/docs/issues.txt new file mode 100644 index 00000000..37f84bc9 --- /dev/null +++ b/docs/issues.txt @@ -0,0 +1,15 @@ + +Check if these issues are valid — if so, understand the root cause of each and fix them. + + + + + +P2: Missing error handling after thread pool timeout. If threads don't complete within 30s, continuing with cleanup while threads are still running could cause use-after-free bugs or crashes. Consider either skipping cleanup and calling exit(), or adding retry logic/forced termination. + + + + + +P2: When the daemon height is unknown (`target <= 1`), the sync status now shows “(approx. )” because `sizeText` stays empty; provide a fallback string (e.g., “unknown”) or skip the parenthetical when no estimate is available. + \ No newline at end of file diff --git a/docs/setup_flags.sh b/docs/setup_flags.sh new file mode 100644 index 00000000..9112cf76 --- /dev/null +++ b/docs/setup_flags.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Configure CMake with custom flags +# - FEATHER_VERSION_DEBUG_BUILD=ON : Use git describe for version string +# - CHECK_UPDATES=OFF : Disable built-in update checker +# - USE_DEVICE_TREZOR=OFF : Disable Trezor hardware wallet support +# - WITH_SCANNER=OFF : Disable webcam QR scanner support + +cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DFEATHER_VERSION_DEBUG_BUILD=ON \ + -DCHECK_UPDATES=OFF \ + -DUSE_DEVICE_TREZOR=OFF \ + -DWITH_SCANNER=OFF \ + .. diff --git a/setup_flags.sh b/setup_flags.sh new file mode 100644 index 00000000..9112cf76 --- /dev/null +++ b/setup_flags.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Configure CMake with custom flags +# - FEATHER_VERSION_DEBUG_BUILD=ON : Use git describe for version string +# - CHECK_UPDATES=OFF : Disable built-in update checker +# - USE_DEVICE_TREZOR=OFF : Disable Trezor hardware wallet support +# - WITH_SCANNER=OFF : Disable webcam QR scanner support + +cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DFEATHER_VERSION_DEBUG_BUILD=ON \ + -DCHECK_UPDATES=OFF \ + -DUSE_DEVICE_TREZOR=OFF \ + -DWITH_SCANNER=OFF \ + .. -- 2.52.0