Skip to main content

max / makenotwork

1.7 KB · 59 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(crate) 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(crate) fn new(session_handle: russh::server::Handle, channel_id: russh::ChannelId) -> Self {
24 let (tx, mut rx) = mpsc::channel::<Vec<u8>>(64);
25
26 tokio::spawn(async move {
27 while let Some(data) = rx.recv().await {
28 // Handle::data() accepts impl Into<Bytes>; Vec<u8> converts directly.
29 if session_handle.data(channel_id, data).await.is_err() {
30 break;
31 }
32 }
33 });
34
35 Self {
36 sink: Vec::with_capacity(4096),
37 tx,
38 }
39 }
40 }
41
42 impl io::Write for TerminalHandle {
43 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
44 self.sink.extend_from_slice(buf);
45 Ok(buf.len())
46 }
47
48 fn flush(&mut self) -> io::Result<()> {
49 if self.sink.is_empty() {
50 return Ok(());
51 }
52 let data = std::mem::take(&mut self.sink);
53 self.tx
54 .try_send(data)
55 .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e))?;
56 Ok(())
57 }
58 }
59