Skip to main content

Mountain/IPC/WindServiceHandler/
Terminal.rs

1#![allow(non_snake_case)]
2
3//! Terminal domain handlers for Wind IPC.
4
5use std::{collections::HashMap, sync::Arc};
6
7use serde_json::{Value, json};
8use CommonLibrary::Terminal::TerminalProvider::TerminalProvider;
9
10use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
11
12/// Create a new PTY terminal via TerminalProvider.
13pub async fn handle_terminal_create(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
14	let Options = Args.first().cloned().unwrap_or(Value::Null);
15	Runtime
16		.Environment
17		.CreateTerminal(Options)
18		.await
19		.map_err(|Error| format!("terminal:create failed: {}", Error))
20}
21
22/// Write text to PTY stdin via TerminalProvider.
23pub async fn handle_terminal_send_text(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
24	let TerminalId = Args
25		.first()
26		.and_then(|V| V.as_u64())
27		.ok_or_else(|| "terminal:sendText requires terminal_id as first argument".to_string())?;
28	let Text = Args.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
29
30	Runtime
31		.Environment
32		.SendTextToTerminal(TerminalId, Text)
33		.await
34		.map(|()| Value::Null)
35		.map_err(|Error| format!("terminal:sendText failed: {}", Error))
36}
37
38/// Dispose a terminal via TerminalProvider.
39pub async fn handle_terminal_dispose(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
40	let TerminalId = Args
41		.first()
42		.and_then(|V| V.as_u64())
43		.ok_or_else(|| "terminal:dispose requires terminal_id as first argument".to_string())?;
44
45	Runtime
46		.Environment
47		.DisposeTerminal(TerminalId)
48		.await
49		.map(|()| Value::Null)
50		.map_err(|Error| format!("terminal:dispose failed: {}", Error))
51}
52
53/// Show a terminal in the UI.
54pub async fn handle_terminal_show(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
55	let TerminalId = Args.first().and_then(|V| V.as_u64()).unwrap_or(0);
56	let PreserveFocus = Args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
57
58	Runtime
59		.Environment
60		.ShowTerminal(TerminalId, PreserveFocus)
61		.await
62		.map(|()| Value::Null)
63		.map_err(|Error| format!("terminal:show failed: {}", Error))
64}
65
66/// Hide a terminal.
67pub async fn handle_terminal_hide(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
68	let TerminalId = Args.first().and_then(|V| V.as_u64()).unwrap_or(0);
69
70	Runtime
71		.Environment
72		.HideTerminal(TerminalId)
73		.await
74		.map(|()| Value::Null)
75		.map_err(|Error| format!("terminal:hide failed: {}", Error))
76}
77
78// ============================================================================
79// Local PTY handlers
80// ============================================================================
81
82/// Detect available terminal profiles - cross-platform
83pub async fn handle_local_pty_get_profiles() -> Result<Value, String> {
84	let mut Profiles = Vec::new();
85
86	#[cfg(unix)]
87	{
88		let DefaultShell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
89
90		let UnixShells = [
91			"/bin/zsh",
92			"/bin/bash",
93			"/bin/sh",
94			"/usr/bin/zsh",
95			"/usr/bin/bash",
96			"/usr/bin/fish",
97			"/usr/local/bin/fish",
98			"/usr/local/bin/zsh",
99			"/usr/local/bin/bash",
100			"/bin/dash",
101			"/usr/bin/ksh",
102			"/usr/bin/tcsh",
103			"/bin/csh",
104			"/usr/bin/pwsh",
105			"/usr/local/bin/pwsh",
106		];
107
108		for Shell in &UnixShells {
109			if std::path::Path::new(Shell).exists() {
110				let Name = std::path::Path::new(Shell)
111					.file_name()
112					.and_then(|N| N.to_str())
113					.unwrap_or("shell");
114
115				Profiles.push(json!({
116					"profileName": Name,
117					"path": Shell,
118					"isDefault": *Shell == DefaultShell.as_str(),
119					"args": [],
120					"env": {},
121					"icon": "terminal"
122				}));
123			}
124		}
125
126		if let Ok(ShellsFile) = std::fs::read_to_string("/etc/shells") {
127			for Line in ShellsFile.lines() {
128				let Trimmed = Line.trim();
129				if Trimmed.starts_with('/') && !Trimmed.starts_with('#') {
130					let AlreadyAdded = Profiles.iter().any(|P| P.get("path").and_then(|V| V.as_str()) == Some(Trimmed));
131					if !AlreadyAdded && std::path::Path::new(Trimmed).exists() {
132						let Name = std::path::Path::new(Trimmed)
133							.file_name()
134							.and_then(|N| N.to_str())
135							.unwrap_or("shell");
136
137						Profiles.push(json!({
138							"profileName": Name,
139							"path": Trimmed,
140							"isDefault": Trimmed == DefaultShell.as_str(),
141							"args": [],
142							"env": {},
143							"icon": "terminal"
144						}));
145					}
146				}
147			}
148		}
149	}
150
151	#[cfg(target_os = "windows")]
152	{
153		let SystemRoot = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
154		let ProgramFiles = std::env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
155		let LocalAppData =
156			std::env::var("LOCALAPPDATA").unwrap_or_else(|_| "C:\\Users\\User\\AppData\\Local".to_string());
157
158		let WindowsShells:Vec<(&str, String, Vec<&str>)> = vec![
159			(
160				"PowerShell",
161				format!("{}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", SystemRoot),
162				vec!["-NoLogo"],
163			),
164			(
165				"PowerShell 7",
166				format!("{}\\PowerShell\\7\\pwsh.exe", ProgramFiles),
167				vec!["-NoLogo"],
168			),
169			("Command Prompt", format!("{}\\System32\\cmd.exe", SystemRoot), vec![]),
170			(
171				"Git Bash",
172				format!("{}\\Git\\bin\\bash.exe", ProgramFiles),
173				vec!["--login", "-i"],
174			),
175			(
176				"Git Bash (User)",
177				format!("{}\\Programs\\Git\\bin\\bash.exe", LocalAppData),
178				vec!["--login", "-i"],
179			),
180			("WSL", format!("{}\\System32\\wsl.exe", SystemRoot), vec![]),
181			("MSYS2", "C:\\msys64\\usr\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
182			("Cygwin", "C:\\cygwin64\\bin\\bash.exe".to_string(), vec!["--login", "-i"]),
183		];
184
185		let mut IsFirstFound = true;
186		for (Name, Path, Args) in &WindowsShells {
187			if std::path::Path::new(Path).exists() {
188				Profiles.push(json!({
189					"profileName": Name,
190					"path": Path,
191					"isDefault": IsFirstFound,
192					"args": Args,
193					"env": {},
194					"icon": "terminal"
195				}));
196				IsFirstFound = false;
197			}
198		}
199	}
200
201	Ok(json!(Profiles))
202}
203
204/// Get default system shell - cross-platform
205pub async fn handle_local_pty_get_default_shell() -> Result<Value, String> {
206	#[cfg(unix)]
207	{
208		let Shell = std::env::var("SHELL").unwrap_or_else(|_| {
209			for Path in &["/bin/zsh", "/bin/bash", "/bin/sh"] {
210				if std::path::Path::new(Path).exists() {
211					return Path.to_string();
212				}
213			}
214			"/bin/sh".to_string()
215		});
216		Ok(json!(Shell))
217	}
218
219	#[cfg(target_os = "windows")]
220	{
221		let SystemRoot = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
222		let PwshPath = format!("{}\\PowerShell\\7\\pwsh.exe", std::env::var("ProgramFiles").unwrap_or_default());
223		if std::path::Path::new(&PwshPath).exists() {
224			return Ok(json!(PwshPath));
225		}
226		Ok(json!(format!(
227			"{}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
228			SystemRoot
229		)))
230	}
231
232	#[cfg(not(any(unix, target_os = "windows")))]
233	{
234		Ok(json!("/bin/sh"))
235	}
236}
237
238/// Get terminal environment
239pub async fn handle_local_pty_get_environment() -> Result<Value, String> {
240	let Env:HashMap<String, String> = std::env::vars().collect();
241	Ok(json!(Env))
242}