crates/q_cli/src/cli/internal/should_figterm_launch.rs (462 lines of code) (raw):

use std::borrow::Cow; #[cfg(any(target_os = "macos", target_os = "linux"))] use std::io::{ Write, stdout, }; use std::process::ExitCode; use fig_os_shim::Context; use fig_util::Terminal; const Q_FORCE_FIGTERM_LAUNCH: &str = "Q_FORCE_FIGTERM_LAUNCH"; const Q_TERM_DISABLED: &str = "Q_TERM_DISABLED"; const INSIDE_EMACS: &str = "INSIDE_EMACS"; const TERM_PROGRAM: &str = "TERM_PROGRAM"; const WARP_TERMINAL: &str = "WarpTerminal"; #[cfg(any(target_os = "macos", target_os = "linux"))] #[allow(dead_code)] struct ProcessInfo { pid: Box<fig_os_shim::process_info::Pid>, exe_name: String, cmdline_name: Option<String>, is_valid: bool, is_special: bool, } #[cfg(any(target_os = "macos", target_os = "linux"))] enum Status { Launch(Cow<'static, str>), DontLaunch(Cow<'static, str>), Process(ProcessInfo), } #[cfg(any(target_os = "macos", target_os = "linux"))] impl Status { fn exit_status(self, quiet: bool) -> Result<ProcessInfo, u8> { match self { Status::Process(info) => Ok(info), Status::Launch(s) => { if !quiet { writeln!(stdout(), "✅ {s}").ok(); } Err(0) }, Status::DontLaunch(s) => { if !quiet { writeln!(stdout(), "❌ {s}").ok(); } Err(1) }, } } } fn parent_status(ctx: &Context, current_pid: fig_os_shim::process_info::Pid) -> Status { use fig_util::env_var::Q_TERM; let env = ctx.env(); let parent_pid = match current_pid.parent() { Some(pid) => pid, None => return Status::DontLaunch("No parent PID".into()), }; let parent_path = match parent_pid.exe() { Some(path) => path, None => return Status::DontLaunch("No parent path".into()), }; let parent_name = match parent_path.file_name() { Some(name) => match name.to_str() { Some(name) => name, None => return Status::DontLaunch("Parent name is not valid unicode".into()), }, None => return Status::DontLaunch("No parent name".into()), }; let valid_parent = ["zsh", "bash", "fish", "nu"].contains(&parent_name); if env.in_ssh() && env.get_os(Q_TERM).is_none() { return Status::Launch(format!("In SSH and {Q_TERM} is not set").into()); } if env.in_codespaces() { return match env.get_os(Q_TERM) { Some(_) => Status::DontLaunch(format!("In Codespaces and {Q_TERM} is set").into()), None => Status::Launch(format!("In Codespaces and {Q_TERM} is not set").into()), }; } Status::Process(ProcessInfo { pid: parent_pid, exe_name: parent_name.into(), cmdline_name: None, is_valid: valid_parent, is_special: false, }) } fn grandparent_status(ctx: &Context, parent_pid: fig_os_shim::process_info::Pid) -> Status { let current_os = ctx.platform().os(); let Some(grandparent_pid) = parent_pid.parent() else { return Status::DontLaunch("No grandparent PID".into()); }; let Some(grandparent_path) = grandparent_pid.exe() else { return Status::DontLaunch("No grandparent path".into()); }; let grandparent_name = match grandparent_path.file_name() { Some(name) => match name.to_str() { Some(name) => name, None => return Status::DontLaunch("Grandparent name is not a valid utf8 str".into()), }, None => return Status::DontLaunch("No grandparent name".into()), }; let grandparent_cmdline_name = if let Some(cmdline) = grandparent_pid.cmdline() { cmdline .split(' ') .take(1) .next() .and_then(|cmd| cmd.split('/').last()) .map(str::to_string) } else { None }; // The terminals the platform supports let terminals = match current_os { fig_os_shim::Os::Mac => fig_util::terminal::MACOS_TERMINALS, fig_os_shim::Os::Linux => fig_util::terminal::LINUX_TERMINALS, _ => panic!("unsupported os"), } .iter() .chain(fig_util::terminal::SPECIAL_TERMINALS) .cloned() .collect::<Vec<_>>(); // Try to find if any of the supported terminals matches the current grandparent process let valid_grandparent = match current_os { fig_os_shim::Os::Mac => terminals .iter() .find(|terminal| terminal.executable_names().contains(&grandparent_name)), fig_os_shim::Os::Linux => terminals.iter().find(|terminal| { terminal.executable_names().contains(&grandparent_name) || grandparent_pid .cmdline() .is_some_and(|cmdline| Terminal::try_from_cmdline(&cmdline, &terminals).is_some()) }), _ => panic!("unsupported os"), }; Status::Process(ProcessInfo { pid: grandparent_pid, exe_name: grandparent_name.into(), cmdline_name: grandparent_cmdline_name, is_valid: valid_grandparent.is_some(), is_special: valid_grandparent.is_some_and(|term| term.is_special()), }) } #[cfg(any(target_os = "macos", target_os = "linux"))] fn should_launch(ctx: &Context, quiet: bool) -> u8 { use fig_os_shim::Os; let process_info = ctx.process_info(); let current_pid = process_info.current_pid(); let parent_info = match parent_status(ctx, current_pid).exit_status(quiet) { Ok(info) => info, Err(i) => return i, }; let grandparent_info = match grandparent_status(ctx, *parent_info.pid.clone()).exit_status(quiet) { Ok(info) => info, Err(i) => return i, }; if !quiet { let ancestry = format!( "{} {} | {} ({}) <- {} {} ({})", if grandparent_info.is_valid { "✅" } else { "❌" }, grandparent_info.exe_name, grandparent_info.cmdline_name.unwrap_or_default(), grandparent_info.pid.clone(), if parent_info.is_valid { "✅" } else { "❌" }, parent_info.exe_name, parent_info.pid.clone(), ); writeln!(stdout(), "{ancestry}").ok(); } if matches!(ctx.platform().os(), Os::Mac) && !grandparent_info.is_special { if !quiet { writeln!(stdout(), "🟡 Falling back to old mechanism since on macOS").ok(); } return 2; } u8::from(!(grandparent_info.is_valid && parent_info.is_valid)) } #[cfg(any(target_os = "macos", target_os = "linux"))] pub fn should_figterm_launch_exit_status(ctx: &Context, quiet: bool) -> u8 { use fig_util::env_var::{ PROCESS_LAUNCHED_BY_Q, Q_PARENT, }; let env = ctx.env(); if env.get_os(Q_FORCE_FIGTERM_LAUNCH).is_some() { if !quiet { writeln!(stdout(), "✅ {Q_FORCE_FIGTERM_LAUNCH}").ok(); } return 0; } if env.get_os(Q_TERM_DISABLED).is_some() { if !quiet { writeln!(stdout(), "❌ {Q_TERM_DISABLED}").ok(); } return 1; } if env.get_os(PROCESS_LAUNCHED_BY_Q).is_some() { if !quiet { writeln!(stdout(), "❌ {PROCESS_LAUNCHED_BY_Q}").ok(); } return 1; } // Check if inside Emacs if env.get_os(INSIDE_EMACS).is_some() { if !quiet { writeln!(stdout(), "❌ INSIDE_EMACS").ok(); } return 1; } // Check for Warp Terminal if let Ok(term_program) = env.get(TERM_PROGRAM) { if term_program == WARP_TERMINAL { if !quiet { writeln!(stdout(), "❌ {TERM_PROGRAM} = {WARP_TERMINAL}").ok(); } return 1; } } // Check for SecureCRT if let Ok(cf_bundle_identifier) = env.get("__CFBundleIdentifier") { if cf_bundle_identifier == "com.vandyke.SecureCRT" { if !quiet { writeln!(stdout(), "❌ __CFBundleIdentifier = com.vandyke.SecureCRT").ok(); } return 1; } } // PWSH var is set when launched by `pwsh -Login`, in which case we don't want to init. if env.get_os("__PWSH_LOGIN_CHECKED").is_some() { if !quiet { writeln!(stdout(), "❌ __PWSH_LOGIN_CHECKED").ok(); } return 1; } // Make sure we're not in CI if env.in_ci() { if !quiet { writeln!(stdout(), "❌ In CI").ok(); } return 1; } // If we are in SSH and there is no Q_PARENT dont launch if env.in_ssh() && env.get_os(Q_PARENT).is_none() { if !quiet { writeln!(stdout(), "❌ In SSH without {Q_PARENT}").ok(); } return 1; } if fig_util::system_info::in_wsl() { if !quiet { writeln!(stdout(), "🟡 Falling back to old mechanism since in WSL").ok(); } 2 } else { should_launch(ctx, quiet) } } #[cfg(any(target_os = "macos", target_os = "linux"))] pub fn should_figterm_launch(ctx: &Context) -> ExitCode { ExitCode::from(should_figterm_launch_exit_status(ctx, false)) } #[cfg(target_os = "windows")] pub fn should_qterm_launch() -> ExitCode { use std::os::windows::io::AsRawHandle; use winapi::um::consoleapi::GetConsoleMode; let mut mode = 0; let stdin_ok = unsafe { GetConsoleMode(std::io::stdin().as_raw_handle() as *mut _, &mut mode) }; ExitCode::from(if stdin_ok == 1 { 2 } else { 1 }); } #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] pub fn should_qterm_launch() -> ExitCode { ExitCode::from(2); } #[cfg(test)] mod tests { #![allow(non_snake_case)] use std::path::PathBuf; use fig_os_shim::process_info::{ FakePid, Pid, ProcessInfo, }; use fig_os_shim::{ ContextBuilder, Env, Os, Platform, }; use fig_util::env_var::{ PROCESS_LAUNCHED_BY_Q, Q_PARENT, Q_TERM, }; use super::*; macro_rules! assert_exit_code { ($ctx:expr, $exit_code:expr, $msg:expr) => { assert_eq!( should_figterm_launch_exit_status(&$ctx, true), $exit_code, "{}", $msg ); assert_eq!( should_figterm_launch_exit_status(&$ctx, false), $exit_code, "{}", $msg ); }; } #[derive(Default, Debug)] struct Test { name: String, os: Option<Os>, env: Option<Env>, parent: Option<FakePid>, grandparent_exe: Option<PathBuf>, grandparent_cmdline: Option<String>, expected_exit_code: u8, } impl Test { fn os(mut self, os: Os) -> Self { self.os = Some(os); self } fn env(mut self, slice: &[(&str, &str)]) -> Self { self.env = Some(Env::from_slice(slice)); self } fn parent_exe(mut self, exe: impl Into<PathBuf>) -> Self { self.parent = Some(FakePid { exe: Some(exe.into()), ..Default::default() }); self } fn grandparent_exe(mut self, exe: impl Into<PathBuf>) -> Self { self.grandparent_exe = Some(exe.into()); self } fn grandparent_cmdline(mut self, cmdline: impl Into<String>) -> Self { self.grandparent_cmdline = Some(cmdline.into()); self } fn expect(mut self, expected_exit_code: u8) -> Self { self.expected_exit_code = expected_exit_code; self } fn run(self) { let ctx = { let mut ctx = ContextBuilder::new().with_env(self.env.unwrap_or(Env::from_slice(&[]))); // Create fake ProcessInfo let mut current_pid = FakePid::default(); if let Some(mut parent) = self.parent { if let Some(grandparent_exe) = self.grandparent_exe { let grandparent = FakePid { exe: Some(grandparent_exe), cmdline: self.grandparent_cmdline, ..Default::default() }; parent.parent = Some(Box::new(Pid::new_fake(grandparent))); } current_pid.parent = Some(Box::new(Pid::new_fake(parent))); } ctx = ctx.with_process_info(ProcessInfo::new_fake(current_pid)); let os = self.os.unwrap_or(Os::Mac); ctx = ctx.with_platform(Platform::new_fake(os)); ctx.build() }; assert_exit_code!(ctx, self.expected_exit_code, self.name); } } fn test(name: impl Into<String>) -> Test { Test { name: name.into(), ..Default::default() } } /// Tests for basic override logic where the state of the parent /// and grandparent processes don't matter. #[test] fn override_tests() { let tests = [ test(Q_FORCE_FIGTERM_LAUNCH) .env(&[(Q_FORCE_FIGTERM_LAUNCH, "1")]) .expect(0), test(Q_TERM_DISABLED).env(&[(Q_TERM_DISABLED, "1")]).expect(1), test(PROCESS_LAUNCHED_BY_Q) .env(&[(PROCESS_LAUNCHED_BY_Q, "1")]) .expect(1), test(INSIDE_EMACS).env(&[(INSIDE_EMACS, "1")]).expect(1), test(WARP_TERMINAL).env(&[(TERM_PROGRAM, WARP_TERMINAL)]).expect(1), test(WARP_TERMINAL).env(&[(TERM_PROGRAM, WARP_TERMINAL)]).expect(1), test("In CI").env(&[("CI", "1")]).expect(1), test(format!("In ssh without {Q_PARENT}")) .env(&[("SSH_CLIENT", "1")]) .expect(1), test("__PWSH_LOGIN_CHECKED") .env(&[("__PWSH_LOGIN_CHECKED", "1")]) .expect(1), test("SecureCRT") .env(&[("__CFBundleIdentifier", "com.vandyke.SecureCRT")]) .expect(1), ]; for test in tests { test.run(); } } /// Tests for when the parent process is not valid. #[test] fn invalid_parent_tests() { let tests = [ test("no parent id").expect(1), test("invalid parent").parent_exe("/usr/bin/invalid").expect(1), test(format!("In Codespaces with {Q_TERM}")) .parent_exe("/usr/bin/zsh") .env(&[("CODESPACES", "1")]) .env(&[(Q_TERM, "1")]) .expect(1), ]; for test in tests { test.run(); } } /// Tests for when the grandparent process is not valid. #[test] fn invalid_grandparent_tests() { let tests = [ test("no grandparentparent id").parent_exe("/usr/bin/zsh").expect(1), test("on mac with valid parent and invalid grandparent") .os(Os::Mac) .parent_exe("/usr/bin/zsh") .grandparent_exe("/usr/bin/invalid") .expect(2), test("on linux with valid parent and invalid grandparent") .os(Os::Linux) .parent_exe("/usr/bin/zsh") .grandparent_exe("/usr/bin/invalid") .expect(1), ]; for test in tests { test.run(); } } /// Tests for when the app should launch, ignoring any of the overrides /// captured under override_tests below. #[test] fn should_launch_tests() { let tests = [ test("on linux with valid parent and valid grandparent exe") .os(Os::Linux) .parent_exe("/usr/bin/zsh") .grandparent_exe("/usr/bin/wezterm") .expect(0), test("on linux with valid parent, invalid grandparent exe, and valid grandparent cmdline") .os(Os::Linux) .parent_exe("/usr/bin/zsh") .grandparent_exe("/usr/bin/ld-2.26.so") .grandparent_cmdline("/usr/bin/tmux random args here") .expect(0), test("on mac with valid parent and special grandparent") .os(Os::Mac) .parent_exe("/usr/bin/zsh") .grandparent_exe("/usr/bin/tmux") .expect(0), test(format!("In Codespaces without {Q_TERM}")) .parent_exe("/usr/bin/zsh") .env(&[("CODESPACES", "1")]) .expect(0), test(format!("In ssh and {Q_TERM} is not set")) .parent_exe("/usr/bin/zsh") .env(&[("SSH_CLIENT", "1"), (Q_PARENT, "1")]) .expect(0), ]; for test in tests { test.run(); } } }