29 Commits

Author SHA1 Message Date
09f876eb60 update build output filenames to include version info 2026-03-25 11:24:21 -05:00
aa3bd4e304 update hardcoded version for mac dmg build 2026-03-25 11:18:03 -05:00
801fa2b96b feat: track shielded send txids via z_viewtransaction
Extract txids from completed z_sendmany operations and store in
send_txids_ so pure shielded sends are discoverable. The network
thread includes them in the enrichment set, calls z_viewtransaction,
caches results in viewtx_cache_, and removes them from send_txids_.
2026-03-25 11:06:09 -05:00
e0bfeb2f29 fix: macOS block index corruption, dbcache auto-sizing, import key rescan height
- Shutdown: 3-phase stop (wait for RPC stop → SIGTERM → SIGKILL) prevents
  LevelDB flush interruption on macOS/APFS that caused full re-sync on restart
- dbcache: auto-detect RAM and set -dbcache to 12.5% (clamped 450-4096 MB)
  on macOS (sysctl), Linux (sysconf), and Windows (GlobalMemoryStatusEx)
- Import key: pass user-entered start height to z_importkey and trigger
  rescanblockchain from that height for t-key imports
- Bump version to 1.1.1
2026-03-25 11:00:14 -05:00
9ed4fbc476 update mac icons 2026-03-19 14:46:33 -05:00
2b3277529e macOS port: build, rendering, daemon, and mining fixes
Build & setup:
- Fix setup.sh and build.sh for macOS (bundle daemon, xmrig, sapling params, asmap.dat into .app)
- Fix CMakeLists.txt libsodium linking for macOS
- Fix incbin.h to use __DATA,__const section on macOS
- Remove vendored libsodium-1.0.18 source tree (use fetch script instead)
- Remove prebuilt-binaries/xmrig (replaced by xmrig-hac)
- Add .DS_Store to .gitignore

Rendering & UI:
- Use GLSL #version 150 and OpenGL 3.2 Core Profile on macOS
- Force dpiScale=1.0 on macOS to fix Retina double-scaling
- Set default window/UI opacity to 100% on Mac/Linux
- Add scroll fade shader guard for macOS GL compatibility
- Add ImGui error recovery around render loop and mining tab

Daemon & bootstrap:
- Fix getDragonXDataDir() to return ~/Library/Application Support/Hush/DRAGONX/ on macOS
- Fix isPortInUse() with connect() fallback (no /proc/net/tcp on macOS)
- Increase daemon watchdog timeout from 3s to 15s
- Add daemon status indicator (colored dot + label) in wizard bootstrap phases

Mining tab:
- Fix EmbeddedDaemon::getMemoryUsageMB() crash on macOS (was using Linux /proc)
- Fix XmrigManager::getMemoryUsageMB() to use ps on macOS instead of /proc
- Restructure RenderMiningTab with wrapper pattern for exception safety
- Fix default pool URL to include port (pool.dragonx.is:3433)
2026-03-19 14:26:04 -05:00
d79d013060 feat: sync thread grid during idle scaling, skip lock screen while pool mining, add paste preview to import key dialog
- Mining tab: sync s_selected_threads with actual thread count when idle
  thread scaling adjusts threads (solo via genproclimit, pool via
  threads_active), skipping sync during user drag
- Auto-lock: bypass lock screen overlay when xmrig pool mining is active
  so the mining UI remains accessible
- Import key dialog: add clipboard hover preview with transparent overlay
  on the input field, inline key type validation next to title (matching
  send tab paste button pattern), configurable via ui.toml
2026-03-19 06:10:46 -05:00
474250bb50 v1.1.0: explorer tab, bootstrap fixes, full theme overlay merge
Explorer tab:
- New block explorer tab with search, chain stats, mempool info,
  recent blocks table, block detail modal with tx expansion
- Sidebar nav entry, i18n strings, ui.toml layout values

Bootstrap fixes:
- Move wizard Done handler into render() — was dead code, preventing
  startEmbeddedDaemon() and tryConnect() from firing post-wizard
- Stop deleting BDB database/ dir during cleanup — caused LSN mismatch
  that salvaged wallet.dat into wallet.{timestamp}.bak
- Add banlist.dat, db.log, .lock to cleanup file list
- Fatal extraction failure for blocks/ and chainstate/ files
- Verification progress: split SHA-256 (0-50%) and MD5 (50-100%)

Theme system:
- Expand overlay merge to apply ALL sections (tabs, dialogs, components,
  screens, flat sections), not just theme+backdrop+effects
- Add screens and security section parsing to UISchema
- Build-time theme expansion via expand_themes.py (CMake + build.sh)

Other:
- Version bump to 1.1.0
- WalletState::clear() resets all fields (sync, daemon info, etc.)
- Sidebar item-height 42 → 36
2026-03-17 18:49:46 -05:00
40dd6d45b2 daemon version check, idle mining control, bootstrap mirror, import key paste, and cleanup
- Add startup binary version checking for dragonxd/xmrig
- Display daemon version in UI
- Add idle mining thread count adjustment
- Add bootstrap mirror option (bootstrap2.dragonx.is) in setup wizard
- Add paste button to import private key dialog with address validation
- Add z-address generation UI feedback (loading indicator)
- Add option to delete blockchain data while preserving wallet.dat
- Add font scale slider hotkey tooltip (Ctrl+Plus/Ctrl+Minus)
- Fix Windows RPC auth: trim \r from config values, add .cookie fallback
- Fix connection status message during block index loading
- Improve application shutdown to prevent lingering background process
2026-03-17 14:57:12 -05:00
1b97476a54 update version to v1.0.2 2026-03-12 02:29:08 -05:00
d7bc5c638a fix: remove D3D11 debug layer flag that prevented startup on user machines
DRAGONX_DEBUG was defined unconditionally, causing D3D11CreateDevice() to
request the debug layer via D3D11_CREATE_DEVICE_DEBUG. This layer is only
available on machines with the Windows SDK or Graphics Tools installed,
so the call fails with DXGI_ERROR_SDK_COMPONENT_MISSING on regular user
machines — causing the app to silently exit.
2026-03-12 00:13:27 -05:00
39f193a264 fix xmrig bundling issues 2026-03-11 21:14:03 -05:00
4023af9466 fix scrolling bug 2026-03-11 03:15:31 -05:00
853e3e0f17 build: Linux release outputs binaries zip + AppImage, bundle sapling params
- Linux --linux-release now produces both ObsidianDragon-Linux-x64.zip
  (raw binaries) and ObsidianDragon.AppImage (single-file)
- Windows --win-release keeps standalone exe alongside zip with binaries
- Bundle sapling-spend.params and sapling-output.params in Linux release
2026-03-11 01:38:59 -05:00
1bcfcd1c73 ui: add idle delay combo to mining tab
Add inline combo box (30s/1m/2m/5m/10m) next to the idle mining
toggle so users can choose how long to wait before idle mining starts.
2026-03-11 01:38:48 -05:00
6bb35bd3e1 ui: reorganize settings page with collapsible sections
- Rename APPEARANCE section to THEME & LANGUAGE
- Move font scale slider out of effects into main section
- Collapse visual effects into "Advanced Effects..." toggle
- Collapse wallet tools into "Tools & Actions..." toggle
- Remove redundant Tools & Actions divider/section from wallet card
- Add i18n strings: theme_language, advanced_effects, tools_actions
2026-03-11 01:38:40 -05:00
2c5a658ea5 feat: Full UI internationalization, pool hashrate stats, and layout caching
- Replace all hardcoded English strings with TR() translation keys across
  every tab, dialog, and component (~20 UI files)
- Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with
  complete translations (~37k lines added)
- Improve i18n loader with exe-relative path fallback and English base
  fallback for missing keys
- Add pool-side hashrate polling via pool stats API in xmrig_manager
- Introduce Layout::beginFrame() per-frame caching and refresh balance
  layout config only on schema generation change
- Offload daemon output parsing to worker thread
- Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
2026-03-11 00:40:50 -05:00
f416ff3d09 Add mine-when-idle, default banlist, and console parsing improvements
Mine-when-idle:
- Auto-start/stop mining based on system idle time detection
- Platform::getSystemIdleSeconds() via XScreenSaver (Linux) / GetLastInputInfo (Win)
- Settings: mine_when_idle toggle + configurable delay (30s–10m)
- Settings page UI with checkbox and delay combo

Console tab:
- Shell-like argument parsing with quote and JSON bracket support
- Pass JSON objects/arrays directly as RPC params
- Fix selection indices when lines are evicted from buffer

Connection & status bar:
- Reduce RPC connect timeout to 1s for localhost fast-fail
- Fast retry timer on daemon startup and external daemon detection
- Show pool mining hashrate in status bar; sidebar badge reflects pool state

UI polish:
- Add logo to About card in settings; expose logo dimensions on App
- Header title offset-y support; adjust content-area margins
- Fix banned peers row cursor position (rawRowPosB.x)

Branding:
- Update copyright to "DragonX Developers" in RC and About section
- Replace logo/icon assets with updated versions

Misc:
- setup.sh: checkout dragonx branch before pulling
- Remove stale prebuilt-binaries/xmrig/.gitkeep
2026-03-07 13:42:31 -06:00
b3a0ce29ed fix: Windows identity, async address creation, mining UI, and chart artifacts
Windows identity:
- Add VERSIONINFO resource (.rc) with ObsidianDragon file description
- Embed application manifest for DPI awareness and shell identity
- Patch libwinpthread/libpthread to remove competing VERSIONINFO
- Set AppUserModelID and HWND property store to override Task Manager cache
- Link patched pthread libs to eliminate "POSIX WinThreads" description

Address creation (+New button):
- Move z_getnewaddress/getnewaddress off UI thread to async worker
- Inject new address into state immediately for instant UI selection
- Trigger background refresh for balance updates

Mining tab:
- Add pool mining dropdown with saved URLs/workers and bookmarks
- Add solo mining log panel from daemon output with chart/log toggle
- Fix toggle button cursor (render after InputTextMultiline)
- Auto-restart miner on pool config change
- Migrate default pool URL to include stratum port

Transactions:
- Sort pending (0-conf) transactions to top of history
- Fall back to timereceived when timestamp is missing

Shutdown:
- Replace blocking sleep_for calls with 100ms polling loops
- Check shutting_down_ flag throughout daemon restart/bootstrap flows
- Reduce daemon stop timeout from 30s to 10s

Other:
- Fix market chart fill artifact (single concave polygon vs per-segment quads)
- Add bootstrap checksum verification state display
- Rename daemon client identifier to ObsidianDragon
2026-03-05 22:43:27 -06:00
e2265b0bdf improve diagnostics, security UX, and network tab refresh
Diagnostics & logging:
- add verbose logging system (VERBOSE_LOGF) with toggle in Settings
- forward app-level log messages to Console tab for in-UI visibility
- add detailed connection attempt logging (attempt #, daemon state,
  config paths, auth failures, port owner identification)
- detect HTTP 401 auth failures and show actionable error messages
- identify port owner process (PID + name) on both Linux and Windows
- demote noisy acrylic/shader traces from DEBUG_LOGF to VERBOSE_LOGF
- persist verbose_logging preference in settings.json
- link iphlpapi on Windows for GetExtendedTcpTable

Security & encryption:
- update local encryption state immediately after encryptwallet RPC
  so Settings reflects the change before daemon restarts
- show notifications for encrypt success/failure and PIN skip
- use dedicated RPC client for z_importwallet during decrypt flow
  to avoid blocking main rpc_ curl_mutex (which starved peer/tx refresh)
- force full state refresh (addresses, transactions, peers) after
  successful wallet import

Network tab:
- redesign peers refresh button as glass-panel with icon + label,
  matching the mining button style
- add spinning arc animation while peer data is loading
  (peer_refresh_in_progress_ atomic flag set/cleared in refreshPeerInfo)
- prevent double-click spam during refresh
- add refresh-button size to ui.toml

Other:
- use fast_rpc_ for rescan polling to avoid blocking on main rpc_
- enable DRAGONX_DEBUG in all build configs (was debug-only)
- setup.sh: pull latest xmrig-hac when repo already exists
2026-03-05 05:26:04 -06:00
9368b945e0 fix text shifting in status bar from font scale changes 2026-03-05 01:29:03 -06:00
eda19ef6d5 improved font scaling text and window adjustment, added ctrl + scroll hotkey for font scaling 2026-03-05 01:22:20 -06:00
7d48936f30 refresh network info instantly when switching to network tab 2026-03-04 15:16:32 -06:00
bc2ef4ffaf feat: RPC caching, background decrypt import, fast-lane peers, mining fix
RPC client:
- Add call() overload with per-call timeout parameter
- z_exportwallet uses 300s, z_importwallet uses 1200s timeout

Decrypt wallet (app_security.cpp, app.cpp):
- Show per-step and overall elapsed timers during decrypt flow
- Reduce dialog to 5 steps; close before key import begins
- Run z_importwallet on detached background thread
- Add pulsing "Importing keys..." status bar indicator
- Report success/failure via notifications instead of dialog

RPC caching (app_network.cpp, app.h):
- Cache z_viewtransaction results in viewtx_cache_ across refresh cycles
- Skip RPC calls for already-cached txids (biggest perf win)
- Build confirmed_tx_cache_ for deeply-confirmed transactions
- Clear all caches on disconnect
- Remove unused refreshTransactions() dead code

Peers (app_network.cpp, peers_tab.cpp):
- Route refreshPeerInfo() through fast_worker_ to avoid head-of-line blocking
- Replace footer "Refresh Peers" button with ICON_MD_REFRESH in toggle header
- Refresh button triggers both peer list and full blockchain data refresh

Mining (mining_tab.cpp):
- Allow pool mining toggle when blockchain is not synced
- Pool mining only needs xmrig, not local daemon sync
2026-03-04 15:12:24 -06:00
0da1657b12 Rename hush→dragonx across wallet codebase
- Rename RESOURCE_HUSHD/HUSH_CLI/HUSH_TX to RESOURCE_DRAGONXD/DRAGONX_CLI/DRAGONX_TX
- Remove unused .bat resource constants (DRAGONXD_BAT, DRAGONX_CLI_BAT)
- Update INCBIN symbols: g_hushd_exe → g_dragonxd_exe, etc.
- Update daemon search paths, removing hush-arrakis-chain fallbacks
- Update process detection (Windows findProcessByName, Linux /proc/comm, macOS pgrep)
- Update build.sh: embed dragonxd.exe/dragonx-cli.exe/dragonx-tx.exe
- Overhaul setup.sh: fix binary names, release paths, add -j passthrough
- Update getDaemonPath/needsDaemonExtraction/hasDaemonAvailable for new names
2026-03-04 03:17:32 -06:00
0d30ebc8e8 setup script improvements, automatically clone xmrig-hac and build for multiple platforms 2026-03-03 01:47:44 -06:00
c778e03f37 update links 2026-03-03 01:20:03 -06:00
0a3f68f1eb change release output names 2026-02-28 15:28:40 -06:00
1a5c4e8744 feat: blockchain rescan via daemon restart + status bar progress
- Fix z_importwallet to use full path instead of filename only
- Add rescanBlockchain() method that restarts daemon with -rescan flag
- Track rescan progress via daemon output parsing and getrescaninfo RPC
- Display rescan progress in status bar with animated indicator when starting
- Improve dark theme card contrast: lighter surface-variant, tinted borders, stronger rim-light
2026-02-28 15:06:35 -06:00
22 changed files with 630 additions and 1189 deletions

