//! TerminalHandle: bridges ratatui's Write trait to an SSH channel. //! //! Buffers writes in a Vec, then on flush() sends the buffer //! contents via an mpsc channel to a spawned task that relays data //! to the SSH session. use std::io; use tokio::sync::mpsc; /// A `Write` sink that buffers output and flushes it to an SSH channel /// via an async mpsc sender. pub struct TerminalHandle { sink: Vec, tx: mpsc::Sender>, } impl TerminalHandle { /// Create a new TerminalHandle and spawn the relay task. /// /// The relay task forwards buffered data to the SSH session's /// channel via `session_handle.data()`. pub fn new( session_handle: russh::server::Handle, channel_id: russh::ChannelId, ) -> Self { let (tx, mut rx) = mpsc::channel::>(64); tokio::spawn(async move { while let Some(data) = rx.recv().await { // Handle::data() accepts impl Into; Vec converts directly. if session_handle.data(channel_id, data).await.is_err() { break; } } }); Self { sink: Vec::with_capacity(4096), tx, } } } impl io::Write for TerminalHandle { fn write(&mut self, buf: &[u8]) -> io::Result { self.sink.extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { if self.sink.is_empty() { return Ok(()); } let data = std::mem::take(&mut self.sink); self.tx .try_send(data) .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e))?; Ok(()) } }