feat(runtime): add interactive SSH PTY entrypoint
test / workspace (push) Successful in 13m28s

This commit is contained in:
Tom You
2026-07-09 00:56:05 -05:00
parent 02ae7a0e6f
commit 2abb2b955e
2 changed files with 105 additions and 1 deletions
+79 -1
View File
@@ -64,6 +64,31 @@ impl TerminalSessionRegistry {
cols: u16, cols: u16,
rows: u16, rows: u16,
) -> Result<TerminalSessionId, TerminalSessionError> { ) -> Result<TerminalSessionId, TerminalSessionError> {
self.spawn_program(shell.as_ref(), &[], cols, rows)
}
pub fn spawn_ssh(
&mut self,
host: impl AsRef<str>,
user: Option<&str>,
port: u16,
cols: u16,
rows: u16,
) -> Result<TerminalSessionId, TerminalSessionError> {
let args = build_ssh_pty_args(host.as_ref(), user, port)?;
self.spawn_program("ssh", &args, cols, rows)
}
fn spawn_program(
&mut self,
program: &str,
args: &[String],
cols: u16,
rows: u16,
) -> Result<TerminalSessionId, TerminalSessionError> {
if program.trim().is_empty() {
return Err(TerminalSessionError::Io("program is required".to_string()));
}
let size = session_size(cols, rows)?; let size = session_size(cols, rows)?;
let pty_system = native_pty_system(); let pty_system = native_pty_system();
let pair = pty_system let pair = pty_system
@@ -74,7 +99,10 @@ impl TerminalSessionRegistry {
pixel_height: 0, pixel_height: 0,
}) })
.map_err(|err| TerminalSessionError::Io(err.to_string()))?; .map_err(|err| TerminalSessionError::Io(err.to_string()))?;
let command = CommandBuilder::new(shell.as_ref()); let mut command = CommandBuilder::new(program);
for arg in args {
command.arg(arg);
}
let child = pair let child = pair
.slave .slave
.spawn_command(command) .spawn_command(command)
@@ -193,6 +221,31 @@ impl TerminalSessionRegistry {
} }
} }
pub fn build_ssh_pty_args(
host: &str,
user: Option<&str>,
port: u16,
) -> Result<Vec<String>, TerminalSessionError> {
if host.trim().is_empty() {
return Err(TerminalSessionError::Io("ssh host is required".to_string()));
}
if port == 0 {
return Err(TerminalSessionError::Io("ssh port is required".to_string()));
}
let destination = match user.map(str::trim).filter(|value| !value.is_empty()) {
Some(user) => format!("{user}@{}", host.trim()),
None => host.trim().to_string(),
};
Ok(vec![
"-tt".to_string(),
"-o".to_string(),
"BatchMode=yes".to_string(),
"-p".to_string(),
port.to_string(),
destination,
])
}
fn session_size(cols: u16, rows: u16) -> Result<TerminalSessionSize, TerminalSessionError> { fn session_size(cols: u16, rows: u16) -> Result<TerminalSessionSize, TerminalSessionError> {
if cols == 0 || rows == 0 { if cols == 0 || rows == 0 {
return Err(TerminalSessionError::InvalidSize); return Err(TerminalSessionError::InvalidSize);
@@ -240,4 +293,29 @@ mod tests {
assert_eq!(err, TerminalSessionError::MissingSession(999)); assert_eq!(err, TerminalSessionError::MissingSession(999));
} }
#[test]
fn ssh_pty_args_enable_tty_and_batch_mode() {
let args = build_ssh_pty_args("example.com", Some("alice"), 2222).unwrap();
assert_eq!(
args,
vec![
"-tt",
"-o",
"BatchMode=yes",
"-p",
"2222",
"alice@example.com"
]
);
}
#[test]
fn ssh_pty_args_reject_blank_host() {
assert_eq!(
build_ssh_pty_args(" ", None, 22).unwrap_err(),
TerminalSessionError::Io("ssh host is required".to_string())
);
}
} }
+26
View File
@@ -118,6 +118,31 @@ fn terminal_session_start(
.map_err(|err| err.to_string()) .map_err(|err| err.to_string())
} }
#[tauri::command]
fn terminal_session_start_ssh(
state: tauri::State<TerminalState>,
host: String,
user: Option<String>,
port: Option<u16>,
cols: Option<u16>,
rows: Option<u16>,
) -> Result<u64, String> {
let mut registry = state
.0
.lock()
.map_err(|_| "terminal registry lock poisoned".to_string())?;
registry
.spawn_ssh(
host,
user.as_deref(),
port.unwrap_or(22),
cols.unwrap_or(80),
rows.unwrap_or(24),
)
.map(|id| id.0)
.map_err(|err| err.to_string())
}
#[tauri::command] #[tauri::command]
fn terminal_session_write( fn terminal_session_write(
state: tauri::State<TerminalState>, state: tauri::State<TerminalState>,
@@ -229,6 +254,7 @@ fn main() {
load_app_config, load_app_config,
save_app_config, save_app_config,
terminal_session_start, terminal_session_start,
terminal_session_start_ssh,
terminal_session_write, terminal_session_write,
terminal_session_read_until, terminal_session_read_until,
terminal_session_resize, terminal_session_resize,