@@ -7,10 +7,11 @@ use ratatui::backend::CrosstermBackend;
77use ratatui:: layout:: Rect ;
88use ratatui:: style:: { Color , Style } ;
99use ratatui:: widgets:: { Block , Borders , Clear , Paragraph } ;
10- use ratatui:: Terminal ;
10+ use ratatui:: { Terminal , TerminalOptions , Viewport } ;
1111use russh:: keys:: ssh_key:: PublicKey ;
1212use russh:: server:: * ;
13- use russh:: { Channel , ChannelId } ;
13+ use russh:: { Channel , ChannelId , Pty } ;
14+ use tokio:: sync:: mpsc:: { unbounded_channel, UnboundedSender } ;
1415use tokio:: sync:: Mutex ;
1516
1617type SshTerminal = Terminal < CrosstermBackend < TerminalHandle > > ;
@@ -25,12 +26,28 @@ impl App {
2526 }
2627}
2728
28- #[ derive( Clone ) ]
2929struct TerminalHandle {
30- handle : Handle ,
31- // The sink collects the data which is finally flushed to the handle .
30+ sender : UnboundedSender < Vec < u8 > > ,
31+ // The sink collects the data which is finally sent to sender .
3232 sink : Vec < u8 > ,
33- channel_id : ChannelId ,
33+ }
34+
35+ impl TerminalHandle {
36+ async fn start ( handle : Handle , channel_id : ChannelId ) -> Self {
37+ let ( sender, mut receiver) = unbounded_channel :: < Vec < u8 > > ( ) ;
38+ tokio:: spawn ( async move {
39+ while let Some ( data) = receiver. recv ( ) . await {
40+ let result = handle. data ( channel_id, data. into ( ) ) . await ;
41+ if result. is_err ( ) {
42+ eprintln ! ( "Failed to send data: {:?}" , result) ;
43+ }
44+ }
45+ } ) ;
46+ Self {
47+ sender,
48+ sink : Vec :: new ( ) ,
49+ }
50+ }
3451}
3552
3653// The crossterm backend writes to the terminal handle.
@@ -41,15 +58,13 @@ impl std::io::Write for TerminalHandle {
4158 }
4259
4360 fn flush ( & mut self ) -> std:: io:: Result < ( ) > {
44- let handle = self . handle . clone ( ) ;
45- let channel_id = self . channel_id ;
46- let data = self . sink . clone ( ) . into ( ) ;
47- futures:: executor:: block_on ( async move {
48- let result = handle. data ( channel_id, data) . await ;
49- if result. is_err ( ) {
50- eprintln ! ( "Failed to send data: {:?}" , result) ;
51- }
52- } ) ;
61+ let result = self . sender . send ( self . sink . clone ( ) ) ;
62+ if result. is_err ( ) {
63+ return Err ( std:: io:: Error :: new (
64+ std:: io:: ErrorKind :: BrokenPipe ,
65+ result. unwrap_err ( ) ,
66+ ) ) ;
67+ }
5368
5469 self . sink . clear ( ) ;
5570 Ok ( ( ) )
@@ -81,8 +96,8 @@ impl AppServer {
8196
8297 terminal
8398 . draw ( |f| {
84- let size = f. size ( ) ;
85- f. render_widget ( Clear , size ) ;
99+ let area = f. area ( ) ;
100+ f. render_widget ( Clear , area ) ;
86101 let style = match app. counter % 3 {
87102 0 => Style :: default ( ) . fg ( Color :: Red ) ,
88103 1 => Style :: default ( ) . fg ( Color :: Green ) ,
@@ -94,7 +109,7 @@ impl AppServer {
94109 let block = Block :: default ( )
95110 . title ( "Press 'c' to reset the counter!" )
96111 . borders ( Borders :: ALL ) ;
97- f. render_widget ( paragraph. block ( block) , size ) ;
112+ f. render_widget ( paragraph. block ( block) , area ) ;
98113 } )
99114 . unwrap ( ) ;
100115 }
@@ -135,20 +150,20 @@ impl Handler for AppServer {
135150 channel : Channel < Msg > ,
136151 session : & mut Session ,
137152 ) -> Result < bool , Self :: Error > {
138- {
139- let mut clients = self . clients . lock ( ) . await ;
140- let terminal_handle = TerminalHandle {
141- handle : session . handle ( ) ,
142- sink : Vec :: new ( ) ,
143- channel_id : channel . id ( ) ,
144- } ;
145-
146- let backend = CrosstermBackend :: new ( terminal_handle . clone ( ) ) ;
147- let terminal = Terminal :: new ( backend) ?;
148- let app = App :: new ( ) ;
149-
150- clients. insert ( self . id , ( terminal , app ) ) ;
151- }
153+ let terminal_handle = TerminalHandle :: start ( session . handle ( ) , channel . id ( ) ) . await ;
154+
155+ let backend = CrosstermBackend :: new ( terminal_handle ) ;
156+
157+ // the correct viewport area will be set when the client request a pty
158+ let options = TerminalOptions {
159+ viewport : Viewport :: Fixed ( Rect :: default ( ) ) ,
160+ } ;
161+
162+ let terminal = Terminal :: with_options ( backend, options ) ?;
163+ let app = App :: new ( ) ;
164+
165+ let mut clients = self . clients . lock ( ) . await ;
166+ clients . insert ( self . id , ( terminal , app ) ) ;
152167
153168 Ok ( true )
154169 }
@@ -192,17 +207,48 @@ impl Handler for AppServer {
192207 _: u32 ,
193208 _: & mut Session ,
194209 ) -> Result < ( ) , Self :: Error > {
195- {
196- let mut clients = self . clients . lock ( ) . await ;
197- let ( terminal, _) = clients. get_mut ( & self . id ) . unwrap ( ) ;
198- let rect = Rect {
199- x : 0 ,
200- y : 0 ,
201- width : col_width as u16 ,
202- height : row_height as u16 ,
203- } ;
204- terminal. resize ( rect) ?;
205- }
210+ let rect = Rect {
211+ x : 0 ,
212+ y : 0 ,
213+ width : col_width as u16 ,
214+ height : row_height as u16 ,
215+ } ;
216+
217+ let mut clients = self . clients . lock ( ) . await ;
218+ let ( terminal, _) = clients. get_mut ( & self . id ) . unwrap ( ) ;
219+ terminal. resize ( rect) ?;
220+
221+ Ok ( ( ) )
222+ }
223+
224+ /// The client requests a pseudo-terminal with the given
225+ /// specifications.
226+ ///
227+ /// **Note:** Success or failure should be communicated to the client by calling
228+ /// `session.channel_success(channel)` or `session.channel_failure(channel)` respectively.
229+ async fn pty_request (
230+ & mut self ,
231+ channel : ChannelId ,
232+ _: & str ,
233+ col_width : u32 ,
234+ row_height : u32 ,
235+ _: u32 ,
236+ _: u32 ,
237+ _: & [ ( Pty , u32 ) ] ,
238+ session : & mut Session ,
239+ ) -> Result < ( ) , Self :: Error > {
240+ let rect = Rect {
241+ x : 0 ,
242+ y : 0 ,
243+ width : col_width as u16 ,
244+ height : row_height as u16 ,
245+ } ;
246+
247+ let mut clients = self . clients . lock ( ) . await ;
248+ let ( terminal, _) = clients. get_mut ( & self . id ) . unwrap ( ) ;
249+ terminal. resize ( rect) ?;
250+
251+ session. channel_success ( channel) ?;
206252
207253 Ok ( ( ) )
208254 }
0 commit comments