Skip to main content

Mountain/IPC/WindServiceHandlers/Terminal/
LocalPTYGetProfiles.rs

1
2//! Discover available terminal profiles. Probes every well-
3//! known shell location plus `/etc/shells` (Unix) or known
4//! Windows install paths. The first existing match flags
5//! `isDefault=true`; on Unix the user's `$SHELL` wins.
6//!
7//! The wire shape matches VS Code's
8//! `ITerminalProfileProvider.profileName / path / Arguments /
9//! env / icon / isDefault` so Wind's terminal picker renders
10//! without reshaping.
11
12use serde_json::{Value, json};
13
14pub async fn Fn() -> Result<Value, String> {
15	let mut Profiles = Vec::new();
16
17	#[cfg(unix)]
18	{
19		let DefaultShell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
20
21		let UnixShells = [
22			"/bin/zsh",
23			"/bin/bash",
24			"/bin/sh",
25			"/usr/bin/zsh",
26			"/usr/bin/bash",
27			"/usr/bin/fish",
28			"/usr/local/bin/fish",
29			"/usr/local/bin/zsh",
30			"/usr/local/bin/bash",
31			"/bin/dash",
32			"/usr/bin/ksh",
33			"/usr/bin/tcsh",
34			"/bin/csh",
35			"/usr/bin/pwsh",
36			"/usr/local/bin/pwsh",
37		];
38
39		for Shell in &UnixShells {
40			if std::path::Path::new(Shell).exists() {
41				let Name = std::path::Path::new(Shell)
42					.file_name()
43					.and_then(|N| N.to_str())
44					.unwrap_or("shell");
45
46				Profiles.push(json!({
47					"profileName": Name,
48					"path": Shell,
49					"isDefault": *Shell == DefaultShell.as_str(),
50					"Arguments": [],
51					"env": {},
52					"icon": "terminal"
53				}));
54			}
55		}
56
57		if let Ok(ShellsFile) = std::fs::read_to_string("/etc/shells") {
58			for Line in ShellsFile.lines() {
59				let Trimmed = Line.trim();
60
61				if Trimmed.starts_with('/') && !Trimmed.starts_with('#') {
62					let AlreadyAdded = Profiles.iter().any(|P| P.get("path").and_then(|V| V.as_str()) == Some(Trimmed));
63
64					if !AlreadyAdded && std::path::Path::new(Trimmed).exists() {
65						let Name = std::path::Path::new(Trimmed)
66							.file_name()
67							.and_then(|N| N.to_str())
68							.unwrap_or("shell");
69
70						Profiles.push(json!({
71							"profileName": Name,
72							"path": Trimmed,
73							"isDefault": Trimmed == DefaultShell.as_str(),
74							"Arguments": [],
75							"env": {},
76							"icon": "terminal"
77						}));
78					}
79				}
80			}
81		}
82	}
83
84	#[cfg(target_os = "windows")]
85	{
86		let SystemRoot = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
87
88		let ProgramFiles = std::env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
89
90		let LocalAppData =
91			std::env::var("LOCALAPPDATA").unwrap_or_else(|_| "C:\\Users\\User\\AppData\\Local".to_string());
92
93		let WindowsShells:Vec<(&str, String, Vec<&str>)> = vec![
94			(
95				"PowerShell",
96				format!("{}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", SystemRoot),
97				vec!["-NoLogo"],
98			),
99			(
100				"PowerShell 7",
101				format!("{}\\PowerShell\\7\\pwsh.exe", ProgramFiles),
102				vec!["-NoLogo"],
103			),
104			("Command Prompt", format!("{}\\System32\\cmd.exe", SystemRoot), vec![]),
105			(
106				"Git Bash",
107				format!("{}\\Git\\bin\\bash.exe", ProgramFiles),
108				vec!["--login", "-i"],
109			),
110			(
111				"Git Bash (User)",
112				format!("{}\\Programs\\Git\\bin\\bash.exe", LocalAppData),
113				vec!["--login", "-i"],
114			),
115			("WSL", format!("{}\\System32\\wsl.exe", SystemRoot), vec![]),
116			("MSYS2", "C:\\msys64\\usr\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
117			("Cygwin", "C:\\cygwin64\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
118		];
119
120		let mut IsFirstFound = true;
121
122		for (Name, Path, Args) in &WindowsShells {
123			if std::path::Path::new(Path).exists() {
124				Profiles.push(json!({
125					"profileName": Name,
126					"path": Path,
127					"isDefault": IsFirstFound,
128					"Arguments": Args,
129					"env": {},
130					"icon": "terminal"
131				}));
132
133				IsFirstFound = false;
134			}
135		}
136	}
137
138	Ok(json!(Profiles))
139}