feat(lite): add ObsidianDragonLite build mode and gate full-node features
Add --lite build flow and ObsidianDragonLite target naming, hide full-node pages/features in lite mode, enforce pool-only mining in lite, and include chat port feasibility audit documentation.
This commit is contained in:
@@ -36,6 +36,16 @@ endif()
|
|||||||
# Options
|
# Options
|
||||||
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
||||||
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
||||||
|
option(DRAGONX_BUILD_LITE "Build ObsidianDragonLite variant without full-node features" OFF)
|
||||||
|
|
||||||
|
if(DRAGONX_BUILD_LITE)
|
||||||
|
set(DRAGONX_APP_NAME "ObsidianDragonLite")
|
||||||
|
set(DRAGONX_BINARY_NAME "ObsidianDragonLite")
|
||||||
|
set(DRAGONX_ENABLE_EMBEDDED_DAEMON OFF CACHE BOOL "Enable embedded dragonxd support" FORCE)
|
||||||
|
else()
|
||||||
|
set(DRAGONX_APP_NAME "ObsidianDragon")
|
||||||
|
set(DRAGONX_BINARY_NAME "ObsidianDragon")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
|
|
||||||
@@ -518,6 +528,8 @@ add_executable(ObsidianDragon
|
|||||||
${WIN_RC_FILE}
|
${WIN_RC_FILE}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(ObsidianDragon PROPERTIES OUTPUT_NAME "${DRAGONX_BINARY_NAME}")
|
||||||
|
|
||||||
target_include_directories(ObsidianDragon PRIVATE
|
target_include_directories(ObsidianDragon PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src
|
${CMAKE_SOURCE_DIR}/src
|
||||||
${CMAKE_SOURCE_DIR}/src/embedded
|
${CMAKE_SOURCE_DIR}/src/embedded
|
||||||
@@ -570,6 +582,8 @@ endif()
|
|||||||
# Compile definitions
|
# Compile definitions
|
||||||
target_compile_definitions(ObsidianDragon PRIVATE
|
target_compile_definitions(ObsidianDragon PRIVATE
|
||||||
DRAGONX_DEBUG
|
DRAGONX_DEBUG
|
||||||
|
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||||
|
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||||
)
|
)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
||||||
@@ -727,7 +741,7 @@ install(TARGETS ObsidianDragon
|
|||||||
)
|
)
|
||||||
|
|
||||||
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
|
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
|
||||||
DESTINATION share/ObsidianDragon
|
DESTINATION share/${DRAGONX_BINARY_NAME}
|
||||||
OPTIONAL
|
OPTIONAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
100
build.sh
100
build.sh
@@ -41,6 +41,7 @@ DO_DEV=false
|
|||||||
DO_LINUX=false
|
DO_LINUX=false
|
||||||
DO_WIN=false
|
DO_WIN=false
|
||||||
DO_MAC=false
|
DO_MAC=false
|
||||||
|
DO_LITE=false
|
||||||
CLEAN=false
|
CLEAN=false
|
||||||
BUILD_TYPE="Release"
|
BUILD_TYPE="Release"
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ Targets (at least one required, or none for dev build):
|
|||||||
--linux-release Linux release (zip + AppImage) -> release/linux/
|
--linux-release Linux release (zip + AppImage) -> release/linux/
|
||||||
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
||||||
--mac-release macOS .app bundle + DMG -> release/mac/
|
--mac-release macOS .app bundle + DMG -> release/mac/
|
||||||
|
--lite Build ObsidianDragonLite variant (no embedded daemon/full-node features)
|
||||||
|
|
||||||
Build trees are stored under build/{linux,windows,mac}/
|
Build trees are stored under build/{linux,windows,mac}/
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--linux-release) DO_LINUX=true; shift ;;
|
--linux-release) DO_LINUX=true; shift ;;
|
||||||
--win-release) DO_WIN=true; shift ;;
|
--win-release) DO_WIN=true; shift ;;
|
||||||
--mac-release) DO_MAC=true; shift ;;
|
--mac-release) DO_MAC=true; shift ;;
|
||||||
|
--lite) DO_LITE=true; shift ;;
|
||||||
-c|--clean) CLEAN=true; shift ;;
|
-c|--clean) CLEAN=true; shift ;;
|
||||||
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
||||||
-j) JOBS="$2"; shift 2 ;;
|
-j) JOBS="$2"; shift 2 ;;
|
||||||
@@ -98,6 +101,14 @@ if ! $DO_LINUX && ! $DO_WIN && ! $DO_MAC; then
|
|||||||
DO_DEV=true
|
DO_DEV=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
APP_BASENAME="ObsidianDragon"
|
||||||
|
CMAKE_LITE_ARGS=()
|
||||||
|
if $DO_LITE; then
|
||||||
|
APP_BASENAME="ObsidianDragonLite"
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=ON")
|
||||||
|
info "Lite mode enabled: building ${APP_BASENAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Helper: find resource files ──────────────────────────────────────────────
|
# ── Helper: find resource files ──────────────────────────────────────────────
|
||||||
find_sapling_params() {
|
find_sapling_params() {
|
||||||
local dirs=(
|
local dirs=(
|
||||||
@@ -215,13 +226,14 @@ build_dev() {
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=ON
|
-DDRAGONX_USE_SYSTEM_SDL3=ON \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "Build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "Build failed"; exit 1; }
|
||||||
info "Dev binary: $bd/bin/ObsidianDragon ($(du -h bin/ObsidianDragon | cut -f1))"
|
info "Dev binary: $bd/bin/${APP_BASENAME} ($(du -h "bin/${APP_BASENAME}" | cut -f1))"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -242,16 +254,17 @@ build_release_linux() {
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=ON
|
-DDRAGONX_USE_SYSTEM_SDL3=ON \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "Linux build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "Linux build failed"; exit 1; }
|
||||||
|
|
||||||
info "Stripping ..."
|
info "Stripping ..."
|
||||||
strip bin/ObsidianDragon
|
strip "bin/${APP_BASENAME}"
|
||||||
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||||
|
|
||||||
# ── Bundle daemon ────────────────────────────────────────────────────────
|
# ── Bundle daemon ────────────────────────────────────────────────────────
|
||||||
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
||||||
@@ -268,11 +281,11 @@ build_release_linux() {
|
|||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
|
||||||
local DIST="ObsidianDragon-${VERSION}-Linux-x64"
|
local DIST="${APP_BASENAME}-${VERSION}-Linux-x64"
|
||||||
local dist_dir="$out/$DIST"
|
local dist_dir="$out/$DIST"
|
||||||
mkdir -p "$dist_dir"
|
mkdir -p "$dist_dir"
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$dist_dir/"
|
cp "bin/${APP_BASENAME}" "$dist_dir/"
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
||||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
||||||
@@ -299,7 +312,7 @@ build_release_linux() {
|
|||||||
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
|
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
|
||||||
"$APPDIR/usr/share/ObsidianDragon/res"
|
"$APPDIR/usr/share/ObsidianDragon/res"
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$APPDIR/usr/bin/"
|
cp "bin/${APP_BASENAME}" "$APPDIR/usr/bin/"
|
||||||
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
||||||
|
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
||||||
@@ -314,12 +327,12 @@ build_release_linux() {
|
|||||||
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
||||||
|
|
||||||
# Desktop entry
|
# Desktop entry
|
||||||
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK'
|
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=DragonX Wallet
|
Name=DragonX Wallet
|
||||||
Comment=DragonX Cryptocurrency Wallet
|
Comment=DragonX Cryptocurrency Wallet
|
||||||
Exec=ObsidianDragon
|
Exec=${APP_BASENAME}
|
||||||
Icon=ObsidianDragon
|
Icon=ObsidianDragon
|
||||||
Categories=Finance;Network;
|
Categories=Finance;Network;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
@@ -350,14 +363,14 @@ SVG
|
|||||||
cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true
|
cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true
|
||||||
|
|
||||||
# AppRun
|
# AppRun
|
||||||
cat > "$APPDIR/AppRun" <<'APPRUN'
|
cat > "$APPDIR/AppRun" <<APPRUN
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SELF=$(readlink -f "$0")
|
SELF=\$(readlink -f "\$0")
|
||||||
HERE=${SELF%/*}
|
HERE=\${SELF%/*}
|
||||||
export DRAGONX_RES_PATH="${HERE}/usr/share/ObsidianDragon/res"
|
export DRAGONX_RES_PATH="\${HERE}/usr/share/ObsidianDragon/res"
|
||||||
export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}"
|
export LD_LIBRARY_PATH="\${HERE}/usr/lib:\${LD_LIBRARY_PATH}"
|
||||||
cd "${HERE}/usr/share/ObsidianDragon"
|
cd "\${HERE}/usr/share/ObsidianDragon"
|
||||||
exec "${HERE}/usr/bin/ObsidianDragon" "$@"
|
exec "\${HERE}/usr/bin/${APP_BASENAME}" "\$@"
|
||||||
APPRUN
|
APPRUN
|
||||||
chmod +x "$APPDIR/AppRun"
|
chmod +x "$APPDIR/AppRun"
|
||||||
|
|
||||||
@@ -386,9 +399,9 @@ APPRUN
|
|||||||
local ARCH
|
local ARCH
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
cd "$bd"
|
cd "$bd"
|
||||||
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "ObsidianDragon-${VERSION}-${ARCH}.AppImage" 2>/dev/null && {
|
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" 2>/dev/null && {
|
||||||
cp "ObsidianDragon-${VERSION}-${ARCH}.AppImage" "$out/ObsidianDragon-${VERSION}.AppImage"
|
cp "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" "$out/${APP_BASENAME}-${VERSION}.AppImage"
|
||||||
info "AppImage: $out/ObsidianDragon-${VERSION}.AppImage ($(du -h "$out/ObsidianDragon-${VERSION}.AppImage" | cut -f1))"
|
info "AppImage: $out/${APP_BASENAME}-${VERSION}.AppImage ($(du -h "$out/${APP_BASENAME}-${VERSION}.AppImage" | cut -f1))"
|
||||||
} || warn "AppImage creation failed — binaries zip still in release/linux/"
|
} || warn "AppImage creation failed — binaries zip still in release/linux/"
|
||||||
|
|
||||||
info "Linux release artifacts: $out/"
|
info "Linux release artifacts: $out/"
|
||||||
@@ -599,22 +612,23 @@ HDR
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon.exe" ]] || { err "Windows build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}.exe" ]] || { err "Windows build failed"; exit 1; }
|
||||||
info "Binary: $(du -h bin/ObsidianDragon.exe | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}.exe" | cut -f1)"
|
||||||
|
|
||||||
# ── Package: release/windows/ ────────────────────────────────────────────
|
# ── Package: release/windows/ ────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
|
||||||
local DIST="ObsidianDragon-${VERSION}-Windows-x64"
|
local DIST="${APP_BASENAME}-${VERSION}-Windows-x64"
|
||||||
local dist_dir="$out/$DIST"
|
local dist_dir="$out/$DIST"
|
||||||
mkdir -p "$dist_dir"
|
mkdir -p "$dist_dir"
|
||||||
cp bin/ObsidianDragon.exe "$dist_dir/"
|
cp "bin/${APP_BASENAME}.exe" "$dist_dir/"
|
||||||
|
|
||||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||||
@@ -635,8 +649,8 @@ HDR
|
|||||||
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
||||||
|
|
||||||
# ── Single-file exe (all resources embedded) ────────────────────────────
|
# ── Single-file exe (all resources embedded) ────────────────────────────
|
||||||
cp bin/ObsidianDragon.exe "$out/ObsidianDragon-${VERSION}.exe"
|
cp "bin/${APP_BASENAME}.exe" "$out/${APP_BASENAME}-${VERSION}.exe"
|
||||||
info "Single-file exe: $out/ObsidianDragon-${VERSION}.exe ($(du -h "$out/ObsidianDragon-${VERSION}.exe" | cut -f1))"
|
info "Single-file exe: $out/${APP_BASENAME}-${VERSION}.exe ($(du -h "$out/${APP_BASENAME}-${VERSION}.exe" | cut -f1))"
|
||||||
|
|
||||||
# ── Zip ──────────────────────────────────────────────────────────────────
|
# ── Zip ──────────────────────────────────────────────────────────────────
|
||||||
if command -v zip &>/dev/null; then
|
if command -v zip &>/dev/null; then
|
||||||
@@ -818,7 +832,8 @@ TOOLCHAIN
|
|||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
||||||
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"}
|
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"} \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
else
|
else
|
||||||
# Build libsodium as universal if needed
|
# Build libsodium as universal if needed
|
||||||
local need_sodium=false
|
local need_sodium=false
|
||||||
@@ -844,39 +859,40 @@ TOOLCHAIN
|
|||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
||||||
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "macOS build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "macOS build failed"; exit 1; }
|
||||||
|
|
||||||
# Strip — use osxcross strip for cross-builds
|
# Strip — use osxcross strip for cross-builds
|
||||||
if $IS_CROSS; then
|
if $IS_CROSS; then
|
||||||
local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
|
local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
|
||||||
if [[ -x "$STRIP_CMD" ]]; then
|
if [[ -x "$STRIP_CMD" ]]; then
|
||||||
info "Stripping (osxcross) ..."
|
info "Stripping (osxcross) ..."
|
||||||
"$STRIP_CMD" bin/ObsidianDragon
|
"$STRIP_CMD" "bin/${APP_BASENAME}"
|
||||||
else
|
else
|
||||||
warn "osxcross strip not found at $STRIP_CMD — skipping"
|
warn "osxcross strip not found at $STRIP_CMD — skipping"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
info "Stripping ..."
|
info "Stripping ..."
|
||||||
strip bin/ObsidianDragon
|
strip "bin/${APP_BASENAME}"
|
||||||
# Verify universal binary
|
# Verify universal binary
|
||||||
if command -v lipo &>/dev/null; then
|
if command -v lipo &>/dev/null; then
|
||||||
info "Architecture info:"
|
info "Architecture info:"
|
||||||
lipo -info bin/ObsidianDragon
|
lipo -info "bin/${APP_BASENAME}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||||
|
|
||||||
# ── Create .app bundle ───────────────────────────────────────────────────
|
# ── Create .app bundle ───────────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
|
||||||
local APP="$out/ObsidianDragon.app"
|
local APP="$out/${APP_BASENAME}.app"
|
||||||
local CONTENTS="$APP/Contents"
|
local CONTENTS="$APP/Contents"
|
||||||
local MACOS="$CONTENTS/MacOS"
|
local MACOS="$CONTENTS/MacOS"
|
||||||
local RESOURCES="$CONTENTS/Resources"
|
local RESOURCES="$CONTENTS/Resources"
|
||||||
@@ -885,8 +901,8 @@ TOOLCHAIN
|
|||||||
mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"
|
mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"
|
||||||
|
|
||||||
# Main binary
|
# Main binary
|
||||||
cp bin/ObsidianDragon "$MACOS/"
|
cp "bin/${APP_BASENAME}" "$MACOS/"
|
||||||
chmod +x "$MACOS/ObsidianDragon"
|
chmod +x "$MACOS/${APP_BASENAME}"
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
||||||
@@ -1053,9 +1069,9 @@ PLIST
|
|||||||
info ".app bundle created: $APP"
|
info ".app bundle created: $APP"
|
||||||
|
|
||||||
# ── Zip the .app bundle ──────────────────────────────────────────────────
|
# ── Zip the .app bundle ──────────────────────────────────────────────────
|
||||||
local APP_ZIP="ObsidianDragon-${VERSION}-macOS-${MAC_ARCH}.app.zip"
|
local APP_ZIP="${APP_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.app.zip"
|
||||||
if command -v zip &>/dev/null; then
|
if command -v zip &>/dev/null; then
|
||||||
(cd "$out" && zip -r "$APP_ZIP" "ObsidianDragon.app")
|
(cd "$out" && zip -r "$APP_ZIP" "${APP_BASENAME}.app")
|
||||||
info "App zip: $out/$APP_ZIP ($(du -h "$out/$APP_ZIP" | cut -f1))"
|
info "App zip: $out/$APP_ZIP ($(du -h "$out/$APP_ZIP" | cut -f1))"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1071,7 +1087,7 @@ PLIST
|
|||||||
--window-pos 200 120 \
|
--window-pos 200 120 \
|
||||||
--window-size 600 400 \
|
--window-size 600 400 \
|
||||||
--icon-size 100 \
|
--icon-size 100 \
|
||||||
--icon "ObsidianDragon.app" 150 190 \
|
--icon "${APP_BASENAME}.app" 150 190 \
|
||||||
--app-drop-link 450 190 \
|
--app-drop-link 450 190 \
|
||||||
--no-internet-enable \
|
--no-internet-enable \
|
||||||
"$out/$DMG_NAME" \
|
"$out/$DMG_NAME" \
|
||||||
|
|||||||
63
docs/chat-port-feasibility-2026-05-06.md
Normal file
63
docs/chat-port-feasibility-2026-05-06.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Chat Port Feasibility Audit (2026-05-06)
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Evaluate whether SilentDragonXLite chat can be ported to the current full-node ImGui wallet.
|
||||||
|
- If feasible, define a safe path with feature flagging.
|
||||||
|
|
||||||
|
## Source Audit (SilentDragonXLite)
|
||||||
|
|
||||||
|
Primary chat surfaces in the reference codebase:
|
||||||
|
- `external/SilentDragonXLite/src/chatmodel.h`
|
||||||
|
- `external/SilentDragonXLite/src/chatmodel.cpp`
|
||||||
|
- `external/SilentDragonXLite/src/Chat/Chat.h`
|
||||||
|
- `external/SilentDragonXLite/src/Chat/Chat.cpp`
|
||||||
|
- `external/SilentDragonXLite/src/controller.cpp` (transaction scan + memo decrypt/encrypt)
|
||||||
|
- `external/SilentDragonXLite/src/Model/ChatItem.*`
|
||||||
|
- `external/SilentDragonXLite/src/Model/ContactRequest*`
|
||||||
|
- `external/SilentDragonXLite/src/DataStore/ChatDataStore.*`
|
||||||
|
- `external/SilentDragonXLite/src/mainwindow.ui` and related Qt UI widgets.
|
||||||
|
|
||||||
|
Observed characteristics:
|
||||||
|
- Chat is tightly coupled to Qt widget/UI classes (`QListView`, `QStandardItemModel`, `.ui` forms).
|
||||||
|
- Message transport relies on memo payload parsing/decryption embedded directly in transaction refresh/controller flow.
|
||||||
|
- Contact requests, chat IDs, and message rendering are mixed with Qt view models and DataStore semantics.
|
||||||
|
- Crypto flow depends on shared secret derivation and memo header conventions implemented inline in controller/chat code.
|
||||||
|
|
||||||
|
## Current Wallet Architecture Gap
|
||||||
|
|
||||||
|
Current app (`src/`) is Dear ImGui + SDL3 with different state/render model:
|
||||||
|
- No existing chat domain model/service in ImGui code path.
|
||||||
|
- No Qt runtime/view-model infrastructure to reuse reference chat modules.
|
||||||
|
- Transaction refresh path is centralized in `NetworkRefreshService` and app refresh orchestration, not Qt controller callbacks.
|
||||||
|
|
||||||
|
## Feasibility Decision
|
||||||
|
|
||||||
|
Decision: **Not safe to directly port in this batch**.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- A direct transplant would require substantial re-architecture, not incremental copy-over.
|
||||||
|
- High risk of regressions in transaction refresh correctness, memo handling, and wallet UX stability.
|
||||||
|
- Crypto/message parsing code requires dedicated security review before enabling in production.
|
||||||
|
|
||||||
|
## Estimated Effort (for a proper staged port)
|
||||||
|
|
||||||
|
1. Domain extraction (chat/contact/message models, storage interfaces): 3-5 days
|
||||||
|
2. Service integration with current refresh pipeline (`NetworkRefreshService` + app): 4-7 days
|
||||||
|
3. ImGui chat UI (list, composer, contact requests, actions): 4-6 days
|
||||||
|
4. Security hardening + memo/crypto validation tests: 3-5 days
|
||||||
|
5. End-to-end QA/regression/perf pass: 2-4 days
|
||||||
|
|
||||||
|
Total estimate: **16-27 engineer-days**.
|
||||||
|
|
||||||
|
## Risk Profile
|
||||||
|
|
||||||
|
- Security risk: high (memo crypto path and malformed payload handling).
|
||||||
|
- Stability risk: high (transaction refresh path is core-wallet critical).
|
||||||
|
- UX risk: medium (new asynchronous chat states and contact flows).
|
||||||
|
|
||||||
|
## Recommended Safe Plan
|
||||||
|
|
||||||
|
1. Add compile/runtime feature flag (`DRAGONX_ENABLE_CHAT`) default OFF.
|
||||||
|
2. Implement read-only parser + storage first (no send), fully test malformed memos.
|
||||||
|
3. Add send path behind same flag with strict size/format guards.
|
||||||
|
4. Ship disabled by default until security and regression acceptance criteria are met.
|
||||||
@@ -130,6 +130,8 @@ public:
|
|||||||
* @brief Whether we are in the shutdown phase
|
* @brief Whether we are in the shutdown phase
|
||||||
*/
|
*/
|
||||||
bool isShuttingDown() const { return shutting_down_; }
|
bool isShuttingDown() const { return shutting_down_; }
|
||||||
|
bool isLiteBuild() const { return DRAGONX_LITE_BUILD != 0; }
|
||||||
|
bool supportsEmbeddedDaemon() const { return DRAGONX_ENABLE_EMBEDDED_DAEMON != 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
|
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
|
||||||
@@ -439,7 +441,7 @@ private:
|
|||||||
bool show_address_book_ = false;
|
bool show_address_book_ = false;
|
||||||
|
|
||||||
// Embedded daemon state
|
// Embedded daemon state
|
||||||
bool use_embedded_daemon_ = true;
|
bool use_embedded_daemon_ = (DRAGONX_ENABLE_EMBEDDED_DAEMON != 0);
|
||||||
std::string daemon_status_;
|
std::string daemon_status_;
|
||||||
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
||||||
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
||||||
|
|||||||
@@ -60,6 +60,23 @@ using NetworkRefreshService = services::NetworkRefreshService;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
bool isPageEnabledForBuild(ui::NavPage page)
|
||||||
|
{
|
||||||
|
#if DRAGONX_LITE_BUILD
|
||||||
|
switch (page) {
|
||||||
|
case ui::NavPage::Console:
|
||||||
|
case ui::NavPage::Peers:
|
||||||
|
case ui::NavPage::Explorer:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)page;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
std::string unencryptedTransactionHistoryCacheKey(const std::string& walletIdentity)
|
std::string unencryptedTransactionHistoryCacheKey(const std::string& walletIdentity)
|
||||||
{
|
{
|
||||||
return std::string("obsidian-dragon-unencrypted-tx-cache-v1:") +
|
return std::string("obsidian-dragon-unencrypted-tx-cache-v1:") +
|
||||||
@@ -545,6 +562,9 @@ bool App::shouldRunWalletTransactionRefresh() const
|
|||||||
|
|
||||||
void App::setCurrentPage(ui::NavPage page)
|
void App::setCurrentPage(ui::NavPage page)
|
||||||
{
|
{
|
||||||
|
if (!isPageEnabledForBuild(page)) {
|
||||||
|
page = ui::NavPage::Overview;
|
||||||
|
}
|
||||||
if (page == current_page_) return;
|
if (page == current_page_) return;
|
||||||
current_page_ = page;
|
current_page_ = page;
|
||||||
applyRefreshPolicy(page);
|
applyRefreshPolicy(page);
|
||||||
@@ -1372,6 +1392,11 @@ void App::refreshMarketData()
|
|||||||
|
|
||||||
void App::startMining(int threads)
|
void App::startMining(int threads)
|
||||||
{
|
{
|
||||||
|
#if DRAGONX_LITE_BUILD
|
||||||
|
(void)threads;
|
||||||
|
ui::Notifications::instance().warning("Solo mining is unavailable in lite build");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
if (!state_.connected || !rpc_ || !worker_) return;
|
if (!state_.connected || !rpc_ || !worker_) return;
|
||||||
if (mining_toggle_in_progress_.exchange(true)) return; // already in progress
|
if (mining_toggle_in_progress_.exchange(true)) return; // already in progress
|
||||||
|
|
||||||
@@ -1401,6 +1426,9 @@ void App::startMining(int threads)
|
|||||||
|
|
||||||
void App::stopMining()
|
void App::stopMining()
|
||||||
{
|
{
|
||||||
|
#if DRAGONX_LITE_BUILD
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
if (!state_.connected || !rpc_ || !worker_) return;
|
if (!state_.connected || !rpc_ || !worker_) return;
|
||||||
if (mining_toggle_in_progress_.exchange(true)) return; // already in progress
|
if (mining_toggle_in_progress_.exchange(true)) return; // already in progress
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define DRAGONX_VERSION_MINOR 2
|
#define DRAGONX_VERSION_MINOR 2
|
||||||
#define DRAGONX_VERSION_PATCH 0
|
#define DRAGONX_VERSION_PATCH 0
|
||||||
|
|
||||||
#define DRAGONX_APP_NAME "ObsidianDragon"
|
#define DRAGONX_APP_NAME "ObsidianDragonLite"
|
||||||
#define DRAGONX_ORG_NAME "Hush"
|
#define DRAGONX_ORG_NAME "Hush"
|
||||||
|
|
||||||
// Default RPC settings
|
// Default RPC settings
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define DRAGONX_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
#define DRAGONX_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||||
#define DRAGONX_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
#define DRAGONX_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||||
|
|
||||||
#define DRAGONX_APP_NAME "ObsidianDragon"
|
#define DRAGONX_APP_NAME "@DRAGONX_APP_NAME@"
|
||||||
#define DRAGONX_ORG_NAME "Hush"
|
#define DRAGONX_ORG_NAME "Hush"
|
||||||
|
|
||||||
// Default RPC settings
|
// Default RPC settings
|
||||||
|
|||||||
@@ -1073,6 +1073,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
|
|
||||||
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
|
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
|
||||||
{
|
{
|
||||||
|
const bool showDaemonOptions = !app->isLiteBuild();
|
||||||
float cbSpacing = Layout::spacingMd();
|
float cbSpacing = Layout::spacingMd();
|
||||||
float fh = ImGui::GetFrameHeight();
|
float fh = ImGui::GetFrameHeight();
|
||||||
float inner = ImGui::GetStyle().ItemInnerSpacing.x;
|
float inner = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||||
@@ -1080,9 +1081,12 @@ void RenderSettingsPage(App* app) {
|
|||||||
|
|
||||||
float totalW = cbW(TR("save_z_transactions")) + cbSpacing
|
float totalW = cbW(TR("save_z_transactions")) + cbSpacing
|
||||||
+ cbW(TR("auto_shield")) + cbSpacing
|
+ cbW(TR("auto_shield")) + cbSpacing
|
||||||
+ cbW(TR("use_tor")) + cbSpacing
|
+ cbW(TR("use_tor")) + cbSpacing;
|
||||||
+ cbW(TR("keep_daemon")) + cbSpacing
|
if (showDaemonOptions) {
|
||||||
+ cbW(TR("stop_external"));
|
totalW += cbW(TR("keep_daemon")) + cbSpacing
|
||||||
|
+ cbW(TR("stop_external")) + cbSpacing;
|
||||||
|
}
|
||||||
|
totalW += cbW(TR("verbose_logging"));
|
||||||
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
||||||
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
||||||
float sp = cbSpacing * scale;
|
float sp = cbSpacing * scale;
|
||||||
@@ -1095,18 +1099,20 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImGui::SameLine(0, sp);
|
ImGui::SameLine(0, sp);
|
||||||
ImGui::Checkbox(TrId("use_tor", "tor").c_str(), &s_settingsState.use_tor);
|
ImGui::Checkbox(TrId("use_tor", "tor").c_str(), &s_settingsState.use_tor);
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_tor"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_tor"));
|
||||||
ImGui::SameLine(0, sp);
|
if (showDaemonOptions) {
|
||||||
if (ImGui::Checkbox(TrId("keep_daemon", "keep_dmn").c_str(), &s_settingsState.keep_daemon_running)) {
|
ImGui::SameLine(0, sp);
|
||||||
saveSettingsPageState(app->settings());
|
if (ImGui::Checkbox(TrId("keep_daemon", "keep_dmn").c_str(), &s_settingsState.keep_daemon_running)) {
|
||||||
|
saveSettingsPageState(app->settings());
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s", TR("tt_keep_daemon"));
|
||||||
|
ImGui::SameLine(0, sp);
|
||||||
|
if (ImGui::Checkbox(TrId("stop_external", "stop_ext").c_str(), &s_settingsState.stop_external_daemon)) {
|
||||||
|
saveSettingsPageState(app->settings());
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s", TR("tt_stop_external"));
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered())
|
|
||||||
ImGui::SetTooltip("%s", TR("tt_keep_daemon"));
|
|
||||||
ImGui::SameLine(0, sp);
|
|
||||||
if (ImGui::Checkbox(TrId("stop_external", "stop_ext").c_str(), &s_settingsState.stop_external_daemon)) {
|
|
||||||
saveSettingsPageState(app->settings());
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered())
|
|
||||||
ImGui::SetTooltip("%s", TR("tt_stop_external"));
|
|
||||||
ImGui::SameLine(0, sp);
|
ImGui::SameLine(0, sp);
|
||||||
if (ImGui::Checkbox(TrId("verbose_logging", "verbose").c_str(), &s_settingsState.verbose_logging)) {
|
if (ImGui::Checkbox(TrId("verbose_logging", "verbose").c_str(), &s_settingsState.verbose_logging)) {
|
||||||
dragonx::util::Logger::instance().setVerbose(s_settingsState.verbose_logging);
|
dragonx::util::Logger::instance().setVerbose(s_settingsState.verbose_logging);
|
||||||
@@ -1469,8 +1475,8 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
// Node maintenance buttons
|
// Node maintenance buttons (full-node build only)
|
||||||
{
|
if (!app->isLiteBuild()) {
|
||||||
ImFont* btnFont = S.resolveFont("button");
|
ImFont* btnFont = S.resolveFont("button");
|
||||||
float nodeBtnW;
|
float nodeBtnW;
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,6 +66,16 @@ inline const char* NavSectionLabel(const NavItem& item) {
|
|||||||
return item.section_tr_key ? TR(item.section_tr_key) : item.section_label;
|
return item.section_tr_key ? TR(item.section_tr_key) : item.section_label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool IsNavPageVisible(NavPage page)
|
||||||
|
{
|
||||||
|
#if DRAGONX_LITE_BUILD
|
||||||
|
return page != NavPage::Console && page != NavPage::Peers && page != NavPage::Explorer;
|
||||||
|
#else
|
||||||
|
(void)page;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Get the Material Design icon string for a navigation page.
|
// Get the Material Design icon string for a navigation page.
|
||||||
inline const char* GetNavIconMD(NavPage page)
|
inline const char* GetNavIconMD(NavPage page)
|
||||||
{
|
{
|
||||||
@@ -446,12 +456,13 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
// Separate fixed parts (don't scale) from flex parts (scale when tight).
|
// Separate fixed parts (don't scale) from flex parts (scale when tight).
|
||||||
float fixedH = stripH; // collapse strip
|
float fixedH = stripH; // collapse strip
|
||||||
for (int i = 0; i < (int)NavPage::Count_; ++i)
|
for (int i = 0; i < (int)NavPage::Count_; ++i)
|
||||||
if (kNavItems[i].section_label && showLabels)
|
if (IsNavPageVisible(kNavItems[i].page) && kNavItems[i].section_label && showLabels)
|
||||||
fixedH += olFsz + 2.0f + sectionLabelPadBot; // section label + pad below
|
fixedH += olFsz + 2.0f + sectionLabelPadBot; // section label + pad below
|
||||||
fixedH += bottomPadding + stripH; // exit area
|
fixedH += bottomPadding + stripH; // exit area
|
||||||
|
|
||||||
float baseFlexH = baseNavGap;
|
float baseFlexH = baseNavGap;
|
||||||
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
||||||
|
if (!IsNavPageVisible(kNavItems[i].page)) continue;
|
||||||
if (kNavItems[i].section_label) {
|
if (kNavItems[i].section_label) {
|
||||||
if (showLabels) baseFlexH += baseSectionGap;
|
if (showLabels) baseFlexH += baseSectionGap;
|
||||||
else baseFlexH += baseSectionGap * 0.8f;
|
else baseFlexH += baseSectionGap * 0.8f;
|
||||||
@@ -479,6 +490,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
|
|
||||||
float curY = stripH + navGap; // after collapse strip + gap
|
float curY = stripH + navGap; // after collapse strip + gap
|
||||||
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
||||||
|
if (!IsNavPageVisible(kNavItems[i].page)) continue;
|
||||||
if (kNavItems[i].section_label) {
|
if (kNavItems[i].section_label) {
|
||||||
if (showLabels) {
|
if (showLabels) {
|
||||||
curY += sectionGap;
|
curY += sectionGap;
|
||||||
@@ -560,6 +572,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
float maxSectionW = panelRight - wp.x - sbSectionLabelPadLeft;
|
float maxSectionW = panelRight - wp.x - sbSectionLabelPadLeft;
|
||||||
int si = 0;
|
int si = 0;
|
||||||
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
||||||
|
if (!IsNavPageVisible(kNavItems[i].page)) continue;
|
||||||
if (kNavItems[i].section_label && si < nSectionLabels) {
|
if (kNavItems[i].section_label && si < nSectionLabels) {
|
||||||
float ly = panelTopY + sectionLabelY[si++];
|
float ly = panelTopY + sectionLabelY[si++];
|
||||||
const char* sLabel = NavSectionLabel(kNavItems[i]);
|
const char* sLabel = NavSectionLabel(kNavItems[i]);
|
||||||
@@ -587,6 +600,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
float btnPadX = collapsed ? btnPadCollapsed : btnPadExpanded;
|
float btnPadX = collapsed ? btnPadCollapsed : btnPadExpanded;
|
||||||
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
for (int i = 0; i < (int)NavPage::Count_; ++i) {
|
||||||
const NavItem& item = kNavItems[i];
|
const NavItem& item = kNavItems[i];
|
||||||
|
if (!IsNavPageVisible(item.page)) continue;
|
||||||
bool selected = (current == item.page);
|
bool selected = (current == item.page);
|
||||||
float btnY = panelTopY + itemY[i];
|
float btnY = panelTopY + itemY[i];
|
||||||
float btnRnd = itemH * 0.22f;
|
float btnRnd = itemH * 0.22f;
|
||||||
@@ -695,10 +709,14 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
|||||||
if (!ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) {
|
if (!ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) {
|
||||||
int idx = static_cast<int>(current);
|
int idx = static_cast<int>(current);
|
||||||
bool nav = false;
|
bool nav = false;
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K))
|
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K)) {
|
||||||
if (idx > 0) { idx--; nav = true; }
|
do { idx--; } while (idx >= 0 && !IsNavPageVisible(static_cast<NavPage>(idx)));
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J))
|
if (idx >= 0) nav = true;
|
||||||
if (idx < (int)NavPage::Count_ - 1) { idx++; nav = true; }
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J)) {
|
||||||
|
do { idx++; } while (idx < (int)NavPage::Count_ && !IsNavPageVisible(static_cast<NavPage>(idx)));
|
||||||
|
if (idx < (int)NavPage::Count_) nav = true;
|
||||||
|
}
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket))
|
if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket))
|
||||||
collapsed = !collapsed;
|
collapsed = !collapsed;
|
||||||
if (nav) { current = static_cast<NavPage>(idx); changed = true; }
|
if (nav) { current = static_cast<NavPage>(idx); changed = true; }
|
||||||
|
|||||||
@@ -151,6 +151,13 @@ static void RenderMiningTabContent(App* app)
|
|||||||
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
|
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
|
||||||
s_pool_state_loaded = true;
|
s_pool_state_loaded = true;
|
||||||
}
|
}
|
||||||
|
#if DRAGONX_LITE_BUILD
|
||||||
|
if (!s_pool_mode) {
|
||||||
|
s_pool_mode = true;
|
||||||
|
app->settings()->setPoolMode(true);
|
||||||
|
app->settings()->save();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Default pool worker to user's first shielded (z) address once available.
|
// Default pool worker to user's first shielded (z) address once available.
|
||||||
// For new wallets without a z-address, leave the field blank so the user
|
// For new wallets without a z-address, leave the field blank so the user
|
||||||
@@ -235,10 +242,11 @@ static void RenderMiningTabContent(App* app)
|
|||||||
// MODE TOGGLE — SOLO | POOL segmented control
|
// MODE TOGGLE — SOLO | POOL segmented control
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
{
|
||||||
|
const bool liteBuild = app->isLiteBuild();
|
||||||
float toggleW = schema::UI().drawElement("tabs.mining", "mode-toggle-width").size * hs;
|
float toggleW = schema::UI().drawElement("tabs.mining", "mode-toggle-width").size * hs;
|
||||||
float toggleH = schema::UI().drawElement("tabs.mining", "mode-toggle-height").size;
|
float toggleH = schema::UI().drawElement("tabs.mining", "mode-toggle-height").size;
|
||||||
float toggleRnd = schema::UI().drawElement("tabs.mining", "mode-toggle-rounding").size;
|
float toggleRnd = schema::UI().drawElement("tabs.mining", "mode-toggle-rounding").size;
|
||||||
float totalW = toggleW * 2;
|
float totalW = liteBuild ? toggleW : (toggleW * 2);
|
||||||
|
|
||||||
ImVec2 tMin = ImGui::GetCursorScreenPos();
|
ImVec2 tMin = ImGui::GetCursorScreenPos();
|
||||||
ImVec2 tMax(tMin.x + totalW, tMin.y + toggleH);
|
ImVec2 tMax(tMin.x + totalW, tMin.y + toggleH);
|
||||||
@@ -247,16 +255,16 @@ static void RenderMiningTabContent(App* app)
|
|||||||
dl->AddRectFilled(tMin, tMax, WithAlpha(OnSurface(), 15), toggleRnd);
|
dl->AddRectFilled(tMin, tMax, WithAlpha(OnSurface(), 15), toggleRnd);
|
||||||
dl->AddRect(tMin, tMax, WithAlpha(OnSurface(), 40), toggleRnd);
|
dl->AddRect(tMin, tMax, WithAlpha(OnSurface(), 40), toggleRnd);
|
||||||
|
|
||||||
// SOLO button (left half)
|
|
||||||
ImVec2 soloMin = tMin;
|
ImVec2 soloMin = tMin;
|
||||||
ImVec2 soloMax(tMin.x + toggleW, tMax.y);
|
ImVec2 soloMax(tMin.x + toggleW, tMax.y);
|
||||||
bool soloHov = material::IsRectHovered(soloMin, soloMax);
|
bool soloHov = material::IsRectHovered(soloMin, soloMax);
|
||||||
if (!s_pool_mode) {
|
if (!liteBuild) {
|
||||||
dl->AddRectFilled(soloMin, soloMax, WithAlpha(Primary(), 180), toggleRnd);
|
// SOLO button (left half)
|
||||||
} else if (soloHov) {
|
if (!s_pool_mode) {
|
||||||
dl->AddRectFilled(soloMin, soloMax, WithAlpha(OnSurface(), 20), toggleRnd);
|
dl->AddRectFilled(soloMin, soloMax, WithAlpha(Primary(), 180), toggleRnd);
|
||||||
}
|
} else if (soloHov) {
|
||||||
{
|
dl->AddRectFilled(soloMin, soloMax, WithAlpha(OnSurface(), 20), toggleRnd);
|
||||||
|
}
|
||||||
const char* label = TR("mining_solo");
|
const char* label = TR("mining_solo");
|
||||||
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
||||||
float lx = soloMin.x + (toggleW - sz.x) * 0.5f;
|
float lx = soloMin.x + (toggleW - sz.x) * 0.5f;
|
||||||
@@ -267,7 +275,7 @@ static void RenderMiningTabContent(App* app)
|
|||||||
|
|
||||||
// POOL button (right half) — disabled when solo mining is active
|
// POOL button (right half) — disabled when solo mining is active
|
||||||
bool soloMiningActive = mining.generate;
|
bool soloMiningActive = mining.generate;
|
||||||
ImVec2 poolMin(tMin.x + toggleW, tMin.y);
|
ImVec2 poolMin(tMin.x + (liteBuild ? 0.0f : toggleW), tMin.y);
|
||||||
ImVec2 poolMax = tMax;
|
ImVec2 poolMax = tMax;
|
||||||
bool poolHov = material::IsRectHovered(poolMin, poolMax);
|
bool poolHov = material::IsRectHovered(poolMin, poolMax);
|
||||||
if (s_pool_mode) {
|
if (s_pool_mode) {
|
||||||
@@ -288,19 +296,21 @@ static void RenderMiningTabContent(App* app)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invisible buttons for click targets
|
// Invisible buttons for click targets
|
||||||
ImGui::SetCursorScreenPos(soloMin);
|
if (!liteBuild) {
|
||||||
ImGui::InvisibleButton("##SoloMode", ImVec2(toggleW, toggleH));
|
ImGui::SetCursorScreenPos(soloMin);
|
||||||
if (ImGui::IsItemClicked() && s_pool_mode) {
|
ImGui::InvisibleButton("##SoloMode", ImVec2(toggleW, toggleH));
|
||||||
s_pool_mode = false;
|
if (ImGui::IsItemClicked() && s_pool_mode) {
|
||||||
app->settings()->setPoolMode(false);
|
s_pool_mode = false;
|
||||||
app->settings()->save();
|
app->settings()->setPoolMode(false);
|
||||||
app->stopPoolMining();
|
app->settings()->save();
|
||||||
|
app->stopPoolMining();
|
||||||
|
}
|
||||||
|
if (soloHov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
}
|
}
|
||||||
if (soloHov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(poolMin);
|
ImGui::SetCursorScreenPos(poolMin);
|
||||||
ImGui::InvisibleButton("##PoolMode", ImVec2(toggleW, toggleH));
|
ImGui::InvisibleButton("##PoolMode", ImVec2(toggleW, toggleH));
|
||||||
if (ImGui::IsItemClicked() && !s_pool_mode && !soloMiningActive) {
|
if (!liteBuild && ImGui::IsItemClicked() && !s_pool_mode && !soloMiningActive) {
|
||||||
s_pool_mode = true;
|
s_pool_mode = true;
|
||||||
app->settings()->setPoolMode(true);
|
app->settings()->setPoolMode(true);
|
||||||
app->settings()->save();
|
app->settings()->save();
|
||||||
@@ -308,7 +318,7 @@ static void RenderMiningTabContent(App* app)
|
|||||||
// so no need to call stopMining() — it would just set the
|
// so no need to call stopMining() — it would just set the
|
||||||
// toggle-in-progress flag and make the button show "STARTING".
|
// toggle-in-progress flag and make the button show "STARTING".
|
||||||
}
|
}
|
||||||
if (poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
if (!liteBuild && poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
if (poolHov && soloMiningActive && !s_pool_mode) {
|
if (poolHov && soloMiningActive && !s_pool_mode) {
|
||||||
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
|
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
|
||||||
}
|
}
|
||||||
@@ -1925,8 +1935,12 @@ static void RenderMiningTabContent(App* app)
|
|||||||
&& tx.memo.find("Mining Pool payout") != std::string::npos);
|
&& tx.memo.find("Mining Pool payout") != std::string::npos);
|
||||||
if (isSoloMined || isPoolPayout) {
|
if (isSoloMined || isPoolPayout) {
|
||||||
// Apply earnings filter
|
// Apply earnings filter
|
||||||
if (s_earnings_filter == 1 && !isSoloMined) continue;
|
if (app->isLiteBuild()) {
|
||||||
if (s_earnings_filter == 2 && !isPoolPayout) continue;
|
if (s_earnings_filter == 1 && !isPoolPayout) continue;
|
||||||
|
} else {
|
||||||
|
if (s_earnings_filter == 1 && !isSoloMined) continue;
|
||||||
|
if (s_earnings_filter == 2 && !isPoolPayout) continue;
|
||||||
|
}
|
||||||
|
|
||||||
double amt = std::abs(tx.amount);
|
double amt = std::abs(tx.amount);
|
||||||
minedAllTime += amt;
|
minedAllTime += amt;
|
||||||
@@ -1970,8 +1984,10 @@ static void RenderMiningTabContent(App* app)
|
|||||||
|
|
||||||
// === Earnings filter toggle button (top-right of card) ===
|
// === Earnings filter toggle button (top-right of card) ===
|
||||||
{
|
{
|
||||||
const char* filterLabels[] = { TR("mining_filter_all"), TR("mining_solo"), TR("mining_pool") };
|
const bool liteBuild = app->isLiteBuild();
|
||||||
const char* filterIcons[] = { ICON_MD_FUNCTIONS, ICON_MD_MEMORY, ICON_MD_CLOUD };
|
const char* filterLabels[] = { TR("mining_filter_all"), liteBuild ? TR("mining_pool") : TR("mining_solo"), TR("mining_pool") };
|
||||||
|
const char* filterIcons[] = { ICON_MD_FUNCTIONS, liteBuild ? ICON_MD_CLOUD : ICON_MD_MEMORY, ICON_MD_CLOUD };
|
||||||
|
const int filterCount = liteBuild ? 2 : 3;
|
||||||
const char* curIcon = filterIcons[s_earnings_filter];
|
const char* curIcon = filterIcons[s_earnings_filter];
|
||||||
const char* curLabel = filterLabels[s_earnings_filter];
|
const char* curLabel = filterLabels[s_earnings_filter];
|
||||||
|
|
||||||
@@ -2011,13 +2027,13 @@ static void RenderMiningTabContent(App* app)
|
|||||||
ImGui::SetCursorScreenPos(bMin);
|
ImGui::SetCursorScreenPos(bMin);
|
||||||
ImGui::InvisibleButton("##EarningsFilter", ImVec2(btnW, btnH));
|
ImGui::InvisibleButton("##EarningsFilter", ImVec2(btnW, btnH));
|
||||||
if (ImGui::IsItemClicked()) {
|
if (ImGui::IsItemClicked()) {
|
||||||
s_earnings_filter = (s_earnings_filter + 1) % 3;
|
s_earnings_filter = (s_earnings_filter + 1) % filterCount;
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
const char* tips[] = {
|
const char* tips[] = {
|
||||||
TR("mining_filter_tip_all"),
|
TR("mining_filter_tip_all"),
|
||||||
TR("mining_filter_tip_solo"),
|
liteBuild ? TR("mining_filter_tip_pool") : TR("mining_filter_tip_solo"),
|
||||||
TR("mining_filter_tip_pool")
|
TR("mining_filter_tip_pool")
|
||||||
};
|
};
|
||||||
ImGui::SetTooltip("%s", tips[s_earnings_filter]);
|
ImGui::SetTooltip("%s", tips[s_earnings_filter]);
|
||||||
|
|||||||
Reference in New Issue
Block a user