fix(terminal): Default to the account's login shell

$SHELL is inherited from whatever launched the app, so starting it from a
terminal running bash opened bash even when the login shell is zsh. Read
the passwd entry first and fall back to $SHELL.

Claude-Session: https://claude.ai/code/session_01B65qLEKnkeYTGdbwhLpjCu
Цей коміт міститься в:
l0ng-ai committed 2026-10-24 12:36:40 +08:00
1 джерело 1bb454bd2f
коміт 1f5fcb477d
2 files changed
+30 -3

No files matched your search

+29 -2
Переглянути файл
@@ -32,7 +32,7 @@ const KILL_GRACE: Duration = Duration::from_millis(1500);
#[serde(rename_all = "camelCase")]
pub struct TerminalOptions {
pub cwd: ide_core::IpcPath,
/// Defaults to the user's login shell (`$SHELL`, or `COMSPEC` on Windows).
/// Defaults to the user's login shell (passwd entry, then `$SHELL`; PowerShell or `COMSPEC` on Windows).
pub shell: Option<String>,
pub args: Vec<String>,
pub env: BTreeMap<String, String>,
@@ -372,14 +372,34 @@ fn resolve_shell(shell: Option<&str>, args: &[String]) -> (String, Vec<String>)
(shell, args)
}
/// The account's login shell comes first: `$SHELL` is inherited from whatever
/// launched the app (a terminal running another shell, `tauri dev`), so it may
/// not be the shell the user picked.
#[cfg(unix)]
fn default_shell() -> String {
if let Some(shell) = std::env::var("SHELL").ok().filter(|s| !s.is_empty() && Path::new(s).exists()) {
let usable = |s: &String| !s.is_empty() && Path::new(s).exists();
if let Some(shell) = login_shell().filter(usable).or_else(|| std::env::var("SHELL").ok().filter(usable)) {
return shell;
}
["/bin/zsh", "/bin/bash", "/bin/sh"].into_iter().find(|p| Path::new(p).exists()).unwrap_or("/bin/sh").to_owned()
}
/// `pw_shell` of the current user's passwd entry.
#[cfg(unix)]
fn login_shell() -> Option<String> {
let mut buf = vec![0 as libc::c_char; 4096];
let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut result: *mut libc::passwd = std::ptr::null_mut();
// SAFETY: every pointer refers to a live local buffer of the stated size.
let rc = unsafe { libc::getpwuid_r(libc::getuid(), &mut pwd, buf.as_mut_ptr(), buf.len(), &mut result) };
if rc != 0 || result.is_null() || pwd.pw_shell.is_null() {
return None;
}
// SAFETY: on success `pw_shell` is a NUL-terminated string inside `buf`.
let shell = unsafe { std::ffi::CStr::from_ptr(pwd.pw_shell) };
Some(shell.to_string_lossy().into_owned())
}
#[cfg(windows)]
fn default_shell() -> String {
for candidate in ["pwsh.exe", "powershell.exe"] {
@@ -421,6 +441,13 @@ fn terminal_env(user: &BTreeMap<String, String>) -> BTreeMap<String, String> {
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[cfg(unix)]
#[test]
fn login_shell_reads_the_passwd_entry() {
let shell = login_shell().expect("passwd entry");
assert!(shell.starts_with('/'), "{shell}");
}
use std::sync::mpsc::Receiver;
struct Harness {
+1 -1
Переглянути файл
@@ -3740,7 +3740,7 @@ export type TerminalInfo = {
export type TerminalOptions = {
cwd: IpcPath,
/** Defaults to the user's login shell (`$SHELL`, or `COMSPEC` on Windows). */
/** Defaults to the user's login shell (passwd entry, then `$SHELL`; PowerShell or `COMSPEC` on Windows). */
shell: string | null,
args: string[],
env: { [key in string]: string },