//! PTY bridge — spawns a shell and bridges I/O to the SSH channel. use anyhow::{Context, Result}; use portable_pty::{native_pty_system, CommandBuilder, PtySize}; use std::io::{Read, Write}; /// Spawn a PTY with the given command and return the bridge. pub fn spawn_pty( command: &str, args: &[String], env: Vec<(String, String)>, cols: u16, rows: u16, ) -> Result { let pty_system = native_pty_system(); let pair = pty_system .openpty(PtySize { rows, cols, pixel_width: 0, pixel_height: 0, }) .context("Failed to open PTY")?; let mut cmd = CommandBuilder::new(command); for arg in args { cmd.arg(arg); } for (key, val) in &env { cmd.env(key, val); } let child = pair .slave .spawn_command(cmd) .context("Failed to spawn shell")?; let reader = pair .master .try_clone_reader() .context("Failed to clone PTY reader")?; let writer = pair .master .take_writer() .context("Failed to take PTY writer")?; Ok(PtyBridge { master: pair.master, child, reader: Some(reader), writer, }) } /// A running PTY session with I/O handles. pub struct PtyBridge { pub master: Box, pub child: Box, /// PTY reader — `take()` this to move it into a dedicated read thread. pub reader: Option>, pub writer: Box, } impl PtyBridge { /// Take the reader out of this bridge for use on a dedicated thread. /// Returns None if already taken. pub fn take_reader(&mut self) -> Option> { self.reader.take() } /// Resize the PTY. pub fn resize(&self, cols: u16, rows: u16) -> Result<()> { self.master .resize(PtySize { rows, cols, pixel_width: 0, pixel_height: 0, }) .context("Failed to resize PTY")?; Ok(()) } }