forked from NotAShelf/beer
beer: report pixel geometry so graphics clients can size images
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I4c11941c887bc75134200cd1471a792b6a6a6964
This commit is contained in:
parent
c8430ae787
commit
92135ddbc1
4 changed files with 53 additions and 13 deletions
|
|
@ -19,9 +19,10 @@ pub struct Pty {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pty {
|
impl Pty {
|
||||||
/// Open a PTY, size it to `cols`x`rows`, and exec the user's login shell on
|
/// Open a PTY, size it to `cols`x`rows` (with `cell` giving the cell size in
|
||||||
/// the slave end with `TERM=term`.
|
/// pixels, so the kernel reports a pixel geometry for graphics clients), and
|
||||||
pub fn spawn(cols: u16, rows: u16, term: &str) -> anyhow::Result<Self> {
|
/// exec the user's login shell on the slave end with `TERM=term`.
|
||||||
|
pub fn spawn(cols: u16, rows: u16, cell: (u16, u16), term: &str) -> anyhow::Result<Self> {
|
||||||
let master = openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC)
|
let master = openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC)
|
||||||
.context("open pty master")?;
|
.context("open pty master")?;
|
||||||
grantpt(&master).context("grantpt")?;
|
grantpt(&master).context("grantpt")?;
|
||||||
|
|
@ -29,7 +30,7 @@ impl Pty {
|
||||||
let slave = ioctl_tiocgptpeer(&master, OpenptFlags::RDWR | OpenptFlags::NOCTTY)
|
let slave = ioctl_tiocgptpeer(&master, OpenptFlags::RDWR | OpenptFlags::NOCTTY)
|
||||||
.context("open pty slave")?;
|
.context("open pty slave")?;
|
||||||
|
|
||||||
set_winsize(&master, cols, rows)?;
|
set_winsize(&master, cols, rows, cell)?;
|
||||||
|
|
||||||
let shell = std::env::var_os("SHELL").unwrap_or_else(|| "/bin/sh".into());
|
let shell = std::env::var_os("SHELL").unwrap_or_else(|| "/bin/sh".into());
|
||||||
let argv0 = login_argv0(&shell);
|
let argv0 = login_argv0(&shell);
|
||||||
|
|
@ -73,9 +74,10 @@ impl Pty {
|
||||||
&self.master
|
&self.master
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inform the kernel (and thus the child) of a new terminal size.
|
/// Inform the kernel (and thus the child) of a new terminal size; `cell` is
|
||||||
pub fn resize(&self, cols: u16, rows: u16) -> anyhow::Result<()> {
|
/// the cell size in pixels, carried into the winsize pixel fields.
|
||||||
set_winsize(&self.master, cols, rows)
|
pub fn resize(&self, cols: u16, rows: u16, cell: (u16, u16)) -> anyhow::Result<()> {
|
||||||
|
set_winsize(&self.master, cols, rows, cell)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reap the child if it has exited.
|
/// Reap the child if it has exited.
|
||||||
|
|
@ -84,12 +86,14 @@ impl Pty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_winsize(master: &OwnedFd, cols: u16, rows: u16) -> anyhow::Result<()> {
|
fn set_winsize(master: &OwnedFd, cols: u16, rows: u16, cell: (u16, u16)) -> anyhow::Result<()> {
|
||||||
let ws = Winsize {
|
let ws = Winsize {
|
||||||
ws_row: rows,
|
ws_row: rows,
|
||||||
ws_col: cols,
|
ws_col: cols,
|
||||||
ws_xpixel: 0,
|
// The pixel geometry lets graphics-protocol clients size images; it is
|
||||||
ws_ypixel: 0,
|
// the grid size times the cell size.
|
||||||
|
ws_xpixel: cols.saturating_mul(cell.0),
|
||||||
|
ws_ypixel: rows.saturating_mul(cell.1),
|
||||||
};
|
};
|
||||||
tcsetwinsize(master.as_fd(), ws).context("set pty winsize")
|
tcsetwinsize(master.as_fd(), ws).context("set pty winsize")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,20 @@ impl Term {
|
||||||
self.graphics.is_animating()
|
self.graphics.is_animating()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Answer a `CSI 14/16/18 t` geometry query: `14` reports the text area in
|
||||||
|
/// pixels (`CSI 4 ; h ; w t`), `16` the cell size in pixels (`CSI 6 ; …`),
|
||||||
|
/// `18` the text area in characters (`CSI 8 ; …`). Graphics clients read
|
||||||
|
/// these to size and place images.
|
||||||
|
fn report_geometry(&mut self, kind: u16) {
|
||||||
|
let (cw, ch) = self.cell_px;
|
||||||
|
let (cols, rows) = (self.grid.cols() as u32, self.grid.rows() as u32);
|
||||||
|
let _ = match kind {
|
||||||
|
14 => write!(self.response, "\x1b[4;{};{}t", rows * ch, cols * cw),
|
||||||
|
16 => write!(self.response, "\x1b[6;{ch};{cw}t"),
|
||||||
|
_ => write!(self.response, "\x1b[8;{rows};{cols}t"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// The working directory last reported by the shell (OSC 7), if any.
|
/// The working directory last reported by the shell (OSC 7), if any.
|
||||||
pub fn cwd(&self) -> Option<&str> {
|
pub fn cwd(&self) -> Option<&str> {
|
||||||
self.cwd.as_deref()
|
self.cwd.as_deref()
|
||||||
|
|
@ -682,6 +696,19 @@ mod tests {
|
||||||
assert!(resp.windows(2).any(|w| w == b"OK"), "expected OK response");
|
assert!(resp.windows(2).any(|w| w == b"OK"), "expected OK response");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reports_pixel_geometry_for_graphics_clients() {
|
||||||
|
// The test harness feeds with an 8x16 cell. A 20x4 grid is then 160x64
|
||||||
|
// pixels. These answers are what an image client needs to size images.
|
||||||
|
let mut t = Term::new(20, 4);
|
||||||
|
feed(&mut t, b"\x1b[16t"); // cell size in pixels
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[6;16;8t");
|
||||||
|
feed(&mut t, b"\x1b[14t"); // text area in pixels
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[4;64;160t");
|
||||||
|
feed(&mut t, b"\x1b[18t"); // text area in cells
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[8;4;20t");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kitty_unicode_placeholder_virtual_placement() {
|
fn kitty_unicode_placeholder_virtual_placement() {
|
||||||
// Transmit + a virtual placement (U=1): no cells are stamped, but the
|
// Transmit + a virtual placement (U=1): no cells are stamped, but the
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,12 @@ impl Perform for Term {
|
||||||
.kitty_set(n(params, 0, 0) as u8, n(params, 1, 1) as u8),
|
.kitty_set(n(params, 0, 0) as u8, n(params, 1, 1) as u8),
|
||||||
_ => self.grid.restore_cursor(),
|
_ => self.grid.restore_cursor(),
|
||||||
},
|
},
|
||||||
't' => self.title_stack_op(params),
|
// `CSI 14/16/18 t` report pixel/character geometry (used by graphics
|
||||||
|
// clients to size images); other `t` operations are title-stack ops.
|
||||||
|
't' => match raw(params, 0) {
|
||||||
|
14 | 16 | 18 => self.report_geometry(raw(params, 0)),
|
||||||
|
_ => self.title_stack_op(params),
|
||||||
|
},
|
||||||
'g' => match raw(params, 0) {
|
'g' => match raw(params, 0) {
|
||||||
3 => self.grid.clear_all_tabs(),
|
3 => self.grid.clear_all_tabs(),
|
||||||
_ => self.grid.clear_tab(),
|
_ => self.grid.clear_tab(),
|
||||||
|
|
|
||||||
|
|
@ -575,7 +575,9 @@ impl App {
|
||||||
/// Spawn the shell at the current window size and start reading its output.
|
/// Spawn the shell at the current window size and start reading its output.
|
||||||
fn spawn_session(&mut self) {
|
fn spawn_session(&mut self) {
|
||||||
let (cols, rows) = self.grid_dims();
|
let (cols, rows) = self.grid_dims();
|
||||||
let pty = match Pty::spawn(cols, rows, &self.config.main.term) {
|
let m = self.renderer.metrics();
|
||||||
|
let cell = (m.width as u16, m.height as u16);
|
||||||
|
let pty = match Pty::spawn(cols, rows, cell, &self.config.main.term) {
|
||||||
Ok(pty) => pty,
|
Ok(pty) => pty,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("spawn shell: {err:#}");
|
tracing::error!("spawn shell: {err:#}");
|
||||||
|
|
@ -1857,6 +1859,8 @@ impl App {
|
||||||
/// PTY about it if it changed.
|
/// PTY about it if it changed.
|
||||||
fn resize_grid(&mut self) {
|
fn resize_grid(&mut self) {
|
||||||
let (cols, rows) = self.grid_dims();
|
let (cols, rows) = self.grid_dims();
|
||||||
|
let m = self.renderer.metrics();
|
||||||
|
let cell = (m.width as u16, m.height as u16);
|
||||||
let Some(session) = self.session.as_mut() else {
|
let Some(session) = self.session.as_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -1866,7 +1870,7 @@ impl App {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
session.term.resize(cols as usize, rows as usize);
|
session.term.resize(cols as usize, rows as usize);
|
||||||
if let Err(err) = session.pty.resize(cols, rows) {
|
if let Err(err) = session.pty.resize(cols, rows, cell) {
|
||||||
tracing::warn!("resize pty: {err}");
|
tracing::warn!("resize pty: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue