13 Commits

Author SHA1 Message Date
dan_s
96c27bb949 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
dan_s
cc617dd5be 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
dan_s
653a90de62 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
dan_s
4b16a2a2c4 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
dan_s
c51d3dafff fix text shifting in status bar from font scale changes 2026-03-05 01:29:03 -06:00
dan_s
68c2a59d09 improved font scaling text and window adjustment, added ctrl + scroll hotkey for font scaling 2026-03-05 01:22:20 -06:00
dan_s
45a2ccd9f3 refresh network info instantly when switching to network tab 2026-03-04 15:16:32 -06:00
dan_s
0ca1caf148 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
dan_s
7fb1f1de9d 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
dan_s
386cc857b0 setup script improvements, automatically clone xmrig-hac and build for multiple platforms 2026-03-03 01:47:44 -06:00
dan_s
3e6136983a update links 2026-03-03 01:20:03 -06:00
dan_s
2c1862aed3 change release output names 2026-02-28 15:28:40 -06:00
dan_s
4b815fc9d1 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
18 changed files with 244 additions and 510 deletions

View File

@@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.20)
project(ObsidianDragon
VERSION 1.0.1
VERSION 1.0.0
LANGUAGES C CXX
DESCRIPTION "DragonX Cryptocurrency Wallet"
)
@@ -373,14 +373,12 @@ endif()
# Windows application icon + VERSIONINFO (.rc -> .res -> linked into .exe)
if(WIN32)
set(OBSIDIAN_ICO_PATH "${CMAKE_SOURCE_DIR}/res/img/ObsidianDragon.ico")
# Generate manifest with version from project()
configure_file(
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest.in
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest
@ONLY
)
set(OBSIDIAN_MANIFEST_PATH "${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest")
# Generate .rc with version from project()
# Version numbers for the VERSIONINFO resource block
set(DRAGONX_VER_MAJOR 1)
set(DRAGONX_VER_MINOR 0)
set(DRAGONX_VER_PATCH 0)
set(DRAGONX_VERSION "1.0.0")
configure_file(
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.rc
${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc
@@ -389,13 +387,6 @@ if(WIN32)
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
endif()
# Generate version.h from the single project(VERSION ...) declaration
configure_file(
${CMAKE_SOURCE_DIR}/src/config/version.h.in
${CMAKE_SOURCE_DIR}/src/config/version.h
@ONLY
)
# Generate INCBIN font embedding source with absolute paths to .ttf files
configure_file(
${CMAKE_SOURCE_DIR}/src/embedded/embedded_fonts.cpp.in

113
build.sh
View File

@@ -5,7 +5,7 @@
#
# Usage:
# ./build.sh # Dev build (Linux, debug-friendly)
# ./build.sh --linux-release # Linux release (zip + AppImage)
# ./build.sh --linux-release # Linux release + AppImage
# ./build.sh --win-release # Windows cross-compile (mingw-w64)
# ./build.sh --mac-release # macOS .app bundle + DMG
# ./build.sh --linux-release --win-release # Multiple targets
@@ -51,7 +51,7 @@ DragonX Wallet — Unified Build Script
Usage: $0 [options]
Targets (at least one required, or none for dev build):
--linux-release Linux release (zip + AppImage) -> release/linux/
--linux-release Linux release build + AppImage -> release/linux/
--win-release Windows cross-compile (mingw-w64) -> release/windows/
--mac-release macOS .app bundle + DMG -> release/mac/
@@ -71,7 +71,7 @@ Cross-compiling from Linux:
Examples:
$0 # Quick dev build (Linux)
$0 --linux-release # Linux release (zip + AppImage)
$0 --linux-release # Linux release + AppImage
$0 --win-release # Windows cross-compile
$0 --mac-release # macOS bundle + DMG (native or osxcross)
$0 --clean --linux-release --win-release # Clean + both
@@ -218,7 +218,7 @@ build_dev() {
}
# ═══════════════════════════════════════════════════════════════════════════════
# RELEASE: LINUX — build + strip + bundle daemon + zip + AppImage
# RELEASE: LINUX — build + strip + bundle daemon + AppImage
# ═══════════════════════════════════════════════════════════════════════════════
build_release_linux() {
header "Release: Linux x86_64"
@@ -249,41 +249,17 @@ build_release_linux() {
# ── Bundle daemon ────────────────────────────────────────────────────────
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
# ── Bundle Sapling params ────────────────────────────────────────────────
SAPLING_SPEND="" SAPLING_OUTPUT=""
find_sapling_params && {
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
info "Bundled Sapling params"
} || warn "Sapling params not found — not bundled"
# ── Package: release/linux/ ──────────────────────────────────────────────
rm -rf "$out"
mkdir -p "$out"
local DIST="ObsidianDragon-Linux-x64"
local dist_dir="$out/$DIST"
mkdir -p "$dist_dir"
cp bin/ObsidianDragon "$out/"
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$out/"
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$out/"
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$out/"
cp -r bin/res "$out/" 2>/dev/null || true
cp bin/ObsidianDragon "$dist_dir/"
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
# Bundle xmrig for mining support
local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
[[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
cp -r bin/res "$dist_dir/" 2>/dev/null || true
# ── Zip ──────────────────────────────────────────────────────────────────
if command -v zip &>/dev/null; then
(cd "$out" && zip -r "$DIST.zip" "$DIST")
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
fi
rm -rf "$dist_dir"
# ── AppImage (single-file) ───────────────────────────────────────────────
# ── AppImage ─────────────────────────────────────────────────────────────
info "Creating AppImage ..."
local APPDIR="$bd/AppDir"
rm -rf "$APPDIR"
@@ -295,16 +271,10 @@ build_release_linux() {
cp bin/ObsidianDragon "$APPDIR/usr/bin/"
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
# Daemon data files must be alongside the daemon binary (usr/bin/)
# because dragonxd searches relative to its own directory.
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
# Bundle xmrig for mining support
local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
# Daemon inside AppImage
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/share/ObsidianDragon/"
# Desktop entry
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK'
@@ -378,11 +348,32 @@ APPRUN
local ARCH
ARCH=$(uname -m)
local IMG_NAME="ObsidianDragon-${ARCH}.AppImage"
cd "$bd"
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "ObsidianDragon-${ARCH}.AppImage" 2>/dev/null && {
cp "ObsidianDragon-${ARCH}.AppImage" "$out/ObsidianDragon.AppImage"
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "$IMG_NAME" 2>/dev/null && {
cp "$IMG_NAME" "$out/"
# Rename to match Windows convention: ObsidianDragon.AppImage
mv "$out/$IMG_NAME" "$out/ObsidianDragon.AppImage"
info "AppImage: $out/ObsidianDragon.AppImage ($(du -h "$out/ObsidianDragon.AppImage" | cut -f1))"
} || warn "AppImage creation failed — binaries zip still in release/linux/"
} || warn "AppImage creation failed (appimagetool issue) — raw binary still in release/linux/"
# Clean up: keep only AppImage + zip in release/linux/
if [[ -f "$out/ObsidianDragon.AppImage" ]]; then
# AppImage succeeded — remove everything except AppImage
find "$out" -maxdepth 1 -type f ! -name 'ObsidianDragon.AppImage' -delete
rm -rf "$out/res" 2>/dev/null
# Create zip matching Windows naming convention
local DIST="ObsidianDragon-Linux-x64"
local dist_dir="$out/$DIST"
mkdir -p "$dist_dir"
cp "$out/ObsidianDragon.AppImage" "$dist_dir/"
if command -v zip &>/dev/null; then
(cd "$out" && zip -r "$DIST.zip" "$DIST")
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
fi
rm -rf "$dist_dir"
fi
info "Linux release artifacts: $out/"
ls -lh "$out/"
@@ -610,25 +601,31 @@ HDR
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
done
# Bundle Sapling params + asmap for the zip distribution
# (The single-file exe has these embedded via INCBIN, but the zip
# needs them on disk so the daemon can find them in its work dir.)
for f in sapling-spend.params sapling-output.params asmap.dat; do
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
done
cat > "$dist_dir/README.txt" <<'README'
DragonX Wallet - Windows Edition
================================
cp -r bin/res "$dist_dir/" 2>/dev/null || true
SINGLE-FILE DISTRIBUTION
========================
This wallet is a true single-file executable with all resources embedded.
Just run ObsidianDragon.exe — no additional files needed!
# ── Single-file exe (all resources embedded) ────────────────────────────
On first run, the wallet will automatically extract:
- Sapling parameters to %APPDATA%\ZcashParams\
- asmap.dat to %APPDATA%\Hush\DRAGONX\
For support: https://git.dragonx.is/dragonx/ObsidianDragon
README
# Copy single-file exe to release dir
cp bin/ObsidianDragon.exe "$out/"
info "Single-file exe: $out/ObsidianDragon.exe ($(du -h "$out/ObsidianDragon.exe" | cut -f1))"
# ── Zip ──────────────────────────────────────────────────────────────────
if command -v zip &>/dev/null; then
(cd "$out" && zip -r "$DIST.zip" "$DIST")
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
# Clean up: keep .zip + single-file exe, remove loose directory
rm -rf "$dist_dir"
fi
rm -rf "$dist_dir"
info "Windows release artifacts: $out/"
ls -lh "$out/"

View File

@@ -5,7 +5,7 @@
<assemblyIdentity
type="win32"
name="DragonX.ObsidianDragon.Wallet"
version="1.0.1.0"
version="1.0.0.0"
processorArchitecture="amd64"
/>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- Application identity —————————————————————————————— -->
<assemblyIdentity
type="win32"
name="DragonX.ObsidianDragon.Wallet"
version="@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0"
processorArchitecture="amd64"
/>
<description>ObsidianDragon Wallet</description>
<!-- Common Controls v6 (themed buttons, etc.) ————————— -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<!-- DPI awareness (Per-Monitor V2) ————————————————————— -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
<!-- Supported OS declarations (Windows 7 → 11) ———————— -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 / 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -19,8 +19,8 @@
#include <winver.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
FILEVERSION @DRAGONX_VER_MAJOR@,@DRAGONX_VER_MINOR@,@DRAGONX_VER_PATCH@,0
PRODUCTVERSION @DRAGONX_VER_MAJOR@,@DRAGONX_VER_MINOR@,@DRAGONX_VER_PATCH@,0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0x0L
FILEOS VOS_NT_WINDOWS32
@@ -33,12 +33,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DragonX Developers\0"
VALUE "FileDescription", "ObsidianDragon Wallet\0"
VALUE "FileVersion", "@PROJECT_VERSION@\0"
VALUE "FileVersion", "@DRAGONX_VERSION@\0"
VALUE "InternalName", "ObsidianDragon\0"
VALUE "LegalCopyright", "Copyright 2024-2026 DragonX Developers. GPLv3.\0"
VALUE "OriginalFilename", "ObsidianDragon.exe\0"
VALUE "ProductName", "ObsidianDragon\0"
VALUE "ProductVersion", "@PROJECT_VERSION@\0"
VALUE "ProductVersion", "@DRAGONX_VERSION@\0"
END
END
BLOCK "VarFileInfo"

View File

@@ -627,13 +627,8 @@ XMRIG_SRC="$PROJECT_DIR/external/xmrig-hac"
XMRIG_PREBUILT="$PROJECT_DIR/prebuilt-binaries/xmrig-hac"
# Clean previous prebuilt xmrig binaries so we always rebuild
# Only clean the binary for the platform(s) we are actually building,
# otherwise a plain ./setup.sh deletes xmrig.exe without rebuilding it.
if ! $CHECK_ONLY; then
rm -f "$XMRIG_PREBUILT/xmrig" 2>/dev/null || true
if $SETUP_WIN; then
rm -f "$XMRIG_PREBUILT/xmrig.exe" 2>/dev/null || true
fi
rm -f "$XMRIG_PREBUILT/xmrig" "$XMRIG_PREBUILT/xmrig.exe" 2>/dev/null || true
fi
# Helper: clone xmrig-hac if not present

View File

@@ -1855,34 +1855,10 @@ bool App::startEmbeddedDaemon()
}
DEBUG_LOGF("Sapling params extracted successfully\n");
} else {
// Fallback: check for params bundled alongside the executable
// (zip distributions bundle sapling-*.params next to the binary)
namespace fs = std::filesystem;
std::string exe_dir = util::Platform::getExecutableDirectory();
std::string daemon_dir = resources::getDaemonDirectory();
const char* paramFiles[] = { "sapling-spend.params", "sapling-output.params", "asmap.dat" };
bool copied = false;
if (!exe_dir.empty()) {
std::error_code ec;
fs::create_directories(daemon_dir, ec);
for (const char* name : paramFiles) {
fs::path src = fs::path(exe_dir) / name;
fs::path dst = fs::path(daemon_dir) / name;
if (fs::exists(src) && !fs::exists(dst)) {
DEBUG_LOGF("Copying bundled %s from exe dir to %s\n", name, daemon_dir.c_str());
fs::copy_file(src, dst, ec);
if (!ec) copied = true;
}
}
}
if (copied && rpc::Connection::verifySaplingParams()) {
DEBUG_LOGF("Sapling params copied from exe directory successfully\n");
} else {
daemon_status_ = "Sapling parameters not found. They should be in: " + rpc::Connection::getSaplingParamsDir();
DEBUG_LOGF("Sapling params not found and no embedded resources available!\n");
DEBUG_LOGF("Expected location: %s\n", rpc::Connection::getSaplingParamsDir().c_str());
return false;
}
daemon_status_ = "Sapling parameters not found. They should be in: " + rpc::Connection::getSaplingParamsDir();
DEBUG_LOGF("Sapling params not found and no embedded resources available!\n");
DEBUG_LOGF("Expected location: %s\n", rpc::Connection::getSaplingParamsDir().c_str());
return false;
}
}

View File

@@ -4,13 +4,10 @@
#pragma once
// !! 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.0.1"
#define DRAGONX_VERSION "1.0.0"
#define DRAGONX_VERSION_MAJOR 1
#define DRAGONX_VERSION_MINOR 0
#define DRAGONX_VERSION_PATCH 1
#define DRAGONX_VERSION_PATCH 0
#define DRAGONX_APP_NAME "ObsidianDragon"
#define DRAGONX_ORG_NAME "Hush"

View File

@@ -1,31 +0,0 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// !! 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 "@PROJECT_VERSION@"
#define DRAGONX_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define DRAGONX_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define DRAGONX_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define DRAGONX_APP_NAME "ObsidianDragon"
#define DRAGONX_ORG_NAME "Hush"
// Default RPC settings
#define DRAGONX_DEFAULT_RPC_HOST "127.0.0.1"
#define DRAGONX_DEFAULT_RPC_PORT "21769"
// Coin parameters
#define DRAGONX_TICKER "DRGX"
#define DRAGONX_COIN_NAME "DragonX"
#define DRAGONX_URI_SCHEME "drgx"
#define DRAGONX_ZATOSHI_PER_COIN 100000000
#define DRAGONX_DEFAULT_FEE 0.0001
// Config file names
#define DRAGONX_CONF_FILENAME "DRAGONX.conf"
#define DRAGONX_WALLET_FILENAME "wallet.dat"

View File

@@ -466,18 +466,6 @@ bool XmrigManager::startProcess(const std::string& xmrigPath, const std::string&
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
// Detach from controlling terminal's stdin to prevent SIGTTIN/SIGTTOU
// when running in a new process group (setpgid below).
int devnull = open("/dev/null", O_RDONLY);
if (devnull >= 0) {
dup2(devnull, STDIN_FILENO);
close(devnull);
}
// Ignore job-control signals that a background process group may receive
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
// New process group so we can kill the whole group
setpgid(0, 0);

View File

@@ -419,20 +419,12 @@ int main(int argc, char* argv[])
if (!g_single_instance.tryLock()) {
fprintf(stderr, "Another instance of ObsidianDragon is already running.\n");
DEBUG_LOGF("Please close the existing instance first.\n");
#ifdef _WIN32
MessageBoxW(nullptr, L"Another instance of ObsidianDragon is already running.\nPlease close it first.",
L"ObsidianDragon", MB_OK | MB_ICONINFORMATION);
#endif
return 1;
}
// Initialize SDL
if (!InitSDL()) {
fprintf(stderr, "Failed to initialize SDL!\n");
#ifdef _WIN32
MessageBoxW(nullptr, L"Failed to initialize SDL. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
#endif
return 1;
}
@@ -503,8 +495,6 @@ int main(int argc, char* argv[])
nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
if (!nativeHwnd) {
fprintf(stderr, "Failed to create native Win32 window (error %lu)\n", GetLastError());
MessageBoxW(nullptr, L"Failed to create window. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
SDL_Quit();
return 1;
}
@@ -544,8 +534,6 @@ int main(int argc, char* argv[])
SDL_DestroyProperties(createProps);
if (window == nullptr) {
fprintf(stderr, "Error: SDL_CreateWindowWithProperties(): %s\n", SDL_GetError());
MessageBoxW(nullptr, L"Failed to create SDL window. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
DestroyWindow(nativeHwnd);
SDL_Quit();
return 1;
@@ -572,8 +560,6 @@ int main(int argc, char* argv[])
dragonx::platform::DX11Context dx;
if (!dx.init(window)) {
fprintf(stderr, "Error: Failed to initialize DirectX 11 context\n");
MessageBoxW(nullptr, L"Failed to initialize DirectX 11.\nPlease ensure your graphics drivers are up to date.\n\nCheck the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
L"ObsidianDragon - Graphics Error", MB_OK | MB_ICONERROR);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
@@ -649,8 +635,6 @@ int main(int argc, char* argv[])
// Initialize ImGui with DX11 backend
if (!InitImGui(window, dx)) {
fprintf(stderr, "Failed to initialize ImGui!\n");
MessageBoxW(nullptr, L"Failed to initialize ImGui. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
Shutdown(window, dx);
return 1;
}
@@ -824,10 +808,6 @@ int main(int argc, char* argv[])
dragonx::App app;
if (!app.init()) {
fprintf(stderr, "Failed to initialize application!\n");
#ifdef _WIN32
MessageBoxW(nullptr, L"Failed to initialize application. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
#endif
#ifdef DRAGONX_USE_DX11
Shutdown(window, dx);
#else

View File

@@ -61,8 +61,13 @@ bool DX11Context::init(SDL_Window* window)
D3D_FEATURE_LEVEL_10_0,
};
UINT createDeviceFlags = 0;
#ifdef DRAGONX_DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Need BGRA support for DirectComposition
UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
createDeviceFlags |= D3D11_CREATE_DEVICE_BGRA_SUPPORT;
HRESULT hr = D3D11CreateDevice(
nullptr, // Default adapter

View File

@@ -509,18 +509,6 @@ std::string getXmrigPath()
return xmrigName;
}
// Check alongside the wallet executable (zip / AppImage distributions)
{
std::string exeDir = util::Platform::getExecutableDirectory();
if (!exeDir.empty()) {
std::string exeDirPath = exeDir + "/" + xmrigName;
if (std::filesystem::exists(exeDirPath)) {
DEBUG_LOGF("[INFO] getXmrigPath: found alongside wallet at %s\n", exeDirPath.c_str());
return exeDirPath;
}
}
}
// Check development paths
#ifdef _WIN32
// Not applicable for cross-compiled Windows builds

View File

@@ -757,26 +757,15 @@ inline void ApplySmoothScroll(float speed = 12.0f)
s.current = actualY;
}
// Capture mouse wheel when the cursor is inside this child window.
// Use a direct position check instead of IsWindowHovered() which can
// return false when an ActiveId exists (focused input, held button) or
// when tooltip/popup windows from row hovers interfere with hover
// routing. Still skip when a real popup (combo dropdown, context menu)
// is open so the popup handles its own scrolling.
{
bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
ImVec2 mpos = ImGui::GetIO().MousePos;
ImVec2 wmin = win->Pos;
ImVec2 wmax = ImVec2(wmin.x + win->Size.x, wmin.y + win->Size.y);
bool inside = (mpos.x >= wmin.x && mpos.x < wmax.x &&
mpos.y >= wmin.y && mpos.y < wmax.y);
if (!popupOpen && inside) {
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f) {
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
s.target -= wheel * step;
s.target = ImClamp(s.target, 0.0f, scrollMaxY);
}
// Capture mouse wheel when hovered, but not when a popup (combo dropdown
// etc.) is open — let the popup handle its own scrolling exclusively.
bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
if (!popupOpen && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f) {
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
s.target -= wheel * step;
s.target = ImClamp(s.target, 0.0f, scrollMaxY);
}
}

View File

@@ -122,8 +122,6 @@ static bool sp_verbose_logging = false;
static std::set<std::string> sp_debug_categories;
static bool sp_debug_cats_dirty = false; // true when changed but daemon not yet restarted
static bool sp_debug_expanded = false; // collapsible card state
static bool sp_effects_expanded = false; // "Advanced Effects..." toggle
static bool sp_tools_expanded = false; // "Tools & Actions..." toggle
static bool sp_confirm_clear_ztx = false; // confirmation dialog for clearing z-tx history
// (APPEARANCE card now uses ChannelsSplit like all other cards)
@@ -379,11 +377,11 @@ void RenderSettingsPage(App* app) {
ImFont* sub1 = Type().subtitle1();
// ====================================================================
// THEME & LANGUAGE — card (draw-first approach; avoids ChannelsSplit
// which breaks BeginCombo popup rendering in some ImGui versions)
// APPEARANCE — card (draw-first approach; avoids ChannelsSplit which
// breaks BeginCombo popup rendering in some ImGui versions)
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language"));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("appearance"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
@@ -563,59 +561,9 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Font Scale slider (always visible) ---
// --- Visual Effects (checkboxes on one row, Quality+Blur paired) ---
{
ImGui::PushFont(body2);
ImGui::TextUnformatted(TR("font_scale"));
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW);
ImGui::SetNextItemWidth(fontSliderW);
sp_font_scale = Layout::userFontScale();
float prev_font_scale = sp_font_scale;
{
char fs_fmt[16];
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
ImGuiSliderFlags_AlwaysClamp);
}
sp_font_scale = std::max(1.0f, std::min(1.5f,
std::round(sp_font_scale * 20.0f) / 20.0f));
if (sp_font_scale != prev_font_scale)
Layout::setUserFontScaleVisual(sp_font_scale);
if (ImGui::IsItemDeactivatedAfterEdit()) {
Layout::setUserFontScale(sp_font_scale);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Advanced Effects... ---
{
const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
{
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
if (ImGui::Button("##EffectsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
sp_effects_expanded = !sp_effects_expanded;
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = body2;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
}
if (sp_effects_expanded) {
ImGui::PushFont(body2);
// Checkbox row: Low-spec | Console scanline | Theme effects | Gradient background
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
@@ -655,6 +603,7 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
// Simple background is lightweight — always interactive, even in low-spec mode
ImGui::SameLine(0, Layout::spacingLg());
if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &sp_gradient_background)) {
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
@@ -686,10 +635,12 @@ void RenderSettingsPage(App* app) {
float baseX = ImGui::GetCursorScreenPos().x;
float rightX = baseX + ctrlW + Layout::spacingLg();
// Acrylic label + slider (left column)
ImGui::TextUnformatted(TR("acrylic"));
float row1Y = ImGui::GetCursorScreenPos().y;
ImGui::SetNextItemWidth(ctrlW);
{
// Build display format: "Off" at zero, percentage otherwise
char blur_fmt[16];
if (sp_blur_amount < 0.01f)
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
@@ -697,6 +648,7 @@ void RenderSettingsPage(App* app) {
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
// Snap to off when dragged near 0%
if (sp_blur_amount > 0.0f && sp_blur_amount < 0.15f) sp_blur_amount = 0.0f;
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
@@ -706,6 +658,7 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
float afterRow1Y = ImGui::GetCursorScreenPos().y;
// Noise label + slider (right column, same row)
float lblH = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y;
ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y - lblH));
ImGui::TextUnformatted(TR("noise"));
@@ -725,8 +678,10 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
// Reset cursor to left column, past row 1
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow1Y));
// Row 2: UI Opacity + Window Opacity (labels above)
ImGui::TextUnformatted(TR("ui_opacity"));
float row2Y = ImGui::GetCursorScreenPos().y;
ImGui::SetNextItemWidth(ctrlW);
@@ -742,6 +697,7 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
float afterRow2Y = ImGui::GetCursorScreenPos().y;
// Window label + slider (right column, same row)
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y - lblH));
ImGui::TextUnformatted(TR("window_opacity"));
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y));
@@ -756,11 +712,12 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_window_opacity"));
// Reset cursor to left column, past row 2
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow2Y));
ImGui::EndDisabled(); // low-spec
ImGui::PopFont();
} // sp_effects_expanded
}
} else {
// ============================================================
// Narrow: stacked combos + 2-column effects (original layout)
@@ -853,62 +810,11 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Font Scale slider (always visible) ---
// --- Visual Effects (checkboxes + controls) ---
{
ImGui::PushFont(body2);
ImGui::TextUnformatted(TR("font_scale"));
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
availWidth - pad * 2);
ImGui::SetNextItemWidth(fontSliderW);
sp_font_scale = Layout::userFontScale();
float prev_font_scale = sp_font_scale;
{
char fs_fmt[16];
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
ImGuiSliderFlags_AlwaysClamp);
}
sp_font_scale = std::max(1.0f, std::min(1.5f,
std::round(sp_font_scale * 20.0f) / 20.0f));
if (sp_font_scale != prev_font_scale)
Layout::setUserFontScaleVisual(sp_font_scale);
if (ImGui::IsItemDeactivatedAfterEdit()) {
Layout::setUserFontScale(sp_font_scale);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Advanced Effects... ---
{
const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
{
float narrowContentW = availWidth - pad * 2;
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
if (ImGui::Button("##EffectsToggleN", ImVec2(narrowContentW, ImGui::GetFrameHeight()))) {
sp_effects_expanded = !sp_effects_expanded;
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = body2;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + narrowContentW - arrowW, textY), OnSurfaceMedium(), arrow);
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
}
if (sp_effects_expanded) {
ImGui::PushFont(body2);
// Checkbox row 1: Low-spec
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
effects::setLowSpecMode(sp_low_spec_mode);
if (sp_low_spec_mode) {
@@ -946,6 +852,7 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
// Simple background is lightweight — always interactive, even in low-spec mode
if (ImGui::Checkbox(TrId("settings_gradient_bg", "gradient_bg").c_str(), &sp_gradient_background)) {
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
saveSettingsPageState(app->settings());
@@ -954,6 +861,7 @@ void RenderSettingsPage(App* app) {
ImGui::BeginDisabled(sp_low_spec_mode);
// Checkbox row 2: Console scanline | Theme effects
if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) {
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
app->settings()->setScanlineEnabled(sp_scanline_enabled);
@@ -968,8 +876,9 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_theme_effects"));
// Acrylic blur slider (label above)
float ctrlW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
availWidth - pad * 2.0f);
contentW);
ImGui::TextUnformatted(TR("acrylic"));
ImGui::SetNextItemWidth(ctrlW);
{
@@ -988,6 +897,7 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
// Noise opacity slider (label above)
ImGui::TextUnformatted(TR("noise"));
ImGui::SetNextItemWidth(ctrlW);
{
@@ -1004,6 +914,7 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
// UI Opacity slider (label above)
ImGui::TextUnformatted(TR("ui_opacity"));
ImGui::SetNextItemWidth(ctrlW);
{
@@ -1017,6 +928,7 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
// Window Opacity slider (label above)
ImGui::TextUnformatted(TR("window_opacity"));
ImGui::SetNextItemWidth(ctrlW);
{
@@ -1031,7 +943,44 @@ void RenderSettingsPage(App* app) {
ImGui::EndDisabled(); // low-spec
ImGui::PopFont();
} // sp_effects_expanded
}
}
// ============================================================
// Font Scale slider (always enabled, not affected by low-spec)
// ============================================================
{
ImGui::PushFont(body2);
ImGui::Spacing();
ImGui::TextUnformatted(TR("font_scale"));
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
availWidth - pad * 2);
ImGui::SetNextItemWidth(fontSliderW);
// Sync from Layout so Alt+scroll hotkey changes are reflected
sp_font_scale = Layout::userFontScale();
float prev_font_scale = sp_font_scale;
{
char fs_fmt[16];
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
ImGuiSliderFlags_AlwaysClamp);
}
// Snap to 0.05 increments for consistent stepping.
// Visual scaling uses FontScaleMain (no atlas rebuild),
// atlas rebuild is deferred to slider release for crisp text.
sp_font_scale = std::max(1.0f, std::min(1.5f,
std::round(sp_font_scale * 20.0f) / 20.0f));
if (sp_font_scale != prev_font_scale) {
// While dragging: update layout scale without atlas rebuild
Layout::setUserFontScaleVisual(sp_font_scale);
}
if (ImGui::IsItemDeactivatedAfterEdit()) {
// On release: rebuild font atlas at final size
Layout::setUserFontScale(sp_font_scale);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
ImGui::PopFont();
}
// Bottom padding
@@ -1051,7 +1000,7 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// WALLET — card (privacy/daemon toggles + collapsible tools)
// WALLET — card (Keys, Backup, Tools, Maintenance, Node/RPC)
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
@@ -1064,6 +1013,56 @@ void RenderSettingsPage(App* app) {
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
float btnSpacing = Layout::spacingMd();
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
// Calculate button width that fits available space
// 6 buttons total: on wide screens 3+3, on narrow screens 2+2+2
int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
// Clamp to reasonable size
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
bw = std::max(minBtnW, bw);
// Row 1 — Tools & Actions
{
if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
AddressBookDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book"));
ImGui::SameLine(0, btnSpacing);
if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button")))
ValidateAddressDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate"));
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button")))
RequestPaymentDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment"));
if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button")))
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining"));
ImGui::SameLine(0, btnSpacing);
if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button")))
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge"));
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) {
sp_confirm_clear_ztx = true;
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx"));
}
// Thin divider
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
{
float divAlpha = S.drawElement("components.settings-page", "section-divider-alpha").opacity;
if (divAlpha <= 0.0f) divAlpha = 0.08f;
ImU32 baseDivCol = S.resolveColor("var(--status-divider)", IM_COL32(255, 255, 255, 20));
ImU32 divCol = material::ScaleAlpha(baseDivCol, divAlpha / 0.08f);
ImVec2 p = ImGui::GetCursorScreenPos();
dl->AddLine(ImVec2(p.x, p.y), ImVec2(p.x + contentW, p.y), divCol);
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
{
@@ -1112,94 +1111,47 @@ void RenderSettingsPage(App* app) {
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Tools & Actions... ---
// Mine when idle — checkbox + delay combo
{
const char* arrow = sp_tools_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
{
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
if (ImGui::Button("##ToolsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
sp_tools_expanded = !sp_tools_expanded;
if (ImGui::Checkbox(TrId("mine_when_idle", "mine_idle").c_str(), &sp_mine_when_idle)) {
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_mine_idle"));
if (sp_mine_when_idle) {
ImGui::SameLine(0, Layout::spacingMd());
ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(1, 1, 1, 0.5f), "%s", TR("settings_idle_after"));
ImGui::SameLine(0, Layout::spacingSm());
struct DelayOption { int seconds; const char* label; };
static const DelayOption delays[] = {
{30, "30s"}, {60, "1m"}, {120, "2m"}, {300, "5m"}, {600, "10m"}
};
const char* previewLabel = "2m";
for (const auto& d : delays) {
if (d.seconds == sp_mine_idle_delay) { previewLabel = d.label; break; }
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("tools_actions"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = body2;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
ImGui::SetNextItemWidth(schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f));
if (ImGui::BeginCombo("##IdleDelay", previewLabel, ImGuiComboFlags_NoArrowButton)) {
for (const auto& d : delays) {
bool selected = (d.seconds == sp_mine_idle_delay);
if (ImGui::Selectable(d.label, selected)) {
sp_mine_idle_delay = d.seconds;
saveSettingsPageState(app->settings());
}
if (selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_idle_delay"));
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
}
if (sp_tools_expanded) {
float btnSpacing = Layout::spacingMd();
int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
bw = std::max(minBtnW, bw);
if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
AddressBookDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book"));
ImGui::SameLine(0, btnSpacing);
if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button")))
ValidateAddressDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate"));
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button")))
RequestPaymentDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment"));
if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button")))
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining"));
ImGui::SameLine(0, btnSpacing);
if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button")))
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge"));
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) {
sp_confirm_clear_ztx = true;
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx"));
}
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// BACKUP & DATA — card
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("backup_data"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
// Bottom row — Keys & Data left-aligned, Setup Wizard right-aligned
{
const char* r1[] = {TR("settings_import_key"), TR("settings_export_key"), TR("settings_export_all"), TR("settings_backup"), TR("settings_export_csv")};
const char* t1[] = {
@@ -1213,12 +1165,13 @@ void RenderSettingsPage(App* app) {
float sp = Layout::spacingSm();
ImFont* btnFont = S.resolveFont("button");
// Measure natural widths
float btnPadX = btnPad * 2;
float naturalW = 0;
for (int i = 0; i < 5; i++)
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
float totalW = naturalW + wizW + sp * 6;
float totalW = naturalW + wizW + sp * 6; // 5 gaps between data btns + 1 gap before wizard
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);

View File

@@ -528,8 +528,7 @@ inline void SettingsScreen::renderAboutSection() {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - nameSize.x) * 0.5f);
Typography::instance().text(TypeStyle::H6, appName);
char version[64];
snprintf(version, sizeof(version), "Version %s-imgui", DRAGONX_VERSION);
const char* version = "Version 1.0.0-imgui";
ImVec2 versionSize = ImGui::CalcTextSize(version);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - versionSize.x) * 0.5f);
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), version);

View File

@@ -153,15 +153,10 @@ void RenderMiningTab(App* app)
s_pool_mode = app->settings()->getPoolMode();
strncpy(s_pool_url, app->settings()->getPoolUrl().c_str(), sizeof(s_pool_url) - 1);
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
s_pool_state_loaded = true;
}
// Default pool worker to user's first shielded address once addresses are available
{
static bool s_pool_worker_defaulted = false;
// If pool worker is empty or placeholder, default to user's first address
std::string workerStr(s_pool_worker);
if (!s_pool_worker_defaulted && !state.addresses.empty() &&
(workerStr.empty() || workerStr == "x")) {
if (workerStr.empty() || workerStr == "x") {
std::string defaultAddr;
for (const auto& addr : state.addresses) {
if (addr.type == "shielded" && !addr.address.empty()) {
@@ -180,10 +175,9 @@ void RenderMiningTab(App* app)
if (!defaultAddr.empty()) {
strncpy(s_pool_worker, defaultAddr.c_str(), sizeof(s_pool_worker) - 1);
s_pool_worker[sizeof(s_pool_worker) - 1] = '\0';
s_pool_settings_dirty = true;
}
s_pool_worker_defaulted = true;
}
s_pool_state_loaded = true;
}
// Persist pool settings when dirty and no field is active
@@ -872,42 +866,9 @@ void RenderMiningTab(App* app)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::SetTooltip("%s", idleOn ? TR("mining_idle_on_tooltip") : TR("mining_idle_off_tooltip"));
}
ImGui::SetCursorScreenPos(savedCur);
idleRightEdge = btnX - 4.0f * dp;
// Idle delay combo (to the left of the icon when enabled)
if (idleOn) {
struct DelayOption { int seconds; const char* label; };
static const DelayOption delays[] = {
{30, "30s"}, {60, "1m"}, {120, "2m"}, {300, "5m"}, {600, "10m"}
};
int curDelay = app->settings()->getMineIdleDelay();
const char* previewLabel = "2m";
for (const auto& d : delays) {
if (d.seconds == curDelay) { previewLabel = d.label; break; }
}
float comboW = schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f);
float comboX = idleRightEdge - comboW;
float comboY = curY + (headerH - ImGui::GetFrameHeight()) * 0.5f;
ImGui::SetCursorScreenPos(ImVec2(comboX, comboY));
ImGui::SetNextItemWidth(comboW);
if (ImGui::BeginCombo("##IdleDelay", previewLabel, ImGuiComboFlags_NoArrowButton)) {
for (const auto& d : delays) {
bool selected = (d.seconds == curDelay);
if (ImGui::Selectable(d.label, selected)) {
app->settings()->setMineIdleDelay(d.seconds);
app->settings()->save();
}
if (selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_idle_delay"));
idleRightEdge = comboX - 4.0f * dp;
}
ImGui::SetCursorScreenPos(savedCur);
}
// Active mining indicator (left of idle toggle)

View File

@@ -174,9 +174,6 @@ void I18n::loadBuiltinEnglish()
// Settings sections
strings_["appearance"] = "APPEARANCE";
strings_["theme_language"] = "THEME & LANGUAGE";
strings_["advanced_effects"] = "Advanced Effects...";
strings_["tools_actions"] = "Tools & Actions...";
strings_["wallet"] = "WALLET";
strings_["node_security"] = "NODE & SECURITY";
strings_["node"] = "NODE";