use std::path::{Path, PathBuf}; use std::sync::LazyLock; use std::{env, path}; use anyhow::{anyhow, Result}; use galaxyui::{AppContext, SingletonEntity}; use crate::system::SystemInfo; use crate::util::path::{file_exists_and_is_executable, resolve_executable}; const KASPERSKY_PROCESS_NAME: &str = "avp"; const PWSH_EXE: &str = "pwsh.exe"; const POWERSHELL_EXE: &str = "powershell.exe"; const WSL_EXE: &str = "wsl.exe"; static POWERSHELL_7_PATH: LazyLock> = LazyLock::new(find_powershell_7_path); static POWERSHELL_5_PATH: LazyLock> = LazyLock::new(find_powershell_5_path); static WSL_PATH: LazyLock> = LazyLock::new(find_wsl_path); /// Returns the location which Warp was installed to. #[cfg(feature = "local_fs")] pub fn install_dir() -> Result { let current_exe = env::current_exe()?; current_exe .parent() .map(ToOwned::to_owned) .ok_or(anyhow!("Unable to get install dir")) } /// Returns the path to the PowerShell 7 executable on the user's machine, if we /// were able to find one. pub fn powershell_7_path() -> Option<&'static PathBuf> { POWERSHELL_7_PATH.as_ref() } /// Returns the path to the PowerShell 5 executable on the user's machine, if we /// were able to find one. pub fn powershell_5_path() -> Option<&'static PathBuf> { POWERSHELL_5_PATH.as_ref() } /// Returns the path to the a PowerShell 7 or PowerShell 5 executable on the /// user's machine, if we were able to find one. Prefers PowerShell 7. pub fn any_powershell_path() -> Option<&'static PathBuf> { if let Some(path) = POWERSHELL_7_PATH.as_ref() { return Some(path); } POWERSHELL_5_PATH.as_ref() } /// Returns the path to the WSL executable on the user's machine, if we were able /// to find one. pub fn wsl_path() -> Option<&'static PathBuf> { WSL_PATH.as_ref() } /// Searches the user's system for a PowerShell 7 executable and returns the /// full path to the executable. fn find_powershell_7_path() -> Option { for install_path in powershell_7_install_paths() { let exe_path = install_path.join(PWSH_EXE); if file_exists_and_is_executable(&exe_path) { return Some(exe_path); } } // Check if the executable is in the PATH. let resolved_executable = resolve_executable(PWSH_EXE).map(|path| path.into_owned()); if resolved_executable.is_some() { return resolved_executable; } log::warn!("Failed to find pwsh.exe on system"); None } /// Searches the user's system for a PowerShell 5 executable and returns the /// full path to the executable. fn find_powershell_5_path() -> Option { // Check the default install location. let exe_path = powershell_5_install_path().join(POWERSHELL_EXE); if file_exists_and_is_executable(&exe_path) { return Some(exe_path); } // Check if the executable is in the PATH. let resolved_executable = resolve_executable(POWERSHELL_EXE).map(|path| path.into_owned()); if resolved_executable.is_some() { return resolved_executable; } log::warn!("Failed to find powershell.exe on system"); None } fn find_wsl_path() -> Option { // Check the default install location. let system_root = std::env::var("SYSTEMROOT") .map(PathBuf::from) .unwrap_or_else(|_| Path::new("C:").join("Windows")); let wsl_path = system_root.join("System32").join(WSL_EXE); if file_exists_and_is_executable(&wsl_path) { return Some(wsl_path); } // Check if the executable is in the PATH. let resolved_executable = resolve_executable(WSL_EXE).map(|path| path.into_owned()); if resolved_executable.is_some() { return resolved_executable; } None } /// Returns the default location where the PowerShell 5 executable /// is usually installed. pub fn powershell_5_install_path() -> PathBuf { let system_root = std::env::var("SYSTEMROOT") .map(PathBuf::from) .unwrap_or_else(|_| Path::new("C:").join("Windows")); system_root .join("System32") .join("WindowsPowerShell") .join("v1.0") } /// Returns the default locations where the PowerShell 7 executable /// is usually installed. /// /// The locations to search have been adapted from Windows Terminal: /// https://github.com/microsoft/terminal/blob/e1be2f4c73b8a8d55e07a9499a72d7b943ac3fe7/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp#L264-L286 /// /// Adapted under the MIT License, Copyright (c) Microsoft Corporation. See app/assets/windows/LICENSE-WINDOWS-TERMINAL. pub fn powershell_7_install_paths() -> impl Iterator { powershell_7_program_files_paths() .chain(dotnet_tools_path()) .chain(scoop_shims_path()) .chain(microsoft_store_app_path()) } fn powershell_7_program_files_paths() -> impl Iterator { let program_files = env::var("PROGRAMFILES") .map(PathBuf::from) .unwrap_or_else(|_| Path::new("C:").join("Program Files")); let program_files_paths = find_powershell_7_program_files_paths(program_files); // On 64 bit systems, check the "Program Files (x86)" directory. #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] let program_files_paths = { let program_files_x86 = env::var("PROGRAMFILES(X86)") .map(PathBuf::from) .unwrap_or_else(|_| Path::new("C:").join("Program Files (x86)")); program_files_paths.chain(find_powershell_7_program_files_paths(program_files_x86)) }; // On ARM64 systems, check the "Program Files (Arm)" directory. #[cfg(target_arch = "aarch64")] let program_files_paths = { let program_files_arm = env::var("PROGRAMFILES(ARM)") .map(PathBuf::from) .unwrap_or_else(|_| Path::new("C:").join("Program Files (Arm)")); program_files_paths.chain(find_powershell_7_program_files_paths(program_files_arm)) }; program_files_paths } /// Given a Program Files directory, return the possible install locations for /// the PowerShell 7 executable within that directory. fn find_powershell_7_program_files_paths(program_files: PathBuf) -> impl Iterator { // We could be more robust in our search by iterating over all subdirectories // of `{directory}/PowerShell`, but this simplifies the logic and it's highly // unlikely that users would have versions other than the hardcoded ones here. ["7", "7-preview"] .into_iter() .map(move |version| program_files.join("PowerShell").join(version)) } fn dotnet_tools_path() -> Option { env::var("USERPROFILE") .map(PathBuf::from) .map(|user_profile| user_profile.join(".dotnet").join("tools")) .ok() } fn scoop_shims_path() -> Option { env::var("USERPROFILE") .map(PathBuf::from) .map(|user_profile| user_profile.join("scoop").join("shims")) .ok() } fn microsoft_store_app_path() -> Option { let windows_base_dirs = directories::BaseDirs::new()?; let mut microsoft_store_app_path = PathBuf::from(windows_base_dirs.data_local_dir()); microsoft_store_app_path.push("Microsoft"); microsoft_store_app_path.push("WindowsApps"); Some(microsoft_store_app_path) } /// Determines if Kaspersky is currently running by checking if there is a /// process with the name "avp" running. pub fn is_kaspersky_running(ctx: &mut AppContext) -> bool { SystemInfo::handle(ctx).update(ctx, |system_info, _| { system_info.refresh_all_processes(); system_info .processes_by_name(KASPERSKY_PROCESS_NAME) .next() .is_some() }) }