fix(agor-pty): implement real PTY resize (TIOCSWINSZ via portable-pty)
Was only updating cached dimensions without calling PTY resize. Shell thought terminal was wrong size → double prompts, escape code leaks. - Session stores master PTY handle (Arc<Mutex<Box<dyn MasterPty>>>) - resize() calls master.resize(PtySize) → issues TIOCSWINSZ - Reader task no longer owns master handle (uses cloned reader only)
This commit is contained in:
parent
0f7024ec8f
commit
621e1c5c8c
2 changed files with 18 additions and 18 deletions
|
|
@ -314,7 +314,9 @@ async fn handle_message(
|
|||
let mut st = state.lock().await;
|
||||
match st.sessions.get_mut(&session_id) {
|
||||
Some(sess) => {
|
||||
sess.note_resize(cols, rows);
|
||||
if let Err(e) = sess.resize(cols, rows).await {
|
||||
log::warn!("resize {session_id}: {e}");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let _ = out_tx.try_send(DaemonMessage::Error {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
|
||||
use portable_pty::{native_pty_system, CommandBuilder, MasterPty, PtySize};
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
|
||||
use crate::protocol::SessionInfo;
|
||||
|
|
@ -12,9 +12,6 @@ use crate::protocol::SessionInfo;
|
|||
const OUTPUT_CHANNEL_CAP: usize = 256;
|
||||
|
||||
/// A live (or recently exited) PTY session.
|
||||
///
|
||||
/// All fields that cross await points are either `Send + Sync` or wrapped in
|
||||
/// `Arc<Mutex<_>>` so the Session itself is `Send`.
|
||||
pub struct Session {
|
||||
pub id: String,
|
||||
pub pid: u32,
|
||||
|
|
@ -23,14 +20,11 @@ pub struct Session {
|
|||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
pub created_at: u64,
|
||||
/// Used to write input into the PTY master. `Box<dyn Write + Send>`.
|
||||
writer: Arc<Mutex<Box<dyn IoWrite + Send>>>,
|
||||
/// Broadcast channel — all subscribers receive raw output chunks.
|
||||
/// Master PTY handle — needed for resize (TIOCSWINSZ).
|
||||
master: Arc<Mutex<Box<dyn MasterPty + Send>>>,
|
||||
pub tx: broadcast::Sender<Vec<u8>>,
|
||||
/// false once the child process exits.
|
||||
pub alive: Arc<AtomicBool>,
|
||||
/// Last known exit code (set by the reader task on child exit).
|
||||
/// Public for callers that poll exit state after SessionClosed is received.
|
||||
#[allow(dead_code)]
|
||||
pub exit_code: Arc<Mutex<Option<i32>>>,
|
||||
}
|
||||
|
|
@ -57,11 +51,14 @@ impl Session {
|
|||
.map_err(|e| format!("PTY write for {}: {e}", self.id))
|
||||
}
|
||||
|
||||
/// Update cached dimensions after a resize. The actual TIOCSWINSZ is issued
|
||||
/// by the daemon before calling this.
|
||||
pub fn note_resize(&mut self, cols: u16, rows: u16) {
|
||||
/// Resize the PTY (issues TIOCSWINSZ) and update cached dimensions.
|
||||
pub async fn resize(&mut self, cols: u16, rows: u16) -> Result<(), String> {
|
||||
let master = self.master.lock().await;
|
||||
master.resize(PtySize { rows, cols, pixel_width: 0, pixel_height: 0 })
|
||||
.map_err(|e| format!("PTY resize for {}: {e}", self.id))?;
|
||||
self.cols = cols;
|
||||
self.rows = rows;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return a new receiver subscribed to this session's broadcast output.
|
||||
|
|
@ -157,13 +154,15 @@ impl SessionManager {
|
|||
let alive = Arc::new(AtomicBool::new(true));
|
||||
let exit_code = Arc::new(Mutex::new(None::<i32>));
|
||||
|
||||
// Spawn the blocking reader task. It takes ownership of `pair.master`
|
||||
// (via `_master`) so the PTY file descriptor stays open.
|
||||
// Keep a reference to the master for resize operations.
|
||||
// The reader task gets the master handle to keep the PTY fd alive.
|
||||
let master_for_session: Box<dyn MasterPty + Send> = pair.master;
|
||||
|
||||
// Spawn the blocking reader task.
|
||||
let tx_clone = tx.clone();
|
||||
let alive_clone = alive.clone();
|
||||
let exit_code_clone = exit_code.clone();
|
||||
let id_clone = id.clone();
|
||||
let _master = pair.master; // keep PTY fd alive inside the task
|
||||
tokio::task::spawn_blocking(move || {
|
||||
read_pty_output(
|
||||
reader,
|
||||
|
|
@ -173,7 +172,6 @@ impl SessionManager {
|
|||
id_clone,
|
||||
on_exit,
|
||||
child,
|
||||
_master,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -186,6 +184,7 @@ impl SessionManager {
|
|||
rows,
|
||||
created_at: unix_now(),
|
||||
writer: Arc::new(Mutex::new(writer)),
|
||||
master: Arc::new(Mutex::new(master_for_session)),
|
||||
tx,
|
||||
alive,
|
||||
exit_code,
|
||||
|
|
@ -244,7 +243,6 @@ fn read_pty_output(
|
|||
id: String,
|
||||
on_exit: impl FnOnce(String, Option<i32>),
|
||||
mut child: Box<dyn portable_pty::Child + Send>,
|
||||
_master: Box<dyn portable_pty::MasterPty + Send>,
|
||||
) {
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue