Skip to main content

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}