use crate::browser::{create_browser, BrowserType}; use crate::profile::BrowserProfile; use std::ffi::OsString; use std::path::Path; use std::process::Command; // Platform-specific modules #[cfg(target_os = "macos")] pub mod macos { use super::*; pub fn is_tor_or_mullvad_browser(exe_name: &str, cmd: &[OsString], browser_type: &str) -> bool { match browser_type { "mullvad-browser" => { let has_mullvad_in_exe = exe_name.contains("mullvad"); let has_firefox_exe = exe_name == "firefox" || exe_name.contains("firefox-bin"); let has_mullvad_in_cmd = cmd.iter().any(|arg| { let arg_str = arg.to_str().unwrap_or(""); arg_str.contains("Mullvad Browser.app") || arg_str.contains("mullvad") || arg_str.contains("Mullvad") || arg_str.contains("/Applications/Mullvad Browser.app/") || arg_str.contains("MullvadBrowser") }); has_mullvad_in_exe || (has_firefox_exe && has_mullvad_in_cmd) } "tor-browser" => { let has_tor_in_exe = exe_name.contains("tor"); let has_firefox_exe = exe_name == "firefox" || exe_name.contains("firefox-bin"); let has_tor_in_cmd = cmd.iter().any(|arg| { let arg_str = arg.to_str().unwrap_or(""); arg_str.contains("Tor Browser.app") || arg_str.contains("tor-browser") || arg_str.contains("TorBrowser") || arg_str.contains("/Applications/Tor Browser.app/") || arg_str.contains("TorBrowser-Data") }); has_tor_in_exe || (has_firefox_exe && has_tor_in_cmd) } _ => false, } } pub async fn launch_browser_process( executable_path: &std::path::Path, args: &[String], ) -> Result> { println!("Launching browser on macOS: {executable_path:?} with args: {args:?}"); // If the executable is inside an app bundle, launch via Launch Services so // macOS recognizes the real application for privacy permissions (e.g. Screen Recording). // This ensures TCC prompts are attributed to the browser app, not our launcher. let mut current = Some(executable_path); let mut app_bundle: Option = None; while let Some(path) = current { if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) { if file_name.ends_with(".app") { app_bundle = Some(path.to_path_buf()); break; } } current = path.parent(); } if let Some(app_path) = app_bundle { // Use `open -n -a .app --args ...` to launch the app bundle. // Note: The returned child PID will belong to `open`, not the browser. // The caller should resolve the actual browser PID after launch. let mut cmd = Command::new("open"); cmd.arg("-n"); cmd.arg("-a"); cmd.arg(app_path); cmd.arg("--args"); for a in args { cmd.arg(a); } Ok(cmd.spawn()?) } else { // Fallback: direct spawn if this is not an app bundle Ok(Command::new(executable_path).args(args).spawn()?) } } pub async fn open_url_in_existing_browser_firefox_like( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, profiles_dir: &Path, ) -> Result<(), Box> { let pid = profile.process_id.unwrap(); let profile_data_path = profile.get_profile_data_path(profiles_dir); // First try: Use Firefox remote command println!("Trying Firefox remote command for PID: {pid}"); let browser = create_browser(browser_type); if let Ok(executable_path) = browser.get_executable_path(browser_dir) { let remote_args = vec![ "-profile".to_string(), profile_data_path.to_string_lossy().to_string(), "-new-tab".to_string(), url.to_string(), ]; let remote_output = Command::new(executable_path).args(&remote_args).output(); match remote_output { Ok(output) if output.status.success() => { println!("Firefox remote command succeeded"); return Ok(()); } Ok(output) => { let stderr = String::from_utf8_lossy(&output.stderr); println!( "Firefox remote command failed with stderr: {stderr}, trying AppleScript fallback" ); } Err(e) => { println!("Firefox remote command error: {e}, trying AppleScript fallback"); } } } // Fallback: Use AppleScript let escaped_url = url .replace("\"", "\\\"") .replace("\\", "\\\\") .replace("'", "\\'"); let script = format!( r#" try tell application "System Events" -- Find the exact process by PID set targetProcess to (first application process whose unix id is {pid}) -- Verify the process exists if not (exists targetProcess) then error "No process found with PID {pid}" end if -- Get the process name for verification set processName to name of targetProcess -- Bring the process to the front first set frontmost of targetProcess to true delay 1.0 -- Check if the process has any visible windows set windowList to windows of targetProcess set hasVisibleWindow to false repeat with w in windowList if visible of w is true then set hasVisibleWindow to true exit repeat end if end repeat if not hasVisibleWindow then -- No visible windows, create a new one tell targetProcess keystroke "n" using command down delay 2.0 end tell end if -- Ensure the process is frontmost again set frontmost of targetProcess to true delay 0.5 -- Focus on the address bar and open URL tell targetProcess -- Open a new tab keystroke "t" using command down delay 1.5 -- Focus address bar (Cmd+L) keystroke "l" using command down delay 0.5 -- Type the URL keystroke "{escaped_url}" delay 0.5 -- Press Enter to navigate keystroke return end tell return "Successfully opened URL in " & processName & " (PID: {pid})" end tell on error errMsg number errNum return "AppleScript failed: " & errMsg & " (Error " & errNum & ")" end try "# ); println!("Executing AppleScript fallback for Firefox-based browser (PID: {pid})..."); let output = Command::new("osascript").args(["-e", &script]).output()?; if !output.status.success() { let error_msg = String::from_utf8_lossy(&output.stderr); println!("AppleScript failed: {error_msg}"); return Err( format!( "Both Firefox remote command and AppleScript failed. AppleScript error: {error_msg}" ) .into(), ); } else { println!("AppleScript succeeded"); } Ok(()) } pub async fn kill_browser_process_impl( pid: u32, ) -> Result<(), Box> { println!("Attempting to kill browser process with PID: {pid}"); // For Chromium-based browsers, use immediate aggressive termination // Chromium browsers are notoriously difficult to kill on macOS due to process spawning // Step 1: Immediate SIGKILL on main process (no graceful shutdown for Chromium) println!("Starting immediate SIGKILL for PID: {pid}"); let _ = Command::new("kill") .args(["-KILL", &pid.to_string()]) .output(); // Step 2: Comprehensive process tree termination using multiple methods simultaneously let _ = kill_chromium_process_tree_aggressive(pid).await; // Step 2.5: Nuclear option - kill all Chromium processes by name pattern let _ = kill_all_chromium_processes_by_name().await; // Step 3: Use multiple kill strategies in parallel let pid_str = pid.to_string(); // Kill by parent PID with SIGKILL let _ = Command::new("pkill") .args(["-KILL", "-P", &pid_str]) .output(); // Kill by process group with SIGKILL let _ = Command::new("pkill") .args(["-KILL", "-g", &pid_str]) .output(); // Kill by session ID let _ = Command::new("pkill") .args(["-KILL", "-s", &pid_str]) .output(); // Wait briefly for initial termination tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; // Step 4: Verify and retry with pattern-based killing for common Chromium process names use sysinfo::{Pid, System}; let system = System::new_all(); // Check if main process still exists if system.process(Pid::from(pid as usize)).is_some() { println!("Main process {pid} still running, using pattern-based termination"); // Kill by common Chromium process patterns let chromium_patterns = [ "Chrome", "Chromium", "Brave", "chrome", "chromium", "brave", "Google Chrome", "Brave Browser", "Chrome Helper", "Chromium Helper", ]; for pattern in &chromium_patterns { let _ = Command::new("pkill") .args(["-KILL", "-f", pattern]) .output(); } } // Step 5: Final aggressive cleanup - kill any remaining processes tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; // One more round of comprehensive killing let _ = Command::new("pkill") .args(["-KILL", "-P", &pid_str]) .output(); let _ = Command::new("pkill") .args(["-KILL", "-g", &pid_str]) .output(); // Final verification with extended wait tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; let system = System::new_all(); if system.process(Pid::from(pid as usize)).is_some() { // Last resort: try system kill command with different signals println!("Process {pid} extremely persistent, trying system-level termination"); let _ = Command::new("/bin/kill").args(["-KILL", &pid_str]).output(); let _ = Command::new("/usr/bin/killall") .args(["-KILL", "-m", "Chrome"]) .output(); let _ = Command::new("/usr/bin/killall") .args(["-KILL", "-m", "Chromium"]) .output(); let _ = Command::new("/usr/bin/killall") .args(["-KILL", "-m", "Brave"]) .output(); tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; let system = System::new_all(); if system.process(Pid::from(pid as usize)).is_some() { println!("WARNING: Process {pid} could not be terminated despite aggressive attempts"); // Don't return error - let the UI update anyway since we tried everything } } println!("Aggressive browser termination completed for PID: {pid}"); Ok(()) } // Helper function to kill process tree (Chromium browsers often spawn child processes) async fn kill_chromium_process_tree_aggressive( pid: u32, ) -> Result<(), Box> { println!("Killing comprehensive process tree for PID: {pid}"); // Get all descendant processes using recursive process tree discovery let descendant_pids = get_all_descendant_pids(pid).await; println!( "Found {} descendant processes to terminate", descendant_pids.len() ); // Kill all descendants first (reverse order - children before parents) for &desc_pid in descendant_pids.iter().rev() { if desc_pid != pid { println!("Terminating descendant process: {desc_pid}"); let _ = Command::new("kill") .args(["-KILL", &desc_pid.to_string()]) .output(); } } // No delay for initial termination // Force kill any remaining descendants for &desc_pid in descendant_pids.iter().rev() { if desc_pid != pid { let _ = Command::new("kill") .args(["-KILL", &desc_pid.to_string()]) .output(); } } // Also use pkill as a backup to catch any processes we might have missed let _ = Command::new("pkill") .args(["-KILL", "-P", &pid.to_string()]) .output(); // On macOS, also try killing by process group for Chromium browsers let _ = Command::new("pkill") .args(["-KILL", "-g", &pid.to_string()]) .output(); Ok(()) } // Helper function to kill all Chromium-related processes by name patterns async fn kill_all_chromium_processes_by_name( ) -> Result<(), Box> { println!("Killing all Chromium-related processes by name patterns"); let chromium_patterns = [ "Chrome", "Chromium", "Brave", "chrome", "chromium", "brave", "Google Chrome", "Brave Browser", "Chrome Helper", "Chromium Helper", ]; for pattern in &chromium_patterns { let _ = Command::new("pkill") .args(["-KILL", "-f", pattern]) .output(); } Ok(()) } // Recursively find all descendant processes async fn get_all_descendant_pids(parent_pid: u32) -> Vec { use sysinfo::System; let system = System::new_all(); let mut descendants = Vec::new(); let mut to_check = vec![parent_pid]; let mut checked = std::collections::HashSet::new(); while let Some(current_pid) = to_check.pop() { if checked.contains(¤t_pid) { continue; } checked.insert(current_pid); // Find direct children of current_pid for (pid, process) in system.processes() { let pid_u32 = pid.as_u32(); if let Some(parent) = process.parent() { if parent.as_u32() == current_pid && !checked.contains(&pid_u32) { descendants.push(pid_u32); to_check.push(pid_u32); } } } } descendants } pub async fn open_url_in_existing_browser_tor_mullvad( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, _profiles_dir: &Path, ) -> Result<(), Box> { let pid = profile.process_id.unwrap(); println!("Opening URL in TOR/Mullvad browser using file-based approach (PID: {pid})"); // Method 1: Try using a temporary HTML file approach println!("Attempting file-based URL opening for TOR/Mullvad browser"); let temp_dir = std::env::temp_dir(); let temp_file_name = format!("donut_browser_url_{}.html", std::process::id()); let temp_file_path = temp_dir.join(&temp_file_name); let html_content = format!( r#" Redirecting...

