1
1
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:
Wez Furlong 2020-06-20 11:04:37 -07:00
parent fea8f2479e
commit b69c9b1c70
3 changed files with 140 additions and 6 deletions

View File

@ -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

View File

@ -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),
}
}
}

View File

@ -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 {