aboutsummaryrefslogtreecommitdiff
path: root/src/protocol
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-09 02:04:39 +0300
committerGitHub <noreply@github.com>2026-08-09 02:04:39 +0300
commit1777e9bba32b953ed1ad203b4a16d01105539000 (patch)
tree22096973812c9baba822453d8470bdcfe64fc2a6 /src/protocol
parent36a8d36a7258e08b40c01bf521b1f4006525f143 (diff)
feat(graphics): add direct pane frame streaming (#2523)
* feat(graphics): add direct pane frame streaming * feat(graphics): expose pane visibility * test(graphics): preserve legacy terminal image output * fix(graphics): harden streaming lifecycle * fix(input): preserve mouse releases outside panes
Diffstat (limited to 'src/protocol')
-rw-r--r--src/protocol/wire.rs81
1 files changed, 79 insertions, 2 deletions
diff --git a/src/protocol/wire.rs b/src/protocol/wire.rs
index 4af2c794..4a445361 100644
--- a/src/protocol/wire.rs
+++ b/src/protocol/wire.rs
@@ -57,6 +57,8 @@ pub enum ClientKeybindings {
pub enum ClientLaunchMode {
/// Full app client.
App,
+ /// Full app client eligible for audited local direct graphics.
+ AppDirectGraphics,
/// Direct terminal attach client.
TerminalAttach,
}
@@ -428,6 +430,25 @@ pub enum ClientMessage {
/// Replace an existing writable controller for this terminal.
takeover: bool,
},
+
+ /// Result of the one armed Herdr-owned direct Kitty transmission.
+ GraphicsTransmissionResult {
+ transfer_id: u64,
+ image_id: u32,
+ success: bool,
+ },
+
+ /// One confirmed SGR 1016 mouse report with read-time host geometry.
+ InputPixels {
+ data: Vec<u8>,
+ cols: u16,
+ rows: u16,
+ width_px: u32,
+ height_px: u32,
+ },
+
+ /// The direct command was written and flushed; terminal response timing starts now.
+ GraphicsTransmissionStarted { transfer_id: u64, image_id: u32 },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -696,6 +717,8 @@ pub enum ServerMessage {
MouseCapture {
/// True when Herdr mouse UI is enabled or the focused pane app requests mouse reporting.
enabled: bool,
+ /// True only while the focused pane requests DEC SGR pixel mode 1016.
+ sgr_pixels: bool,
},
/// Whether the focused terminal requests Kitty report-all keyboard input.
@@ -717,6 +740,19 @@ pub enum ServerMessage {
/// Number of BEL characters parsed from one PTY read.
count: u16,
},
+
+ /// One validated Herdr-owned Kitty regular-file RGBA transmission.
+ GraphicsFile {
+ path: String,
+ expected_len: u64,
+ image_id: u32,
+ transfer_id: u64,
+ leading: Vec<u8>,
+ control: String,
+ },
+
+ /// Suppress a direct command that expired before terminal delivery.
+ GraphicsTransmissionRetired { transfer_id: u64, image_id: u32 },
}
// ---------------------------------------------------------------------------
@@ -1148,7 +1184,7 @@ mod tests {
],
};
let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap();
- // Freeze the protocol 19 input envelope before it is published.
+ // Freeze the protocol 20 input envelope before it is published.
assert_eq!(
encoded,
vec![
@@ -1534,7 +1570,10 @@ mod tests {
#[test]
fn server_mouse_capture_roundtrip() {
- let msg = ServerMessage::MouseCapture { enabled: true };
+ let msg = ServerMessage::MouseCapture {
+ enabled: true,
+ sgr_pixels: true,
+ };
let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap();
let (decoded, _): (ServerMessage, _) =
bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
@@ -1551,6 +1590,44 @@ mod tests {
}
#[test]
+ fn direct_graphics_messages_roundtrip() {
+ let client = ClientMessage::GraphicsTransmissionResult {
+ transfer_id: 7,
+ image_id: 42,
+ success: false,
+ };
+ let encoded = bincode::serde::encode_to_vec(&client, bincode::config::standard()).unwrap();
+ let (decoded, _): (ClientMessage, _) =
+ bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
+ assert_eq!(client, decoded);
+
+ let server = ServerMessage::GraphicsFile {
+ path: "/run/user/1000/herdr/source/frame".into(),
+ expected_len: 4,
+ image_id: 42,
+ transfer_id: 7,
+ leading: b"\x1b[2;3H".to_vec(),
+ control: "a=T,f=32,i=42,q=0".into(),
+ };
+ let encoded = bincode::serde::encode_to_vec(&server, bincode::config::standard()).unwrap();
+ let (decoded, _): (ServerMessage, _) =
+ bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
+ assert_eq!(server, decoded);
+
+ let pixels = ClientMessage::InputPixels {
+ data: b"\x1b[<35;321;241M".to_vec(),
+ cols: 80,
+ rows: 24,
+ width_px: 800,
+ height_px: 480,
+ };
+ let encoded = bincode::serde::encode_to_vec(&pixels, bincode::config::standard()).unwrap();
+ let (decoded, _): (ClientMessage, _) =
+ bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
+ assert_eq!(pixels, decoded);
+ }
+
+ #[test]
fn server_prefix_input_source_roundtrip() {
for active in [true, false] {
let msg = ServerMessage::PrefixInputSource { active };