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;
|
let mut st = state.lock().await;
|
||||||
match st.sessions.get_mut(&session_id) {
|
match st.sessions.get_mut(&session_id) {
|
||||||
Some(sess) => {
|
Some(sess) => {
|
||||||
sess.note_resize(cols, rows);
|
if let Err(e) = sess.resize(cols, rows).await {
|
||||||
|
log::warn!("resize {session_id}: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let _ = out_tx.try_send(DaemonMessage::Error {
|
let _ = out_tx.try_send(DaemonMessage::Error {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
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 tokio::sync::{broadcast, Mutex};
|
||||||
|
|
||||||
use crate::protocol::SessionInfo;
|
use crate::protocol::SessionInfo;
|
||||||
|
|
@ -12,9 +12,6 @@ use crate::protocol::SessionInfo;
|
||||||
const OUTPUT_CHANNEL_CAP: usize = 256;
|
const OUTPUT_CHANNEL_CAP: usize = 256;
|
||||||
|
|
||||||
/// A live (or recently exited) PTY session.
|
/// 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 struct Session {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub pid: u32,
|
pub pid: u32,
|
||||||
|
|
@ -23,14 +20,11 @@ pub struct Session {
|
||||||
pub cols: u16,
|
pub cols: u16,
|
||||||
pub rows: u16,
|
pub rows: u16,
|
||||||
pub created_at: u64,
|
pub created_at: u64,
|
||||||
/// Used to write input into the PTY master. `Box<dyn Write + Send>`.
|
|
||||||
writer: Arc<Mutex<Box<dyn IoWrite + 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>>,
|
pub tx: broadcast::Sender<Vec<u8>>,
|
||||||
/// false once the child process exits.
|
|
||||||
pub alive: Arc<AtomicBool>,
|
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)]
|
#[allow(dead_code)]
|
||||||
pub exit_code: Arc<Mutex<Option<i32>>>,
|
pub exit_code: Arc<Mutex<Option<i32>>>,
|
||||||
}
|
}
|
||||||
|
|
@ -57,11 +51,14 @@ impl Session {
|
||||||
.map_err(|e| format!("PTY write for {}: {e}", self.id))
|
.map_err(|e| format!("PTY write for {}: {e}", self.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update cached dimensions after a resize. The actual TIOCSWINSZ is issued
|
/// Resize the PTY (issues TIOCSWINSZ) and update cached dimensions.
|
||||||
/// by the daemon before calling this.
|
pub async fn resize(&mut self, cols: u16, rows: u16) -> Result<(), String> {
|
||||||
pub fn note_resize(&mut self, cols: u16, rows: u16) {
|
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.cols = cols;
|
||||||
self.rows = rows;
|
self.rows = rows;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new receiver subscribed to this session's broadcast output.
|
/// 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 alive = Arc::new(AtomicBool::new(true));
|
||||||
let exit_code = Arc::new(Mutex::new(None::<i32>));
|
let exit_code = Arc::new(Mutex::new(None::<i32>));
|
||||||
|
|
||||||
// Spawn the blocking reader task. It takes ownership of `pair.master`
|
// Keep a reference to the master for resize operations.
|
||||||
// (via `_master`) so the PTY file descriptor stays open.
|
// 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 tx_clone = tx.clone();
|
||||||
let alive_clone = alive.clone();
|
let alive_clone = alive.clone();
|
||||||
let exit_code_clone = exit_code.clone();
|
let exit_code_clone = exit_code.clone();
|
||||||
let id_clone = id.clone();
|
let id_clone = id.clone();
|
||||||
let _master = pair.master; // keep PTY fd alive inside the task
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
read_pty_output(
|
read_pty_output(
|
||||||
reader,
|
reader,
|
||||||
|
|
@ -173,7 +172,6 @@ impl SessionManager {
|
||||||
id_clone,
|
id_clone,
|
||||||
on_exit,
|
on_exit,
|
||||||
child,
|
child,
|
||||||
_master,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -186,6 +184,7 @@ impl SessionManager {
|
||||||
rows,
|
rows,
|
||||||
created_at: unix_now(),
|
created_at: unix_now(),
|
||||||
writer: Arc::new(Mutex::new(writer)),
|
writer: Arc::new(Mutex::new(writer)),
|
||||||
|
master: Arc::new(Mutex::new(master_for_session)),
|
||||||
tx,
|
tx,
|
||||||
alive,
|
alive,
|
||||||
exit_code,
|
exit_code,
|
||||||
|
|
@ -244,7 +243,6 @@ fn read_pty_output(
|
||||||
id: String,
|
id: String,
|
||||||
on_exit: impl FnOnce(String, Option<i32>),
|
on_exit: impl FnOnce(String, Option<i32>),
|
||||||
mut child: Box<dyn portable_pty::Child + Send>,
|
mut child: Box<dyn portable_pty::Child + Send>,
|
||||||
_master: Box<dyn portable_pty::MasterPty + Send>,
|
|
||||||
) {
|
) {
|
||||||
let mut buf = [0u8; 4096];
|
let mut buf = [0u8; 4096];
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue