Skip to content

Commit dcbe4ba

Browse files
committed
update examples to new APIs
1 parent 6795ef4 commit dcbe4ba

File tree

2 files changed

+422
-0
lines changed

2 files changed

+422
-0
lines changed

russh/examples/ratatui_app.rs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
use async_trait::async_trait;
2+
use ratatui::{
3+
backend::CrosstermBackend,
4+
layout::Rect,
5+
style::{Color, Style},
6+
widgets::{Block, Borders, Clear, Paragraph},
7+
Terminal,
8+
};
9+
use russh::{server::*, Channel, ChannelId};
10+
use russh_keys::key::PublicKey;
11+
use std::collections::HashMap;
12+
use std::sync::Arc;
13+
use tokio::sync::Mutex;
14+
15+
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>;
16+
17+
struct App {
18+
pub counter: usize,
19+
}
20+
21+
impl App {
22+
pub fn new() -> App {
23+
Self { counter: 0 }
24+
}
25+
}
26+
27+
#[derive(Clone)]
28+
struct TerminalHandle {
29+
handle: Handle,
30+
// The sink collects the data which is finally flushed to the handle.
31+
sink: Vec<u8>,
32+
channel_id: ChannelId,
33+
}
34+
35+
// The crossterm backend writes to the terminal handle.
36+
impl std::io::Write for TerminalHandle {
37+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
38+
self.sink.extend_from_slice(buf);
39+
Ok(buf.len())
40+
}
41+
42+
fn flush(&mut self) -> std::io::Result<()> {
43+
let handle = self.handle.clone();
44+
let channel_id = self.channel_id.clone();
45+
let data = self.sink.clone().into();
46+
futures::executor::block_on(async move {
47+
let result = handle.data(channel_id, data).await;
48+
if result.is_err() {
49+
eprintln!("Failed to send data: {:?}", result);
50+
}
51+
});
52+
53+
self.sink.clear();
54+
Ok(())
55+
}
56+
}
57+
58+
#[derive(Clone)]
59+
struct AppServer {
60+
clients: Arc<Mutex<HashMap<usize, (SshTerminal, App)>>>,
61+
id: usize,
62+
}
63+
64+
impl AppServer {
65+
pub fn new() -> Self {
66+
Self {
67+
clients: Arc::new(Mutex::new(HashMap::new())),
68+
id: 0,
69+
}
70+
}
71+
72+
pub async fn run(&mut self) -> Result<(), anyhow::Error> {
73+
let clients = self.clients.clone();
74+
tokio::spawn(async move {
75+
loop {
76+
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
77+
78+
for (_, (terminal, app)) in clients.lock().await.iter_mut() {
79+
app.counter += 1;
80+
81+
terminal
82+
.draw(|f| {
83+
let size = f.size();
84+
f.render_widget(Clear, size);
85+
let style = match app.counter % 3 {
86+
0 => Style::default().fg(Color::Red),
87+
1 => Style::default().fg(Color::Green),
88+
_ => Style::default().fg(Color::Blue),
89+
};
90+
let paragraph = Paragraph::new(format!("Counter: {}", app.counter))
91+
.alignment(ratatui::layout::Alignment::Center)
92+
.style(style);
93+
let block = Block::default()
94+
.title("Press 'c' to reset the counter!")
95+
.borders(Borders::ALL);
96+
f.render_widget(paragraph.block(block), size);
97+
})
98+
.unwrap();
99+
}
100+
}
101+
});
102+
103+
let config = Config {
104+
inactivity_timeout: Some(std::time::Duration::from_secs(3600)),
105+
auth_rejection_time: std::time::Duration::from_secs(3),
106+
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)),
107+
keys: vec![russh_keys::key::KeyPair::generate_ed25519().unwrap()],
108+
..Default::default()
109+
};
110+
111+
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222))
112+
.await?;
113+
Ok(())
114+
}
115+
}
116+
117+
impl Server for AppServer {
118+
type Handler = Self;
119+
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
120+
let s = self.clone();
121+
self.id += 1;
122+
s
123+
}
124+
}
125+
126+
#[async_trait]
127+
impl Handler for AppServer {
128+
type Error = anyhow::Error;
129+
130+
async fn channel_open_session(
131+
&mut self,
132+
channel: Channel<Msg>,
133+
session: &mut Session,
134+
) -> Result<bool, Self::Error> {
135+
{
136+
let mut clients = self.clients.lock().await;
137+
let terminal_handle = TerminalHandle {
138+
handle: session.handle(),
139+
sink: Vec::new(),
140+
channel_id: channel.id(),
141+
};
142+
143+
let backend = CrosstermBackend::new(terminal_handle.clone());
144+
let terminal = Terminal::new(backend)?;
145+
let app = App::new();
146+
147+
clients.insert(self.id, (terminal, app));
148+
}
149+
150+
Ok(true)
151+
}
152+
153+
async fn auth_publickey(&mut self, _: &str, _: &PublicKey) -> Result<Auth, Self::Error> {
154+
Ok(Auth::Accept)
155+
}
156+
157+
async fn data(
158+
&mut self,
159+
channel: ChannelId,
160+
data: &[u8],
161+
session: &mut Session,
162+
) -> Result<(), Self::Error> {
163+
match data {
164+
// Pressing 'q' closes the connection.
165+
b"q" => {
166+
self.clients.lock().await.remove(&self.id);
167+
session.close(channel);
168+
}
169+
// Pressing 'c' resets the counter for the app.
170+
// Only the client with the id sees the counter reset.
171+
b"c" => {
172+
let mut clients = self.clients.lock().await;
173+
let (_, app) = clients.get_mut(&self.id).unwrap();
174+
app.counter = 0;
175+
}
176+
_ => {}
177+
}
178+
179+
Ok(())
180+
}
181+
182+
/// The client's window size has changed.
183+
async fn window_change_request(
184+
&mut self,
185+
_: ChannelId,
186+
col_width: u32,
187+
row_height: u32,
188+
_: u32,
189+
_: u32,
190+
_: &mut Session,
191+
) -> Result<(), Self::Error> {
192+
{
193+
let mut clients = self.clients.lock().await;
194+
let (terminal, _) = clients.get_mut(&self.id).unwrap();
195+
let rect = Rect {
196+
x: 0,
197+
y: 0,
198+
width: col_width as u16,
199+
height: row_height as u16,
200+
};
201+
terminal.resize(rect)?;
202+
}
203+
204+
Ok(())
205+
}
206+
}
207+
208+
#[tokio::main]
209+
async fn main() {
210+
let mut server = AppServer::new();
211+
server.run().await.expect("Failed running server");
212+
}

0 commit comments

Comments
 (0)