mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 13:21:38 +03:00
termwiz: recognize the XTGETTCAP DCS sequence
Parse and respond to this sequence docs can be found https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Device-Control-functions:DCS-plus-q-Pt-ST.F95 refs: https://github.com/wez/wezterm/issues/954
This commit is contained in:
parent
365a68dfb8
commit
6ddc8afc64
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -1913,6 +1913,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
@ -4306,6 +4312,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"filedescriptor",
|
||||
"fnv",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
@ -5110,6 +5117,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"hex",
|
||||
"image",
|
||||
"k9",
|
||||
"lazy_static",
|
||||
@ -5122,6 +5130,7 @@ dependencies = [
|
||||
"pretty_env_logger",
|
||||
"serde",
|
||||
"sha2",
|
||||
"terminfo",
|
||||
"termwiz",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
|
@ -16,6 +16,7 @@ use_serde = ["termwiz/use_serde"]
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bitflags = "1.0"
|
||||
hex = "0.4"
|
||||
image = "0.23"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
@ -25,6 +26,7 @@ ordered-float = "2.7"
|
||||
palette = "0.5"
|
||||
serde = {version="1.0", features = ["rc"]}
|
||||
sha2 = "0.9"
|
||||
terminfo = "0.7"
|
||||
unicode-segmentation = "1.7"
|
||||
unicode-width = "0.1"
|
||||
url = "2"
|
||||
|
@ -13,6 +13,7 @@ use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::sync::Arc;
|
||||
use terminfo::{Database, Value};
|
||||
use termwiz::escape::csi::{
|
||||
Cursor, CursorStyle, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay,
|
||||
EraseInLine, Mode, Sgr, TabulationClear, TerminalMode, TerminalModeCode, Window, XtSmGraphics,
|
||||
@ -30,6 +31,13 @@ use termwiz::image::{ImageCell, ImageData, TextureCoordinate};
|
||||
use termwiz::surface::{CursorShape, CursorVisibility};
|
||||
use url::Url;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref DB: Database = {
|
||||
let data = include_bytes!("../../termwiz/data/wezterm");
|
||||
Database::from_buffer(&data[..]).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
struct TabStop {
|
||||
tabs: Vec<bool>,
|
||||
tab_width: usize,
|
||||
@ -1455,6 +1463,68 @@ impl TerminalState {
|
||||
});
|
||||
}
|
||||
|
||||
/// <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Device-Control-functions:DCS-plus-q-Pt-ST.F95>
|
||||
fn xt_get_tcap(&mut self, names: Vec<String>) {
|
||||
let mut res = "\x1bP".to_string();
|
||||
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
if i > 0 {
|
||||
res.push(';');
|
||||
}
|
||||
|
||||
let encoded_name = hex::encode_upper(&name);
|
||||
match name.as_str() {
|
||||
"TN" | "name" => {
|
||||
res.push_str("1+r");
|
||||
res.push_str(&encoded_name);
|
||||
res.push('=');
|
||||
|
||||
let encoded_val = hex::encode_upper(&self.term_program);
|
||||
res.push_str(&encoded_val);
|
||||
}
|
||||
|
||||
"Co" | "colors" => {
|
||||
res.push_str("1+r");
|
||||
res.push_str(&encoded_name);
|
||||
res.push('=');
|
||||
res.push_str(&256.to_string());
|
||||
}
|
||||
|
||||
"RGB" => {
|
||||
res.push_str("1+r");
|
||||
res.push_str(&encoded_name);
|
||||
res.push('=');
|
||||
res.push_str("8/8/8");
|
||||
}
|
||||
|
||||
_ => {
|
||||
if let Some(value) = DB.raw(name) {
|
||||
res.push_str("1+r");
|
||||
res.push_str(&encoded_name);
|
||||
res.push('=');
|
||||
match value {
|
||||
Value::True => res.push('1'),
|
||||
Value::Number(n) => res.push_str(&n.to_string()),
|
||||
Value::String(s) => {
|
||||
for &b in s {
|
||||
res.push(b as char);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::trace!("xt_get_tcap: unknown name {}", name);
|
||||
res.push_str("0+r");
|
||||
res.push_str(&encoded_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.push_str("\x1b\\");
|
||||
log::trace!("responding with {}", res.escape_debug());
|
||||
self.writer.write_all(res.as_bytes()).ok();
|
||||
}
|
||||
|
||||
fn sixel(&mut self, sixel: Box<Sixel>) {
|
||||
let (width, height) = sixel.dimensions();
|
||||
|
||||
@ -3240,6 +3310,7 @@ impl<'a> Performer<'a> {
|
||||
Action::Esc(esc) => self.esc_dispatch(esc),
|
||||
Action::CSI(csi) => self.csi_dispatch(csi),
|
||||
Action::Sixel(sixel) => self.sixel(sixel),
|
||||
Action::XtGetTcap(names) => self.xt_get_tcap(names),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ cfg-if = "1.0"
|
||||
anyhow = "1.0"
|
||||
filedescriptor = { version="0.8", path = "../filedescriptor" }
|
||||
fnv = {version="1.0", optional=true}
|
||||
hex = "0.4"
|
||||
lazy_static = "1.4"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
|
BIN
termwiz/data/wezterm
Normal file
BIN
termwiz/data/wezterm
Normal file
Binary file not shown.
@ -36,6 +36,9 @@ pub enum Action {
|
||||
CSI(CSI),
|
||||
Esc(Esc),
|
||||
Sixel(Box<Sixel>),
|
||||
/// A list of termcap, terminfo names for which the application
|
||||
/// whats information
|
||||
XtGetTcap(Vec<String>),
|
||||
}
|
||||
|
||||
/// Encode self as an escape sequence. The escape sequence may potentially
|
||||
@ -50,6 +53,19 @@ impl Display for Action {
|
||||
Action::CSI(csi) => csi.fmt(f),
|
||||
Action::Esc(esc) => esc.fmt(f),
|
||||
Action::Sixel(sixel) => sixel.fmt(f),
|
||||
Action::XtGetTcap(names) => {
|
||||
write!(f, "\x1bP+q")?;
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
for &b in name.as_bytes() {
|
||||
write!(f, "{:x}", b)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,7 +192,9 @@ impl Display for DeviceControlMode {
|
||||
}
|
||||
f.write_char(mode.byte as char)
|
||||
}
|
||||
Self::Exit => write!(f, "\x1b\\"),
|
||||
// We don't need to emit a sequence for the Exit, as we're
|
||||
// followed by eg: StringTerminator
|
||||
Self::Exit => Ok(()),
|
||||
Self::Data(c) => f.write_char(*c as char),
|
||||
Self::ShortDeviceControl(s) => s.fmt(f),
|
||||
}
|
||||
|
@ -19,10 +19,40 @@ struct SixelBuilder {
|
||||
coloruse_re: Regex,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GetTcapBuilder {
|
||||
current: Vec<u8>,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
impl GetTcapBuilder {
|
||||
fn flush(&mut self) {
|
||||
let decoded = hex::decode(&self.current)
|
||||
.map(|s| String::from_utf8_lossy(&s).to_string())
|
||||
.unwrap_or_else(|_| String::from_utf8_lossy(&self.current).to_string());
|
||||
self.names.push(decoded);
|
||||
self.current.clear();
|
||||
}
|
||||
|
||||
pub fn push(&mut self, data: u8) {
|
||||
if data == b';' {
|
||||
self.flush();
|
||||
} else {
|
||||
self.current.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(mut self) -> Vec<String> {
|
||||
self.flush();
|
||||
self.names
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ParseState {
|
||||
sixel: Option<SixelBuilder>,
|
||||
dcs: Option<ShortDeviceControl>,
|
||||
get_tcap: Option<GetTcapBuilder>,
|
||||
}
|
||||
|
||||
/// The `Parser` struct holds the state machine that is used to decode
|
||||
@ -162,10 +192,14 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
intermediates: &[u8],
|
||||
ignored_extra_intermediates: bool,
|
||||
) {
|
||||
self.state.sixel.take();
|
||||
self.state.get_tcap.take();
|
||||
self.state.dcs.take();
|
||||
if byte == b'q' && intermediates.is_empty() && !ignored_extra_intermediates {
|
||||
self.state.sixel.replace(SixelBuilder::new(params));
|
||||
} else if byte == b'q' && intermediates == [b'+'] {
|
||||
self.state.get_tcap.replace(GetTcapBuilder::default());
|
||||
} else if !ignored_extra_intermediates && is_short_dcs(intermediates, byte) {
|
||||
self.state.sixel.take();
|
||||
self.state.dcs.replace(ShortDeviceControl {
|
||||
params: params.to_vec(),
|
||||
intermediates: intermediates.to_vec(),
|
||||
@ -189,6 +223,8 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
dcs.data.push(data);
|
||||
} else if let Some(sixel) = self.state.sixel.as_mut() {
|
||||
sixel.push(data);
|
||||
} else if let Some(tcap) = self.state.get_tcap.as_mut() {
|
||||
tcap.push(data);
|
||||
} else {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Data(data)));
|
||||
}
|
||||
@ -202,6 +238,8 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
} else if let Some(mut sixel) = self.state.sixel.take() {
|
||||
sixel.finish();
|
||||
(self.callback)(Action::Sixel(Box::new(sixel.sixel)));
|
||||
} else if let Some(tcap) = self.state.get_tcap.take() {
|
||||
(self.callback)(Action::XtGetTcap(tcap.finish()));
|
||||
} else {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Exit));
|
||||
}
|
||||
@ -739,6 +777,17 @@ mod test {
|
||||
actions
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xtgettcap() {
|
||||
assert_eq!(
|
||||
round_trip_parse("\x1bP+q544e\x1b\\"),
|
||||
vec![
|
||||
Action::XtGetTcap(vec!["TN".to_string()]),
|
||||
Action::Esc(Esc::Code(EscCode::StringTerminator)),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xterm_key() {
|
||||
assert_eq!(
|
||||
|
@ -414,7 +414,7 @@ fn parse_status_text(text: &str, default_cell: CellAttributes) -> Vec<Cell> {
|
||||
Action::Esc(_) => {
|
||||
flush_print(&mut print_buffer, &mut cells, &pen);
|
||||
}
|
||||
Action::Sixel(_) => {
|
||||
Action::XtGetTcap(_) | Action::Sixel(_) => {
|
||||
flush_print(&mut print_buffer, &mut cells, &pen);
|
||||
}
|
||||
Action::DeviceControl(_) => {
|
||||
|
Loading…
Reference in New Issue
Block a user