1
.gitignore vendored
View File

@@ -37,7 +37,6 @@ asmap.dat
/memory
/todo.md
/.github/
/ObsidianDragon-agent/
# macOS
.DS_Store

View File

@@ -3,19 +3,8 @@
# Released under the GPLv3
cmake_minimum_required(VERSION 3.20)
# macOS: set deployment target and universal architectures BEFORE project()
# so they propagate to all targets, including FetchContent dependencies (SDL3, etc.)
if(APPLE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum macOS version" FORCE)
# Build universal binary (Apple Silicon + Intel) unless the user explicitly set architectures
if(NOT DEFINED CMAKE_OSX_ARCHITECTURES OR CMAKE_OSX_ARCHITECTURES STREQUAL "")
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "macOS architectures" FORCE)
endif()
endif()
project(ObsidianDragon
VERSION 1.1.2
VERSION 1.1.1
LANGUAGES C CXX
DESCRIPTION "DragonX Cryptocurrency Wallet"
)

View File

@@ -197,14 +197,7 @@ bundle_linux_daemon() {
# ═══════════════════════════════════════════════════════════════════════════════
build_dev() {
header "Dev Build ($(uname -s) / $BUILD_TYPE)"
# Use platform-appropriate build directory
if [[ "$(uname -s)" == "Darwin" ]]; then
local bd="$SCRIPT_DIR/build/mac"
export MACOSX_DEPLOYMENT_TARGET="11.0"
else
local bd="$SCRIPT_DIR/build/linux"
fi
local bd="$SCRIPT_DIR/build/linux"
if $CLEAN; then
info "Cleaning $bd ..."; rm -rf "$bd"
@@ -739,9 +732,7 @@ build_release_mac() {
fi
info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)"
else
# Native macOS: build universal binary (arm64 + x86_64)
MAC_ARCH="universal"
export MACOSX_DEPLOYMENT_TARGET="11.0"
MAC_ARCH=$(uname -m)
fi
header "Release: macOS ($MAC_ARCH$(${IS_CROSS} && echo ' — cross-compile'))"
@@ -820,31 +811,12 @@ TOOLCHAIN
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"}
else
# Build libsodium as universal if needed
local need_sodium=false
if [[ ! -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]] && \
[[ ! -f "$SCRIPT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then
need_sodium=true
elif [[ -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]]; then
# Rebuild if existing lib is not universal (single-arch won't link)
if ! lipo -info "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" 2>/dev/null | grep -q "arm64.*x86_64\|x86_64.*arm64"; then
info "Existing libsodium is not universal — rebuilding ..."
rm -rf "$SCRIPT_DIR/libs/libsodium"
need_sodium=true
fi
fi
if $need_sodium; then
info "Building libsodium (universal) ..."
"$SCRIPT_DIR/scripts/fetch-libsodium.sh"
fi
info "Configuring (native universal arm64+x86_64) ..."
info "Configuring (native) ..."
cmake "$SCRIPT_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
fi
info "Building with $JOBS jobs ..."
@@ -864,11 +836,6 @@ TOOLCHAIN
else
info "Stripping ..."
strip bin/ObsidianDragon
# Verify universal binary
if command -v lipo &>/dev/null; then
info "Architecture info:"
lipo -info bin/ObsidianDragon
fi
fi
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"

View File

@@ -50,20 +50,12 @@ if [[ ! -f "$TARBALL" ]]; then
curl -fSL -o "$TARBALL" "$SODIUM_URL"
fi
# Verify checksum (sha256sum on Linux, shasum on macOS)
if command -v sha256sum &>/dev/null; then
echo "$SODIUM_SHA256 $TARBALL" | sha256sum -c - || {
echo "[fetch-libsodium] ERROR: SHA256 mismatch! Removing corrupted download."
rm -f "$TARBALL"
exit 1
}
elif command -v shasum &>/dev/null; then
echo "$SODIUM_SHA256 $TARBALL" | shasum -a 256 -c - || {
echo "[fetch-libsodium] ERROR: SHA256 mismatch! Removing corrupted download."
rm -f "$TARBALL"
exit 1
}
fi
# Verify checksum
echo "$SODIUM_SHA256 $TARBALL" | sha256sum -c - || {
echo "[fetch-libsodium] ERROR: SHA256 mismatch! Removing corrupted download."
rm -f "$TARBALL"
exit 1
}
# ── Extract ─────────────────────────────────────────────────────────────────
if [[ ! -d "$SRC_DIR" ]]; then
@@ -123,69 +115,6 @@ case "$TARGET" in
;;
esac
# ── Native macOS: build universal binary (arm64 + x86_64) ───────────────────
IS_MACOS_NATIVE=false
if [[ "$TARGET" == "native" && "$(uname -s)" == "Darwin" ]]; then
IS_MACOS_NATIVE=true
fi
if $IS_MACOS_NATIVE; then
echo "[fetch-libsodium] Building universal (arm64 + x86_64) for macOS..."
export MACOSX_DEPLOYMENT_TARGET="11.0"
INSTALL_ARM64="$PROJECT_DIR/libs/libsodium-arm64"
INSTALL_X86_64="$PROJECT_DIR/libs/libsodium-x86_64"
for ARCH in arm64 x86_64; do
echo "[fetch-libsodium] Building for $ARCH..."
cd "$SRC_DIR"
make clean 2>/dev/null || true
make distclean 2>/dev/null || true
if [[ "$ARCH" == "arm64" ]]; then
ARCH_INSTALL="$INSTALL_ARM64"
HOST_TRIPLE="aarch64-apple-darwin"
else
ARCH_INSTALL="$INSTALL_X86_64"
HOST_TRIPLE="x86_64-apple-darwin"
fi
ARCH_CFLAGS="-arch $ARCH -mmacosx-version-min=11.0"
./configure \
--prefix="$ARCH_INSTALL" \
--disable-shared \
--enable-static \
--with-pic \
--host="$HOST_TRIPLE" \
CFLAGS="$ARCH_CFLAGS" \
LDFLAGS="-arch $ARCH" \
> /dev/null
make -j"$(sysctl -n hw.ncpu 2>/dev/null || echo 4)" > /dev/null 2>&1
make install > /dev/null
done
# Merge with lipo
echo "[fetch-libsodium] Creating universal binary with lipo..."
mkdir -p "$INSTALL_DIR/lib" "$INSTALL_DIR/include"
lipo -create \
"$INSTALL_ARM64/lib/libsodium.a" \
"$INSTALL_X86_64/lib/libsodium.a" \
-output "$INSTALL_DIR/lib/libsodium.a"
cp -R "$INSTALL_ARM64/include/"* "$INSTALL_DIR/include/"
# Clean up per-arch builds
rm -rf "$INSTALL_ARM64" "$INSTALL_X86_64"
cd "$PROJECT_DIR"
rm -rf "$SRC_DIR"
rm -f "$TARBALL"
echo "[fetch-libsodium] Done (universal): $INSTALL_DIR/lib/libsodium.a"
lipo -info "$INSTALL_DIR/lib/libsodium.a"
exit 0
fi
echo "[fetch-libsodium] Configuring for target: $TARGET ..."
./configure "${CONFIGURE_ARGS[@]}" > /dev/null

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// app.cpp — Main application: init, shutdown, ImGui render loop, NavPage
// dispatch, dialog rendering, and frame-level state management.
#include "app.h"
#include "config/version.h"
@@ -343,10 +340,7 @@ void App::update()
}
// Update timers
core_timer_ += io.DeltaTime;
address_timer_ += io.DeltaTime;
transaction_timer_ += io.DeltaTime;
peer_timer_ += io.DeltaTime;
refresh_timer_ += io.DeltaTime;
price_timer_ += io.DeltaTime;
fast_refresh_timer_ += io.DeltaTime;
tx_age_timer_ += io.DeltaTime;
@@ -594,37 +588,20 @@ void App::update()
transactions_dirty_ = true;
addresses_dirty_ = true;
last_tx_block_height_ = -1;
core_timer_ = active_core_interval_;
transaction_timer_ = active_tx_interval_;
address_timer_ = active_addr_interval_;
refresh_timer_ = REFRESH_INTERVAL;
}
};
});
}
// Per-category refresh with tab-aware intervals
// Regular refresh every 5 seconds
// Skip when wallet is locked — same reason as above.
if (state_.connected && !state_.isLocked()) {
if (core_timer_ >= active_core_interval_) {
core_timer_ = 0.0f;
refreshCoreData();
}
if (transaction_timer_ >= active_tx_interval_) {
transaction_timer_ = 0.0f;
refreshTransactionData();
}
if (address_timer_ >= active_addr_interval_) {
address_timer_ = 0.0f;
refreshAddressData();
}
if (peer_timer_ >= active_peer_interval_) {
peer_timer_ = 0.0f;
refreshEncryptionState();
}
} else if (core_timer_ >= active_core_interval_) {
core_timer_ = 0.0f;
if (!connection_in_progress_ &&
wizard_phase_ == WizardPhase::None) {
if (refresh_timer_ >= REFRESH_INTERVAL) {
refresh_timer_ = 0.0f;
if (state_.connected && !state_.isLocked()) {
refreshData();
} else if (!connection_in_progress_ &&
wizard_phase_ == WizardPhase::None) {
tryConnect();
}
}
@@ -639,7 +616,7 @@ void App::update()
// Keyboard shortcut: Ctrl+, to open Settings page
if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Comma)) {
setCurrentPage(ui::NavPage::Settings);
current_page_ = ui::NavPage::Settings;
}
// Keyboard shortcut: Ctrl+Left/Right to cycle themes
@@ -1131,10 +1108,9 @@ void App::render()
case ui::NavPage::Console:
// Use fast-lane worker for console commands to avoid head-of-line
// blocking behind the consolidated refreshData() batch.
// Fall back to main rpc/worker if fast-lane hasn't connected yet.
console_tab_.render(embedded_daemon_.get(),
(fast_rpc_ && fast_rpc_->isConnected()) ? fast_rpc_.get() : rpc_.get(),
(fast_rpc_ && fast_rpc_->isConnected() && fast_worker_) ? fast_worker_.get() : worker_.get(),
fast_rpc_ ? fast_rpc_.get() : rpc_.get(),
fast_worker_ ? fast_worker_.get() : worker_.get(),
xmrig_manager_.get());
break;
case ui::NavPage::Settings:
@@ -1906,11 +1882,7 @@ void App::renderAntivirusHelpDialog()
void App::refreshNow()
{
// Trigger immediate refresh on all categories
core_timer_ = active_core_interval_;
transaction_timer_ = active_tx_interval_;
address_timer_ = active_addr_interval_;
peer_timer_ = active_peer_interval_;
refresh_timer_ = REFRESH_INTERVAL; // Trigger immediate refresh
transactions_dirty_ = true; // Force transaction list update
addresses_dirty_ = true; // Force address/balance update
last_tx_block_height_ = -1; // Reset tx cache
@@ -1933,7 +1905,7 @@ void App::handlePaymentURI(const std::string& uri)
pending_label_ = payment.label;
// Switch to Send page
setCurrentPage(ui::NavPage::Send);
current_page_ = ui::NavPage::Send;
// Notify user
std::string msg = "Payment request loaded";
@@ -1960,7 +1932,7 @@ void App::setCurrentTab(int tab) {
ui::NavPage::Settings, // 9 = Settings
};
if (tab >= 0 && tab < static_cast<int>(sizeof(kTabMap)/sizeof(kTabMap[0])))
setCurrentPage(kTabMap[tab]);
current_page_ = kTabMap[tab];
}
bool App::startEmbeddedDaemon()

