Skip to main content

max / mnw-cli

1.7 KB · 62 lines History Blame Raw
1 //! TerminalHandle: bridges ratatui's Write trait to an SSH channel.
2 //!
3 //! Buffers writes in a Vec<u8>, then on flush() sends the buffer
4 //! contents via an mpsc channel to a spawned task that relays data
5 //! to the SSH session.
6
7 use std::io;
8
9 use tokio::sync::mpsc;
10
11 /// A `Write` sink that buffers output and flushes it to an SSH channel
12 /// via an async mpsc sender.
13 pub struct TerminalHandle {
14 sink: Vec<u8>,
15 tx: mpsc::Sender<Vec<u8>>,
16 }
17
18 impl TerminalHandle {
19 /// Create a new TerminalHandle and spawn the relay task.
20 ///
21 /// The relay task forwards buffered data to the SSH session's
22 /// channel via `session_handle.data()`.
23 pub fn new(
24 session_handle: russh::server::Handle,
25 channel_id: russh::ChannelId,
26 ) -> Self {
27 let (tx, mut rx) = mpsc::channel::<Vec<u8>>(64);
28
29 tokio::spawn(async move {
30 while let Some(data) = rx.recv().await {
31 // Handle::data() accepts impl Into<Bytes>; Vec<u8> converts directly.
32 if session_handle.data(channel_id, data).await.is_err() {
33 break;
34 }
35 }
36 });
37
38 Self {
39 sink: Vec::with_capacity(4096),
40 tx,
41 }
42 }
43 }
44
45 impl io::Write for TerminalHandle {
46 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
47 self.sink.extend_from_slice(buf);
48 Ok(buf.len())
49 }
50
51 fn flush(&mut self) -> io::Result<()> {
52 if self.sink.is_empty() {
53 return Ok(());
54 }
55 let data = std::mem::take(&mut self.sink);
56 self.tx
57 .try_send(data)
58 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e))?;
59 Ok(())
60 }
61 }
62