Files
navegator_dashboard/chrome_scanner.cpp
T
2026-05-09 18:11:21 +02:00

204 lines
6.8 KiB
C++

#include "chrome_scanner.h"
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
namespace navegator {
namespace {
#ifdef _WIN32
// Ejecuta un comando con CreateProcess + pipe stdout, OCULTANDO la consola.
// _popen siempre muestra cmd.exe en GUI apps WIN32_EXECUTABLE — para auto-rescan
// cada 2s eso parpadea sin parar. CREATE_NO_WINDOW evita el flicker.
std::string run_capture(const std::string& cmd) {
HANDLE r_pipe = nullptr;
HANDLE w_pipe = nullptr;
SECURITY_ATTRIBUTES sa{};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
if (!CreatePipe(&r_pipe, &w_pipe, &sa, 0)) return "";
SetHandleInformation(r_pipe, HANDLE_FLAG_INHERIT, 0);
STARTUPINFOA si{};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = w_pipe;
si.hStdError = w_pipe;
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
PROCESS_INFORMATION pi{};
std::string mutable_cmd = cmd;
BOOL ok = CreateProcessA(
nullptr,
mutable_cmd.data(),
nullptr, nullptr, TRUE,
CREATE_NO_WINDOW,
nullptr, nullptr, &si, &pi);
CloseHandle(w_pipe); // padre no escribe
if (!ok) {
CloseHandle(r_pipe);
return "";
}
std::string out;
char buf[4096];
DWORD nread = 0;
while (ReadFile(r_pipe, buf, sizeof(buf), &nread, nullptr) && nread > 0) {
out.append(buf, nread);
}
CloseHandle(r_pipe);
WaitForSingleObject(pi.hProcess, 30000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return out;
}
#else
std::string run_capture(const std::string& cmd) {
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) return "";
std::string out;
char buf[4096];
while (fgets(buf, sizeof(buf), pipe)) out.append(buf);
pclose(pipe);
return out;
}
#endif
// Extrae el valor de --flag=VALUE o --flag VALUE de una commandline.
// Devuelve cadena vacia si no se encuentra.
std::string extract_flag(const std::string& cmd, const std::string& flag) {
// Busca "--flag=" primero.
std::string needle_eq = flag + "=";
auto p = cmd.find(needle_eq);
if (p != std::string::npos) {
size_t start = p + needle_eq.size();
// Si empieza con comilla, leer hasta la siguiente.
char quote = 0;
if (start < cmd.size() && (cmd[start] == '"' || cmd[start] == '\'')) {
quote = cmd[start];
++start;
}
size_t end = start;
while (end < cmd.size()) {
if (quote) {
if (cmd[end] == quote) break;
} else {
if (cmd[end] == ' ') break;
}
++end;
}
return cmd.substr(start, end - start);
}
// Fallback: "--flag VALUE".
auto p2 = cmd.find(flag + " ");
if (p2 != std::string::npos) {
size_t start = p2 + flag.size() + 1;
size_t end = cmd.find(' ', start);
if (end == std::string::npos) end = cmd.size();
return cmd.substr(start, end - start);
}
return "";
}
std::string basename_of_path(const std::string& path) {
size_t s1 = path.find_last_of('\\');
size_t s2 = path.find_last_of('/');
size_t s = std::string::npos;
if (s1 != std::string::npos) s = s1;
if (s2 != std::string::npos && (s == std::string::npos || s2 > s)) s = s2;
if (s == std::string::npos) return path;
return path.substr(s + 1);
}
} // namespace
std::vector<ChromeInstance> scan_chrome_instances() {
#ifndef _WIN32
return {};
#else
// Format: por cada chrome.exe que tenga --remote-debugging-port, escribimos
// dos lineas: "PID:<n>" y "CMD:<commandline>", separadas por "---".
// Quoting: el CommandLine puede tener caracteres raros, pero lo escribimos
// tal cual y parseamos por prefijo. Sin JSON => sin dependencias.
// Filtrar SOLO el master process: tiene --remote-debugging-port y NO tiene
// --type= (los renderers/gpu-process/utility heredan el cmdline del padre
// pero llevan ademas --type=renderer / --type=gpu-process / etc).
const char* ps_script =
"powershell.exe -NoProfile -Command \""
"Get-CimInstance Win32_Process -Filter \\\"Name='chrome.exe'\\\" "
"| Where-Object { $_.CommandLine -like '*--remote-debugging-port=*' "
" -and $_.CommandLine -notlike '*--type=*' } "
"| ForEach-Object { "
" Write-Output '---'; "
" Write-Output ('PID:' + $_.ProcessId); "
" Write-Output ('CMD:' + $_.CommandLine) "
"}\"";
std::string out = run_capture(ps_script);
std::vector<ChromeInstance> result;
std::istringstream ss(out);
std::string line;
ChromeInstance cur;
bool have_cur = false;
while (std::getline(ss, line)) {
// Trim CR si viene con \r\n.
while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) line.pop_back();
if (line == "---") {
if (have_cur && cur.port > 0) result.push_back(cur);
cur = ChromeInstance{};
have_cur = true;
} else if (line.rfind("PID:", 0) == 0) {
try { cur.pid = (uint32_t)std::stoul(line.substr(4)); } catch (...) { cur.pid = 0; }
} else if (line.rfind("CMD:", 0) == 0) {
cur.command_line = line.substr(4);
std::string port_str = extract_flag(cur.command_line, "--remote-debugging-port");
try { cur.port = std::stoi(port_str); } catch (...) { cur.port = 0; }
cur.user_data_dir = extract_flag(cur.command_line, "--user-data-dir");
cur.profile_name = basename_of_path(cur.user_data_dir);
cur.headless =
cur.command_line.find("--headless") != std::string::npos;
}
}
if (have_cur && cur.port > 0) result.push_back(cur);
return result;
#endif
}
int kill_chromes_by_userdata(const std::string& user_data_dir_substr) {
#ifndef _WIN32
(void)user_data_dir_substr;
return -1;
#else
if (user_data_dir_substr.empty()) return -1;
// Escapamos comillas simples duplicandolas (PowerShell single-quote rule).
std::string esc;
esc.reserve(user_data_dir_substr.size() * 2);
for (char c : user_data_dir_substr) {
if (c == '\'') esc += "''";
else esc += c;
}
std::string cmd =
"powershell.exe -NoProfile -Command \""
"$n = (Get-CimInstance Win32_Process -Filter \\\"Name='chrome.exe'\\\" "
"| Where-Object { $_.CommandLine -like '*" + esc + "*' } "
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue; 1 } "
"| Measure-Object).Count; Write-Output $n\"";
std::string out = run_capture(cmd);
try { return std::stoi(out); } catch (...) { return -1; }
#endif
}
} // namespace navegator