uncurses/ansi/passthrough.rs
1//! DCS passthrough wrappers for terminal multiplexers.
2//!
3//! ## Category
4//!
5//! Passthrough strings tunnel an inner escape sequence through an intermediate
6//! terminal layer so the outer terminal receives it.
7//!
8//! ## DCS framing
9//!
10//! Both writers emit 7-bit DCS strings terminated by `ST` (`ESC \\`). The tmux
11//! form prefixes `tmux;` and doubles literal ESC bytes inside the payload; the
12//! screen form can split long payloads into multiple adjacent DCS strings.
13//!
14//! ## Mode interaction
15//!
16//! Passthrough is not controlled by an ANSI/DEC mode. It is a framing convention
17//! around another sequence, so the inner sequence may have its own mode
18//! requirements.
19
20use std::io::{self, Write};
21
22use crate::ansi::c0::ESC;
23
24/// Wrap `seq` in one or more DCS passthrough strings for screen-style forwarding.
25///
26/// The basic frame is `ESC P <seq> ESC \`. When `limit > 0`, `seq` is split into chunks of at most `limit` bytes separated by `ESC \ ESC P`; `limit == 0` writes one frame.
27pub fn write_screen_passthrough<W: Write>(w: &mut W, seq: &[u8], limit: usize) -> io::Result<()> {
28 w.write_all(b"\x1bP")?;
29 if limit > 0 {
30 let mut i = 0;
31 while i < seq.len() {
32 let end = (i + limit).min(seq.len());
33 w.write_all(&seq[i..end])?;
34 if end < seq.len() {
35 w.write_all(b"\x1b\\\x1bP")?;
36 }
37 i = end;
38 }
39 } else {
40 w.write_all(seq)?;
41 }
42 w.write_all(b"\x1b\\")
43}
44
45/// Wrap `seq` in a tmux passthrough frame, `ESC P tmux ; <escaped-seq> ESC \`.
46///
47/// Each literal ESC byte inside `seq` is doubled so the intermediate layer passes it through to the outer terminal.
48pub fn write_tmux_passthrough<W: Write>(w: &mut W, seq: &[u8]) -> io::Result<()> {
49 w.write_all(b"\x1bPtmux;")?;
50 for &b in seq {
51 if b == ESC {
52 w.write_all(&[ESC])?;
53 }
54 w.write_all(&[b])?;
55 }
56 w.write_all(b"\x1b\\")
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn test_tmux_passthrough() {
65 let mut buf = Vec::new();
66 write_tmux_passthrough(&mut buf, b"\x1b[31m").unwrap();
67 assert_eq!(buf, b"\x1bPtmux;\x1b\x1b[31m\x1b\\");
68 }
69
70 #[test]
71 fn test_screen_passthrough_chunked() {
72 let mut buf = Vec::new();
73 write_screen_passthrough(&mut buf, b"123456", 2).unwrap();
74 assert_eq!(buf, b"\x1bP12\x1b\\\x1bP34\x1b\\\x1bP56\x1b\\");
75 }
76}