feat: fallback to CSI 16 t for certain terminals that do not support TIOCGWINSZ (#2004)

This commit is contained in:
三咲雅 · Misaki Masa 2024-12-07 00:46:10 +08:00 committed by GitHub
parent 11cf555b47
commit 9d3c0c72fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 38 deletions

View file

@ -4,8 +4,6 @@ on:
push:
branches: [main]
tags: ["v[0-9]+.[0-9]+.[0-9]+"]
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
env:

40
Cargo.lock generated
View file

@ -117,9 +117,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.93"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]]
name = "arbitrary"
@ -366,9 +366,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.21"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
@ -376,9 +376,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.21"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
@ -429,9 +429,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "clipboard-win"
@ -777,9 +777,9 @@ dependencies = [
[[package]]
name = "fdeflate"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
@ -2562,9 +2562,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.36"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@ -2585,9 +2585,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@ -2605,9 +2605,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.41.1"
version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [
"backtrace",
"bytes",
@ -2634,9 +2634,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.16"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
@ -2645,9 +2645,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.12"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",

View file

@ -11,10 +11,10 @@ strip = true
[workspace.dependencies]
ansi-to-tui = "7.0.0"
anyhow = "1.0.93"
anyhow = "1.0.94"
base64 = "0.22.1"
bitflags = "2.6.0"
clap = { version = "4.5.21", features = [ "derive" ] }
clap = { version = "4.5.23", features = [ "derive" ] }
crossterm = { version = "0.28.1", features = [ "event-stream" ] }
dirs = "5.0.1"
futures = "0.3.31"
@ -29,9 +29,9 @@ scopeguard = "1.2.0"
serde = { version = "1.0.215", features = [ "derive" ] }
serde_json = "1.0.133"
shell-words = "1.1.0"
tokio = { version = "1.41.1", features = [ "full" ] }
tokio-stream = "0.1.16"
tokio-util = "0.7.12"
tokio = { version = "1.42.0", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.13"
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
unicode-width = "0.2.0"
uzers = "0.12.1"

View file

@ -2,6 +2,8 @@ use std::mem;
use crossterm::terminal::WindowSize;
use crate::EMULATOR;
pub struct Dimension;
impl Dimension {
@ -27,9 +29,10 @@ impl Dimension {
#[inline]
pub fn ratio() -> Option<(f64, f64)> {
let s = Self::available();
if s.width == 0 || s.height == 0 {
return None;
}
Some((f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows)))
Some(if s.width == 0 || s.height == 0 {
(EMULATOR.cell_size?.0 as f64, EMULATOR.cell_size?.1 as f64)
} else {
(f64::from(s.width) / f64::from(s.columns), f64::from(s.height) / f64::from(s.rows))
})
}
}

View file

@ -11,18 +11,21 @@ use crate::{Adapter, Brand, Mux, TMUX, Unknown};
#[derive(Clone, Copy, Debug)]
pub struct Emulator {
pub kind: Either<Brand, Unknown>,
pub light: bool,
pub kind: Either<Brand, Unknown>,
pub light: bool,
pub cell_size: Option<(u16, u16)>,
}
impl Default for Emulator {
fn default() -> Self { Self { kind: Either::Right(Unknown::default()), light: false } }
fn default() -> Self {
Self { kind: Either::Right(Unknown::default()), light: false, cell_size: None }
}
}
impl Emulator {
pub fn detect() -> Self {
if let Some(brand) = Brand::from_env() {
Self { kind: Either::Left(brand), light: Self::detect_base().unwrap_or_default().light }
Self { kind: Either::Left(brand), ..Self::detect_base().unwrap_or_default() }
} else {
Self::detect_full().unwrap_or_default()
}
@ -37,6 +40,7 @@ impl Emulator {
SavePosition,
Print(Mux::csi("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")), // Detect KGP
Print(Mux::csi("\x1b[>q")), // Request terminal version
Print("\x1b[16t"), // Request cell size
Print("\x1b]11;?\x07"), // Request background color
Print(Mux::csi("\x1b[0c")), // Request device attributes
RestorePosition
@ -52,7 +56,11 @@ impl Emulator {
})
};
Ok(Self { kind, light: Self::light_bg(&resp).unwrap_or_default() })
Ok(Self {
kind,
light: Self::light_bg(&resp).unwrap_or_default(),
cell_size: Self::cell_size(&resp),
})
}
pub fn adapters(self) -> &'static [Adapter] {
@ -117,7 +125,7 @@ impl Emulator {
match timeout(Duration::from_secs(10), read).await {
Err(e) => error!("read_until_da1 timed out: {buf:?}, error: {e:?}"),
Ok(Err(e)) => error!("read_until_da1 failed: {buf:?}, error: {e:?}"),
Ok(Ok(())) => {}
Ok(Ok(())) => debug!("read_until_da1: {buf:?}"),
}
String::from_utf8_lossy(&buf).into_owned()
}
@ -128,12 +136,30 @@ impl Emulator {
execute!(
LineWriter::new(stderr()),
Print("\x1b[16t"), // Request cell size
Print("\x1b]11;?\x07"), // Request background color
Print(Mux::csi("\x1b[0c")), // Request device attributes
)?;
let resp = futures::executor::block_on(Self::read_until_da1());
Ok(Self { light: Self::light_bg(&resp).unwrap_or_default(), ..Default::default() })
Ok(Self {
light: Self::light_bg(&resp).unwrap_or_default(),
cell_size: Self::cell_size(&resp),
..Default::default()
})
}
fn cell_size(resp: &str) -> Option<(u16, u16)> {
let b = resp.split_at(resp.find("\x1b[6;")? + 4).1.as_bytes();
let h: Vec<_> = b.iter().copied().take_while(|&c| c.is_ascii_digit()).collect();
b.get(h.len()).filter(|&&c| c == b';')?;
let w: Vec<_> = b[h.len() + 1..].iter().copied().take_while(|&c| c.is_ascii_digit()).collect();
b.get(h.len() + 1 + w.len()).filter(|&&c| c == b't')?;
let (w, h) = unsafe { (String::from_utf8_unchecked(w), String::from_utf8_unchecked(h)) };
Some((w.parse().ok()?, h.parse().ok()?))
}
fn light_bg(resp: &str) -> Result<bool> {