1+ use std:: fs:: File ;
2+ use std:: io:: { self , Read , Write } ;
3+ use std:: os:: unix:: io:: { FromRawFd , RawFd } ;
4+ use std:: sync:: Arc ;
5+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
6+ use std:: sync:: Mutex ;
7+ use std:: thread;
8+ use std:: time:: Duration ;
9+
10+ use crate :: pty_buffer:: PtyBuffer ;
11+
12+ /// Handle reading from PTY master and broadcasting to clients
13+ pub struct PtyIoHandler {
14+ master_fd : RawFd ,
15+ buffer_size : usize ,
16+ }
17+
18+ impl PtyIoHandler {
19+ pub fn new ( master_fd : RawFd ) -> Self {
20+ Self {
21+ master_fd,
22+ buffer_size : 4096 ,
23+ }
24+ }
25+
26+ /// Read from PTY master file descriptor
27+ pub fn read_from_pty ( & self , buffer : & mut [ u8 ] ) -> io:: Result < usize > {
28+ let master_file = unsafe { File :: from_raw_fd ( self . master_fd ) } ;
29+ let mut master_file_clone = master_file. try_clone ( ) ?;
30+ std:: mem:: forget ( master_file) ; // Don't close the fd
31+
32+ master_file_clone. read ( buffer)
33+ }
34+
35+ /// Write to PTY master file descriptor
36+ pub fn write_to_pty ( & self , data : & [ u8 ] ) -> io:: Result < ( ) > {
37+ let mut master_file = unsafe { File :: from_raw_fd ( self . master_fd ) } ;
38+ let result = master_file. write_all ( data) ;
39+ std:: mem:: forget ( master_file) ; // Don't close the fd
40+ result
41+ }
42+
43+ /// Send a control character to the PTY
44+ pub fn send_control_char ( & self , ch : u8 ) -> io:: Result < ( ) > {
45+ self . write_to_pty ( & [ ch] )
46+ }
47+
48+ /// Send Ctrl+L to refresh the display
49+ pub fn send_refresh ( & self ) -> io:: Result < ( ) > {
50+ self . send_control_char ( 0x0c ) // Ctrl+L
51+ }
52+ }
53+
54+ /// Handle scrollback buffer management
55+ pub struct ScrollbackHandler {
56+ buffer : Arc < Mutex < Vec < u8 > > > ,
57+ max_size : usize ,
58+ }
59+
60+ impl ScrollbackHandler {
61+ pub fn new ( max_size : usize ) -> Self {
62+ Self {
63+ buffer : Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ,
64+ max_size,
65+ }
66+ }
67+
68+ /// Add data to the scrollback buffer
69+ pub fn add_data ( & self , data : & [ u8 ] ) {
70+ let mut buffer = self . buffer . lock ( ) . unwrap ( ) ;
71+ buffer. extend_from_slice ( data) ;
72+
73+ // Trim if too large
74+ if buffer. len ( ) > self . max_size {
75+ let remove = buffer. len ( ) - self . max_size ;
76+ buffer. drain ( ..remove) ;
77+ }
78+ }
79+
80+ /// Get a clone of the scrollback buffer
81+ pub fn get_buffer ( & self ) -> Vec < u8 > {
82+ self . buffer . lock ( ) . unwrap ( ) . clone ( )
83+ }
84+
85+ /// Get a reference to the shared buffer
86+ pub fn get_shared_buffer ( & self ) -> Arc < Mutex < Vec < u8 > > > {
87+ Arc :: clone ( & self . buffer )
88+ }
89+ }
90+
91+ /// Thread that reads from socket and writes to stdout
92+ pub fn spawn_socket_to_stdout_thread (
93+ mut socket : std:: os:: unix:: net:: UnixStream ,
94+ running : Arc < AtomicBool > ,
95+ scrollback : Arc < Mutex < Vec < u8 > > > ,
96+ ) -> thread:: JoinHandle < ( ) > {
97+ thread:: spawn ( move || {
98+ let mut stdout = io:: stdout ( ) ;
99+ let mut buffer = [ 0u8 ; 4096 ] ;
100+
101+ while running. load ( Ordering :: SeqCst ) {
102+ match socket. read ( & mut buffer) {
103+ Ok ( 0 ) => break , // Socket closed
104+ Ok ( n) => {
105+ // Write to stdout
106+ if stdout. write_all ( & buffer[ ..n] ) . is_err ( ) {
107+ break ;
108+ }
109+ let _ = stdout. flush ( ) ;
110+
111+ // Add to scrollback buffer
112+ let mut scrollback = scrollback. lock ( ) . unwrap ( ) ;
113+ scrollback. extend_from_slice ( & buffer[ ..n] ) ;
114+
115+ // Trim if too large
116+ let scrollback_max = 10 * 1024 * 1024 ; // 10MB
117+ if scrollback. len ( ) > scrollback_max {
118+ let remove = scrollback. len ( ) - scrollback_max;
119+ scrollback. drain ( ..remove) ;
120+ }
121+ }
122+ Err ( ref e) if e. kind ( ) == io:: ErrorKind :: WouldBlock => {
123+ thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
124+ }
125+ Err ( ref e) if e. kind ( ) == io:: ErrorKind :: BrokenPipe => {
126+ // Expected when socket is closed, just exit cleanly
127+ break ;
128+ }
129+ Err ( _) => break ,
130+ }
131+ }
132+ } )
133+ }
134+
135+ /// Thread that monitors terminal size changes
136+ pub fn spawn_resize_monitor_thread (
137+ mut socket : std:: os:: unix:: net:: UnixStream ,
138+ running : Arc < AtomicBool > ,
139+ initial_size : ( u16 , u16 ) ,
140+ ) -> thread:: JoinHandle < ( ) > {
141+ use crate :: pty:: socket:: send_resize_command;
142+ use crossterm:: terminal;
143+
144+ thread:: spawn ( move || {
145+ let mut last_size = initial_size;
146+
147+ while running. load ( Ordering :: SeqCst ) {
148+ if let Ok ( ( new_cols, new_rows) ) = terminal:: size ( ) {
149+ if ( new_cols, new_rows) != last_size {
150+ // Terminal size changed, send resize command
151+ let _ = send_resize_command ( & mut socket, new_cols, new_rows) ;
152+ last_size = ( new_cols, new_rows) ;
153+ }
154+ }
155+ thread:: sleep ( Duration :: from_millis ( 250 ) ) ;
156+ }
157+ } )
158+ }
159+
160+ /// Helper to send buffered output to a new client
161+ pub fn send_buffered_output (
162+ stream : & mut std:: os:: unix:: net:: UnixStream ,
163+ output_buffer : & PtyBuffer ,
164+ io_handler : & PtyIoHandler ,
165+ ) -> io:: Result < ( ) > {
166+ if !output_buffer. is_empty ( ) {
167+ let mut buffered_data = Vec :: new ( ) ;
168+ output_buffer. drain_to ( & mut buffered_data) ;
169+
170+ // Save cursor position, clear screen, and reset
171+ let init_sequence = b"\x1b 7\x1b [?47h\x1b [2J\x1b [H" ; // Save cursor, alt screen, clear, home
172+ stream. write_all ( init_sequence) ?;
173+ stream. flush ( ) ?;
174+
175+ // Send buffered data in chunks to avoid overwhelming the client
176+ for chunk in buffered_data. chunks ( 4096 ) {
177+ stream. write_all ( chunk) ?;
178+ stream. flush ( ) ?;
179+ thread:: sleep ( Duration :: from_millis ( 1 ) ) ;
180+ }
181+
182+ // Exit alt screen and restore cursor
183+ let restore_sequence = b"\x1b [?47l\x1b 8" ; // Exit alt screen, restore cursor
184+ stream. write_all ( restore_sequence) ?;
185+ stream. flush ( ) ?;
186+
187+ // Small delay for terminal to process
188+ thread:: sleep ( Duration :: from_millis ( 50 ) ) ;
189+
190+ // Send a full redraw command to the shell
191+ io_handler. send_refresh ( ) ?;
192+
193+ // Give time for the refresh to complete
194+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
195+ } else {
196+ // No buffer, just request a refresh to sync state
197+ io_handler. send_refresh ( ) ?;
198+ }
199+
200+ Ok ( ( ) )
201+ }
0 commit comments