From 477d57b9a01102b6f79ad9b10b2dcfd280faa1cb Mon Sep 17 00:00:00 2001 From: bitflash Date: Tue, 18 Aug 2026 16:26:34 -0300 Subject: gui: offer to convert a Berkeley DB wallet to SQLite on first run A first-run dialog for an existing wallet.dat with no backend choice yet: "Convert to SQLite" exports the wallet and records the sqlite marker so the next start opens it; "Keep Berkeley DB" records that choice so the offer does not return; "Decide later" leaves no marker and asks again. wallet.dat is never touched, and the change takes effect on restart -- matching the restart-to-apply model. The conversion itself is DoConvertWalletToSQLite(), a headless-testable function the button calls: it reuses the export path (extracted as ExportActiveWalletToSQLite, shared with -walletsqliteexport, which keeps its own specific messages), refuses if already on SQLite or if a wallet.sqlite exists, and writes the marker only after a verified export. New wallet-convert self-test proves the action end to end: build a Berkeley DB wallet, convert, and confirm wallet.sqlite + the marker are written, wallet.dat is left untouched, a second convert refuses, and a flagless restart opens the converted SQLite wallet with its phrase intact. All wallet suites pass. The modal itself is thin UI over this tested action. --- src/gui.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index 8dcb854..89a7ab6 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1391,6 +1391,84 @@ static void DrawAboutDialog() ImGui::End(); } +// First-run offer to move a Berkeley DB wallet to the SQLite backend. Only the +// button actions touch anything: "Convert" exports wallet.dat to wallet.sqlite +// and records the backend marker (wallet.dat is left untouched, so a restart +// picks up SQLite while the old file stays as a fallback); "Keep Berkeley DB" +// just records that choice so the offer does not return; "Decide later" leaves +// no marker and asks again next start. +static bool g_showBackendWizard = false; +static std::string g_wizardStatus; +static bool g_wizardConverted = false; + +static void DrawBackendWizardDialog() +{ + if (!g_showBackendWizard) return; + ImGui::SetNextWindowSize(ImVec2(560.0f, 0.0f), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + if (ImGui::Begin("Wallet Storage", &g_showBackendWizard, + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) + { + if (g_wizardConverted) + { + ImGui::TextColored(ImVec4(0.35f, 1.0f, 0.45f, 1.0f), "Converted to SQLite."); + ImGui::TextWrapped("%s", g_wizardStatus.c_str()); + ImGui::Spacing(); + ImGui::TextWrapped( + "Restart Bitflash to start using the SQLite wallet. Your Berkeley " + "DB wallet.dat is kept untouched as a fallback."); + ImGui::Spacing(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) + g_showBackendWizard = false; + } + else + { + ImGui::TextWrapped( + "Bitflash can store your wallet in a new SQLite format: a single " + "file that is easier to back up and does not depend on the " + "database/ directory beside it."); + ImGui::Spacing(); + ImGui::TextWrapped( + "Your current Berkeley DB wallet (wallet.dat) is kept exactly as it " + "is -- nothing is deleted, and you can go back to it at any time. " + "The change takes effect after you restart."); + ImGui::Spacing(); + if (ImGui::Button("Convert to SQLite", ImVec2(180.0f, 0.0f))) + { + int nCopied = 0; + std::string strError; + if (DoConvertWalletToSQLite(nCopied, strError)) + { + g_wizardStatus = + strprintf("%d record(s) copied to wallet.sqlite.", nCopied); + g_wizardConverted = true; + } + else + g_wizardStatus = "Conversion failed: " + strError; + } + ImGui::SameLine(); + if (ImGui::Button("Keep Berkeley DB", ImVec2(160.0f, 0.0f))) + { + WriteWalletBackendMarker("bdb"); + g_showBackendWizard = false; + } + ImGui::SameLine(); + if (ImGui::Button("Decide later", ImVec2(120.0f, 0.0f))) + g_showBackendWizard = false; + + if (!g_wizardStatus.empty()) + { + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.35f, 1.0f)); + ImGui::TextWrapped("%s", g_wizardStatus.c_str()); + ImGui::PopStyleColor(); + } + } + } + ImGui::End(); +} + // --------------------------------------------------------------------------- // Entry point // --------------------------------------------------------------------------- @@ -1457,6 +1535,17 @@ int RunGUI(int argc, char* argv[]) g_mineRadio = nMineMode; strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); + // First run on a Berkeley DB wallet with no recorded choice yet: offer to + // convert to SQLite. Never when already on SQLite, once wallet.sqlite exists, + // or once a choice has been recorded in the backend marker. + if (!WalletSQLiteRuntimeActive() && + FileExists((GetAppDir() + "/wallet.dat").c_str()) && + !FileExists((GetAppDir() + "/wallet.sqlite").c_str()) && + ReadWalletBackendMarker().empty()) + { + g_showBackendWizard = true; + } + int frame = 0; while (!glfwWindowShouldClose(window) && !fShutdown) { @@ -1480,6 +1569,7 @@ int RunGUI(int argc, char* argv[]) DrawRestorePhraseDialog(); DrawDiagnosticsDialog(); DrawAboutDialog(); + DrawBackendWizardDialog(); DrawStatusBar(); ImGui::Render(); -- cgit v1.2.3 From 8a47568dcfec8869bc08c555f0070be2d7ddddce Mon Sep 17 00:00:00 2001 From: bitflash Date: Wed, 19 Aug 2026 09:23:01 -0300 Subject: gui: add a "Restart Now" button to the conversion wizard After converting to SQLite the wizard said "restart to apply" but offered no way to do it, so a user could close the dialog and keep mining on the old Berkeley DB wallet without realizing the switch had not taken effect -- and coins mined in the meantime land only in wallet.dat, not the SQLite wallet they think they are now on. The post-conversion screen now offers "Restart Now", which relaunches Bitflash with the same command line and datadir (a short delay lets this instance release the network port and block-index files first) and asks this one to exit, so mining resumes on the SQLite wallet. "Later" keeps the old behaviour. A dimmed line warns that mining before the restart still credits the old wallet. wallet.dat is still never touched. The conversion action itself is unchanged (wallet-convert self-test still green); this is GUI wiring only. --- src/gui.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 3 deletions(-) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index 89a7ab6..81dd8e0 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -11,6 +11,9 @@ #include #include #include +#ifndef _WIN32 +#include +#endif #include #include #include @@ -1400,6 +1403,67 @@ static void DrawAboutDialog() static bool g_showBackendWizard = false; static std::string g_wizardStatus; static bool g_wizardConverted = false; +static int g_restartArgc = 0; +static char** g_restartArgv = NULL; + +// Relaunch Bitflash with the same arguments and datadir, then ask this instance +// to exit. A short delay lets the old process release the network port and the +// block-index files before the new one opens them, so mining resumes cleanly on +// the SQLite wallet instead of both instances fighting over the same files. +static void RestartApplication() +{ +#ifdef _WIN32 + char exePath[MAX_PATH]; + if (GetModuleFileNameA(NULL, exePath, MAX_PATH) == 0) + return; + std::string strArgs; + bool fHasDataDir = false; + for (int i = 1; i < g_restartArgc; i++) + { + std::string a = g_restartArgv[i] ? g_restartArgv[i] : ""; + if (a.find("-datadir=") == 0 || a.find("/datadir=") == 0) + fHasDataDir = true; + strArgs += " \"" + a + "\""; + } + if (!fHasDataDir) + strArgs += " \"-datadir=" + GetAppDir() + "\""; + + std::string strBat = GetAppDir() + "\\bitflash-restart.bat"; + FILE* pf = fopen(strBat.c_str(), "wb"); + if (pf) + { + // ping is the reliable no-console delay on Windows; timeout needs a tty. + fprintf(pf, + "@echo off\r\n" + "ping 127.0.0.1 -n 3 >nul\r\n" + "start \"\" \"%s\"%s\r\n" + "del \"%%~f0\"\r\n", + exePath, strArgs.c_str()); + fclose(pf); + std::string strCmd = "cmd /c \"" + strBat + "\""; + STARTUPINFOA si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); + PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); + if (CreateProcessA(NULL, (char*)strCmd.c_str(), NULL, NULL, FALSE, + CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + } +#else + pid_t pid = fork(); + if (pid == 0) + { + sleep(2); + if (g_restartArgv) + execv(g_restartArgv[0], g_restartArgv); + _exit(127); + } +#endif + fShutdown = true; + if (glfwGetCurrentContext()) + glfwSetWindowShouldClose(glfwGetCurrentContext(), true); +} static void DrawBackendWizardDialog() { @@ -1416,11 +1480,19 @@ static void DrawBackendWizardDialog() ImGui::TextWrapped("%s", g_wizardStatus.c_str()); ImGui::Spacing(); ImGui::TextWrapped( - "Restart Bitflash to start using the SQLite wallet. Your Berkeley " - "DB wallet.dat is kept untouched as a fallback."); + "The switch takes effect when Bitflash restarts. Until then it is " + "still running on the old Berkeley DB wallet. Your wallet.dat is " + "kept untouched as a fallback."); ImGui::Spacing(); - if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) + if (ImGui::Button("Restart Now", ImVec2(140.0f, 0.0f))) + RestartApplication(); + ImGui::SameLine(); + if (ImGui::Button("Later", ImVec2(120.0f, 0.0f))) g_showBackendWizard = false; + ImGui::Spacing(); + ImGui::TextDisabled( + "If you keep mining before restarting, new coins go to the old " + "wallet until you switch."); } else { @@ -1535,6 +1607,10 @@ int RunGUI(int argc, char* argv[]) g_mineRadio = nMineMode; strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); + // Kept for the wizard's "Restart Now": relaunch with the same command line. + g_restartArgc = argc; + g_restartArgv = argv; + // First run on a Berkeley DB wallet with no recorded choice yet: offer to // convert to SQLite. Never when already on SQLite, once wallet.sqlite exists, // or once a choice has been recorded in the backend marker. -- cgit v1.2.3 From cf155d40ddd260975240e72b5e72cd3decc30291 Mon Sep 17 00:00:00 2001 From: bitflash Date: Wed, 19 Aug 2026 09:30:40 -0300 Subject: gui/wallet: name backups by backend, and stop saying "wallet.dat" for phrase gaps Two things that read wrong once the wallet is on SQLite: - The Wallet Safety backup dialog defaulted the file name to .dat, but BackupWallet copies wallet.sqlite under the SQLite backend -- so the suggested name put SQLite contents in a wallet-backup-*.dat file. The default now matches the live backend (.sqlite on SQLite, .dat on BDB). - The recovery audit called coins on non-phrase keys "wallet.dat-only" and told the user to "keep wallet.dat backups". Under SQLite those coins live in wallet.sqlite, not wallet.dat. Reworded to the backend-neutral truth: "not phrase-backed" balance, and "keep a file backup of this wallet" -- accurate on either backend. Same wording in the CLI -recoveryaudit and the GUI Wallet Safety panel. Display/naming only; audit numbers and BackupWallet are unchanged. All 13 wallet suites still pass. --- src/gui.cpp | 14 +++++++++----- src/walletcmd.cpp | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index 81dd8e0..672f0ff 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -175,7 +175,11 @@ static bool PathIsInsideDataDir(const std::string& strPath) static void SetDefaultBackupPath() { - std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + ".dat"; + // Match the extension to the live backend: BackupWallet copies wallet.sqlite + // under the SQLite backend and wallet.dat under Berkeley DB, so a ".dat" + // default name would put SQLite contents in a misleadingly-named file. + const char* pszExt = WalletSQLiteRuntimeActive() ? ".sqlite" : ".dat"; + std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + pszExt; strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1); g_backupPath[sizeof(g_backupPath)-1] = '\0'; } @@ -1257,11 +1261,11 @@ static void DrawWalletSafetyDialog() } ImGui::Text("Phrase-backed spendable balance: %s BTF", FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str()); - ImGui::Text("Wallet.dat-only spendable balance: %s BTF", + ImGui::Text("Not phrase-backed spendable balance: %s BTF", FmtMoney(g_recoveryAudit.nLegacyCredit).c_str()); ImGui::Text("Phrase-backed immature mining rewards: %s BTF", FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str()); - ImGui::Text("Wallet.dat-only immature mining rewards: %s BTF", + ImGui::Text("Not phrase-backed immature mining rewards: %s BTF", FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str()); if (!g_recoveryAudit.fDeriveComplete) { @@ -1275,8 +1279,8 @@ static void DrawWalletSafetyDialog() ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f)); ImGui::TextWrapped( "Some coins are on keys the phrase does not reproduce. " - "Keep wallet.dat backups until that balance has been moved to a " - "phrase-backed address."); + "Keep a file backup of this wallet until that balance has been " + "moved to a phrase-backed address."); ImGui::PopStyleColor(); } else if (HaveHDSeed() && diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index 1f1fd27..f59fc0c 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp @@ -429,13 +429,13 @@ int CmdRecoveryAudit() } printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); - printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", + printf(" not phrase-backed balance: %s BTF (%d transaction(s))\n", FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", FormatMoney(audit.nRecoverableImmatureCredit).c_str(), audit.nRecoverableImmatureTx); - printf(" wallet.dat-only immature: %s BTF (%d transaction(s))\n", + printf(" not phrase-backed immature: %s BTF (%d transaction(s))\n", FormatMoney(audit.nLegacyImmatureCredit).c_str(), audit.nLegacyImmatureTx); @@ -457,7 +457,7 @@ int CmdRecoveryAudit() { printf("\n"); printf("Warning: some coins are on keys the phrase does not reproduce.\n"); - printf("Keep wallet.dat backups until that balance has been moved to a phrase-backed address.\n"); + printf("Keep a file backup of this wallet until that balance has been moved to a phrase-backed address.\n"); fflush(stdout); return 2; } -- cgit v1.2.3 From d4c49085a10c0f7c3f5a1dcc72184938dca38fea Mon Sep 17 00:00:00 2001 From: bitflash Date: Wed, 19 Aug 2026 09:48:07 -0300 Subject: gui: harden the restart relaunch (ultra edge cases) Follow-up to the Restart Now button. Addresses the edge cases the review flagged, and drops the fragile batch layer entirely: - Windows no longer writes a .bat and shells through cmd (which re-parsed the arguments and could leave a stray file on failure). It builds the command line once, quoted correctly (only backslashes before a quote are doubled, so ordinary paths are not mangled), and CreateProcess launches the new instance directly. - The delay moved into the new instance: a -restartwait flag makes it wait ~2.5s before it opens the network port and the block index, so the old process is gone first. No shell delay hack. Verified: -restartwait adds the wait (0.16s -> 2.67s). - Linux re-execs /proc/self/exe (absolute) and only falls back to argv[0], which may be relative or a bare PATH name that execv cannot resolve. - This instance now exits only if the relaunch actually started; on failure it keeps running and tells the user to restart manually, so a failed relaunch never leaves them with no node. GUI/process wiring only; wallet code unchanged, suites still green. --- src/gui.cpp | 91 +++++++++++++++++++++++++++++++++++++------------------- src/main_gui.cpp | 8 +++++ 2 files changed, 69 insertions(+), 30 deletions(-) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index 672f0ff..296d20c 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1410,63 +1410,94 @@ static bool g_wizardConverted = false; static int g_restartArgc = 0; static char** g_restartArgv = NULL; +#ifdef _WIN32 +// Quote one argument so CommandLineToArgvW parses it back to exactly this +// string: only backslashes that immediately precede a double quote (including +// the closing one) are doubled, so ordinary Windows paths are not mangled. +static std::string QuoteArgWin(const std::string& a) +{ + if (!a.empty() && a.find_first_of(" \t\"") == std::string::npos) + return a; + std::string r = "\""; + size_t bs = 0; + for (size_t i = 0; i < a.size(); i++) + { + if (a[i] == '\\') { bs++; continue; } + if (a[i] == '"') { r.append(bs * 2 + 1, '\\'); r += '"'; bs = 0; continue; } + r.append(bs, '\\'); bs = 0; r += a[i]; + } + r.append(bs * 2, '\\'); + r += "\""; + return r; +} +#endif + // Relaunch Bitflash with the same arguments and datadir, then ask this instance -// to exit. A short delay lets the old process release the network port and the -// block-index files before the new one opens them, so mining resumes cleanly on -// the SQLite wallet instead of both instances fighting over the same files. +// to exit -- but only once the relaunch has actually started, so a failure never +// leaves the user with no node running. The new instance waits (-restartwait) +// before it opens the network port and the block index, so this one is gone +// first and they never fight over the same files. static void RestartApplication() { + bool fLaunched = false; #ifdef _WIN32 char exePath[MAX_PATH]; if (GetModuleFileNameA(NULL, exePath, MAX_PATH) == 0) return; - std::string strArgs; + std::string strCmd = QuoteArgWin(exePath); bool fHasDataDir = false; for (int i = 1; i < g_restartArgc; i++) { std::string a = g_restartArgv[i] ? g_restartArgv[i] : ""; if (a.find("-datadir=") == 0 || a.find("/datadir=") == 0) fHasDataDir = true; - strArgs += " \"" + a + "\""; + strCmd += " " + QuoteArgWin(a); } if (!fHasDataDir) - strArgs += " \"-datadir=" + GetAppDir() + "\""; - - std::string strBat = GetAppDir() + "\\bitflash-restart.bat"; - FILE* pf = fopen(strBat.c_str(), "wb"); - if (pf) + strCmd += " " + QuoteArgWin("-datadir=" + GetAppDir()); + strCmd += " -restartwait"; + + std::vector cmdBuf(strCmd.begin(), strCmd.end()); + cmdBuf.push_back('\0'); + STARTUPINFOA si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); + PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); + if (CreateProcessA(NULL, &cmdBuf[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { - // ping is the reliable no-console delay on Windows; timeout needs a tty. - fprintf(pf, - "@echo off\r\n" - "ping 127.0.0.1 -n 3 >nul\r\n" - "start \"\" \"%s\"%s\r\n" - "del \"%%~f0\"\r\n", - exePath, strArgs.c_str()); - fclose(pf); - std::string strCmd = "cmd /c \"" + strBat + "\""; - STARTUPINFOA si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); - PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); - if (CreateProcessA(NULL, (char*)strCmd.c_str(), NULL, NULL, FALSE, - CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) - { - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + fLaunched = true; } #else pid_t pid = fork(); if (pid == 0) { + // Child: wait for the parent to release the port and files, then re-exec. + // /proc/self/exe is the reliable absolute path; argv[0] may be relative + // or a bare name that execv() cannot resolve. sleep(2); + char exePath[4096]; + ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); + if (len > 0) + { + exePath[len] = '\0'; + execv(exePath, g_restartArgv); + } if (g_restartArgv) execv(g_restartArgv[0], g_restartArgv); _exit(127); } + fLaunched = (pid > 0); #endif - fShutdown = true; - if (glfwGetCurrentContext()) - glfwSetWindowShouldClose(glfwGetCurrentContext(), true); + + if (fLaunched) + { + fShutdown = true; + if (glfwGetCurrentContext()) + glfwSetWindowShouldClose(glfwGetCurrentContext(), true); + } + else + g_wizardStatus = "Could not relaunch automatically -- please restart " + "Bitflash yourself to switch to the SQLite wallet."; } static void DrawBackendWizardDialog() diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 6cd8b75..55ca8c6 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -509,6 +509,14 @@ int main(int argc, char* argv[]) ParseStartupArguments(argc, argv); +#ifdef _WIN32 + // Set by the GUI "Restart Now" button: give the previous instance a moment + // to release the network port and the block-index files before this one + // opens them, so a relaunch does not race the process it is replacing. + if (arg(argc, argv, "/restartwait") || arg(argc, argv, "-restartwait")) + Sleep(2500); +#endif + // Hidden self-test helper. It must run before LoadWallet(): several // storage-sanity scenarios deliberately make wallet.dat unsafe to load. string strSelfTestMutateWallet = -- cgit v1.2.3 From 2bf8941be913c113e90a62094e6b69848a87ed38 Mon Sep 17 00:00:00 2001 From: bitflash Date: Wed, 19 Aug 2026 10:00:29 -0300 Subject: gui: show the real version in About, add the website link The About dialog had "v1.1.0" hardcoded and no home for the release version, so it drifted. Add a single BITFLASH_VERSION_STRING in headers_core.h (bumped at release, unrelated to the protocol nVersion fields) and show it in About -- now v1.2.20. Fill the empty line under the version with a clickable https://bitflash.network that opens in the browser (ShellExecute on Windows, xdg-open elsewhere), and grow the dialog to fit it. --- src/gui.cpp | 30 +++++++++++++++++++++++++++--- src/headers_core.h | 5 +++++ 2 files changed, 32 insertions(+), 3 deletions(-) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index 296d20c..2a7f350 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -11,7 +11,9 @@ #include #include #include -#ifndef _WIN32 +#ifdef _WIN32 +#include // ShellExecuteA, to open the website in the browser +#else #include #endif #include @@ -1376,16 +1378,38 @@ static void DrawDiagnosticsDialog() ImGui::End(); } +static void OpenURL(const char* pszUrl) +{ +#ifdef _WIN32 + ShellExecuteA(NULL, "open", pszUrl, NULL, NULL, SW_SHOWNORMAL); +#else + std::string cmd = std::string("xdg-open '") + pszUrl + "' >/dev/null 2>&1 &"; + int r = system(cmd.c_str()); + (void)r; +#endif +} + static void DrawAboutDialog() { if (!g_showAbout) return; - ImGui::SetNextWindowSize(ImVec2(380.0f, 170.0f), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(380.0f, 205.0f), ImGuiCond_Always); ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); if (ImGui::Begin("About Bitflash", &g_showAbout, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) { - ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Bitflash BTF v1.1.0"); + ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), + "Bitflash BTF v" BITFLASH_VERSION_STRING); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.40f, 0.70f, 1.0f, 1.0f)); + ImGui::TextUnformatted("https://bitflash.network"); + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (ImGui::IsMouseClicked(0)) + OpenURL("https://bitflash.network"); + } ImGui::Spacing(); ImGui::TextWrapped( "CPU-only cryptocurrency. RandomX proof of work. " diff --git a/src/headers_core.h b/src/headers_core.h index 6c0c602..f890c2f 100644 --- a/src/headers_core.h +++ b/src/headers_core.h @@ -1,6 +1,11 @@ // Core node headers -- no GUI framework dependency. // Included by gui.cpp and all non-GUI source files. +// The one place the human-readable release version lives -- bump it at release. +// It is what the About dialog shows. Protocol and transaction versions elsewhere +// (nVersion fields) are unrelated and must not be changed for a release. +#define BITFLASH_VERSION_STRING "1.2.20" + #ifdef _MSC_VER #pragma warning(disable:4786) #endif -- cgit v1.2.3