rmux_core/
terminal_passthrough.rs1use std::sync::Arc;
2
3pub(crate) const MAX_TERMINAL_PASSTHROUGH_PAYLOAD_BYTES: usize = 8 * 1024 * 1024;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct TerminalPassthrough {
9 kind: TerminalPassthroughKind,
10 cursor_x: u32,
11 cursor_y: u32,
12 payload: Arc<[u8]>,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum TerminalPassthroughKind {
18 Raw,
20 Clipboard,
22 KittyGraphics,
24 Sixel,
26}
27
28impl TerminalPassthrough {
29 #[must_use]
31 pub fn raw(cursor_x: u32, cursor_y: u32, payload: impl Into<Vec<u8>>) -> Self {
32 Self {
33 kind: TerminalPassthroughKind::Raw,
34 cursor_x,
35 cursor_y,
36 payload: Arc::from(payload.into()),
37 }
38 }
39
40 #[must_use]
42 pub fn clipboard(payload: impl Into<Vec<u8>>) -> Self {
43 Self {
44 kind: TerminalPassthroughKind::Clipboard,
45 cursor_x: 0,
46 cursor_y: 0,
47 payload: Arc::from(payload.into()),
48 }
49 }
50
51 #[must_use]
53 pub fn kitty_graphics(cursor_x: u32, cursor_y: u32, payload: impl Into<Vec<u8>>) -> Self {
54 Self {
55 kind: TerminalPassthroughKind::KittyGraphics,
56 cursor_x,
57 cursor_y,
58 payload: Arc::from(payload.into()),
59 }
60 }
61
62 #[must_use]
64 pub fn sixel(cursor_x: u32, cursor_y: u32, payload: impl Into<Vec<u8>>) -> Self {
65 Self {
66 kind: TerminalPassthroughKind::Sixel,
67 cursor_x,
68 cursor_y,
69 payload: Arc::from(payload.into()),
70 }
71 }
72
73 #[must_use]
75 pub const fn kind(&self) -> TerminalPassthroughKind {
76 self.kind
77 }
78
79 #[must_use]
81 pub const fn cursor_x(&self) -> u32 {
82 self.cursor_x
83 }
84
85 #[must_use]
87 pub const fn cursor_y(&self) -> u32 {
88 self.cursor_y
89 }
90
91 #[must_use]
93 pub fn payload(&self) -> &[u8] {
94 &self.payload
95 }
96
97 #[must_use]
99 pub fn render_sequence(&self) -> Vec<u8> {
100 match self.kind {
101 TerminalPassthroughKind::Raw => self.payload.to_vec(),
102 TerminalPassthroughKind::Clipboard => self.payload.to_vec(),
103 TerminalPassthroughKind::KittyGraphics => {
104 let mut sequence = Vec::with_capacity(self.payload.len() + 4);
105 sequence.extend_from_slice(b"\x1b_");
106 sequence.extend_from_slice(&self.payload);
107 sequence.extend_from_slice(b"\x1b\\");
108 sequence
109 }
110 TerminalPassthroughKind::Sixel => {
111 let mut sequence = Vec::with_capacity(self.payload.len() + 4);
112 sequence.extend_from_slice(b"\x1bP");
113 sequence.extend_from_slice(&self.payload);
114 sequence.extend_from_slice(b"\x1b\\");
115 sequence
116 }
117 }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::TerminalPassthrough;
124
125 #[test]
126 fn renders_kitty_apc_sequence() {
127 let passthrough = TerminalPassthrough::kitty_graphics(0, 0, b"Gf=100;AAAA".to_vec());
128
129 assert_eq!(passthrough.render_sequence(), b"\x1b_Gf=100;AAAA\x1b\\");
130 }
131
132 #[test]
133 fn renders_raw_sequence_verbatim() {
134 let passthrough = TerminalPassthrough::raw(0, 0, b"\x1b]52;c;QQ==\x1b\\".to_vec());
135
136 assert_eq!(passthrough.render_sequence(), b"\x1b]52;c;QQ==\x1b\\");
137 }
138
139 #[test]
140 fn renders_clipboard_sequence_verbatim() {
141 let passthrough = TerminalPassthrough::clipboard(b"\x1b]52;c;QQ==\x07".to_vec());
142
143 assert_eq!(passthrough.render_sequence(), b"\x1b]52;c;QQ==\x07");
144 }
145
146 #[test]
147 fn renders_sixel_dcs_sequence() {
148 let passthrough = TerminalPassthrough::sixel(0, 0, b"q#0!10~".to_vec());
149
150 assert_eq!(passthrough.render_sequence(), b"\x1bPq#0!10~\x1b\\");
151 }
152}