use anyhow::{Context, Result}; use portable_pty::{CommandBuilder, MasterPty, NativePtySystem, PtySize, PtySystem}; use std::io::{Read, Write}; use std::path::Path; use std::sync::{Arc, Mutex}; use std::thread; use tokio::sync::mpsc; pub struct PtyHandle { writer: Arc>>, _master: Box, } impl PtyHandle { pub fn write_all(&self, bytes: &[u8]) -> Result<()> { let mut writer = self.writer.lock().expect("pty writer poisoned"); writer.write_all(bytes)?; writer.flush()?; Ok(()) } pub fn resize(&self, cols: u16, rows: u16) -> Result<()> { self._master.resize(PtySize { rows, cols, pixel_width: 0, pixel_height: 0, })?; Ok(()) } } #[derive(Debug)] pub struct PtyOutput { pub session: String, pub bytes: Vec, pub exited: bool, } pub fn spawn_pty_session( session: String, shell: &str, cols: u16, rows: u16, tx: mpsc::UnboundedSender, ) -> Result { let pty_system = NativePtySystem::default(); let pair = pty_system .openpty(PtySize { rows, cols, pixel_width: 0, pixel_height: 0, }) .context("open pty")?; let mut cmd = CommandBuilder::new(shell); cmd.env("TERM", "xterm-256color"); cmd.env("COLORTERM", "truecolor"); cmd.env("SHELL", shell); if let Some(home) = dirs::home_dir() { cmd.env("HOME", home.as_os_str()); cmd.env("PWD", home.as_os_str()); cmd.cwd(home.as_os_str()); } else if let Some(parent) = Path::new(shell).parent() { cmd.env("PWD", parent.as_os_str()); } let _child = pair.slave.spawn_command(cmd).context("spawn shell")?; drop(pair.slave); let writer = pair.master.take_writer().context("take pty writer")?; let mut reader = pair.master.try_clone_reader().context("clone pty reader")?; let reader_session = session.clone(); thread::Builder::new() .name(format!("dosh-pty-{session}")) .spawn(move || { let mut buf = [0u8; 8192]; loop { match reader.read(&mut buf) { Ok(0) => { let _ = tx.send(PtyOutput { session: reader_session.clone(), bytes: Vec::new(), exited: true, }); break; } Ok(n) => { let _ = tx.send(PtyOutput { session: reader_session.clone(), bytes: buf[..n].to_vec(), exited: false, }); } Err(_) => { let _ = tx.send(PtyOutput { session: reader_session.clone(), bytes: Vec::new(), exited: true, }); break; } } } }) .context("spawn pty reader")?; Ok(PtyHandle { writer: Arc::new(Mutex::new(writer)), _master: pair.master, }) }