Skip to main content

little_kitty/
command.rs

1use super::{control::*, reader::*, response::*, utils::*, writer::*};
2
3use std::{
4    io::{self, Write},
5    path::*,
6};
7
8const MAX_PAYLOAD_CHUNK_SIZE: usize = 4096;
9const MAX_PAYLOAD_CHUNK_SIZE_U64: u64 = MAX_PAYLOAD_CHUNK_SIZE as u64;
10
11// https://vt100.net/docs/vt510-rm/DA1.html
12const VT100_QUERY_DA1: &str = "\x1b[c";
13
14//
15// Command
16//
17
18/// Kitty graphics protocol APC (Application Programming Command).
19///
20/// See [documentation](https://sw.kovidgoyal.net/kitty/graphics-protocol/).
21#[derive(Clone, Debug, Default)]
22pub struct Command {
23    pub(crate) controls: Controls,
24    expect_response: bool,
25}
26
27impl Command {
28    /// Add control.
29    pub fn add_control<ControlT>(&mut self, code: char, value: ControlT)
30    where
31        ControlT: Into<ControlValue>,
32    {
33        self.controls.add(code, value);
34    }
35
36    /// Add control.
37    ///
38    /// Chainable.
39    pub fn with_control<ControlT>(mut self, code: char, value: ControlT) -> Self
40    where
41        ControlT: Into<ControlValue>,
42    {
43        self.add_control(code, value);
44        self
45    }
46
47    /// Set whether we expect a response.
48    pub fn set_expect_response(&mut self) {
49        self.expect_response = true;
50    }
51
52    /// Set whether we expect a response.
53    ///
54    /// Chainable.
55    pub fn with_expect_response(mut self) -> Self {
56        self.set_expect_response();
57        self
58    }
59
60    /// Execute.
61    ///
62    /// If we are expecting a response then we will wait for it.
63    pub fn execute(&self) -> io::Result<Option<Response>> {
64        let reader = self.reader();
65
66        let mut writer = io::stdout();
67        writer.write_start()?;
68        self.controls.write(&mut writer)?;
69        writer.write_end()?;
70
71        reader.and_then(|mut reader| in_raw_mode(|| reader.read_response()).transpose()).transpose()
72    }
73
74    /// Execute with payload.
75    ///
76    /// If we are expecting a response then we will wait for it.
77    pub fn execute_with_payload(&self, payload: &[u8]) -> io::Result<Option<Response>> {
78        if payload.len() > MAX_PAYLOAD_CHUNK_SIZE {
79            self.execute_with_payload_from(payload)
80        } else {
81            let reader = self.reader();
82            let mut writer = io::stdout();
83
84            writer.write_start()?;
85            self.controls.write(&mut writer)?;
86            write!(writer, ";")?;
87            writer = writer.write_base64(payload)?;
88            writer.write_end()?;
89
90            reader.and_then(|mut reader| in_raw_mode(|| reader.read_response()).transpose()).transpose()
91        }
92    }
93
94    /// Execute with payload from a reader.
95    ///
96    /// If we are expecting a response then we will wait for it.
97    pub fn execute_with_payload_from<ReadT>(&self, mut payload: ReadT) -> io::Result<Option<Response>>
98    where
99        ReadT: io::Read,
100    {
101        let reader = self.reader();
102        let mut writer = io::stdout();
103
104        writer.write_start()?;
105        if !self.controls.is_empty() {
106            self.controls.write(&mut writer)?;
107            write!(writer, ",")?;
108        }
109        write!(writer, "m=1;")?;
110
111        let mut chunk = Vec::with_capacity(MAX_PAYLOAD_CHUNK_SIZE);
112
113        loop {
114            // Read next chunk
115            chunk.clear();
116            payload = payload.read_chunk(MAX_PAYLOAD_CHUNK_SIZE_U64, &mut chunk)?;
117
118            if chunk.is_empty() {
119                // End chunk
120                writer.write_end()?;
121
122                // End payload
123                writer.write_start()?;
124                write!(writer, "m=0;")?;
125                writer.write_end()?;
126                break;
127            }
128
129            // End chunk
130            writer = writer.write_base64(&chunk)?;
131            writer.write_end()?;
132
133            // Start new chunk
134            writer.write_start()?;
135            write!(writer, "m=1;")?;
136        }
137
138        reader.and_then(|mut reader| in_raw_mode(|| reader.read_response()).transpose()).transpose()
139    }
140
141    /// Execute with path payload.
142    ///
143    /// If we are expecting a response then we will wait for it.
144    pub fn execute_with_path_payload<PathT>(&self, path: PathT) -> io::Result<Option<Response>>
145    where
146        PathT: AsRef<Path>,
147    {
148        // Notes:
149        // * The protocol specification doesn't mention string encoding
150        // * It also doesn't mention that the path must be absolute, but implementations seem to expect it
151        self.execute_with_payload(absolute(path)?.as_os_str().as_encoded_bytes())
152    }
153
154    /// Whether the terminal supports the Kitty graphics protocol.
155    pub fn is_supported() -> io::Result<bool> {
156        // https://sw.kovidgoyal.net/kitty/graphics-protocol/#querying-support-and-available-transmission-mediums
157
158        let mut reader = io::stdin().lock();
159
160        let mut writer = io::stdout();
161        writer.write_start()?;
162        // Simplest query (should always respond with "OK")
163        Controls::default().with('a', 'q').with('i', 1).write(&mut writer)?;
164        write!(writer, "{}{}", COMMAND_END, VT100_QUERY_DA1)?;
165        writer.flush()?;
166
167        in_raw_mode(|| reader.has_kitty_and_vt100_da1_responses())
168    }
169
170    // Locked stdin.
171    pub(crate) fn reader(&self) -> Option<io::StdinLock<'_>> {
172        if self.expect_response { Some(io::stdin().lock()) } else { None }
173    }
174}