diff --git a/src/app_network.cpp b/src/app_network.cpp index b05ca64..8641598 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -5043,21 +5043,12 @@ void App::rebuildWalletDatabase() const std::string tmpOut = datadir + "/wallet.rebuilt-" + std::string(ts) + ".tmp"; { std::error_code ec; fs::remove(tmpOut, ec); } // helper uses DB_EXCL — path must be fresh - // 3. Run the helper (src -> tmpOut). Quote both paths; capture its JSON line. - std::string cmd = "\"" + helper + "\" \"" + src + "\" \"" + tmpOut + "\""; -#ifdef _WIN32 - cmd = "\"" + cmd + "\""; // cmd.exe strips the outermost quotes - FILE* fp = _popen(cmd.c_str(), "r"); -#else - FILE* fp = popen(cmd.c_str(), "r"); -#endif - std::string jout; - if (fp) { char b[512]; while (std::fgets(b, sizeof b, fp)) jout += b; } -#ifdef _WIN32 - const int rc = fp ? _pclose(fp) : -1; -#else - const int rc = fp ? pclose(fp) : -1; -#endif + // 3. Run the helper (src -> tmpOut). Quote both paths; capture its JSON line. Windowless + // (runHiddenCapture) so a wallet rebuild never flashes a cmd.exe console; it runs the + // helper via CreateProcess directly on Windows, so no cmd.exe outer-quote wrap is needed. + const std::string cmd = "\"" + helper + "\" \"" + src + "\" \"" + tmpOut + "\""; + int rc = -1; + const std::string jout = util::Platform::runHiddenCapture(cmd, /*mergeStderr=*/false, &rc); DEBUG_LOGF("[App] wallet-rebuild helper rc=%d out=%s\n", rc, jout.c_str()); // 4. Verify-before-swap: the output must be a readable BDB with the fund-critical keys. diff --git a/src/daemon/xmrig_manager.cpp b/src/daemon/xmrig_manager.cpp index 4d76c21..776896b 100644 --- a/src/daemon/xmrig_manager.cpp +++ b/src/daemon/xmrig_manager.cpp @@ -23,6 +23,7 @@ #include #include "../util/logger.h" +#include "../util/platform.h" #include "../util/pool_registry.h" #ifdef _WIN32 @@ -145,32 +146,18 @@ std::string XmrigManager::findXmrigBinary() { return path; } - // Fallback: system PATH + // Fallback: system PATH — windowless so it never flashes a console. #ifdef _WIN32 - FILE* f = _popen("where xmrig.exe 2>nul", "r"); + std::string out = util::Platform::runHiddenCapture("where xmrig.exe"); #else - FILE* f = popen("which xmrig 2>/dev/null", "r"); -#endif - if (f) { - char line[512]; - if (fgets(line, sizeof(line), f)) { - std::string s(line); - while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) - s.pop_back(); - if (!s.empty() && fs::exists(s)) { -#ifdef _WIN32 - _pclose(f); -#else - pclose(f); -#endif - return s; - } - } -#ifdef _WIN32 - _pclose(f); -#else - pclose(f); + std::string out = util::Platform::runHiddenCapture("which xmrig"); #endif + { + std::string s = out; + const auto nl = s.find_first_of("\r\n"); // first line only + if (nl != std::string::npos) s.erase(nl); + while (!s.empty() && (s.back() == ' ' || s.back() == '\t')) s.pop_back(); + if (!s.empty() && fs::exists(s)) return s; } return {}; @@ -927,24 +914,10 @@ void XmrigManager::startVersionDetection() const bool binShellSafe = !bin.empty() && bin.find_first_of("\"'`$;&|<>^%\n\r") == std::string::npos; if (binShellSafe) { - const std::string cmd = "\"" + bin + "\" --version 2>&1"; -#ifdef _WIN32 - FILE* fp = _popen(cmd.c_str(), "r"); -#else - FILE* fp = popen(cmd.c_str(), "r"); -#endif - if (fp) { - std::string out; - char buf[256]; - size_t n; - while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) out.append(buf, n); -#ifdef _WIN32 - _pclose(fp); -#else - pclose(fp); -#endif - ver = parseMinerVersion(out); - } + // Windowless capture (mergeStderr: xmrig may print --version to stderr) — never flashes. + const std::string cmd = "\"" + bin + "\" --version"; + const std::string out = util::Platform::runHiddenCapture(cmd, /*mergeStderr=*/true); + if (!out.empty()) ver = parseMinerVersion(out); } std::lock_guard lk(g_installed_ver_mutex); g_installed_ver = ver; diff --git a/src/util/platform.cpp b/src/util/platform.cpp index c0f7d4f..b177f65 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -867,6 +867,70 @@ int Platform::getSystemIdleSeconds() // GPU utilization detection // ============================================================================ +std::string Platform::runHiddenCapture(const std::string& cmdLine, bool mergeStderr, int* exitCode) +{ + if (exitCode) *exitCode = -1; +#ifdef _WIN32 + SECURITY_ATTRIBUTES sa; + ZeroMemory(&sa, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + HANDLE hRead = NULL, hWrite = NULL; + if (!CreatePipe(&hRead, &hWrite, &sa, 0)) return {}; + SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0); // parent's read end stays private + + HANDLE hNul = INVALID_HANDLE_VALUE; + if (!mergeStderr) { + hNul = CreateFileA("NUL", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } + + STARTUPINFOA si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = hWrite; + si.hStdError = mergeStderr ? hWrite : hNul; + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + std::string cl = cmdLine; // CreateProcessA may modify lpCommandLine → needs a mutable buffer + std::string out; + if (CreateProcessA(NULL, cl.empty() ? NULL : &cl[0], NULL, NULL, TRUE, + CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + CloseHandle(hWrite); hWrite = NULL; // close our copy so ReadFile hits EOF when the child exits + if (hNul != INVALID_HANDLE_VALUE) { CloseHandle(hNul); hNul = INVALID_HANDLE_VALUE; } + char buf[4096]; + DWORD n = 0; + while (ReadFile(hRead, buf, sizeof(buf), &n, NULL) && n > 0) out.append(buf, n); + WaitForSingleObject(pi.hProcess, INFINITE); + if (exitCode) { + DWORD code = 0; + if (GetExitCodeProcess(pi.hProcess, &code)) *exitCode = static_cast(code); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + if (hWrite != NULL) CloseHandle(hWrite); + if (hNul != INVALID_HANDLE_VALUE) CloseHandle(hNul); + CloseHandle(hRead); + return out; +#else + const std::string full = cmdLine + (mergeStderr ? " 2>&1" : " 2>/dev/null"); + std::string out; + FILE* f = popen(full.c_str(), "r"); + if (!f) return out; + char buf[512]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) out.append(buf, n); + const int st = pclose(f); + if (exitCode) *exitCode = st; // raw status (matches prior pclose-based rc checks) + return out; +#endif +} + int Platform::getGpuUtilization() { #ifdef _WIN32 @@ -877,23 +941,16 @@ int Platform::getGpuUtilization() static bool s_has_nvidia = false; if (!s_tried_nvidia) { s_tried_nvidia = true; - FILE* f = _popen("where nvidia-smi 2>nul", "r"); - if (f) { - char buf[256]; - s_has_nvidia = (fgets(buf, sizeof(buf), f) != nullptr); - _pclose(f); - } + // Windowless (runHiddenCapture) so GPU-aware idle detection never flashes a cmd.exe console. + const std::string w = runHiddenCapture("where nvidia-smi"); + s_has_nvidia = (w.find_first_not_of(" \t\r\n") != std::string::npos); } if (s_has_nvidia) { - FILE* f = _popen("nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>nul", "r"); - if (f) { - char buf[64]; - int util = -1; - if (fgets(buf, sizeof(buf), f)) { - util = atoi(buf); - if (util < 0 || util > 100) util = -1; - } - _pclose(f); + const std::string o = runHiddenCapture( + "nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits"); + if (!o.empty()) { + int util = atoi(o.c_str()); + if (util < 0 || util > 100) util = -1; return util; } } diff --git a/src/util/platform.h b/src/util/platform.h index f5fbc79..baad2ce 100644 --- a/src/util/platform.h +++ b/src/util/platform.h @@ -179,6 +179,15 @@ public: * @return GPU busy percent, or -1 if unavailable. */ static int getGpuUtilization(); + + // Run a command line and capture its stdout WITHOUT ever popping a console window: Windows uses + // CreateProcess + CREATE_NO_WINDOW (a plain popen()/_popen() flashes a cmd.exe console), POSIX uses + // popen(). Use this instead of _popen for anything run while the GUI is up. `mergeStderr` folds the + // child's stderr into the result (like "2>&1"); otherwise stderr is discarded. `exitCode`, if given, + // receives the child's exit status (raw pclose() status on POSIX, GetExitCodeProcess on Windows; -1 + // if the process could not be launched). + static std::string runHiddenCapture(const std::string& cmdLine, bool mergeStderr = false, + int* exitCode = nullptr); }; /**