From 5c87bc6e8774a1093fad22fe94a5da6795397acd Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 6 Jun 2026 18:16:28 -0500 Subject: [PATCH] feat(mining): "Update miner" button + dialog wiring the xmrig updater MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires util::XmrigUpdater into the GUI: - ui/windows/xmrig_download_dialog.h: a modal (mirrors BootstrapDownloadDialog) that drives the updater — Checking -> Up-to-date/Update-available -> Downloading/Verifying/Extracting -> Done/Failed, with a progress bar and a "verified against its published checksum" note. On success it persists the installed release tag to settings. Rendered each frame from App::render. - mining_tab: an "Update miner…" button in the pool section, disabled (with a tooltip) while xmrig is running so a live binary is never replaced. - settings: persist the installed DRG-XMRig tag (xmrig_version) for update detection. Both variants build; suite passes; GUI smoke-launched without crashing. Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 2 + src/config/settings.cpp | 2 + src/config/settings.h | 5 + src/ui/windows/mining_tab.cpp | 16 +++ src/ui/windows/xmrig_download_dialog.h | 160 +++++++++++++++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 src/ui/windows/xmrig_download_dialog.h diff --git a/src/app.cpp b/src/app.cpp index 8502852..f1e1012 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -46,6 +46,7 @@ #include "ui/windows/address_label_dialog.h" #include "ui/windows/address_transfer_dialog.h" #include "ui/windows/bootstrap_download_dialog.h" +#include "ui/windows/xmrig_download_dialog.h" #include "ui/windows/console_tab.h" #include "ui/pages/settings_page.h" #include "ui/theme.h" @@ -1529,6 +1530,7 @@ void App::render() // Bootstrap download from settings ui::BootstrapDownloadDialog::render(); + ui::XmrigDownloadDialog::render(); // Windows Defender antivirus help dialog renderAntivirusHelpDialog(); diff --git a/src/config/settings.cpp b/src/config/settings.cpp index d033291..6c55e0f 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -251,6 +251,7 @@ bool Settings::load(const std::string& path) if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get(); if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get(); if (j.contains("mine_when_idle")) mine_when_idle_ = j["mine_when_idle"].get(); + if (j.contains("xmrig_version")) xmrig_version_ = j["xmrig_version"].get(); if (j.contains("mine_idle_delay")) mine_idle_delay_= std::max(30, j["mine_idle_delay"].get()); if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get(); if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get(); @@ -383,6 +384,7 @@ bool Settings::save(const std::string& path) j["pool_hugepages"] = pool_hugepages_; j["pool_mode"] = pool_mode_; j["mine_when_idle"] = mine_when_idle_; + j["xmrig_version"] = xmrig_version_; j["mine_idle_delay"]= mine_idle_delay_; j["idle_thread_scaling"] = idle_thread_scaling_; j["idle_threads_active"] = idle_threads_active_; diff --git a/src/config/settings.h b/src/config/settings.h index b4a8ea7..60c48be 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -300,6 +300,10 @@ public: bool getPoolMode() const { return pool_mode_; } void setPoolMode(bool v) { pool_mode_ = v; } + // Installed DRG-XMRig release tag (for in-app miner update detection); empty if unknown/bundled. + std::string getXmrigVersion() const { return xmrig_version_; } + void setXmrigVersion(const std::string& v) { xmrig_version_ = v; } + // Mine when idle (auto-start mining when system is idle) bool getMineWhenIdle() const { return mine_when_idle_; } void setMineWhenIdle(bool v) { mine_when_idle_ = v; } @@ -428,6 +432,7 @@ private: bool pool_tls_ = false; bool pool_hugepages_ = true; bool pool_mode_ = false; // false=solo, true=pool + std::string xmrig_version_; // installed DRG-XMRig release tag (update detection) bool mine_when_idle_ = false; // auto-start mining when system idle int mine_idle_delay_= 120; // seconds of idle before mining starts bool idle_thread_scaling_ = false; // scale threads instead of start/stop diff --git a/src/ui/windows/mining_tab.cpp b/src/ui/windows/mining_tab.cpp index 3c5df51..e5cfdf5 100644 --- a/src/ui/windows/mining_tab.cpp +++ b/src/ui/windows/mining_tab.cpp @@ -6,6 +6,7 @@ #include "mining_benchmark.h" #include "mining_tab_helpers.h" #include "mining_pool_panel.h" +#include "xmrig_download_dialog.h" #include "../../app.h" #include "../../util/i18n.h" #include "../../config/version.h" @@ -1574,6 +1575,21 @@ static void RenderMiningTabContent(App* app) ImGui::Dummy(ImVec2(0, gap)); } + // ================================================================ + // POOL MINER — download / update the bundled DRG-XMRig miner from the project release server. + // (Pool mode uses xmrig; disabled while it's running so we never replace a live binary.) + // ================================================================ + if (s_pool_mode) { + const bool minerBusy = state.pool_mining.xmrig_running; + ImGui::BeginDisabled(minerBusy); + if (TactileButton("Update miner…", ImVec2(availWidth, 0))) + XmrigDownloadDialog::show(app); + ImGui::EndDisabled(); + if (minerBusy && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + ImGui::SetTooltip("Stop mining before updating the miner."); + ImGui::Dummy(ImVec2(0, gap)); + } + // ================================================================ // HASHRATE + STATS — Combined glass card: stat values on top, chart below // (Or full-card log view when toggled in pool mode) diff --git a/src/ui/windows/xmrig_download_dialog.h b/src/ui/windows/xmrig_download_dialog.h new file mode 100644 index 0000000..20cddc6 --- /dev/null +++ b/src/ui/windows/xmrig_download_dialog.h @@ -0,0 +1,160 @@ +// DragonX Wallet - ImGui Edition +// Copyright 2024-2026 The Hush Developers +// Released under the GPLv3 +// +// Modal dialog to download / update the DRG-XMRig miner from the project Gitea, driven by +// util::XmrigUpdater. States follow the updater: Checking -> UpToDate/UpdateAvailable -> +// Downloading/Verifying/Extracting -> Done / Failed. Header-only (static inline state), mirroring +// BootstrapDownloadDialog. The installed release tag is persisted to settings on success. + +#pragma once + +#include +#include + +#include "../../app.h" +#include "../../config/settings.h" +#include "../../resources/embedded_resources.h" // resources::getDaemonDirectory() +#include "../../util/xmrig_updater.h" +#include "../material/draw_helpers.h" +#include "../material/type.h" +#include "../material/colors.h" +#include "../theme.h" +#include "imgui.h" + +namespace dragonx { +namespace ui { + +class XmrigDownloadDialog { +public: + static void show(App* app) { + if (!app) return; + s_app = app; + s_open = true; + s_persisted = false; + s_updater = std::make_unique(); + s_updater->startCheck(app->settings() ? app->settings()->getXmrigVersion() : std::string()); + } + + static bool isOpen() { return s_open; } + + static void render() { + if (!s_open || !s_app || !s_updater) { + if (!s_open) s_updater.reset(); // closed: drop the updater (dtor joins the worker) + return; + } + using namespace material; + const float dp = Layout::dpiScale(); + if (BeginOverlayDialog("Update Miner", &s_open, 480.0f, 0.94f)) { + const auto p = s_updater->getProgress(); + using St = util::XmrigUpdater::State; + switch (p.state) { + case St::Checking: renderChecking(dp, p); break; + case St::UpToDate: + case St::UpdateAvailable: renderPrompt(dp, p); break; + case St::Downloading: + case St::Verifying: + case St::Extracting: renderProgress(dp, p); break; + case St::Done: renderDone(dp, p); break; + case St::Failed: renderFailed(dp, p); break; + default: break; + } + EndOverlayDialog(); + } + if (!s_open) s_updater.reset(); + } + +private: + using Progress = util::XmrigUpdater::Progress; + using St = util::XmrigUpdater::State; + + static float fullW() { return ImGui::GetContentRegionAvail().x; } + + static void renderChecking(float, const Progress&) { + using namespace material; + Type().text(TypeStyle::Body2, "Checking for the latest miner…"); + } + + static void renderPrompt(float, const Progress& p) { + using namespace material; + const std::string installed = p.installed_tag.empty() ? "none" : p.installed_tag; + if (p.state == St::UpdateAvailable) { + Type().text(TypeStyle::Subtitle2, "A new miner is available"); + ImGui::Spacing(); + ImGui::Text("Latest: %s", p.latest_tag.c_str()); + ImGui::Text("Installed: %s", installed.c_str()); + ImGui::Spacing(); + Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), + "The download is verified against its published SHA-256 checksum before install."); + ImGui::Spacing(); + if (TactileButton("Download & install", ImVec2(fullW(), 0))) + s_updater->startInstall(resources::getDaemonDirectory()); + } else { + Type().text(TypeStyle::Subtitle2, "The miner is up to date"); + ImGui::Spacing(); + ImGui::Text("Installed: %s", p.latest_tag.c_str()); + ImGui::Spacing(); + if (TactileButton("Reinstall", ImVec2(fullW(), 0))) + s_updater->startInstall(resources::getDaemonDirectory()); + } + ImGui::Spacing(); + if (TactileButton("Close", ImVec2(fullW(), 0))) s_open = false; + } + + static void renderProgress(float dp, const Progress& p) { + using namespace material; + const char* title = p.state == St::Downloading ? "Downloading…" + : p.state == St::Verifying ? "Verifying…" + : "Installing…"; + Type().text(TypeStyle::Subtitle2, title); + ImGui::Spacing(); + const float barH = 8.0f * dp; + const float barW = fullW(); + const ImVec2 bMin = ImGui::GetCursorScreenPos(); + const ImVec2 bMax(bMin.x + barW, bMin.y + barH); + ImDrawList* dl = ImGui::GetWindowDrawList(); + dl->AddRectFilled(bMin, bMax, IM_COL32(255, 255, 255, 30), 4.0f * dp); + const float fillW = barW * (p.percent / 100.0f); + if (fillW > 0) + dl->AddRectFilled(bMin, ImVec2(bMin.x + fillW, bMax.y), Primary(), 4.0f * dp); + ImGui::Dummy(ImVec2(0, barH)); + ImGui::Spacing(); + ImGui::Text("%s", p.status_text.c_str()); + } + + static void renderDone(float, const Progress& p) { + using namespace material; + if (!s_persisted) { + if (s_app->settings() && !p.latest_tag.empty()) { + s_app->settings()->setXmrigVersion(p.latest_tag); + s_app->settings()->save(); + } + s_persisted = true; + } + Type().textColored(TypeStyle::Subtitle2, Success(), "Miner installed"); + ImGui::Spacing(); + ImGui::Text("Version: %s", p.latest_tag.c_str()); + ImGui::Spacing(); + if (TactileButton("Close", ImVec2(fullW(), 0))) s_open = false; + } + + static void renderFailed(float, const Progress& p) { + using namespace material; + Type().textColored(TypeStyle::Subtitle2, Error(), "Update failed"); + ImGui::Spacing(); + ImGui::TextWrapped("%s", p.error.empty() ? "Unknown error." : p.error.c_str()); + ImGui::Spacing(); + if (TactileButton("Retry", ImVec2(fullW(), 0))) + s_updater->startInstall(resources::getDaemonDirectory()); + ImGui::Spacing(); + if (TactileButton("Close", ImVec2(fullW(), 0))) s_open = false; + } + + static inline bool s_open = false; + static inline bool s_persisted = false; + static inline App* s_app = nullptr; + static inline std::unique_ptr s_updater; +}; + +} // namespace ui +} // namespace dragonx