use portable_pty::{native_pty_system, CommandBuilder, PtySize};
use std::io::{self, Read, Write};
use std::process::exit;
use std::thread;
const USAGE: &str = "usage: pty_bridge <cols> <rows> -- <program> [args...]";
struct Args {
cols: u16,
rows: u16,
program: String,
program_args: Vec<String>,
}
fn parse_args() -> Args {
let argv: Vec<String> = std::env::args().collect();
if argv.len() < 5 || argv[3] != "--" {
eprintln!("{USAGE}");
exit(2);
}
let cols: u16 = argv[1].parse().unwrap_or_else(|_| {
eprintln!("{USAGE}\ncols must be a number, got {:?}", argv[1]);
exit(2);
});
let rows: u16 = argv[2].parse().unwrap_or_else(|_| {
eprintln!("{USAGE}\nrows must be a number, got {:?}", argv[2]);
exit(2);
});
let rest = &argv[4..];
Args {
cols,
rows,
program: rest[0].clone(),
program_args: rest[1..].to_vec(),
}
}
fn exit_code_for(status: &portable_pty::ExitStatus) -> i32 {
if status.signal().is_some() {
128
} else {
status.exit_code() as i32
}
}
fn main() {
let args = parse_args();
let pty_system = native_pty_system();
let pair = pty_system
.openpty(PtySize { rows: args.rows, cols: args.cols, pixel_width: 0, pixel_height: 0 })
.unwrap_or_else(|e| {
eprintln!("pty_bridge: failed to open pty: {e}");
exit(1);
});
let mut cmd = CommandBuilder::new(&args.program);
cmd.args(&args.program_args);
let child = pair.slave.spawn_command(cmd).unwrap_or_else(|e| {
eprintln!("pty_bridge: failed to spawn '{}': {e}", args.program);
exit(1);
});
drop(pair.slave);
let mut reader = pair.master.try_clone_reader().unwrap_or_else(|e| {
eprintln!("pty_bridge: failed to clone pty reader: {e}");
exit(1);
});
let mut writer = pair.master.take_writer().unwrap_or_else(|e| {
eprintln!("pty_bridge: failed to take pty writer: {e}");
exit(1);
});
thread::spawn(move || {
let mut buf = [0u8; 8192];
let mut stdout = io::stdout();
loop {
match reader.read(&mut buf) {
Ok(0) | Err(_) => return, Ok(n) => {
if stdout.write_all(&buf[..n]).is_err() || stdout.flush().is_err() {
return;
}
}
}
}
});
thread::spawn(move || {
let mut child = child;
let code = match child.wait() {
Ok(status) => exit_code_for(&status),
Err(_) => 1,
};
exit(code);
});
let mut buf = [0u8; 8192];
let mut stdin = io::stdin();
loop {
match stdin.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => {
if writer.write_all(&buf[..n]).is_err() {
break;
}
}
}
}
drop(writer);
loop {
thread::park();
}
}