From 5ef66544c74f5837ca53cf9409a70a8c5dd8c7ce Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 25 Nov 2023 23:26:57 +0100 Subject: [PATCH] Assertion-based terminal sizing --- protocols/schema-bundle.bin | 2 +- protocols/schemas/pty.prs | 6 +++- src/main.rs | 65 ++++++++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/protocols/schema-bundle.bin b/protocols/schema-bundle.bin index a281b49..68b3e76 100644 --- a/protocols/schema-bundle.bin +++ b/protocols/schema-bundle.bin @@ -1,4 +1,4 @@ -´³bundle·µ³pty„´³schema·³version°³ definitions·³PtyInput´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³input„´³tupleµ´³named³data´³atom³ +´³bundle·µ³pty„´³schema·³version°³ definitions·³PtySize´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³size„´³tupleµ´³named³columns´³atom³ SignedInteger„„´³named³rows´³atom³ SignedInteger„„„„„„„„³PtyInput´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³input„´³tupleµ´³named³data´³atom³ ByteString„„„„„„„„³ PtyOutput´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³output„´³tupleµ´³named³data´³atom³ ByteString„„„„„„„„³ PtyResize´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³resize„´³tupleµ´³named³columns´³atom³ SignedInteger„„´³named³rows´³atom³ SignedInteger„„„„„„„„³ PtySession´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³command„´³tupleµ´³named³ commandLine´³refµ„³ CommandLine„„„„„„„„³ CommandLine´³ tuplePrefixµ´³named³command³any„„´³named³args´³seqof³any„„„³PtySessionRunning´³rec´³lit³pty„´³tupleµ´³named³id³any„´³rec´³lit³session-running„´³tupleµ„„„„„„„³ embeddedType€„„„„ \ No newline at end of file diff --git a/protocols/schemas/pty.prs b/protocols/schemas/pty.prs index 3f01d57..5cdc153 100644 --- a/protocols/schemas/pty.prs +++ b/protocols/schemas/pty.prs @@ -30,9 +30,13 @@ CommandLine = [@command any @args any ...] . # PtySessionRunning = > . -# Message. The driver interprets it as a request to execute TIOCSWINSZ. +# Assertion. The driver interprets these as a request to execute TIOCSWINSZ +# with the minima of all concurrently asserted values. PtyResize = > . +# Assertion. The driver asserts this to report the current size of the tty. +PtySize = > . + # Message. Causes `data` to be delivered by the driver to the subprocess via the pty. PtyInput = > . diff --git a/src/main.rs b/src/main.rs index 7c41646..8a3f14d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,12 +12,14 @@ use syndicate::actor::ActorResult; use syndicate::actor::AnyValue; use syndicate::actor::Cap; use syndicate::actor::Field; +use syndicate::actor::Handle; use syndicate::actor::LinkedTaskTermination; use syndicate::dataspace::Dataspace; use syndicate::enclose; use syndicate::relay; use syndicate::value::NestedValue; use syndicate::value::NoEmbeddedDomainCodec; +use syndicate::value::Set; use syndicate::value::TextWriter; use syndicate::value::Value; use syndicate_macros::during; @@ -34,7 +36,7 @@ preserves_schema::define_language!(language(): Language { main: crate::schemas::Language, }); -use schemas::pty::{PtySession, PtySessionRunning, PtyOutput}; +use schemas::pty::{PtySession, PtySessionRunning, PtyOutput, PtySize}; fn stringify(v: &AnyValue) -> Result { match v.value() { @@ -79,7 +81,14 @@ fn terminate_pid(t: &mut Activation, child_pid: &Field>) -> ActorRes } } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +struct TerminalSize { + columns: u16, + rows: u16, +} + fn pty_session(t: &mut Activation, local_ds: Arc, s: PtySession) -> ActorResult { + let session_id = s.id.clone(); let mut pieces: Vec = Vec::new(); for piece_value in s.command_line.args.into_iter() { pieces.push(stringify(&piece_value)?); @@ -101,7 +110,7 @@ fn pty_session(t: &mut Activation, local_ds: Arc, s: PtySession) let facet = t.facet.clone(); local_ds.assert(t, language(), &PtySessionRunning { - id: s.id.clone(), + id: session_id.clone(), }); t.on_stop(enclose!((child_pid) move |t| { @@ -109,7 +118,7 @@ fn pty_session(t: &mut Activation, local_ds: Arc, s: PtySession) terminate_pid(t, &child_pid) })); - on_message!(t, local_ds, language(), >, |_t| { + on_message!(t, local_ds, language(), >, |_t| { match body.value().as_bytestring() { Some(bytes) => { tracing::trace!(?bytes, "sending to child"); @@ -121,23 +130,63 @@ fn pty_session(t: &mut Activation, local_ds: Arc, s: PtySession) Ok(()) }); - on_message!(t, local_ds, language(), >, |_t| { + let sizes = t.named_field("sizes", Set::::new()); + let size = t.named_field("size", TerminalSize { columns: 80, rows: 24 }); + enclose!((sizes) during!( + t, local_ds, language(), >, + enclose!((sizes) move |t: &mut Activation| { + let ts = TerminalSize { + columns: columns.value().as_u16().unwrap_or(80), + rows: rows.value().as_u16().unwrap_or(24), + }; + tracing::trace!(?ts, "size added"); + t.get_mut(&sizes).insert(ts.clone()); + t.on_stop(enclose!((sizes) move |t| { + tracing::trace!(?ts, "size removed"); + t.get_mut(&sizes).remove(&ts); + Ok(()) + })); + Ok(()) + }))); + + t.dataflow(enclose!((sizes, size) move |t| { + let mut ts: Option = None; + tracing::trace!(sizes=?t.get(&sizes), "candidate sizes"); + for s in t.get(&sizes).iter() { + ts = Some(ts.unwrap_or(s.clone()).min(s.clone())); + } + if let Some(s) = ts { + if t.get(&size) != &s { + *t.get_mut(&size) = s; + } + } + Ok(()) + }))?; + + let mut size_handle: Option = None; + t.dataflow(enclose!((local_ds, session_id, size) move |t| { + let TerminalSize { columns, rows } = t.get(&size).clone(); tracing::trace!(?columns, ?rows, "size change"); let result = unsafe { libc::ioctl(child_fd, libc::TIOCSWINSZ, &libc::winsize { - ws_row: rows.value().as_u16().unwrap_or(24), - ws_col: columns.value().as_u16().unwrap_or(80), + ws_row: rows, + ws_col: columns, ws_xpixel: 0, ws_ypixel: 0, }) }; if result == 0 { tracing::trace!(?columns, ?rows, "size changed"); + local_ds.update(t, &mut size_handle, language(), Some(&PtySize { + id: session_id.clone(), + columns: columns.into(), + rows: rows.into(), + })); } else { tracing::error!(err=?std::io::Error::last_os_error(), ?columns, ?rows, ?child_fd, "size change failed"); } Ok(()) - }); + }))?; t.every(tokio::time::Duration::from_secs(1), enclose!((child_pid) move |t| { match t.get(&child_pid) { @@ -170,7 +219,7 @@ fn pty_session(t: &mut Activation, local_ds: Arc, s: PtySession) } else { if !facet.activate(&read_account, None, |t| { local_ds.message(t, language(), &PtyOutput { - id: s.id.clone(), + id: session_id.clone(), data: buf[0..n].to_vec(), }); Ok(())