use portable_pty::{native_pty_system, CommandBuilder, MasterPty, PtySize};
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
pub type SharedPtyWriter = Arc<Mutex<Box<dyn Write + Send>>>;
pub type SharedMasterPty = Arc<Mutex<Box<dyn MasterPty + Send>>>;
pub type SharedChild = Arc<Mutex<Box<dyn portable_pty::Child + Send + Sync>>>;
pub struct Pty {
writer: SharedPtyWriter,
master: SharedMasterPty,
child: SharedChild,
}
impl Pty {
pub fn spawn(cols: u16, rows: u16) -> anyhow::Result<Self> {
let pty_system = native_pty_system();
let pair = pty_system.openpty(PtySize {
rows,
cols,
pixel_width: 0,
pixel_height: 0,
})?;
let mut cmd = CommandBuilder::new_default_prog();
cmd.env("TERM", "xterm-256color");
let child = pair.slave.spawn_command(cmd)?;
let writer = pair.master.take_writer()?;
Ok(Self {
writer: Arc::new(Mutex::new(writer)),
master: Arc::new(Mutex::new(pair.master)),
child: Arc::new(Mutex::new(child)),
})
}
pub fn writer_arc(&self) -> SharedPtyWriter {
self.writer.clone()
}
pub fn child_arc(&self) -> SharedChild {
self.child.clone()
}
pub fn master_arc(&self) -> SharedMasterPty {
self.master.clone()
}
pub fn is_child_alive(&self) -> bool {
match self.child.try_lock() {
Ok(mut c) => c.try_wait().ok().flatten().is_none(),
Err(std::sync::TryLockError::WouldBlock) => true,
Err(std::sync::TryLockError::Poisoned(e)) => {
tracing::warn!(error = %e, "child mutex poisoned in is_alive");
false
}
}
}
pub fn clone_reader(&self) -> anyhow::Result<Box<dyn Read + Send>> {
let master = self
.master
.lock()
.map_err(|e| anyhow::anyhow!("master mutex poisoned: {}", e))?;
master
.try_clone_reader()
.map_err(|e| anyhow::anyhow!("failed to clone PTY reader: {}", e))
}
}