diff options
Diffstat (limited to 'src/gui.cpp')
| -rw-r--r-- | src/gui.cpp | 239 |
1 files changed, 232 insertions, 7 deletions
diff --git a/src/gui.cpp b/src/gui.cpp index 9b3802b..ff6059a 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -11,6 +11,11 @@ | |||
| 11 | #include <ctime> | 11 | #include <ctime> |
| 12 | #include <cstdio> | 12 | #include <cstdio> |
| 13 | #include <sstream> | 13 | #include <sstream> |
| 14 | #ifdef _WIN32 | ||
| 15 | #include <shellapi.h> // ShellExecuteA, to open the website in the browser | ||
| 16 | #else | ||
| 17 | #include <unistd.h> | ||
| 18 | #endif | ||
| 14 | #include <iomanip> | 19 | #include <iomanip> |
| 15 | #include <atomic> | 20 | #include <atomic> |
| 16 | #include <mutex> | 21 | #include <mutex> |
| @@ -173,7 +178,11 @@ static bool PathIsInsideDataDir(const std::string& strPath) | |||
| 173 | 178 | ||
| 174 | static void SetDefaultBackupPath() | 179 | static void SetDefaultBackupPath() |
| 175 | { | 180 | { |
| 176 | std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + ".dat"; | 181 | // Match the extension to the live backend: BackupWallet copies wallet.sqlite |
| 182 | // under the SQLite backend and wallet.dat under Berkeley DB, so a ".dat" | ||
| 183 | // default name would put SQLite contents in a misleadingly-named file. | ||
| 184 | const char* pszExt = WalletSQLiteRuntimeActive() ? ".sqlite" : ".dat"; | ||
| 185 | std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + pszExt; | ||
| 177 | strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1); | 186 | strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1); |
| 178 | g_backupPath[sizeof(g_backupPath)-1] = '\0'; | 187 | g_backupPath[sizeof(g_backupPath)-1] = '\0'; |
| 179 | } | 188 | } |
| @@ -1280,11 +1289,11 @@ static void DrawWalletSafetyDialog() | |||
| 1280 | } | 1289 | } |
| 1281 | ImGui::Text("Phrase-backed spendable balance: %s BTF", | 1290 | ImGui::Text("Phrase-backed spendable balance: %s BTF", |
| 1282 | FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str()); | 1291 | FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str()); |
| 1283 | ImGui::Text("Wallet.dat-only spendable balance: %s BTF", | 1292 | ImGui::Text("Not phrase-backed spendable balance: %s BTF", |
| 1284 | FmtMoney(g_recoveryAudit.nLegacyCredit).c_str()); | 1293 | FmtMoney(g_recoveryAudit.nLegacyCredit).c_str()); |
| 1285 | ImGui::Text("Phrase-backed immature mining rewards: %s BTF", | 1294 | ImGui::Text("Phrase-backed immature mining rewards: %s BTF", |
| 1286 | FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str()); | 1295 | FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str()); |
| 1287 | ImGui::Text("Wallet.dat-only immature mining rewards: %s BTF", | 1296 | ImGui::Text("Not phrase-backed immature mining rewards: %s BTF", |
| 1288 | FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str()); | 1297 | FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str()); |
| 1289 | if (!g_recoveryAudit.fDeriveComplete) | 1298 | if (!g_recoveryAudit.fDeriveComplete) |
| 1290 | { | 1299 | { |
| @@ -1298,8 +1307,8 @@ static void DrawWalletSafetyDialog() | |||
| 1298 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f)); | 1307 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f)); |
| 1299 | ImGui::TextWrapped( | 1308 | ImGui::TextWrapped( |
| 1300 | "Some coins are on keys the phrase does not reproduce. " | 1309 | "Some coins are on keys the phrase does not reproduce. " |
| 1301 | "Keep wallet.dat backups until that balance has been moved to a " | 1310 | "Keep a file backup of this wallet until that balance has been " |
| 1302 | "phrase-backed address."); | 1311 | "moved to a phrase-backed address."); |
| 1303 | ImGui::PopStyleColor(); | 1312 | ImGui::PopStyleColor(); |
| 1304 | } | 1313 | } |
| 1305 | else if (HaveHDSeed() && | 1314 | else if (HaveHDSeed() && |
| @@ -1395,16 +1404,38 @@ static void DrawDiagnosticsDialog() | |||
| 1395 | ImGui::End(); | 1404 | ImGui::End(); |
| 1396 | } | 1405 | } |
| 1397 | 1406 | ||
| 1407 | static void OpenURL(const char* pszUrl) | ||
| 1408 | { | ||
| 1409 | #ifdef _WIN32 | ||
| 1410 | ShellExecuteA(NULL, "open", pszUrl, NULL, NULL, SW_SHOWNORMAL); | ||
| 1411 | #else | ||
| 1412 | std::string cmd = std::string("xdg-open '") + pszUrl + "' >/dev/null 2>&1 &"; | ||
| 1413 | int r = system(cmd.c_str()); | ||
| 1414 | (void)r; | ||
| 1415 | #endif | ||
| 1416 | } | ||
| 1417 | |||
| 1398 | static void DrawAboutDialog() | 1418 | static void DrawAboutDialog() |
| 1399 | { | 1419 | { |
| 1400 | if (!g_showAbout) return; | 1420 | if (!g_showAbout) return; |
| 1401 | ImGui::SetNextWindowSize(ImVec2(380.0f, 170.0f), ImGuiCond_Always); | 1421 | ImGui::SetNextWindowSize(ImVec2(380.0f, 205.0f), ImGuiCond_Always); |
| 1402 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), | 1422 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), |
| 1403 | ImGuiCond_Always, ImVec2(0.5f, 0.5f)); | 1423 | ImGuiCond_Always, ImVec2(0.5f, 0.5f)); |
| 1404 | if (ImGui::Begin("About Bitflash", &g_showAbout, | 1424 | if (ImGui::Begin("About Bitflash", &g_showAbout, |
| 1405 | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) | 1425 | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) |
| 1406 | { | 1426 | { |
| 1407 | ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Bitflash BTF v1.1.0"); | 1427 | ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), |
| 1428 | "Bitflash BTF v" BITFLASH_VERSION_STRING); | ||
| 1429 | ImGui::Spacing(); | ||
| 1430 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.40f, 0.70f, 1.0f, 1.0f)); | ||
| 1431 | ImGui::TextUnformatted("https://bitflash.network"); | ||
| 1432 | ImGui::PopStyleColor(); | ||
| 1433 | if (ImGui::IsItemHovered()) | ||
| 1434 | { | ||
| 1435 | ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); | ||
| 1436 | if (ImGui::IsMouseClicked(0)) | ||
| 1437 | OpenURL("https://bitflash.network"); | ||
| 1438 | } | ||
| 1408 | ImGui::Spacing(); | 1439 | ImGui::Spacing(); |
| 1409 | ImGui::TextWrapped( | 1440 | ImGui::TextWrapped( |
| 1410 | "CPU-only cryptocurrency. RandomX proof of work. " | 1441 | "CPU-only cryptocurrency. RandomX proof of work. " |
| @@ -1417,6 +1448,184 @@ static void DrawAboutDialog() | |||
| 1417 | ImGui::End(); | 1448 | ImGui::End(); |
| 1418 | } | 1449 | } |
| 1419 | 1450 | ||
| 1451 | // First-run offer to move a Berkeley DB wallet to the SQLite backend. Only the | ||
| 1452 | // button actions touch anything: "Convert" exports wallet.dat to wallet.sqlite | ||
| 1453 | // and records the backend marker (wallet.dat is left untouched, so a restart | ||
| 1454 | // picks up SQLite while the old file stays as a fallback); "Keep Berkeley DB" | ||
| 1455 | // just records that choice so the offer does not return; "Decide later" leaves | ||
| 1456 | // no marker and asks again next start. | ||
| 1457 | static bool g_showBackendWizard = false; | ||
| 1458 | static std::string g_wizardStatus; | ||
| 1459 | static bool g_wizardConverted = false; | ||
| 1460 | static int g_restartArgc = 0; | ||
| 1461 | static char** g_restartArgv = NULL; | ||
| 1462 | |||
| 1463 | #ifdef _WIN32 | ||
| 1464 | // Quote one argument so CommandLineToArgvW parses it back to exactly this | ||
| 1465 | // string: only backslashes that immediately precede a double quote (including | ||
| 1466 | // the closing one) are doubled, so ordinary Windows paths are not mangled. | ||
| 1467 | static std::string QuoteArgWin(const std::string& a) | ||
| 1468 | { | ||
| 1469 | if (!a.empty() && a.find_first_of(" \t\"") == std::string::npos) | ||
| 1470 | return a; | ||
| 1471 | std::string r = "\""; | ||
| 1472 | size_t bs = 0; | ||
| 1473 | for (size_t i = 0; i < a.size(); i++) | ||
| 1474 | { | ||
| 1475 | if (a[i] == '\\') { bs++; continue; } | ||
| 1476 | if (a[i] == '"') { r.append(bs * 2 + 1, '\\'); r += '"'; bs = 0; continue; } | ||
| 1477 | r.append(bs, '\\'); bs = 0; r += a[i]; | ||
| 1478 | } | ||
| 1479 | r.append(bs * 2, '\\'); | ||
| 1480 | r += "\""; | ||
| 1481 | return r; | ||
| 1482 | } | ||
| 1483 | #endif | ||
| 1484 | |||
| 1485 | // Relaunch Bitflash with the same arguments and datadir, then ask this instance | ||
| 1486 | // to exit -- but only once the relaunch has actually started, so a failure never | ||
| 1487 | // leaves the user with no node running. The new instance waits (-restartwait) | ||
| 1488 | // before it opens the network port and the block index, so this one is gone | ||
| 1489 | // first and they never fight over the same files. | ||
| 1490 | static void RestartApplication() | ||
| 1491 | { | ||
| 1492 | bool fLaunched = false; | ||
| 1493 | #ifdef _WIN32 | ||
| 1494 | char exePath[MAX_PATH]; | ||
| 1495 | if (GetModuleFileNameA(NULL, exePath, MAX_PATH) == 0) | ||
| 1496 | return; | ||
| 1497 | std::string strCmd = QuoteArgWin(exePath); | ||
| 1498 | bool fHasDataDir = false; | ||
| 1499 | for (int i = 1; i < g_restartArgc; i++) | ||
| 1500 | { | ||
| 1501 | std::string a = g_restartArgv[i] ? g_restartArgv[i] : ""; | ||
| 1502 | if (a.find("-datadir=") == 0 || a.find("/datadir=") == 0) | ||
| 1503 | fHasDataDir = true; | ||
| 1504 | strCmd += " " + QuoteArgWin(a); | ||
| 1505 | } | ||
| 1506 | if (!fHasDataDir) | ||
| 1507 | strCmd += " " + QuoteArgWin("-datadir=" + GetAppDir()); | ||
| 1508 | strCmd += " -restartwait"; | ||
| 1509 | |||
| 1510 | std::vector<char> cmdBuf(strCmd.begin(), strCmd.end()); | ||
| 1511 | cmdBuf.push_back('\0'); | ||
| 1512 | STARTUPINFOA si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); | ||
| 1513 | PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); | ||
| 1514 | if (CreateProcessA(NULL, &cmdBuf[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) | ||
| 1515 | { | ||
| 1516 | CloseHandle(pi.hProcess); | ||
| 1517 | CloseHandle(pi.hThread); | ||
| 1518 | fLaunched = true; | ||
| 1519 | } | ||
| 1520 | #else | ||
| 1521 | pid_t pid = fork(); | ||
| 1522 | if (pid == 0) | ||
| 1523 | { | ||
| 1524 | // Child: wait for the parent to release the port and files, then re-exec. | ||
| 1525 | // /proc/self/exe is the reliable absolute path; argv[0] may be relative | ||
| 1526 | // or a bare name that execv() cannot resolve. | ||
| 1527 | sleep(2); | ||
| 1528 | char exePath[4096]; | ||
| 1529 | ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); | ||
| 1530 | if (len > 0) | ||
| 1531 | { | ||
| 1532 | exePath[len] = '\0'; | ||
| 1533 | execv(exePath, g_restartArgv); | ||
| 1534 | } | ||
| 1535 | if (g_restartArgv) | ||
| 1536 | execv(g_restartArgv[0], g_restartArgv); | ||
| 1537 | _exit(127); | ||
| 1538 | } | ||
| 1539 | fLaunched = (pid > 0); | ||
| 1540 | #endif | ||
| 1541 | |||
| 1542 | if (fLaunched) | ||
| 1543 | { | ||
| 1544 | fShutdown = true; | ||
| 1545 | if (glfwGetCurrentContext()) | ||
| 1546 | glfwSetWindowShouldClose(glfwGetCurrentContext(), true); | ||
| 1547 | } | ||
| 1548 | else | ||
| 1549 | g_wizardStatus = "Could not relaunch automatically -- please restart " | ||
| 1550 | "Bitflash yourself to switch to the SQLite wallet."; | ||
| 1551 | } | ||
| 1552 | |||
| 1553 | static void DrawBackendWizardDialog() | ||
| 1554 | { | ||
| 1555 | if (!g_showBackendWizard) return; | ||
| 1556 | ImGui::SetNextWindowSize(ImVec2(560.0f, 0.0f), ImGuiCond_Always); | ||
| 1557 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), | ||
| 1558 | ImGuiCond_Always, ImVec2(0.5f, 0.5f)); | ||
| 1559 | if (ImGui::Begin("Wallet Storage", &g_showBackendWizard, | ||
| 1560 | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) | ||
| 1561 | { | ||
| 1562 | if (g_wizardConverted) | ||
| 1563 | { | ||
| 1564 | ImGui::TextColored(ImVec4(0.35f, 1.0f, 0.45f, 1.0f), "Converted to SQLite."); | ||
| 1565 | ImGui::TextWrapped("%s", g_wizardStatus.c_str()); | ||
| 1566 | ImGui::Spacing(); | ||
| 1567 | ImGui::TextWrapped( | ||
| 1568 | "The switch takes effect when Bitflash restarts. Until then it is " | ||
| 1569 | "still running on the old Berkeley DB wallet. Your wallet.dat is " | ||
| 1570 | "kept untouched as a fallback."); | ||
| 1571 | ImGui::Spacing(); | ||
| 1572 | if (ImGui::Button("Restart Now", ImVec2(140.0f, 0.0f))) | ||
| 1573 | RestartApplication(); | ||
| 1574 | ImGui::SameLine(); | ||
| 1575 | if (ImGui::Button("Later", ImVec2(120.0f, 0.0f))) | ||
| 1576 | g_showBackendWizard = false; | ||
| 1577 | ImGui::Spacing(); | ||
| 1578 | ImGui::TextDisabled( | ||
| 1579 | "If you keep mining before restarting, new coins go to the old " | ||
| 1580 | "wallet until you switch."); | ||
| 1581 | } | ||
| 1582 | else | ||
| 1583 | { | ||
| 1584 | ImGui::TextWrapped( | ||
| 1585 | "Bitflash can store your wallet in a new SQLite format: a single " | ||
| 1586 | "file that is easier to back up and does not depend on the " | ||
| 1587 | "database/ directory beside it."); | ||
| 1588 | ImGui::Spacing(); | ||
| 1589 | ImGui::TextWrapped( | ||
| 1590 | "Your current Berkeley DB wallet (wallet.dat) is kept exactly as it " | ||
| 1591 | "is -- nothing is deleted, and you can go back to it at any time. " | ||
| 1592 | "The change takes effect after you restart."); | ||
| 1593 | ImGui::Spacing(); | ||
| 1594 | if (ImGui::Button("Convert to SQLite", ImVec2(180.0f, 0.0f))) | ||
| 1595 | { | ||
| 1596 | int nCopied = 0; | ||
| 1597 | std::string strError; | ||
| 1598 | if (DoConvertWalletToSQLite(nCopied, strError)) | ||
| 1599 | { | ||
| 1600 | g_wizardStatus = | ||
| 1601 | strprintf("%d record(s) copied to wallet.sqlite.", nCopied); | ||
| 1602 | g_wizardConverted = true; | ||
| 1603 | } | ||
| 1604 | else | ||
| 1605 | g_wizardStatus = "Conversion failed: " + strError; | ||
| 1606 | } | ||
| 1607 | ImGui::SameLine(); | ||
| 1608 | if (ImGui::Button("Keep Berkeley DB", ImVec2(160.0f, 0.0f))) | ||
| 1609 | { | ||
| 1610 | WriteWalletBackendMarker("bdb"); | ||
| 1611 | g_showBackendWizard = false; | ||
| 1612 | } | ||
| 1613 | ImGui::SameLine(); | ||
| 1614 | if (ImGui::Button("Decide later", ImVec2(120.0f, 0.0f))) | ||
| 1615 | g_showBackendWizard = false; | ||
| 1616 | |||
| 1617 | if (!g_wizardStatus.empty()) | ||
| 1618 | { | ||
| 1619 | ImGui::Spacing(); | ||
| 1620 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.35f, 1.0f)); | ||
| 1621 | ImGui::TextWrapped("%s", g_wizardStatus.c_str()); | ||
| 1622 | ImGui::PopStyleColor(); | ||
| 1623 | } | ||
| 1624 | } | ||
| 1625 | } | ||
| 1626 | ImGui::End(); | ||
| 1627 | } | ||
| 1628 | |||
| 1420 | // --------------------------------------------------------------------------- | 1629 | // --------------------------------------------------------------------------- |
| 1421 | // Entry point | 1630 | // Entry point |
| 1422 | // --------------------------------------------------------------------------- | 1631 | // --------------------------------------------------------------------------- |
| @@ -1483,6 +1692,21 @@ int RunGUI(int argc, char* argv[]) | |||
| 1483 | g_mineRadio = nMineMode; | 1692 | g_mineRadio = nMineMode; |
| 1484 | strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); | 1693 | strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); |
| 1485 | 1694 | ||
| 1695 | // Kept for the wizard's "Restart Now": relaunch with the same command line. | ||
| 1696 | g_restartArgc = argc; | ||
| 1697 | g_restartArgv = argv; | ||
| 1698 | |||
| 1699 | // First run on a Berkeley DB wallet with no recorded choice yet: offer to | ||
| 1700 | // convert to SQLite. Never when already on SQLite, once wallet.sqlite exists, | ||
| 1701 | // or once a choice has been recorded in the backend marker. | ||
| 1702 | if (!WalletSQLiteRuntimeActive() && | ||
| 1703 | FileExists((GetAppDir() + "/wallet.dat").c_str()) && | ||
| 1704 | !FileExists((GetAppDir() + "/wallet.sqlite").c_str()) && | ||
| 1705 | ReadWalletBackendMarker().empty()) | ||
| 1706 | { | ||
| 1707 | g_showBackendWizard = true; | ||
| 1708 | } | ||
| 1709 | |||
| 1486 | int frame = 0; | 1710 | int frame = 0; |
| 1487 | while (!glfwWindowShouldClose(window) && !fShutdown) | 1711 | while (!glfwWindowShouldClose(window) && !fShutdown) |
| 1488 | { | 1712 | { |
| @@ -1506,6 +1730,7 @@ int RunGUI(int argc, char* argv[]) | |||
| 1506 | DrawRestorePhraseDialog(); | 1730 | DrawRestorePhraseDialog(); |
| 1507 | DrawDiagnosticsDialog(); | 1731 | DrawDiagnosticsDialog(); |
| 1508 | DrawAboutDialog(); | 1732 | DrawAboutDialog(); |
| 1733 | DrawBackendWizardDialog(); | ||
| 1509 | DrawStatusBar(); | 1734 | DrawStatusBar(); |
| 1510 | 1735 | ||
| 1511 | ImGui::Render(); | 1736 | ImGui::Render(); |