#include "chrome_scanner.h" #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include #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 scan_chrome_instances() { #ifndef _WIN32 return {}; #else // Format: por cada chrome.exe que tenga --remote-debugging-port, escribimos // dos lineas: "PID:" y "CMD:", 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 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