Redirecting to {url}...

"# ); match std::fs::write(&temp_file_path, html_content) { Ok(()) => { println!("Created temporary HTML file: {temp_file_path:?}"); let browser = create_browser(browser_type.clone()); if let Ok(executable_path) = browser.get_executable_path(browser_dir) { let open_result = Command::new("open") .args([ "-a", executable_path.to_str().unwrap(), temp_file_path.to_str().unwrap(), ]) .output(); // Clean up the temporary file after a short delay let temp_file_path_clone = temp_file_path.clone(); tokio::spawn(async move { tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; let _ = std::fs::remove_file(temp_file_path_clone); }); match open_result { Ok(output) if output.status.success() => { println!("Successfully opened URL using file-based approach"); return Ok(()); } Ok(output) => { let stderr = String::from_utf8_lossy(&output.stderr); println!("File-based approach failed: {stderr}"); } Err(e) => { println!("File-based approach error: {e}"); } } } let _ = std::fs::remove_file(&temp_file_path); } Err(e) => { println!("Failed to create temporary HTML file: {e}"); } } // Method 2: Try using the 'open' command directly with the URL println!("Attempting direct URL opening with 'open' command"); let browser = create_browser(browser_type.clone()); if let Ok(executable_path) = browser.get_executable_path(browser_dir) { let direct_open_result = Command::new("open") .args(["-a", executable_path.to_str().unwrap(), url]) .output(); match direct_open_result { Ok(output) if output.status.success() => { println!("Successfully opened URL using direct 'open' command"); return Ok(()); } Ok(output) => { let stderr = String::from_utf8_lossy(&output.stderr); println!("Direct 'open' command failed: {stderr}"); } Err(e) => { println!("Direct 'open' command error: {e}"); } } } // If all methods fail, return a helpful error message Err( format!( "Failed to open URL in existing TOR/Mullvad browser (PID: {pid}). All methods failed:\n\ 1. File-based approach failed\n\ 2. Direct 'open' command failed\n\ \n\ This may be due to browser security restrictions or the browser process may have changed.\n\ Try closing and reopening the browser, or manually paste the URL: {url}" ) .into(), ) } pub async fn open_url_in_existing_browser_chromium( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, _profiles_dir: &Path, ) -> Result<(), Box> { let pid = profile.process_id.unwrap(); // First, try using the browser's built-in URL opening capability println!("Trying Chromium URL opening for PID: {pid}"); let browser = create_browser(browser_type); if let Ok(executable_path) = browser.get_executable_path(browser_dir) { let profile_data_path = profile.get_profile_data_path(_profiles_dir); let remote_output = Command::new(executable_path) .args([ &format!("--user-data-dir={}", profile_data_path.to_string_lossy()), url, ]) .output(); match remote_output { Ok(output) if output.status.success() => { println!("Chromium URL opening succeeded"); return Ok(()); } Ok(output) => { let stderr = String::from_utf8_lossy(&output.stderr); println!("Chromium URL opening failed: {stderr}, trying AppleScript"); } Err(e) => { println!("Chromium URL opening error: {e}, trying AppleScript"); } } } // Fallback to AppleScript let escaped_url = url .replace("\"", "\\\"") .replace("\\", "\\\\") .replace("'", "\\'"); let script = format!( r#" try tell application "System Events" -- Find the exact process by PID set targetProcess to (first application process whose unix id is {pid}) -- Verify the process exists if not (exists targetProcess) then error "No process found with PID {pid}" end if -- Get the process name for verification set processName to name of targetProcess -- Bring the process to the front first set frontmost of targetProcess to true delay 1.0 -- Check if the process has any visible windows set windowList to windows of targetProcess set hasVisibleWindow to false repeat with w in windowList if visible of w is true then set hasVisibleWindow to true exit repeat end if end repeat if not hasVisibleWindow then -- No visible windows, create a new one tell targetProcess keystroke "n" using command down delay 2.0 end tell end if -- Ensure the process is frontmost again set frontmost of targetProcess to true delay 0.5 -- Focus on the address bar and open URL tell targetProcess -- Open a new tab keystroke "t" using command down delay 1.5 -- Focus address bar (Cmd+L) keystroke "l" using command down delay 0.5 -- Type the URL keystroke "{escaped_url}" delay 0.5 -- Press Enter to navigate keystroke return end tell return "Successfully opened URL in " & processName & " (PID: {pid})" end tell on error errMsg number errNum return "AppleScript failed: " & errMsg & " (Error " & errNum & ")" end try "# ); println!("Executing AppleScript for Chromium-based browser (PID: {pid})..."); let output = Command::new("osascript").args(["-e", &script]).output()?; if !output.status.success() { let error_msg = String::from_utf8_lossy(&output.stderr); println!("AppleScript failed: {error_msg}"); return Err( format!("Failed to open URL in existing Chromium-based browser: {error_msg}").into(), ); } else { println!("AppleScript succeeded"); } Ok(()) } } #[cfg(target_os = "windows")] pub mod windows { use super::*; pub fn is_tor_or_mullvad_browser(exe_name: &str, cmd: &[OsString], browser_type: &str) -> bool { let exe_lower = exe_name.to_lowercase(); // Check for Firefox-based browsers first by executable name let is_firefox_family = exe_lower.contains("firefox") || exe_lower.contains(".exe"); if !is_firefox_family { return false; } // Check command arguments for profile paths and browser-specific indicators let cmd_line = cmd .iter() .map(|s| s.to_string_lossy().to_lowercase()) .collect::>() .join(" "); match browser_type { "tor-browser" => { // Check for TOR browser specific paths and arguments cmd_line.contains("tor") || cmd_line.contains("browser\\torbrowser") || cmd_line.contains("tor-browser") || cmd_line.contains("profile") && (cmd_line.contains("tor") || cmd_line.contains("tbb")) } "mullvad-browser" => { // Check for Mullvad browser specific paths and arguments cmd_line.contains("mullvad") || cmd_line.contains("browser\\mullvadbrowser") || cmd_line.contains("mullvad-browser") || cmd_line.contains("profile") && cmd_line.contains("mullvad") } _ => false, } } pub async fn launch_browser_process( executable_path: &std::path::Path, args: &[String], ) -> Result> { println!( "Launching browser on Windows: {:?} with args: {:?}", executable_path, args ); // Check if the executable exists if !executable_path.exists() { return Err(format!("Browser executable not found: {:?}", executable_path).into()); } // On Windows, set up the command with proper working directory let mut cmd = Command::new(executable_path); cmd.args(args); // Set working directory to the executable's directory for better compatibility if let Some(parent_dir) = executable_path.parent() { cmd.current_dir(parent_dir); } // For Windows 7 compatibility, set some environment variables cmd.env( "PROCESSOR_ARCHITECTURE", std::env::var("PROCESSOR_ARCHITECTURE").unwrap_or_else(|_| "x86".to_string()), ); // Ensure proper PATH for DLL loading if let Some(exe_dir) = executable_path.parent() { let mut path_var = std::env::var("PATH").unwrap_or_default(); if !path_var.is_empty() { path_var = format!("{};{}", exe_dir.display(), path_var); } else { path_var = exe_dir.display().to_string(); } cmd.env("PATH", path_var); } // Launch the process let child = cmd .spawn() .map_err(|e| format!("Failed to launch browser process: {}", e))?; println!( "Successfully launched browser process with PID: {}", child.id() ); Ok(child) } pub async fn open_url_in_existing_browser_firefox_like( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, profiles_dir: &Path, ) -> Result<(), Box> { let browser = create_browser(browser_type); let executable_path = browser .get_executable_path(browser_dir) .map_err(|e| format!("Failed to get executable path: {}", e))?; let profile_data_path = profile.get_profile_data_path(profiles_dir); // For Windows, try using the -requestPending approach for Firefox let mut cmd = Command::new(executable_path); cmd.args([ "-profile", &profile_data_path.to_string_lossy(), "-requestPending", "-new-tab", url, ]); // Set working directory if let Some(parent_dir) = browser_dir .parent() .or_else(|| browser_dir.ancestors().nth(1)) { cmd.current_dir(parent_dir); } let output = cmd.output()?; if !output.status.success() { // Fallback: try without -requestPending let executable_path = browser .get_executable_path(browser_dir) .map_err(|e| format!("Failed to get executable path: {}", e))?; let mut fallback_cmd = Command::new(executable_path); let profile_data_path = profile.get_profile_data_path(profiles_dir); fallback_cmd.args([ "-profile", &profile_data_path.to_string_lossy(), "-new-tab", url, ]); if let Some(parent_dir) = browser_dir .parent() .or_else(|| browser_dir.ancestors().nth(1)) { fallback_cmd.current_dir(parent_dir); } let fallback_output = fallback_cmd.output()?; if !fallback_output.status.success() { return Err( format!( "Failed to open URL in existing browser: {}", String::from_utf8_lossy(&fallback_output.stderr) ) .into(), ); } } Ok(()) } pub async fn open_url_in_existing_browser_tor_mullvad( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, profiles_dir: &Path, ) -> Result<(), Box> { // On Windows, TOR and Mullvad browsers can sometimes accept URLs via command line // even with -no-remote, by launching a new instance that hands off to existing one let browser = create_browser(browser_type.clone()); let executable_path = browser .get_executable_path(browser_dir) .map_err(|e| format!("Failed to get executable path: {}", e))?; let mut cmd = Command::new(&executable_path); let profile_data_path = profile.get_profile_data_path(profiles_dir); cmd.args(["-profile", &profile_data_path.to_string_lossy(), url]); // Set working directory if let Some(parent_dir) = browser_dir .parent() .or_else(|| browser_dir.ancestors().nth(1)) { cmd.current_dir(parent_dir); } let output = cmd.output()?; if !output.status.success() { return Err( format!( "Failed to open URL in existing {}: {}. Note: TOR and Mullvad browsers may require manual URL opening for security reasons.", browser_type.as_str(), String::from_utf8_lossy(&output.stderr) ) .into(), ); } Ok(()) } pub async fn open_url_in_existing_browser_chromium( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, profiles_dir: &Path, ) -> Result<(), Box> { let browser = create_browser(browser_type.clone()); let executable_path = browser .get_executable_path(browser_dir) .map_err(|e| format!("Failed to get executable path: {}", e))?; let mut cmd = Command::new(&executable_path); cmd.args([ &format!( "--user-data-dir={}", profile .get_profile_data_path(profiles_dir) .to_string_lossy() ), "--new-window", url, ]); // Set working directory if let Some(parent_dir) = browser_dir .parent() .or_else(|| browser_dir.ancestors().nth(1)) { cmd.current_dir(parent_dir); } // Do not call output() to avoid blocking the UI thread while the browser processes the request. // Spawn the helper process and return immediately. This applies to Chromium-based browsers // including Brave to prevent UI freezes observed in production. let _child = cmd.spawn()?; Ok(()) } pub async fn kill_browser_process_impl( pid: u32, ) -> Result<(), Box> { // First try using sysinfo (cross-platform approach) use sysinfo::{Pid, System}; let system = System::new_all(); if let Some(process) = system.process(Pid::from(pid as usize)) { if process.kill() { println!("Successfully killed browser process with PID: {pid}"); return Ok(()); } } // Fallback to Windows-specific process termination use std::process::Command; // Try taskkill command as fallback let output = Command::new("taskkill") .args(["/F", "/PID", &pid.to_string()]) .output(); match output { Ok(result) => { if result.status.success() { println!("Successfully killed browser process with PID: {pid} using taskkill"); Ok(()) } else { Err( format!( "Failed to kill process {} with taskkill: {}", pid, String::from_utf8_lossy(&result.stderr) ) .into(), ) } } Err(e) => Err(format!("Failed to execute taskkill for process {}: {}", pid, e).into()), } } } #[cfg(target_os = "linux")] pub mod linux { use super::*; pub fn is_tor_or_mullvad_browser( _exe_name: &str, _cmd: &[OsString], _browser_type: &str, ) -> bool { // Linux implementation would go here false } pub async fn launch_browser_process( executable_path: &std::path::Path, args: &[String], ) -> Result> { println!( "Launching browser on Linux: {:?} with args: {:?}", executable_path, args ); // Check if the executable exists and is executable if !executable_path.exists() { return Err(format!("Browser executable not found: {:?}", executable_path).into()); } // Check if we can read the executable to detect architecture issues early if let Err(e) = std::fs::File::open(executable_path) { return Err(format!("Cannot access browser executable: {}", e).into()); } // Ensure the executable has proper permissions if let Err(e) = std::fs::metadata(executable_path) { return Err(format!("Cannot get executable metadata: {}", e).into()); } // On Linux, we might need to set LD_LIBRARY_PATH for some browsers let mut cmd = Command::new(executable_path); cmd.args(args); // For Firefox-based browsers, ensure library path includes the installation directory if let Some(install_dir) = executable_path.parent() { let mut ld_library_path = Vec::new(); // Add multiple potential library directories let lib_dirs = [ install_dir.join("lib"), install_dir.join("../lib"), // Parent directory lib install_dir.join("../../lib"), // Grandparent directory lib install_dir.to_path_buf(), // Installation directory itself ]; for lib_dir in &lib_dirs { if lib_dir.exists() { ld_library_path.push(lib_dir.to_string_lossy().to_string()); } } // For Firefox specifically, add common system library paths that might be needed let firefox_lib_paths = [ "/usr/lib/firefox", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/lib/x86_64-linux-gnu", "/lib/aarch64-linux-gnu", ]; for lib_path in &firefox_lib_paths { let path = std::path::Path::new(lib_path); if path.exists() { ld_library_path.push(lib_path.to_string()); } } // Preserve existing LD_LIBRARY_PATH if let Ok(existing_path) = std::env::var("LD_LIBRARY_PATH") { ld_library_path.push(existing_path); } // Set the combined LD_LIBRARY_PATH if !ld_library_path.is_empty() { cmd.env("LD_LIBRARY_PATH", ld_library_path.join(":")); println!("Set LD_LIBRARY_PATH to: {}", ld_library_path.join(":")); } } // Additional Linux-specific environment variables for better compatibility cmd.env( "DISPLAY", std::env::var("DISPLAY").unwrap_or(":0".to_string()), ); // Set MOZ_ENABLE_WAYLAND for better Wayland support if std::env::var("WAYLAND_DISPLAY").is_ok() { cmd.env("MOZ_ENABLE_WAYLAND", "1"); } // Disable GPU acceleration if running in headless environments if std::env::var("DISPLAY").is_err() || std::env::var("WAYLAND_DISPLAY").is_err() { println!("No display detected, browser may fail to start"); } // Attempt to spawn with better error handling for architecture issues match cmd.spawn() { Ok(child) => Ok(child), Err(e) => { // Detect architecture mismatch errors if e.kind() == std::io::ErrorKind::Other { let error_msg = e.to_string(); if error_msg.contains("Exec format error") { return Err(format!( "Architecture mismatch: The browser executable is not compatible with your system architecture ({}). \ This typically happens when trying to run x86_64 binaries on ARM64 systems. \ Please use a browser that supports your architecture, such as Zen Browser or Brave. \ Executable: {:?}", std::env::consts::ARCH, executable_path ).into()); } else if error_msg.contains("No such file or directory") { return Err(format!( "Executable or required library not found. This might be due to missing dependencies or incorrect executable path. \ Try installing missing libraries or verify the browser installation. \ Executable: {:?}, Error: {}", executable_path, error_msg ).into()); } } Err(format!("Failed to launch browser: {}", e).into()) } } } pub async fn open_url_in_existing_browser_firefox_like( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, profiles_dir: &Path, ) -> Result<(), Box> { let browser = create_browser(browser_type); let executable_path = browser .get_executable_path(browser_dir) .map_err(|e| format!("Failed to get executable path: {}", e))?; let profile_data_path = profile.get_profile_data_path(profiles_dir); let output = Command::new(executable_path) .args([ "-profile", &profile_data_path.to_string_lossy(), "-new-tab", url, ]) .output()?; if !output.status.success() { return Err( format!( "Failed to open URL in existing browser: {}", String::from_utf8_lossy(&output.stderr) ) .into(), ); } Ok(()) } pub async fn open_url_in_existing_browser_tor_mullvad( _profile: &BrowserProfile, _url: &str, _browser_type: BrowserType, _browser_dir: &Path, _profiles_dir: &Path, ) -> Result<(), Box> { Err("Opening URLs in existing Firefox-based browsers is not supported on Linux when using -no-remote".into()) } pub async fn open_url_in_existing_browser_chromium( profile: &BrowserProfile, url: &str, browser_type: BrowserType, browser_dir: &Path, profiles_dir: &Path, ) -> Result<(), Box> { let browser = create_browser(browser_type); let executable_path = browser .get_executable_path(browser_dir) .map_err(|e| format!("Failed to get executable path: {}", e))?; let profile_data_path = profile.get_profile_data_path(profiles_dir); let output = Command::new(executable_path) .args([ &format!("--user-data-dir={}", profile_data_path.to_string_lossy()), url, ]) .output()?; if !output.status.success() { return Err( format!( "Failed to open URL in existing Chromium-based browser: {}", String::from_utf8_lossy(&output.stderr) ) .into(), ); } Ok(()) } pub async fn kill_browser_process_impl( pid: u32, ) -> Result<(), Box> { use sysinfo::{Pid, System}; let system = System::new_all(); if let Some(process) = system.process(Pid::from(pid as usize)) { if !process.kill() { return Err(format!("Failed to kill process {}", pid).into()); } } else { return Err(format!("Process {} not found", pid).into()); } println!("Successfully killed browser process with PID: {pid}"); Ok(()) } }