Files
dosh/src/pty.rs
T
Codex 555d738a85
ci / test (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
Initial Dosh implementation
2026-06-11 08:42:28 -04:00

85 lines
2.3 KiB
Rust

use anyhow::{Context, Result};
use portable_pty::{CommandBuilder, MasterPty, NativePtySystem, PtySize, PtySystem};
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
use std::thread;
use tokio::sync::mpsc;
pub struct PtyHandle {
writer: Arc<Mutex<Box<dyn Write + Send>>>,
_master: Box<dyn MasterPty + Send>,
}
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<u8>,
}
pub fn spawn_pty_session(
session: String,
shell: &str,
cols: u16,
rows: u16,
tx: mpsc::UnboundedSender<PtyOutput>,
) -> Result<PtyHandle> {
let pty_system = NativePtySystem::default();
let pair = pty_system
.openpty(PtySize {
rows,
cols,
pixel_width: 0,
pixel_height: 0,
})
.context("open pty")?;
let cmd = CommandBuilder::new(shell);
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) => break,
Ok(n) => {
let _ = tx.send(PtyOutput {
session: reader_session.clone(),
bytes: buf[..n].to_vec(),
});
}
Err(_) => break,
}
}
})
.context("spawn pty reader")?;
Ok(PtyHandle {
writer: Arc::new(Mutex::new(writer)),
_master: pair.master,
})
}