mirror of
https://github.com/wez/wezterm.git
synced 2024-11-13 07:22:52 +03:00
termwiz+term: add basic support for DECRQSS
Now that we're reporting a higher level from DA1, apps are asking more exotic codes. eg: vttest now asks about the conformance level, but doesn't have a timeout on that request and hangs if we don't respond. This commit adds a bit of plumbing to make it easier to consume and parse DCS sequences that are known to be short/short-lived, and teaches the term layer to respond to a couple of possible DECRQSS queries.
This commit is contained in:
parent
fea8f2479e
commit
b69c9b1c70
@ -2506,7 +2506,49 @@ impl<'a> Performer<'a> {
|
||||
}
|
||||
|
||||
fn device_control(&mut self, ctrl: DeviceControlMode) {
|
||||
log::error!("unhandled {:?}", ctrl);
|
||||
match &ctrl {
|
||||
DeviceControlMode::ShortDeviceControl(s) => {
|
||||
match (s.byte, s.intermediates.as_slice()) {
|
||||
(b'q', &[b'$']) => {
|
||||
// DECRQSS - Request Status String
|
||||
// https://vt100.net/docs/vt510-rm/DECRQSS.html
|
||||
// The response is described here:
|
||||
// https://vt100.net/docs/vt510-rm/DECRPSS.html
|
||||
// but note that *that* text has the validity value
|
||||
// inverted; there's a note about this in the xterm
|
||||
// ctlseqs docs.
|
||||
match s.data.as_slice() {
|
||||
&[b'"', b'p'] => {
|
||||
// DECSCL - select conformance level
|
||||
write!(self.writer, "{}1$r65;1\"p{}", DCS, ST).ok();
|
||||
}
|
||||
&[b'r'] => {
|
||||
// DECSTBM - top and bottom margins
|
||||
let margins = self.top_and_bottom_margins.clone();
|
||||
write!(
|
||||
self.writer,
|
||||
"{}1$r{};{}r{}",
|
||||
DCS,
|
||||
margins.start + 1,
|
||||
margins.end,
|
||||
ST
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
_ => {
|
||||
log::error!("unhandled DECRQSS {:?}", s);
|
||||
// Reply that the request is invalid
|
||||
write!(self.writer, "{}0$r{}", DCS, ST).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => log::error!("unhandled {:?}", s),
|
||||
}
|
||||
}
|
||||
ctrl => {
|
||||
log::error!("unhandled {:?}", ctrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a character to the screen
|
||||
|
@ -51,6 +51,67 @@ impl Display for Action {
|
||||
}
|
||||
}
|
||||
|
||||
/// A fully parsed DCS sequence.
|
||||
/// The parser emits these for byte/intermediate sequences that are
|
||||
/// known to be relatively short and self contained (eg: DECRQSS)
|
||||
/// as opposed to larger ones like Sixel (which is parsed separately),
|
||||
/// or long lived terminal modes such as the TMUX CC protocol.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ShortDeviceControl {
|
||||
/// Integer parameter values
|
||||
pub params: Vec<i64>,
|
||||
/// Intermediate bytes to refine the control
|
||||
pub intermediates: Vec<u8>,
|
||||
/// The final byte
|
||||
pub byte: u8,
|
||||
/// The data prior to the string terminator
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ShortDeviceControl {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
fmt,
|
||||
"ShortDeviceControl(params: {:?}, intermediates: [",
|
||||
&self.params
|
||||
)?;
|
||||
for b in &self.intermediates {
|
||||
write!(fmt, "{:?} 0x{:x}, ", *b as char, *b)?;
|
||||
}
|
||||
write!(
|
||||
fmt,
|
||||
"], byte: {:?} 0x{:x}, data=[",
|
||||
self.byte as char, self.byte
|
||||
)?;
|
||||
|
||||
for b in &self.data {
|
||||
write!(fmt, "{:?} 0x{:x}, ", *b as char, *b)?;
|
||||
}
|
||||
|
||||
write!(fmt, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ShortDeviceControl {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||
write!(f, "\x1bP")?;
|
||||
for (idx, p) in self.params.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
write!(f, "{}", p)?;
|
||||
}
|
||||
for b in &self.intermediates {
|
||||
f.write_char(*b as char)?;
|
||||
}
|
||||
f.write_char(self.byte as char)?;
|
||||
for b in &self.data {
|
||||
f.write_char(*b as char)?;
|
||||
}
|
||||
write!(f, "\x1b\\")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct EnterDeviceControlMode {
|
||||
/// The final byte in the DCS mode
|
||||
@ -92,6 +153,8 @@ pub enum DeviceControlMode {
|
||||
Exit,
|
||||
/// Data for the device mode to consume
|
||||
Data(u8),
|
||||
/// A self contained (Enter, Data*, Exit) sequence
|
||||
ShortDeviceControl(Box<ShortDeviceControl>),
|
||||
}
|
||||
|
||||
impl Display for DeviceControlMode {
|
||||
@ -112,6 +175,7 @@ impl Display for DeviceControlMode {
|
||||
}
|
||||
Self::Exit => write!(f, "\x1b\\"),
|
||||
Self::Data(c) => f.write_char(*c as char),
|
||||
Self::ShortDeviceControl(s) => s.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,6 +186,7 @@ impl std::fmt::Debug for DeviceControlMode {
|
||||
Self::Enter(mode) => write!(fmt, "Enter({:?})", mode),
|
||||
Self::Exit => write!(fmt, "Exit"),
|
||||
Self::Data(b) => write!(fmt, "Data({:?} 0x{:x})", *b as char, *b),
|
||||
Self::ShortDeviceControl(s) => write!(fmt, "ShortDeviceControl({:?})", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::color::RgbColor;
|
||||
use crate::escape::{
|
||||
Action, DeviceControlMode, EnterDeviceControlMode, Esc, OperatingSystemCommand, Sixel,
|
||||
SixelData, CSI,
|
||||
Action, DeviceControlMode, EnterDeviceControlMode, Esc, OperatingSystemCommand,
|
||||
ShortDeviceControl, Sixel, SixelData, CSI,
|
||||
};
|
||||
use log::error;
|
||||
use num_traits::FromPrimitive;
|
||||
@ -21,6 +21,7 @@ struct SixelBuilder {
|
||||
#[derive(Default)]
|
||||
struct ParseState {
|
||||
sixel: Option<SixelBuilder>,
|
||||
dcs: Option<ShortDeviceControl>,
|
||||
}
|
||||
|
||||
/// The `Parser` struct holds the state machine that is used to decode
|
||||
@ -129,6 +130,15 @@ struct Performer<'a, F: FnMut(Action) + 'a> {
|
||||
state: &'a mut ParseState,
|
||||
}
|
||||
|
||||
fn is_short_dcs(intermediates: &[u8], byte: u8) -> bool {
|
||||
if intermediates == &[b'$'] && byte == b'q' {
|
||||
// DECRQSS
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
fn print(&mut self, c: char) {
|
||||
(self.callback)(Action::Print(c));
|
||||
@ -137,7 +147,10 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
fn execute_c0_or_c1(&mut self, byte: u8) {
|
||||
match FromPrimitive::from_u8(byte) {
|
||||
Some(code) => (self.callback)(Action::Control(code)),
|
||||
None => error!("impossible C0/C1 control code {:?} was dropped", byte),
|
||||
None => error!(
|
||||
"impossible C0/C1 control code {:?} 0x{:x} was dropped",
|
||||
byte as char, byte
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +163,14 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
) {
|
||||
if byte == b'q' && intermediates.is_empty() && !ignored_extra_intermediates {
|
||||
self.state.sixel.replace(SixelBuilder::new(params));
|
||||
} 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(),
|
||||
byte,
|
||||
data: vec![],
|
||||
});
|
||||
} else {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Enter(Box::new(
|
||||
EnterDeviceControlMode {
|
||||
@ -163,7 +184,9 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
}
|
||||
|
||||
fn dcs_put(&mut self, data: u8) {
|
||||
if let Some(sixel) = self.state.sixel.as_mut() {
|
||||
if let Some(dcs) = self.state.dcs.as_mut() {
|
||||
dcs.data.push(data);
|
||||
} else if let Some(sixel) = self.state.sixel.as_mut() {
|
||||
sixel.push(data);
|
||||
} else {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Data(data)));
|
||||
@ -171,7 +194,11 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
|
||||
}
|
||||
|
||||
fn dcs_unhook(&mut self) {
|
||||
if let Some(mut sixel) = self.state.sixel.take() {
|
||||
if let Some(dcs) = self.state.dcs.take() {
|
||||
(self.callback)(Action::DeviceControl(
|
||||
DeviceControlMode::ShortDeviceControl(Box::new(dcs)),
|
||||
));
|
||||
} else if let Some(mut sixel) = self.state.sixel.take() {
|
||||
sixel.finish();
|
||||
(self.callback)(Action::Sixel(Box::new(sixel.sixel)));
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user