use std::io::{Read, Write};
use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize};
use crate::shell::Launch;
pub struct Pty {
master: Option<Box<dyn MasterPty + Send>>,
writer: Option<Box<dyn Write + Send>>,
child: Box<dyn Child + Send + Sync>,
}
pub struct SpawnOptions {
pub cols: u16,
pub rows: u16,
pub cwd: Option<String>,
pub env: Vec<(String, String)>,
}
impl Pty {
pub fn spawn(
target: &str,
args: &[String],
opts: &SpawnOptions,
) -> anyhow::Result<(Pty, Box<dyn Read + Send>)> {
let pty_system = native_pty_system();
let pair = pty_system.openpty(PtySize {
rows: opts.rows,
cols: opts.cols,
pixel_width: 0,
pixel_height: 0,
})?;
let mut cmd = CommandBuilder::new(target);
for arg in args {
cmd.arg(arg);
}
for (k, v) in std::env::vars() {
cmd.env(k, v);
}
cmd.env("TERM", "xterm-256color");
for (k, v) in &opts.env {
cmd.env(k, v);
}
if let Some(cwd) = &opts.cwd {
cmd.cwd(cwd);
} else if let Ok(cwd) = std::env::current_dir() {
cmd.cwd(cwd);
}
let child = pair.slave.spawn_command(cmd)?;
drop(pair.slave);
let reader = pair.master.try_clone_reader()?;
let writer = pair.master.take_writer()?;
Ok((
Pty {
master: Some(pair.master),
writer: Some(writer),
child,
},
reader,
))
}
pub fn spawn_launch(
launch: &Launch,
cols: u16,
rows: u16,
cwd: Option<String>,
) -> anyhow::Result<(Pty, Box<dyn Read + Send>)> {
let opts = SpawnOptions {
cols,
rows,
cwd,
env: launch.env.clone(),
};
Pty::spawn(&launch.target, &launch.args, &opts)
}
pub fn write(&mut self, data: &[u8]) -> std::io::Result<()> {
let writer = self
.writer
.as_mut()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::BrokenPipe, "PTY is closed"))?;
writer.write_all(data)?;
writer.flush()
}
pub fn resize(&mut self, cols: u16, rows: u16) -> anyhow::Result<()> {
let master = self
.master
.as_ref()
.ok_or_else(|| anyhow::anyhow!("PTY is closed"))?;
master.resize(PtySize {
rows,
cols,
pixel_width: 0,
pixel_height: 0,
})?;
Ok(())
}
pub fn pid(&self) -> Option<u32> {
self.child.process_id()
}
pub fn kill(&mut self) {
let _ = self.child.kill();
}
pub fn close(&mut self) {
self.kill();
self.writer.take();
self.master.take();
}
pub fn signal(&mut self, name: &str) -> anyhow::Result<()> {
let upper = name.trim_start_matches("SIG").to_uppercase();
match upper.as_str() {
"INT" => self.write(b"\x03")?,
"TERM" | "KILL" | "QUIT" => self.kill(),
other => anyhow::bail!("unsupported signal: {other}"),
}
Ok(())
}
pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
self.child
.try_wait()
.map(|status| status.map(|status| status.exit_code() as i32))
}
}