omniterm 0.2.13

Web-based tmux terminal manager — one browser tab to watch and drive your AI coding agents
pub mod session;
pub mod terminal_ws;

use std::sync::{Arc, Mutex};
use tokio::task::spawn_blocking;
use tracing::debug;

use crate::engine::pty::session as pty;

#[allow(dead_code)]
pub struct PtyEngine;

#[allow(dead_code)]
impl PtyEngine {
    pub fn new() -> Self {
        Self
    }

    pub async fn spawn_command(
        &self,
        cmd: portable_pty::CommandBuilder,
        size: portable_pty::PtySize,
    ) -> anyhow::Result<PtySessionHandle> {
        let session = pty::PtySession::spawn(cmd, size)?;
        let mut reader = session.try_clone_reader()?;
        let (tx, rx) = tokio::sync::mpsc::channel::<Vec<u8>>(256);

        let session = Arc::new(Mutex::new(Some(session)));
        let reader_session = session.clone();
        spawn_blocking(move || {
            use std::io::Read;
            let mut buf = [0u8; 8192];
            loop {
                match reader.read(&mut buf) {
                    Ok(0) => break,
                    Ok(n) => {
                        if tx.blocking_send(buf[..n].to_vec()).is_err() {
                            break;
                        }
                    }
                    Err(_) => break,
                }
            }
            debug!("pty engine reader exited");
            drop(reader_session.lock());
        });

        Ok(PtySessionHandle { session, rx })
    }
}

#[allow(dead_code)]
pub struct PtySessionHandle {
    session: Arc<Mutex<Option<pty::PtySession>>>,
    rx: tokio::sync::mpsc::Receiver<Vec<u8>>,
}

#[allow(dead_code)]
impl PtySessionHandle {
    pub fn rx(&mut self) -> &mut tokio::sync::mpsc::Receiver<Vec<u8>> {
        &mut self.rx
    }

    pub fn write(&self, data: &[u8]) -> anyhow::Result<usize> {
        self.session
            .lock()
            .map_err(|_| anyhow::anyhow!("pty session mutex poisoned"))?
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("pty session already taken"))?
            .write(data)
            .map_err(Into::into)
    }

    pub fn resize(&self, size: portable_pty::PtySize) -> anyhow::Result<()> {
        self.session
            .lock()
            .map_err(|_| anyhow::anyhow!("pty session mutex poisoned"))?
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("pty session already taken"))?
            .resize(size)
            .map_err(Into::into)
    }

    pub fn kill(&self) {
        if let Ok(guard) = self.session.lock()
            && let Some(session) = guard.as_ref()
        {
            session.kill();
        }
    }
}

impl Drop for PtySessionHandle {
    fn drop(&mut self) {
        self.kill();
        if let Ok(mut guard) = self.session.lock() {
            guard.take();
        }
        debug!("pty engine session dropped");
    }
}