@@ -210,6 +210,7 @@ fn record_command(real_shell: &str, command: &str) -> Result<i32, Box<dyn std::e
210210 let user = std:: env:: var ( "USER" ) . unwrap_or_else ( |_| "user" . into ( ) ) ;
211211 let mut sink = connect_actions_sink ( ) ;
212212 if let Some ( s) = sink. as_mut ( ) {
213+ send_cast_start_event ( s, tty_size ( std:: io:: stdout ( ) . as_raw_fd ( ) ) ) ;
213214 send_event (
214215 s,
215216 & ActionEvent :: SshSessionStart {
@@ -219,11 +220,14 @@ fn record_command(real_shell: &str, command: &str) -> Result<i32, Box<dyn std::e
219220 } ,
220221 ) ;
221222 if !command. is_empty ( ) {
223+ let mut input = command. to_string ( ) ;
224+ input. push ( '\n' ) ;
225+ let b64 = base64:: engine:: general_purpose:: STANDARD . encode ( input. as_bytes ( ) ) ;
222226 send_event (
223227 s,
224- & ActionEvent :: SshLine {
228+ & ActionEvent :: SshRawInput {
225229 ts_unix_ms : unix_ms ( ) ,
226- line : command . to_string ( ) ,
230+ data_b64 : b64 ,
227231 } ,
228232 ) ;
229233 }
@@ -233,27 +237,23 @@ fn record_command(real_shell: &str, command: &str) -> Result<i32, Box<dyn std::e
233237 let code = output. status . code ( ) . unwrap_or ( 1 ) ;
234238
235239 if let Some ( s) = sink. as_mut ( ) {
236- for line in String :: from_utf8_lossy ( & output. stdout ) . lines ( ) {
237- if line. trim ( ) . is_empty ( ) {
238- continue ;
239- }
240+ if !output. stdout . is_empty ( ) {
241+ let b64 = base64:: engine:: general_purpose:: STANDARD . encode ( & output. stdout ) ;
240242 send_event (
241243 s,
242- & ActionEvent :: SshOutput {
244+ & ActionEvent :: SshRawOutput {
243245 ts_unix_ms : unix_ms ( ) ,
244- line : line . to_string ( ) ,
246+ data_b64 : b64 ,
245247 } ,
246248 ) ;
247249 }
248- for line in String :: from_utf8_lossy ( & output. stderr ) . lines ( ) {
249- if line. trim ( ) . is_empty ( ) {
250- continue ;
251- }
250+ if !output. stderr . is_empty ( ) {
251+ let b64 = base64:: engine:: general_purpose:: STANDARD . encode ( & output. stderr ) ;
252252 send_event (
253253 s,
254- & ActionEvent :: SshOutput {
254+ & ActionEvent :: SshRawOutput {
255255 ts_unix_ms : unix_ms ( ) ,
256- line : line . to_string ( ) ,
256+ data_b64 : b64 ,
257257 } ,
258258 ) ;
259259 }
@@ -285,6 +285,10 @@ fn record_ssh(real_shell: &str) -> Result<i32, Box<dyn std::error::Error>> {
285285 close ( slave_fd) . ok ( ) ;
286286
287287 let ( tx, writer_thread) = start_actions_event_stream ( ) ;
288+ send_cast_start (
289+ & tx,
290+ tty_size ( stdout. as_raw_fd ( ) ) . or_else ( || tty_size ( stdin. as_raw_fd ( ) ) ) ,
291+ ) ;
288292 send_session_start ( & tx) ;
289293
290294 let proxy_result = proxy_pty_session ( master_fd, & tx) ;
@@ -369,11 +373,6 @@ fn proxy_pty_session(
369373 let master_borrowed = unsafe { BorrowedFd :: borrow_raw ( master_fd) } ;
370374
371375 let mut buf = [ 0u8 ; 4096 ] ;
372- let mut line = String :: new ( ) ;
373- let mut input_escape = false ;
374- let mut output_line = String :: new ( ) ;
375- let mut output_escape = false ;
376-
377376 loop {
378377 let mut fds = [
379378 PollFd :: new ( stdin_borrowed, PollFlags :: POLLIN ) ,
@@ -392,7 +391,11 @@ fn proxy_pty_session(
392391 Ok ( n) => {
393392 stdout. write_all ( & buf[ ..n] ) ?;
394393 stdout. flush ( ) ?;
395- derive_lines_from_output ( & buf[ ..n] , & mut output_line, & mut output_escape, tx) ;
394+ let b64 = base64:: engine:: general_purpose:: STANDARD . encode ( & buf[ ..n] ) ;
395+ let _ = tx. send ( ActionEvent :: SshRawOutput {
396+ ts_unix_ms : unix_ms ( ) ,
397+ data_b64 : b64,
398+ } ) ;
396399 }
397400 Err ( Errno :: EINTR ) => { }
398401 Err ( e) => return Err ( e. into ( ) ) ,
@@ -411,23 +414,13 @@ fn proxy_pty_session(
411414 ts_unix_ms : unix_ms ( ) ,
412415 data_b64 : b64,
413416 } ) ;
414-
415- derive_lines_from_input ( chunk, & mut line, & mut input_escape, tx) ;
416417 }
417418 Err ( Errno :: EINTR ) => { }
418419 Err ( e) => return Err ( e. into ( ) ) ,
419420 }
420421 }
421422 }
422423
423- let trimmed = output_line. trim ( ) ;
424- if !trimmed. is_empty ( ) && !is_prompt_line ( trimmed) {
425- let _ = tx. send ( ActionEvent :: SshOutput {
426- ts_unix_ms : unix_ms ( ) ,
427- line : trimmed. to_string ( ) ,
428- } ) ;
429- }
430-
431424 Ok ( ( ) )
432425}
433426
@@ -438,107 +431,16 @@ fn is_fd_readable(fd: &PollFd<'_>) -> bool {
438431 || revents. contains ( PollFlags :: POLLERR )
439432}
440433
441- fn derive_lines_from_input (
442- chunk : & [ u8 ] ,
443- line : & mut String ,
444- in_escape : & mut bool ,
445- tx : & std:: sync:: mpsc:: Sender < ActionEvent > ,
446- ) {
447- for & b in chunk {
448- if * in_escape {
449- if ( b as char ) . is_ascii_alphabetic ( ) || b == b'~' {
450- * in_escape = false ;
451- }
452- continue ;
453- }
454-
455- match b {
456- 0x1b => {
457- * in_escape = true ;
458- }
459- b'\r' | b'\n' => {
460- let trimmed = line. trim ( ) ;
461- if !trimmed. is_empty ( ) {
462- let _ = tx. send ( ActionEvent :: SshLine {
463- ts_unix_ms : unix_ms ( ) ,
464- line : trimmed. to_string ( ) ,
465- } ) ;
466- }
467- line. clear ( ) ;
468- }
469- 0x7f | 0x08 => {
470- let _ = line. pop ( ) ;
471- }
472- b'\t' => line. push ( '\t' ) ,
473- b if b. is_ascii_graphic ( ) || b == b' ' => line. push ( char:: from ( b) ) ,
474- _ => { }
475- }
476- }
477- }
478-
479- fn derive_lines_from_output (
480- chunk : & [ u8 ] ,
481- line : & mut String ,
482- in_escape : & mut bool ,
483- tx : & std:: sync:: mpsc:: Sender < ActionEvent > ,
484- ) {
485- for & b in chunk {
486- if * in_escape {
487- if ( b as char ) . is_ascii_alphabetic ( ) || b == b'~' {
488- * in_escape = false ;
489- }
490- continue ;
491- }
492-
493- match b {
494- 0x1b => {
495- * in_escape = true ;
496- }
497- b'\r' | b'\n' => {
498- let trimmed = line. trim ( ) ;
499- if !trimmed. is_empty ( ) && !is_prompt_line ( trimmed) {
500- let _ = tx. send ( ActionEvent :: SshOutput {
501- ts_unix_ms : unix_ms ( ) ,
502- line : trimmed. to_string ( ) ,
503- } ) ;
504- }
505- line. clear ( ) ;
506- }
507- 0x7f | 0x08 => {
508- let _ = line. pop ( ) ;
509- }
510- b'\t' => line. push ( '\t' ) ,
511- b if b. is_ascii_graphic ( ) || b == b' ' => line. push ( char:: from ( b) ) ,
512- _ => { }
513- }
514- }
515- }
516-
517- fn is_prompt_line ( line : & str ) -> bool {
518- let trimmed = line. trim ( ) ;
519- if trimmed. is_empty ( ) {
520- return false ;
521- }
522-
523- if trimmed == "$" || trimmed == "#" {
524- return true ;
525- }
526-
527- let mut pos = trimmed. rfind ( '$' ) ;
528- if pos. is_none ( ) {
529- pos = trimmed. rfind ( '#' ) ;
530- }
531-
532- let Some ( pos) = pos else {
533- return false ;
534- } ;
535-
536- if !trimmed[ pos + 1 ..] . starts_with ( ' ' ) && !trimmed[ pos + 1 ..] . is_empty ( ) {
537- return false ;
538- }
539-
540- let prefix = & trimmed[ ..pos] ;
541- prefix. contains ( '@' ) && prefix. contains ( ':' )
434+ fn send_cast_start_event ( stream : & mut UnixStream , size : Option < ( u16 , u16 ) > ) {
435+ let ( width, height) = size. unwrap_or ( ( 80 , 24 ) ) ;
436+ send_event (
437+ stream,
438+ & ActionEvent :: SshCastStart {
439+ ts_unix_ms : unix_ms ( ) ,
440+ width,
441+ height,
442+ } ,
443+ ) ;
542444}
543445
544446fn write_all_fd ( fd : BorrowedFd < ' _ > , mut data : & [ u8 ] ) -> Result < ( ) , Errno > {
@@ -588,6 +490,15 @@ fn send_session_start(tx: &std::sync::mpsc::Sender<ActionEvent>) {
588490 } ) ;
589491}
590492
493+ fn send_cast_start ( tx : & std:: sync:: mpsc:: Sender < ActionEvent > , size : Option < ( u16 , u16 ) > ) {
494+ let ( width, height) = size. unwrap_or ( ( 80 , 24 ) ) ;
495+ let _ = tx. send ( ActionEvent :: SshCastStart {
496+ ts_unix_ms : unix_ms ( ) ,
497+ width,
498+ height,
499+ } ) ;
500+ }
501+
591502fn send_session_end ( tx : & std:: sync:: mpsc:: Sender < ActionEvent > , exit_code : i32 ) {
592503 let _ = tx. send ( ActionEvent :: SshSessionEnd {
593504 ts_unix_ms : unix_ms ( ) ,
@@ -637,6 +548,22 @@ fn is_tty(fd: RawFd) -> bool {
637548 unsafe { nix:: libc:: isatty ( fd) == 1 }
638549}
639550
551+ fn tty_size ( fd : RawFd ) -> Option < ( u16 , u16 ) > {
552+ let mut size = nix:: libc:: winsize {
553+ ws_row : 0 ,
554+ ws_col : 0 ,
555+ ws_xpixel : 0 ,
556+ ws_ypixel : 0 ,
557+ } ;
558+
559+ let res = unsafe { nix:: libc:: ioctl ( fd, nix:: libc:: TIOCGWINSZ , & mut size) } ;
560+ if res == -1 || size. ws_row == 0 || size. ws_col == 0 {
561+ return None ;
562+ }
563+
564+ Some ( ( size. ws_col , size. ws_row ) )
565+ }
566+
640567fn to_io_err ( e : nix:: Error ) -> std:: io:: Error {
641568 std:: io:: Error :: other ( e. to_string ( ) )
642569}
0 commit comments