112 lines
3.3 KiB
Rust
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,
|
|
})
|
|
}
|