View File

@@ -173,9 +173,6 @@ public:
// Pool mining (xmrig)
void startPoolMining(int threads);
void stopPoolMining();
int getXmrigRequestedThreads() const {
return xmrig_manager_ ? xmrig_manager_->getRequestedThreads() : 0;
}
// Mine-when-idle state query
bool isIdleMiningActive() const { return idle_mining_active_; }
@@ -221,19 +218,13 @@ public:
void refreshPeerInfo();
void refreshMarketData();
/// @brief Per-category refresh intervals, adjusted by active tab
struct RefreshIntervals {
float core; // balance + sync status
float transactions; // tx list + enrichment
float addresses; // address lists + balances
float peers; // peer info (0 = disabled)
};
/// @brief Get recommended refresh intervals for a given page
static RefreshIntervals getIntervalsForPage(ui::NavPage page);
// UI navigation
void setCurrentPage(ui::NavPage page);
void setCurrentPage(ui::NavPage page) {
if (page != current_page_) {
current_page_ = page;
if (page == ui::NavPage::Peers) refreshPeerInfo();
}
}
ui::NavPage getCurrentPage() const { return current_page_; }
// Dialog triggers (used by settings page to open modal dialogs)
@@ -368,6 +359,7 @@ private:
// Shutdown state
std::atomic<bool> shutting_down_{false};
std::atomic<bool> shutdown_complete_{false};
std::atomic<bool> refresh_in_progress_{false};
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
std::string shutdown_status_;
std::thread shutdown_thread_;
@@ -443,33 +435,16 @@ private:
std::string pending_memo_;
std::string pending_label_;
// Per-category timers (in seconds since last refresh)
float core_timer_ = 0.0f; // balance + sync status
float address_timer_ = 0.0f; // address lists
float transaction_timer_ = 0.0f; // transaction list
float peer_timer_ = 0.0f; // peer info
// Timers (in seconds since last update)
float refresh_timer_ = 0.0f;
float price_timer_ = 0.0f;
float fast_refresh_timer_ = 0.0f; // For mining stats
// Default refresh intervals (seconds)
static constexpr float CORE_INTERVAL_DEFAULT = 5.0f;
static constexpr float ADDRESS_INTERVAL_DEFAULT = 15.0f;
static constexpr float TX_INTERVAL_DEFAULT = 10.0f;
static constexpr float PEER_INTERVAL_DEFAULT = 10.0f;
// Refresh intervals (seconds)
static constexpr float REFRESH_INTERVAL = 5.0f;
static constexpr float PRICE_INTERVAL = 60.0f;
static constexpr float FAST_REFRESH_INTERVAL = 1.0f;
// Active intervals — adjusted by tab priority via applyRefreshPolicy()
float active_core_interval_ = CORE_INTERVAL_DEFAULT;
float active_tx_interval_ = TX_INTERVAL_DEFAULT;
float active_addr_interval_ = ADDRESS_INTERVAL_DEFAULT;
float active_peer_interval_ = PEER_INTERVAL_DEFAULT;
// Per-category refresh guards (prevent worker queue pileup)
std::atomic<bool> core_refresh_in_progress_{false};
std::atomic<bool> address_refresh_in_progress_{false};
std::atomic<bool> tx_refresh_in_progress_{false};
// Mining refresh guard (prevents worker queue pileup)
std::atomic<bool> mining_refresh_in_progress_{false};
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
@@ -626,17 +601,11 @@ private:
void applyDefaultBanlist();
// Private methods - data refresh
void refreshData(); // Orchestrator: dispatches per-category refreshes
void refreshCoreData(); // Balance + blockchain info (can use fast_worker_)
void refreshAddressData(); // Address lists + balances
void refreshTransactionData(); // Transaction list + z_viewtransaction enrichment
void refreshEncryptionState(); // Wallet encryption/lock state
void refreshBalance(); // Legacy: balance-only refresh (used by specific callers)
void refreshAddresses(); // Legacy: standalone address refresh
void refreshData();
void refreshBalance();
void refreshAddresses();
void refreshPrice();
void refreshWalletEncryptionState();
void applyRefreshPolicy(ui::NavPage page);
bool shouldRefreshTransactions() const;
void checkAutoLock();
void checkIdleMining();
};

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@
#include "ui/schema/ui_schema.h"
#include "ui/theme.h"
#include "ui/effects/imgui_acrylic.h"
#include "ui/windows/mining_tab.h"
#include "util/platform.h"
#include "util/secure_vault.h"
#include "util/perf_log.h"
@@ -440,25 +439,12 @@ void App::checkIdleMining() {
return;
}
// Skip idle mining adjustments while thread benchmark is running
if (ui::IsMiningBenchmarkActive()) return;
int idleSec = util::Platform::getSystemIdleSeconds();
int delay = settings_->getMineIdleDelay();
bool isPool = settings_->getPoolMode();
bool threadScaling = settings_->getIdleThreadScaling();
int maxThreads = std::max(1, (int)std::thread::hardware_concurrency());
// GPU-aware idle detection: if enabled, treat GPU utilization >= 10%
// as "user active" (e.g. watching a video). Disabled = unrestricted
// mode that only looks at keyboard/mouse input.
bool gpuBusy = false;
if (settings_->getIdleGpuAware()) {
int gpuUtil = util::Platform::getGpuUtilization();
gpuBusy = (gpuUtil >= 10);
}
bool systemIdle = (idleSec >= delay) && !gpuBusy;
// Check if mining is already running (manually started by user)
bool miningActive = isPool
? (xmrig_manager_ && xmrig_manager_->isRunning())
@@ -475,7 +461,7 @@ void App::checkIdleMining() {
if (activeThreads <= 0) activeThreads = std::max(1, maxThreads / 2);
if (idleThreads <= 0) idleThreads = maxThreads;
if (systemIdle) {
if (idleSec >= delay) {
// System is idle — scale up to idle thread count
if (!idle_scaled_to_idle_) {
idle_scaled_to_idle_ = true;
@@ -488,7 +474,7 @@ void App::checkIdleMining() {
DEBUG_LOGF("[App] Idle thread scaling: %d -> %d threads (idle)\n", activeThreads, idleThreads);
}
} else {
// User is active (or GPU busy) — scale down to active thread count
// User is active — scale down to active thread count
if (idle_scaled_to_idle_) {
idle_scaled_to_idle_ = false;
if (isPool) {
@@ -498,26 +484,11 @@ void App::checkIdleMining() {
startMining(activeThreads);
}
DEBUG_LOGF("[App] Idle thread scaling: %d -> %d threads (active)\n", idleThreads, activeThreads);
} else {
// Mining just started while user is active — ensure active
// thread count is applied (grid selection may differ).
int currentThreads = isPool
? xmrig_manager_->getStats().threads_active
: state_.mining.genproclimit;
if (currentThreads > 0 && currentThreads != activeThreads) {
if (isPool) {
stopPoolMining();
startPoolMining(activeThreads);
} else {
startMining(activeThreads);
}
DEBUG_LOGF("[App] Idle thread scaling: initial %d -> %d threads (active)\n", currentThreads, activeThreads);
}
}
}
} else {
// --- Start/Stop mode (original behavior) ---
if (systemIdle) {
if (idleSec >= delay) {
// System is idle — start mining if not already running
if (!miningActive && !idle_mining_active_ && !mining_toggle_in_progress_.load()) {
// For solo mining, need daemon connected and synced

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// settings.cpp — JSON settings persistence. Loads/saves user preferences
// to ~/.config/ObsidianDragon/settings.json (Linux/macOS) or %APPDATA% (Windows).
#include "settings.h"
#include "version.h"
@@ -156,7 +153,6 @@ bool Settings::load(const std::string& path)
if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get<bool>();
if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get<int>();
if (j.contains("idle_threads_idle")) idle_threads_idle_ = j["idle_threads_idle"].get<int>();
if (j.contains("idle_gpu_aware")) idle_gpu_aware_ = j["idle_gpu_aware"].get<bool>();
if (j.contains("saved_pool_urls") && j["saved_pool_urls"].is_array()) {
saved_pool_urls_.clear();
for (const auto& u : j["saved_pool_urls"])
@@ -254,7 +250,6 @@ bool Settings::save(const std::string& path)
j["idle_thread_scaling"] = idle_thread_scaling_;
j["idle_threads_active"] = idle_threads_active_;
j["idle_threads_idle"] = idle_threads_idle_;
j["idle_gpu_aware"] = idle_gpu_aware_;
j["saved_pool_urls"] = json::array();
for (const auto& u : saved_pool_urls_)
j["saved_pool_urls"].push_back(u);

View File

@@ -221,8 +221,6 @@ public:
void setIdleThreadsActive(int v) { idle_threads_active_ = std::max(0, v); }
int getIdleThreadsIdle() const { return idle_threads_idle_; }
void setIdleThreadsIdle(int v) { idle_threads_idle_ = std::max(0, v); }
bool getIdleGpuAware() const { return idle_gpu_aware_; }
void setIdleGpuAware(bool v) { idle_gpu_aware_ = v; }
// Saved pool URLs (user-managed favorites dropdown)
const std::vector<std::string>& getSavedPoolUrls() const { return saved_pool_urls_; }
@@ -319,7 +317,6 @@ private:
bool idle_thread_scaling_ = false; // scale threads instead of start/stop
int idle_threads_active_ = 0; // threads when user active (0 = auto)
int idle_threads_idle_ = 0; // threads when idle (0 = auto = all)
bool idle_gpu_aware_ = true; // treat GPU activity as non-idle
std::vector<std::string> saved_pool_urls_; // user-saved pool URL favorites
std::vector<std::string> saved_pool_workers_; // user-saved worker address favorites

View File

@@ -7,10 +7,10 @@
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
#define DRAGONX_VERSION "1.1.2"
#define DRAGONX_VERSION "1.1.1"
#define DRAGONX_VERSION_MAJOR 1
#define DRAGONX_VERSION_MINOR 1
#define DRAGONX_VERSION_PATCH 2
#define DRAGONX_VERSION_PATCH 1
#define DRAGONX_APP_NAME "ObsidianDragon"
#define DRAGONX_ORG_NAME "Hush"

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// embedded_daemon.cpp — Manages the dragonxd child process lifecycle:
// binary discovery, process spawn, stdout/stderr monitoring, crash recovery.
#include "embedded_daemon.h"
#include "../config/version.h"

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// xmrig_manager.cpp — Pool mining process management via xmrig-hac.
// Spawns xmrig, monitors via HTTP API, tracks hashrate and shares.
#include "xmrig_manager.h"
#include "../resources/embedded_resources.h"

View File

@@ -88,10 +88,6 @@ public:
const PoolStats& getStats() const { return stats_; }
const std::string& getLastError() const { return last_error_; }
/// Thread count requested at start() — available immediately, unlike
/// PoolStats::threads_active which requires an API response.
int getRequestedThreads() const { return threads_; }
/**
* @brief Get last N lines of xmrig stdout (thread-safe snapshot).
*/

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// rpc_client.cpp — JSON-RPC client over HTTPS using libcurl.
// All calls are blocking; run on RPCWorker threads, never on main thread.
#include "rpc_client.h"
#include "../config/version.h"

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// rpc_worker.cpp — Background work queue. Executes WorkFn on its own thread,
// returns MainCb callbacks drained each frame on the main thread.
#include "rpc_worker.h"
#include <cstdio>

View File

@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// console_tab.cpp — Interactive RPC console with command history,
// tab completion, daemon log display, and color-coded output.
#include "console_tab.h"
#include "../material/colors.h"

View File

@@ -43,81 +43,6 @@ static int s_drag_anchor_thread = 0; // thread# where drag started
// Earnings filter: 0 = All, 1 = Solo, 2 = Pool
static int s_earnings_filter = 0;
// Thread benchmark state
struct ThreadBenchmark {
enum class Phase { Idle, Starting, WarmingUp, Measuring, Advancing, Done };
Phase phase = Phase::Idle;
std::vector<int> candidates;
int current_index = 0;
struct Result {
int threads;
double hashrate;
};
std::vector<Result> results;
float phase_timer = 0.0f;
static constexpr float WARMUP_SECS = 20.0f;
static constexpr float MEASURE_SECS = 10.0f;
double best_sample = 0.0; // best hashrate_10s during current measurement window
int sample_count = 0; // number of non-zero hashrate samples collected
int optimal_threads = 0;
double optimal_hashrate = 0.0;
bool was_pool_running = false;
int prev_threads = 0;
void reset() {
phase = Phase::Idle;
candidates.clear();
current_index = 0;
results.clear();
phase_timer = 0.0f;
best_sample = 0.0;
sample_count = 0;
optimal_threads = 0;
optimal_hashrate = 0.0;
was_pool_running = false;
prev_threads = 0;
}
void buildCandidates(int max_threads) {
candidates.clear();
if (max_threads <= 16) {
for (int t = 1; t <= max_threads; t++)
candidates.push_back(t);
} else {
// Sample: 1, then every ceil(max/10) step, always including max
int step = std::max(1, (max_threads + 9) / 10);
for (int t = 1; t <= max_threads; t += step)
candidates.push_back(t);
if (candidates.back() != max_threads)
candidates.push_back(max_threads);
}
}
float totalEstimatedSecs() const {
return (float)candidates.size() * (WARMUP_SECS + MEASURE_SECS);
}
float elapsedSecs() const {
float completed = (float)current_index * (WARMUP_SECS + MEASURE_SECS);
return completed + phase_timer;
}
float progress() const {
float total = totalEstimatedSecs();
return (total > 0.0f) ? std::min(1.0f, elapsedSecs() / total) : 0.0f;
}
};
static ThreadBenchmark s_benchmark;
bool IsMiningBenchmarkActive() {
return s_benchmark.phase != ThreadBenchmark::Phase::Idle &&
s_benchmark.phase != ThreadBenchmark::Phase::Done;
}
// Pool mode state
static bool s_pool_mode = false;
static char s_pool_url[256] = "pool.dragonx.is:3433";
@@ -237,16 +162,9 @@ static void RenderMiningTabContent(App* app)
}
// Sync thread grid with actual count when idle thread scaling adjusts threads
// Skip during benchmark — the benchmark controls thread counts directly
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active && !IsMiningBenchmarkActive()) {
if (s_pool_mode && state.pool_mining.xmrig_running) {
// Use the requested thread count (available immediately) rather
// than threads_active from the xmrig API which lags during restarts.
int reqThreads = app->getXmrigRequestedThreads();
if (reqThreads > 0)
s_selected_threads = std::min(reqThreads, max_threads);
else if (state.pool_mining.threads_active > 0)
s_selected_threads = std::min(state.pool_mining.threads_active, max_threads);
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active) {
if (s_pool_mode && state.pool_mining.xmrig_running && state.pool_mining.threads_active > 0) {
s_selected_threads = std::min(state.pool_mining.threads_active, max_threads);
} else if (mining.generate && mining.genproclimit > 0) {
s_selected_threads = std::min(mining.genproclimit, max_threads);
}
@@ -321,84 +239,6 @@ static void RenderMiningTabContent(App* app)
? state.pool_mining.xmrig_running
: (mining.generate || state.pool_mining.xmrig_running);
// ================================================================
// Thread Benchmark state machine — runs pool mining at each candidate
// thread count to find the optimal setting for this CPU.
// ================================================================
if (s_benchmark.phase != ThreadBenchmark::Phase::Idle &&
s_benchmark.phase != ThreadBenchmark::Phase::Done) {
float dt = ImGui::GetIO().DeltaTime;
s_benchmark.phase_timer += dt;
switch (s_benchmark.phase) {
case ThreadBenchmark::Phase::Starting:
// Start pool mining at current candidate
if (s_benchmark.current_index < (int)s_benchmark.candidates.size()) {
int t = s_benchmark.candidates[s_benchmark.current_index];
app->stopPoolMining();
app->startPoolMining(t);
s_benchmark.phase = ThreadBenchmark::Phase::WarmingUp;
s_benchmark.phase_timer = 0.0f;
s_benchmark.best_sample = 0.0;
s_benchmark.sample_count = 0;
} else {
s_benchmark.phase = ThreadBenchmark::Phase::Done;
}
break;
case ThreadBenchmark::Phase::WarmingUp:
if (s_benchmark.phase_timer >= ThreadBenchmark::WARMUP_SECS) {
s_benchmark.phase = ThreadBenchmark::Phase::Measuring;
s_benchmark.phase_timer = 0.0f;
s_benchmark.best_sample = 0.0;
s_benchmark.sample_count = 0;
}
break;
case ThreadBenchmark::Phase::Measuring:
// Sample hashrate during measurement window
if (state.pool_mining.hashrate_10s > 0.0) {
s_benchmark.sample_count++;
if (state.pool_mining.hashrate_10s > s_benchmark.best_sample)
s_benchmark.best_sample = state.pool_mining.hashrate_10s;
}
if (s_benchmark.phase_timer >= ThreadBenchmark::MEASURE_SECS) {
int t = s_benchmark.candidates[s_benchmark.current_index];
s_benchmark.results.push_back({t, s_benchmark.best_sample});
if (s_benchmark.best_sample > s_benchmark.optimal_hashrate) {
s_benchmark.optimal_hashrate = s_benchmark.best_sample;
s_benchmark.optimal_threads = t;
}
s_benchmark.phase = ThreadBenchmark::Phase::Advancing;
s_benchmark.phase_timer = 0.0f;
}
break;
case ThreadBenchmark::Phase::Advancing:
app->stopPoolMining();
s_benchmark.current_index++;
if (s_benchmark.current_index < (int)s_benchmark.candidates.size()) {
s_benchmark.phase = ThreadBenchmark::Phase::Starting;
} else {
// Done — apply optimal thread count
s_benchmark.phase = ThreadBenchmark::Phase::Done;
if (s_benchmark.optimal_threads > 0) {
s_selected_threads = s_benchmark.optimal_threads;
app->settings()->setPoolThreads(s_selected_threads);
app->settings()->save();
}
// Restart mining if it was running before, using optimal count
if (s_benchmark.was_pool_running && s_benchmark.optimal_threads > 0) {
app->startPoolMining(s_benchmark.optimal_threads);
}
}
break;
default:
break;
}
}
// ================================================================
// Proportional section budget — ensures all content fits without
// scrolling at the minimum window size (1024×775).
@@ -1096,41 +936,6 @@ static void RenderMiningTabContent(App* app)
idleRightEdge = sBtnX - 4.0f * dp;
}
// GPU-aware idle toggle (to the left, when idle is on)
// When ON (default): GPU utilization >= 10% counts as "not idle"
// When OFF: unrestricted mode, only keyboard/mouse input matters
if (idleOn) {
bool gpuAware = app->settings()->getIdleGpuAware();
const char* gpuIcon = gpuAware ? ICON_MD_MONITOR : ICON_MD_MONITOR;
float gBtnX = idleRightEdge - btnSz;
float gBtnY = btnY;
if (gpuAware) {
dl->AddRectFilled(ImVec2(gBtnX, gBtnY), ImVec2(gBtnX + btnSz, gBtnY + btnSz),
WithAlpha(Primary(), 40), btnSz * 0.5f);
}
ImVec2 gIcoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, gpuIcon);
ImU32 gIcoCol = gpuAware ? Primary() : OnSurfaceDisabled();
dl->AddText(icoFont, icoFont->LegacySize,
ImVec2(gBtnX + (btnSz - gIcoSz.x) * 0.5f, gBtnY + (btnSz - gIcoSz.y) * 0.5f),
gIcoCol, gpuIcon);
ImGui::SetCursorScreenPos(ImVec2(gBtnX, gBtnY));
ImGui::InvisibleButton("##IdleGpuAware", ImVec2(btnSz, btnSz));
if (ImGui::IsItemClicked()) {
app->settings()->setIdleGpuAware(!gpuAware);
app->settings()->save();
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("%s", gpuAware
? TR("mining_idle_gpu_on_tooltip")
: TR("mining_idle_gpu_off_tooltip"));
}
idleRightEdge = gBtnX - 4.0f * dp;
}
// Idle delay combo (to the left, when idle is enabled and NOT in thread scaling mode)
if (idleOn && !threadScaling) {
struct DelayOption { int seconds; const char* label; };
@@ -1271,145 +1076,6 @@ static void RenderMiningTabContent(App* app)
ImGui::SetCursorScreenPos(savedCur);
}
// --- Thread Benchmark button / progress (left of idle toggle) ---
{
ImVec2 benchSavedCur = ImGui::GetCursorScreenPos();
bool benchRunning = s_benchmark.phase != ThreadBenchmark::Phase::Idle &&
s_benchmark.phase != ThreadBenchmark::Phase::Done;
bool benchDone = s_benchmark.phase == ThreadBenchmark::Phase::Done;
ImFont* icoFont = Type().iconSmall();
if (benchRunning) {
// Show progress bar + current test info
float barW = std::min(180.0f * hs, idleRightEdge - (cardMin.x + pad) - 10.0f * dp);
float barH = 4.0f * dp;
float barX = idleRightEdge - barW;
float barY = curY + headerH - barH - 2.0f * dp;
// Progress bar track
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
WithAlpha(OnSurface(), 30), barH * 0.5f);
// Progress bar fill
float pct = s_benchmark.progress();
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW * pct, barY + barH),
Primary(), barH * 0.5f);
// Status text above bar
int ct = s_benchmark.current_index < (int)s_benchmark.candidates.size()
? s_benchmark.candidates[s_benchmark.current_index] : 0;
snprintf(buf, sizeof(buf), "%s %d/%d (%dt)",
TR("mining_benchmark_testing"),
s_benchmark.current_index + 1,
(int)s_benchmark.candidates.size(), ct);
ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(barX + (barW - txtSz.x) * 0.5f, barY - txtSz.y - 2.0f * dp),
OnSurfaceMedium(), buf);
// Cancel button (small X)
float cancelSz = icoFont->LegacySize + 4.0f * dp;
float cancelX = barX - cancelSz - 4.0f * dp;
float cancelY = curY + (headerH - cancelSz) * 0.5f;
ImGui::SetCursorScreenPos(ImVec2(cancelX, cancelY));
ImGui::InvisibleButton("##BenchCancel", ImVec2(cancelSz, cancelSz));
if (ImGui::IsItemClicked()) {
app->stopPoolMining();
if (s_benchmark.was_pool_running)
app->startPoolMining(s_benchmark.prev_threads);
s_benchmark.reset();
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("%s", TR("mining_benchmark_cancel"));
}
const char* cancelIcon = ICON_MD_CLOSE;
ImVec2 cIcoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, cancelIcon);
dl->AddText(icoFont, icoFont->LegacySize,
ImVec2(cancelX + (cancelSz - cIcoSz.x) * 0.5f,
cancelY + (cancelSz - cIcoSz.y) * 0.5f),
OnSurfaceMedium(), cancelIcon);
idleRightEdge = cancelX - 4.0f * dp;
} else if (benchDone && s_benchmark.optimal_threads > 0) {
// Show result briefly, then reset on next click
snprintf(buf, sizeof(buf), "%s: %dt (%.1f H/s)",
TR("mining_benchmark_result"),
s_benchmark.optimal_threads, s_benchmark.optimal_hashrate);
ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
float txtX = idleRightEdge - txtSz.x;
dl->AddText(capFont, capFont->LegacySize,
ImVec2(txtX, curY + (headerH - txtSz.y) * 0.5f),
WithAlpha(Success(), 220), buf);
// Dismiss button
float dismissSz = icoFont->LegacySize + 4.0f * dp;
float dismissX = txtX - dismissSz - 4.0f * dp;
float dismissY = curY + (headerH - dismissSz) * 0.5f;
ImGui::SetCursorScreenPos(ImVec2(dismissX, dismissY));
ImGui::InvisibleButton("##BenchDismiss", ImVec2(dismissSz, dismissSz));
if (ImGui::IsItemClicked())
s_benchmark.reset();
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("%s", TR("mining_benchmark_dismiss"));
}
const char* okIcon = ICON_MD_CHECK;
ImVec2 oIcoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, okIcon);
dl->AddText(icoFont, icoFont->LegacySize,
ImVec2(dismissX + (dismissSz - oIcoSz.x) * 0.5f,
dismissY + (dismissSz - oIcoSz.y) * 0.5f),
WithAlpha(Success(), 200), okIcon);
idleRightEdge = dismissX - 4.0f * dp;
} else if (s_pool_mode) {
// Show benchmark button (only in pool mode)
float btnSz = icoFont->LegacySize + 8.0f * dp;
float btnX = idleRightEdge - btnSz;
float btnY = curY + (headerH - btnSz) * 0.5f;
ImGui::SetCursorScreenPos(ImVec2(btnX, btnY));
ImGui::InvisibleButton("##BenchStart", ImVec2(btnSz, btnSz));
bool benchHovered = ImGui::IsItemHovered();
bool benchClicked = ImGui::IsItemClicked();
// Hover highlight
if (benchHovered) {
dl->AddRectFilled(ImVec2(btnX, btnY), ImVec2(btnX + btnSz, btnY + btnSz),
StateHover(), btnSz * 0.5f);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("%s", TR("mining_benchmark_tooltip"));
}
const char* benchIcon = ICON_MD_SPEED;
ImVec2 bIcoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, benchIcon);
dl->AddText(icoFont, icoFont->LegacySize,
ImVec2(btnX + (btnSz - bIcoSz.x) * 0.5f,
btnY + (btnSz - bIcoSz.y) * 0.5f),
OnSurfaceMedium(), benchIcon);
if (benchClicked) {
// Require a wallet address for pool mining
std::string worker(s_pool_worker);
if (!worker.empty()) {
s_benchmark.reset();
s_benchmark.was_pool_running = state.pool_mining.xmrig_running;
s_benchmark.prev_threads = s_selected_threads;
s_benchmark.buildCandidates(max_threads);
s_benchmark.phase = ThreadBenchmark::Phase::Starting;
// Stop any active solo mining first
if (mining.generate)
app->stopMining();
}
}
idleRightEdge = btnX - 4.0f * dp;
}
ImGui::SetCursorScreenPos(benchSavedCur);
}
// Active mining indicator (left of idle toggle)
if (mining.generate) {
float pulse = effects::isLowSpecMode()
@@ -1449,13 +1115,11 @@ static void RenderMiningTabContent(App* app)
}
// Show pointer cursor when hovering the thread grid
bool benchActive = s_benchmark.phase != ThreadBenchmark::Phase::Idle &&
s_benchmark.phase != ThreadBenchmark::Phase::Done;
if (hovered_thread > 0 && !benchActive)
if (hovered_thread > 0)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
// Drag-to-select logic (disabled during benchmark)
if (!benchActive && ImGui::IsMouseClicked(0) && hovered_thread > 0) {
// Drag-to-select logic
if (ImGui::IsMouseClicked(0) && hovered_thread > 0) {
// Begin drag
s_drag_active = true;
s_drag_anchor_thread = hovered_thread;

View File

@@ -15,11 +15,5 @@ namespace ui {
*/
void RenderMiningTab(App* app);
/**
* @brief Returns true when the thread benchmark is actively running.
* Used by idle mining to avoid interfering with measurements.
*/
bool IsMiningBenchmarkActive();
} // namespace ui
} // namespace dragonx

View File

@@ -845,8 +845,6 @@ void I18n::loadBuiltinEnglish()
strings_["mining_idle_on_tooltip"] = "Disable idle mining";
strings_["mining_idle_scale_on_tooltip"] = "Thread scaling: ON\nClick to switch to start/stop mode";
strings_["mining_idle_scale_off_tooltip"] = "Start/stop mode: ON\nClick to switch to thread scaling mode";
strings_["mining_idle_gpu_on_tooltip"] = "GPU-aware: ON\nGPU activity (video, games) prevents idle mining\nClick for unrestricted mode";
strings_["mining_idle_gpu_off_tooltip"] = "Unrestricted: ON\nOnly keyboard/mouse input determines idle state\nClick to enable GPU-aware detection";
strings_["mining_idle_threads_active_tooltip"] = "Threads when user is active";
strings_["mining_idle_threads_idle_tooltip"] = "Threads when system is idle";
strings_["mining_local_hashrate"] = "Local Hashrate";
@@ -868,11 +866,6 @@ void I18n::loadBuiltinEnglish()
strings_["mining_recent_payouts"] = "RECENT POOL PAYOUTS";
strings_["mining_remove"] = "Remove";
strings_["mining_reset_defaults"] = "Reset Defaults";
strings_["mining_benchmark_tooltip"] = "Find optimal thread count for this CPU";
strings_["mining_benchmark_testing"] = "Testing";
strings_["mining_benchmark_cancel"] = "Cancel benchmark";
strings_["mining_benchmark_result"] = "Optimal";
strings_["mining_benchmark_dismiss"] = "Dismiss";
strings_["mining_save_payout_address"] = "Save payout address";
strings_["mining_save_pool_url"] = "Save pool URL";
strings_["mining_saved_addresses"] = "Saved Addresses:";

View File

@@ -688,106 +688,5 @@ int Platform::getSystemIdleSeconds()
#endif
}
// ============================================================================
// GPU utilization detection
// ============================================================================
int Platform::getGpuUtilization()
{
#ifdef _WIN32
// Windows: read GPU utilization via SetupAPI / D3DKMT
// Not all GPUs expose this; return -1 if unavailable.
// Use a popen fallback: nvidia-smi for NVIDIA, or return -1.
static bool s_tried_nvidia = false;
static bool s_has_nvidia = false;
if (!s_tried_nvidia) {
s_tried_nvidia = true;
FILE* f = _popen("where nvidia-smi 2>nul", "r");
if (f) {
char buf[256];
s_has_nvidia = (fgets(buf, sizeof(buf), f) != nullptr);
_pclose(f);
}
}
if (s_has_nvidia) {
FILE* f = _popen("nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>nul", "r");
if (f) {
char buf[64];
int util = -1;
if (fgets(buf, sizeof(buf), f)) {
util = atoi(buf);
if (util < 0 || util > 100) util = -1;
}
_pclose(f);
return util;
}
}
return -1;
#elif defined(__APPLE__)
return -1;
#else
// Linux: try multiple GPU sysfs paths
// AMD: /sys/class/drm/card*/device/gpu_busy_percent
{
// Try card0 through card3
char path[128];
for (int i = 0; i < 4; i++) {
snprintf(path, sizeof(path), "/sys/class/drm/card%d/device/gpu_busy_percent", i);
std::ifstream ifs(path);
if (ifs.is_open()) {
int val = -1;
ifs >> val;
if (val >= 0 && val <= 100)
return val;
}
}
}
// NVIDIA: nvidia-smi (binary may exist even without sysfs)
{
static bool s_tried = false;
static bool s_has_nvidia_smi = false;
if (!s_tried) {
s_tried = true;
FILE* f = popen("which nvidia-smi 2>/dev/null", "r");
if (f) {
char buf[256];
s_has_nvidia_smi = (fgets(buf, sizeof(buf), f) != nullptr);
pclose(f);
}
}
if (s_has_nvidia_smi) {
FILE* f = popen("nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null", "r");
if (f) {
char buf[64];
int util = -1;
if (fgets(buf, sizeof(buf), f)) {
util = atoi(buf);
if (util < 0 || util > 100) util = -1;
}
pclose(f);
return util;
}
}
}
// Intel: compare current vs max freq as a rough proxy
{
std::ifstream curF("/sys/class/drm/card0/gt_cur_freq_mhz");
std::ifstream maxF("/sys/class/drm/card0/gt_max_freq_mhz");
if (curF.is_open() && maxF.is_open()) {
int cur = 0, mx = 0;
curF >> cur;
maxF >> mx;
if (mx > 0)
return std::min(100, (cur * 100) / mx);
}
}
return -1;
#endif
}
} // namespace util
} // namespace dragonx

View File

@@ -131,14 +131,6 @@ public:
* @return Seconds since last user input, or 0 on failure
*/
static int getSystemIdleSeconds();
/**
* @brief Get GPU utilization percentage (0100).
* Linux: reads sysfs for AMD, /proc for NVIDIA.
* Windows: queries PDH GPU engine counters.
* @return GPU busy percent, or -1 if unavailable.
*/
static int getGpuUtilization();
};
/**