fix(win): route shell-outs through a windowless helper (no cmd.exe flash)
_popen/_popen-style shell-outs flash a cmd.exe console window on Windows. Add
Platform::runHiddenCapture() — CreateProcess + CREATE_NO_WINDOW capturing stdout on
Windows, popen on POSIX — and route the remaining shell-outs through it:
- GPU-aware idle detection (getGpuUtilization: "where nvidia-smi" / "nvidia-smi --query-gpu")
- xmrig discovery + version (findXmrigBinary "where xmrig.exe"; "<bin> --version", stderr merged)
- wallet-rebuild helper (app_network) — keeps its exit-code check via the new exitCode out-param
None of these are on the launch path (that was the daemon spawn, fixed in a2f84be); each
would flash a console only when it ran (idle-GPU mining, mining tab, wallet recovery).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<int>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user