mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
fix(sixel): only advertise capability if host terminal supports it (#5432)
* fix(sixel): only advertize capability if host terminal supports it * add pr
This commit is contained in:
@@ -342,6 +342,10 @@ impl InputHandler {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::KittyGraphicsSupport { supported });
|
||||
},
|
||||
AnsiStdinInstruction::SixelSupport(supported) => {
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::SixelSupport { supported });
|
||||
},
|
||||
}
|
||||
}
|
||||
fn handle_nested_session_frame_from_host(&mut self, payload_bytes: Vec<u8>) {
|
||||
|
||||
@@ -63,6 +63,7 @@ pub enum HostReply {
|
||||
/// terminal's color-palette theme mode (CSI 2031).
|
||||
HostTerminalThemeChanged(HostTerminalThemeMode),
|
||||
KittyGraphicsSupport(bool),
|
||||
SixelSupport(bool),
|
||||
}
|
||||
|
||||
/// Retained alias for the pre-refactor type name used by other modules in
|
||||
@@ -156,6 +157,22 @@ impl HostReply {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Classify a Primary Device Attributes reply (`CSI ? Ps ; Ps ... c`)
|
||||
/// into a Sixel-capability advertisement. Attribute `4` in the reply
|
||||
/// means the host terminal supports Sixel graphics.
|
||||
///
|
||||
/// Replies that are not a primary-DA form (eg. secondary-DA `CSI > ... c`)
|
||||
/// yield `None` so they do not clobber the host capability state.
|
||||
pub fn sixel_support_from_primary_da(raw: &[u8]) -> Option<HostReply> {
|
||||
lazy_static! {
|
||||
static ref PRIMARY_DA_RE: Regex = Regex::new(r"^\u{1b}\[\?([0-9;]*)c$").unwrap();
|
||||
}
|
||||
let s = std::str::from_utf8(raw).ok()?;
|
||||
let caps = PRIMARY_DA_RE.captures(s)?;
|
||||
let supports_sixel = caps[1].split(';').any(|attribute| attribute == "4");
|
||||
Some(HostReply::SixelSupport(supports_sixel))
|
||||
}
|
||||
}
|
||||
|
||||
/// The "slot" tracking state for a single forwarded query currently in
|
||||
@@ -419,6 +436,9 @@ impl StdinAnsiParser {
|
||||
self.startup_kitty_probe = StartupKittyProbe::Resolved;
|
||||
out.replies.push(HostReply::KittyGraphicsSupport(false));
|
||||
}
|
||||
if let Some(reply) = HostReply::sixel_support_from_primary_da(&raw) {
|
||||
out.replies.push(reply);
|
||||
}
|
||||
// Primary-DA — the barrier. Close the slot and
|
||||
// emit the completed forwarded reply if active.
|
||||
if let Some(slot) = self.active_forward.take() {
|
||||
|
||||
@@ -146,9 +146,11 @@ fn forwarding_window_accumulates_and_barrier_closes() {
|
||||
chunk.extend_from_slice(b"\x1b]11;rgb:aaaa/bbbb/dddd\x1b\\");
|
||||
chunk.extend_from_slice(b"\x1b[?65;1c");
|
||||
let out = parser.feed(&chunk);
|
||||
// OSC 11 was classified (double-dispatch).
|
||||
assert_eq!(out.replies.len(), 1);
|
||||
// OSC 11 was classified (double-dispatch), and the barrier itself
|
||||
// advertises the host's sixel capability.
|
||||
assert_eq!(out.replies.len(), 2);
|
||||
matches!(out.replies[0], HostReply::BackgroundColor(_));
|
||||
matches!(out.replies[1], HostReply::SixelSupport(false));
|
||||
// Barrier closed the window, producing a completed forward.
|
||||
let (token, reply_bytes) = out
|
||||
.completed_forward
|
||||
@@ -1241,11 +1243,59 @@ fn kitty_probe_absence_resolves_false_on_barrier() {
|
||||
parser.expect_kitty_probe_reply();
|
||||
let (replies, residue) = feed_once(&mut parser, b"\x1b[?62;4;52c");
|
||||
assert!(residue.is_empty());
|
||||
assert_eq!(replies.len(), 1);
|
||||
match &replies[0] {
|
||||
HostReply::KittyGraphicsSupport(supported) => assert!(!*supported),
|
||||
other => panic!("unexpected reply: {:?}", other),
|
||||
}
|
||||
let kitty_replies: Vec<bool> = replies
|
||||
.iter()
|
||||
.filter_map(|r| match r {
|
||||
HostReply::KittyGraphicsSupport(supported) => Some(*supported),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(kitty_replies, vec![false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primary_da_with_sixel_attribute_classifies_sixel_support_true() {
|
||||
let mut parser = StdinAnsiParser::new();
|
||||
let (replies, residue) = feed_once(&mut parser, b"\x1b[?62;4;52c");
|
||||
assert!(residue.is_empty());
|
||||
let sixel_replies: Vec<bool> = replies
|
||||
.iter()
|
||||
.filter_map(|r| match r {
|
||||
HostReply::SixelSupport(supported) => Some(*supported),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(sixel_replies, vec![true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primary_da_without_sixel_attribute_classifies_sixel_support_false() {
|
||||
let mut parser = StdinAnsiParser::new();
|
||||
let (replies, residue) = feed_once(&mut parser, b"\x1b[?62;22c");
|
||||
assert!(residue.is_empty());
|
||||
let sixel_replies: Vec<bool> = replies
|
||||
.iter()
|
||||
.filter_map(|r| match r {
|
||||
HostReply::SixelSupport(supported) => Some(*supported),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(sixel_replies, vec![false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primary_da_sixel_attribute_is_not_matched_as_a_substring() {
|
||||
let mut parser = StdinAnsiParser::new();
|
||||
let (replies, residue) = feed_once(&mut parser, b"\x1b[?64;14;21;22c");
|
||||
assert!(residue.is_empty());
|
||||
let sixel_replies: Vec<bool> = replies
|
||||
.iter()
|
||||
.filter_map(|r| match r {
|
||||
HostReply::SixelSupport(supported) => Some(*supported),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(sixel_replies, vec![false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -61,6 +61,7 @@ pub(crate) fn stdin_loop(
|
||||
} else {
|
||||
let _ = send_input_instructions.send(InputInstruction::AnsiStdinInstructions(vec![
|
||||
HostReply::KittyGraphicsSupport(false),
|
||||
HostReply::SixelSupport(false),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user