forked from NotAShelf/beer
vt: parse terminal output into a grid model
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Iee271b093801326cff2489218063ab4c6a6a6964
This commit is contained in:
parent
740aefffa8
commit
bc53393aec
6 changed files with 1169 additions and 4 deletions
|
|
@ -3,6 +3,7 @@
|
|||
//! Uses smithay-client-toolkit for protocol boilerplate and calloop for the
|
||||
//! event loop, so the PTY master fd and timers share one loop.
|
||||
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
|
|
@ -11,6 +12,7 @@ use calloop::{EventLoop, Interest, Mode, PostAction};
|
|||
use calloop_wayland_source::WaylandSource;
|
||||
|
||||
use crate::pty::Pty;
|
||||
use crate::vt::Term;
|
||||
use smithay_client_toolkit::{
|
||||
compositor::{CompositorHandler, CompositorState},
|
||||
delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_shm,
|
||||
|
|
@ -39,7 +41,7 @@ const DEFAULT_W: u32 = 800;
|
|||
const DEFAULT_H: u32 = 600;
|
||||
/// Background fill, 0xAARRGGBB. Foot-ish dark grey.
|
||||
const BG: u32 = 0xFF18_1818;
|
||||
/// Terminal size handed to the shell until cell geometry drives it.
|
||||
/// Terminal size handed to the shell.
|
||||
const COLS: u16 = 80;
|
||||
const ROWS: u16 = 24;
|
||||
|
||||
|
|
@ -72,15 +74,17 @@ pub fn run() -> anyhow::Result<()> {
|
|||
.context("create shm slot pool")?;
|
||||
|
||||
let pty = Pty::spawn(COLS, ROWS).context("spawn shell on pty")?;
|
||||
let term = Term::new(COLS as usize, ROWS as usize);
|
||||
let mut parser = vte::Parser::new();
|
||||
|
||||
// Read child output off a clone of the master; the original stays in `pty`
|
||||
// for writes and resizing.
|
||||
// for writing input back.
|
||||
let read_fd = pty.master().try_clone().context("clone pty master")?;
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(
|
||||
Generic::new(read_fd, Interest::READ, Mode::Level),
|
||||
|_, fd, app: &mut App| {
|
||||
move |_, fd, app: &mut App| {
|
||||
let mut buf = [0u8; 4096];
|
||||
match rustix::io::read(&*fd, &mut buf) {
|
||||
Ok(0) => {
|
||||
|
|
@ -88,7 +92,8 @@ pub fn run() -> anyhow::Result<()> {
|
|||
Ok(PostAction::Remove)
|
||||
}
|
||||
Ok(n) => {
|
||||
tracing::debug!("pty -> {n} bytes: {:02x?}", &buf[..n]);
|
||||
parser.advance(&mut app.term, &buf[..n]);
|
||||
app.after_feed();
|
||||
Ok(PostAction::Continue)
|
||||
}
|
||||
Err(rustix::io::Errno::INTR | rustix::io::Errno::AGAIN) => {
|
||||
|
|
@ -111,6 +116,8 @@ pub fn run() -> anyhow::Result<()> {
|
|||
pool,
|
||||
window,
|
||||
pty,
|
||||
term,
|
||||
title: None,
|
||||
width: DEFAULT_W,
|
||||
height: DEFAULT_H,
|
||||
configured: false,
|
||||
|
|
@ -125,6 +132,19 @@ pub fn run() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Write every byte to `fd`, retrying short writes and interrupts.
|
||||
fn write_all(fd: &OwnedFd, mut buf: &[u8]) -> rustix::io::Result<()> {
|
||||
while !buf.is_empty() {
|
||||
match rustix::io::write(fd, buf) {
|
||||
Ok(0) => return Err(rustix::io::Errno::IO),
|
||||
Ok(n) => buf = &buf[n..],
|
||||
Err(rustix::io::Errno::INTR) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Window + Wayland client state shared across all protocol handlers.
|
||||
#[derive(Debug)]
|
||||
struct App {
|
||||
|
|
@ -135,6 +155,9 @@ struct App {
|
|||
pool: SlotPool,
|
||||
window: Window,
|
||||
pty: Pty,
|
||||
term: Term,
|
||||
/// Last title applied to the toplevel, to avoid redundant requests.
|
||||
title: Option<String>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
configured: bool,
|
||||
|
|
@ -142,6 +165,29 @@ struct App {
|
|||
}
|
||||
|
||||
impl App {
|
||||
/// After parsing child output: send any replies and sync the title.
|
||||
fn after_feed(&mut self) {
|
||||
let reply = self.term.take_response();
|
||||
if !reply.is_empty()
|
||||
&& let Err(err) = write_all(self.pty.master(), &reply)
|
||||
{
|
||||
tracing::warn!("write to pty: {err}");
|
||||
}
|
||||
|
||||
if tracing::enabled!(tracing::Level::TRACE) {
|
||||
let grid = self.term.grid();
|
||||
for y in 0..grid.rows() {
|
||||
tracing::trace!("{y:2}|{}", grid.row_text(y));
|
||||
}
|
||||
}
|
||||
|
||||
if self.term.title() != self.title.as_deref() {
|
||||
self.title = self.term.title().map(str::to_owned);
|
||||
self.window
|
||||
.set_title(self.title.clone().unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
/// The child shell has gone away; reap it and tear the window down.
|
||||
fn child_exited(&mut self) {
|
||||
match self.pty.wait() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue