Files
dosh/src/pty.rs
T
Codex 0eca4f1cf4
ci / test (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
Add fresh default sessions and terminal status handling
2026-06-11 09:41:46 -04:00

112 lines
3.3 KiB
Rust

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<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 exited: bool,
}
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 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,
})
}