diff --git a/src/daemon/shell_integration.rs b/src/daemon/shell_integration.rs index ab058a73..a03054c8 100644 --- a/src/daemon/shell_integration.rs +++ b/src/daemon/shell_integration.rs @@ -810,20 +810,6 @@ fn shell_kind(program: Option<&str>) -> Option { } } -/// The shell kind named by a path *inside a distro*, where the Windows-specific -/// disambiguation in [`shell_kind`] must not apply: `/bin/bash` there is -/// genuinely bash, not the WSL launcher, even though the daemon asking the -/// question is a Windows process. -#[cfg_attr(not(windows), allow(dead_code))] -fn inner_shell_kind(path: &str) -> Option { - match Path::new(path).file_name()?.to_str()? { - "zsh" => Some(ShellKind::Zsh), - "bash" => Some(ShellKind::Bash), - "fish" => Some(ShellKind::Fish), - _ => None, - } -} - /// The distro named by a `wsl.exe` argv, if any. tty7's own launch args spell it /// `--distribution ` (`core::shells::detect_shells`); `-d` is the short /// form a user-configured shell may use. Absent means "the default distro", @@ -1075,75 +1061,59 @@ fn setup_bash() -> Option { }) } -/// The login shell of record inside `distro`, probed once and cached — the -/// daemon would otherwise pay a `wsl.exe` startup on every WSL pane spawn. -/// -/// `getent passwd` rather than `$SHELL`: the latter is whatever the launching -/// environment exported, which for a shell `wsl.exe` starts for a probe is not -/// necessarily the user's configured login shell. `$(id -u)` rather than -/// `$(id -un)` keeps the command free of nested quotes. -#[cfg(windows)] -fn wsl_login_shell(distro: Option<&str>) -> Option { - use std::sync::{Mutex, OnceLock}; - static CACHE: OnceLock>>> = OnceLock::new(); - - let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new())); - let key = distro.unwrap_or_default().to_string(); - if let Some(hit) = cache.lock().ok()?.get(&key) { - return hit.clone(); - } - - let mut cmd = std::process::Command::new("wsl.exe"); - if let Some(d) = distro { - cmd.args(["--distribution", d]); - } - cmd.args(["-e", "sh", "-c", "getent passwd $(id -u) | cut -d: -f7"]); - // `hide_console` keeps the probe from flashing a console window — tty7 is a - // GUI process (see `core::proc`). - let probed = crate::core::proc::hide_console(&mut cmd) - .output() - .ok() - .filter(|o| o.status.success()) - .and_then(|o| { - let s = String::from_utf8_lossy(&o.stdout).trim().to_string(); - (!s.is_empty()).then_some(s) - }); - cache.lock().ok()?.insert(key, probed.clone()); - probed -} - /// Env var carrying the rcfile path across the Windows/WSL boundary. Listed in /// `WSLENV` with the `/p` flag so WSL rewrites it to the distro's view of the /// path (`C:\Users\…` -> `/mnt/c/Users/…`), which is why we don't hardcode the /// `/mnt` automount root ourselves — it is configurable in `/etc/wsl.conf`. const WSL_RCFILE_ENV: &str = "TTY7_RC"; +/// Pick the distro's shell and exec it, *inside the distro*. +/// +/// Deliberately not a Windows-side probe. Spawning `wsl.exe` to ask which shell +/// a distro uses blocks the whole spawn path: the client waits synchronously for +/// the daemon's `Spawn` reply (see `terminal::remote::spawn`), so on a cold WSL +/// start — seconds, while the distro boots — the entire window freezes. Folding +/// the decision into the one `wsl.exe` invocation we were always going to make +/// costs nothing and cannot block, because there is no second invocation. +/// +/// `$SHELL` rather than `getent passwd`: WSL populates it from the user's passwd +/// entry, so inside the distro it already *is* the login shell of record — the +/// same source `shell_kind` trusts on Unix. Written without a variable +/// assignment so the whole thing stays one `case`, which keeps it robust to the +/// layers of quoting between here and `sh`. +const WSL_EXEC_SCRIPT: &str = concat!( + r#"case "${SHELL:-}" in "#, + r#"*/bash) exec "$SHELL" --rcfile "$TTY7_RC" -i ;; "#, + r#"*) exec "${SHELL:-/bin/sh}" -l ;; "#, + "esac" +); + /// Reach through `wsl.exe` to the distro's own shell. /// /// `wsl.exe` is a launcher, not a shell: injecting into it directly would never -/// reach the thing that draws the prompt. So we probe the distro's login shell, -/// write the matching integration rcfile on the Windows side, and hand `wsl.exe` -/// an explicit command that starts that shell with our rcfile — the distro's -/// own startup chain replayed inside it exactly as on any other bash. +/// reach the thing that draws the prompt. So we write the integration rcfile on +/// the Windows side and hand `wsl.exe` a command that starts the distro's shell +/// with it — the distro's own startup chain replayed inside it exactly as on any +/// other bash. /// -/// The argv shape is `[] -- sh -c 'exec --rcfile "$RC" -i'` -/// rather than `-- --rcfile ` because the path only exists as an -/// env var *inside* the distro after `WSLENV` translation, and `wsl.exe` execs -/// its command directly without a shell to expand it. The one-shot `sh` costs a +/// The argv shape is `[] -- sh -c