1use std::sync::atomic::{AtomicBool, Ordering};
7
8static KITTY_PROTOCOL_ACTIVE: AtomicBool = AtomicBool::new(false);
10
11pub fn set_kitty_protocol_active(active: bool) {
13 KITTY_PROTOCOL_ACTIVE.store(active, Ordering::SeqCst);
14}
15
16pub fn is_kitty_protocol_active() -> bool {
18 KITTY_PROTOCOL_ACTIVE.load(Ordering::SeqCst)
19}
20
21pub type KeyId = String;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum Modifier {
27 Ctrl,
28 Shift,
29 Alt,
30 Super,
31}
32
33impl Modifier {
34 pub fn as_str(&self) -> &'static str {
35 match self {
36 Modifier::Ctrl => "ctrl",
37 Modifier::Shift => "shift",
38 Modifier::Alt => "alt",
39 Modifier::Super => "super",
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct Key {
47 pub key: String,
49 pub modifiers: Vec<Modifier>,
51 pub event_type: KeyEventType,
53}
54
55impl Key {
56 pub fn new(key: impl Into<String>) -> Self {
58 Self {
59 key: key.into(),
60 modifiers: Vec::new(),
61 event_type: KeyEventType::Press,
62 }
63 }
64
65 pub fn with_modifier(mut self, modifier: Modifier) -> Self {
67 self.modifiers.push(modifier);
68 self
69 }
70
71 pub fn with_event_type(mut self, event_type: KeyEventType) -> Self {
73 self.event_type = event_type;
74 self
75 }
76
77 pub fn to_key_id(&self) -> KeyId {
79 let mut parts: Vec<&str> = self.modifiers.iter().map(|m| m.as_str()).collect();
80 parts.sort(); parts.push(&self.key);
82 parts.join("+")
83 }
84
85 pub fn matches(&self, key_id: &str) -> bool {
87 self.to_key_id() == key_id
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub enum KeyEventType {
94 Press,
95 Release,
96 Repeat,
97}
98
99pub struct KeyHelper;
101
102impl KeyHelper {
103 pub const ESCAPE: &'static str = "escape";
105 pub const ESC: &'static str = "esc";
106 pub const ENTER: &'static str = "enter";
107 pub const RETURN: &'static str = "return";
108 pub const TAB: &'static str = "tab";
109 pub const SPACE: &'static str = "space";
110 pub const BACKSPACE: &'static str = "backspace";
111 pub const DELETE: &'static str = "delete";
112 pub const INSERT: &'static str = "insert";
113 pub const CLEAR: &'static str = "clear";
114 pub const HOME: &'static str = "home";
115 pub const END: &'static str = "end";
116 pub const PAGE_UP: &'static str = "pageUp";
117 pub const PAGE_DOWN: &'static str = "pageDown";
118 pub const UP: &'static str = "up";
119 pub const DOWN: &'static str = "down";
120 pub const LEFT: &'static str = "left";
121 pub const RIGHT: &'static str = "right";
122
123 pub const F1: &'static str = "f1";
125 pub const F2: &'static str = "f2";
126 pub const F3: &'static str = "f3";
127 pub const F4: &'static str = "f4";
128 pub const F5: &'static str = "f5";
129 pub const F6: &'static str = "f6";
130 pub const F7: &'static str = "f7";
131 pub const F8: &'static str = "f8";
132 pub const F9: &'static str = "f9";
133 pub const F10: &'static str = "f10";
134 pub const F11: &'static str = "f11";
135 pub const F12: &'static str = "f12";
136
137 pub const BACKTICK: &'static str = "`";
139 pub const HYPHEN: &'static str = "-";
140 pub const EQUALS: &'static str = "=";
141 pub const LEFT_BRACKET: &'static str = "[";
142 pub const RIGHT_BRACKET: &'static str = "]";
143 pub const BACKSLASH: &'static str = "\\";
144 pub const SEMICOLON: &'static str = ";";
145 pub const QUOTE: &'static str = "'";
146 pub const COMMA: &'static str = ",";
147 pub const PERIOD: &'static str = ".";
148 pub const SLASH: &'static str = "/";
149
150 pub fn ctrl(key: &str) -> String {
152 format!("ctrl+{}", key)
153 }
154
155 pub fn shift(key: &str) -> String {
156 format!("shift+{}", key)
157 }
158
159 pub fn alt(key: &str) -> String {
160 format!("alt+{}", key)
161 }
162
163 pub fn super_key(key: &str) -> String {
164 format!("super+{}", key)
165 }
166
167 pub fn ctrl_shift(key: &str) -> String {
169 format!("ctrl+shift+{}", key)
170 }
171
172 pub fn ctrl_alt(key: &str) -> String {
173 format!("ctrl+alt+{}", key)
174 }
175
176 pub fn alt_shift(key: &str) -> String {
177 format!("alt+shift+{}", key)
178 }
179}
180
181const SYMBOL_KEYS: &[&str] = &[
183 "`", "-", "=", "[", "]", "\\", ";", "'", ",", ".", "/", "!", "@", "#", "$", "%", "^", "&", "*",
184 "(", ")", "_", "+", "|", "~", "{", "}", ":", "<", ">", "?",
185];
186
187pub fn is_symbol_key(key: &str) -> bool {
189 SYMBOL_KEYS.contains(&key)
190}
191
192const MOD_CTRL: u8 = 1;
194const MOD_SHIFT: u8 = 2;
195const MOD_ALT: u8 = 4;
196const MOD_SUPER: u8 = 8;
197#[allow(dead_code)]
198const LOCK_MASK: u8 = 64 + 128; pub fn parse_key(data: &str) -> Option<Key> {
203 if data.is_empty() {
204 return None;
205 }
206
207 if data.len() == 1 {
209 let c = data.chars().next()?;
210 return parse_single_char(c);
211 }
212
213 if data.starts_with('\x1b') {
215 return parse_escape_sequence(data);
216 }
217
218 None
219}
220
221fn parse_single_char(c: char) -> Option<Key> {
223 let code = c as u32;
224
225 match code {
227 0x00 => return Some(Key::new("ctrl+space")),
228 0x01..=0x07 => {
229 let letter = (b'a' + (code - 1) as u8) as char;
231 return Some(Key::new(KeyHelper::ctrl(&letter.to_string())));
232 }
233 0x08 => return Some(Key::new(KeyHelper::BACKSPACE)), 0x09 => return Some(Key::new(KeyHelper::TAB)), 0x0a | 0x0d => return Some(Key::new(KeyHelper::ENTER)), 0x0b..=0x1a => {
237 let letter = (b'k' + (code - 0x0b) as u8) as char;
239 return Some(Key::new(KeyHelper::ctrl(&letter.to_string())));
240 }
241 0x1b => return Some(Key::new(KeyHelper::ESCAPE)),
242 0x7f => return Some(Key::new(KeyHelper::BACKSPACE)), _ => {}
244 }
245
246 if c >= ' ' && c != '\x7f' {
248 let key_str = c.to_string();
249 if key_str.chars().all(|c| c.is_uppercase()) {
250 Some(Key::new(key_str.to_lowercase()).with_modifier(Modifier::Shift))
251 } else {
252 Some(Key::new(key_str))
253 }
254 } else {
255 None
256 }
257}
258
259fn parse_escape_sequence(data: &str) -> Option<Key> {
261 let bytes = data.as_bytes();
262
263 if bytes.len() >= 3 && bytes[1] == b'[' {
265 return parse_csi_sequence(data);
266 }
267
268 if bytes.len() >= 3 && bytes[1] == b'O' {
270 return parse_ss3_sequence(data);
271 }
272
273 if bytes.len() == 2 {
275 let c = bytes[1] as char;
276 let mut key = parse_single_char(c)?;
277 key.modifiers.push(Modifier::Alt);
278 return Some(key);
279 }
280
281 None
282}
283
284fn parse_csi_sequence(data: &str) -> Option<Key> {
286 let bytes = data.as_bytes();
287
288 let final_idx = bytes.iter().rposition(|&b| (0x40..=0x7E).contains(&b))?;
290 let final_byte = bytes[final_idx];
291
292 let params_str = &data[2..final_idx];
294 let params: Vec<u32> = params_str
295 .split(';')
296 .filter_map(|s| s.parse().ok())
297 .collect();
298
299 let mut modifiers = Vec::new();
301 if params.len() >= 2 {
302 let mod_byte = params[1] as u8;
303 if mod_byte & MOD_CTRL != 0 {
304 modifiers.push(Modifier::Ctrl);
305 }
306 if mod_byte & MOD_SHIFT != 0 {
307 modifiers.push(Modifier::Shift);
308 }
309 if mod_byte & MOD_ALT != 0 {
310 modifiers.push(Modifier::Alt);
311 }
312 if mod_byte & MOD_SUPER != 0 {
313 modifiers.push(Modifier::Super);
314 }
315 }
316
317 let event_type = if params.len() >= 2 {
319 let event_type_code = params.get(0).copied().unwrap_or(1);
320 match event_type_code {
321 1 => KeyEventType::Press,
322 2 => KeyEventType::Repeat,
323 3 => KeyEventType::Release,
324 _ => KeyEventType::Press,
325 }
326 } else {
327 KeyEventType::Press
328 };
329
330 let key = match final_byte {
332 b'~' => parse_csi_tilde_key(¶ms)?,
333 b'A' => Key::new(KeyHelper::UP),
334 b'B' => Key::new(KeyHelper::DOWN),
335 b'C' => Key::new(KeyHelper::RIGHT),
336 b'D' => Key::new(KeyHelper::LEFT),
337 b'E' => Key::new("begin"),
338 b'F' => Key::new(KeyHelper::END),
339 b'H' => Key::new(KeyHelper::HOME),
340 b'Z' => Key::new(KeyHelper::TAB).with_modifier(Modifier::Shift),
341 b'u' => {
342 decode_kitty_printable(¶ms)?
344 }
345 _ => return None,
346 };
347
348 Some(key).map(|mut k| {
349 k.modifiers.extend(modifiers);
350 k.event_type = event_type;
351 k
352 })
353}
354
355fn parse_csi_tilde_key(params: &[u32]) -> Option<Key> {
357 let code = params.first().copied().unwrap_or(0);
358 match code {
359 1 => Some(Key::new(KeyHelper::F1)),
360 2 => Some(Key::new(KeyHelper::INSERT)),
361 3 => Some(Key::new(KeyHelper::DELETE)),
362 4 => Some(Key::new(KeyHelper::END)),
363 5 => Some(Key::new(KeyHelper::PAGE_UP)),
364 6 => Some(Key::new(KeyHelper::PAGE_DOWN)),
365 7 => Some(Key::new(KeyHelper::HOME)),
366 8 => Some(Key::new(KeyHelper::END)),
367 11..=14 => Some(Key::new(KeyHelper::F1)),
368 15..=18 => Some(Key::new(KeyHelper::F5)),
369 19..=20 => Some(Key::new(KeyHelper::F9)),
370 21..=24 => Some(Key::new(KeyHelper::F11)),
371 _ => None,
372 }
373}
374
375fn parse_ss3_sequence(data: &str) -> Option<Key> {
377 let final_char = data.chars().nth(2)?;
378 match final_char {
379 'A' => Some(Key::new(KeyHelper::UP)),
380 'B' => Some(Key::new(KeyHelper::DOWN)),
381 'C' => Some(Key::new(KeyHelper::RIGHT)),
382 'D' => Some(Key::new(KeyHelper::LEFT)),
383 'E' => Some(Key::new("begin")),
384 'F' => Some(Key::new(KeyHelper::END)),
385 'H' => Some(Key::new(KeyHelper::HOME)),
386 'P' => Some(Key::new(KeyHelper::F1)),
387 'Q' => Some(Key::new(KeyHelper::F2)),
388 'R' => Some(Key::new(KeyHelper::F3)),
389 'S' => Some(Key::new(KeyHelper::F4)),
390 _ => None,
391 }
392}
393
394fn decode_kitty_printable(params: &[u32]) -> Option<Key> {
396 let code = params.first().copied().unwrap_or(0);
397
398 if code >= 32 && code < 127 {
400 let c = code as u8 as char;
401 return Some(Key::new(c.to_string()));
402 }
403
404 match code {
406 57399 => Some(Key::new(KeyHelper::INSERT)),
407 57414 => Some(Key::new(KeyHelper::DELETE)),
408 57415 => Some(Key::new(KeyHelper::PAGE_UP)),
409 57416 => Some(Key::new(KeyHelper::PAGE_DOWN)),
410 57399..=57428 => {
411 let offset = code - 57399;
413 let fn_key = match offset {
414 0 => "f1",
415 1 => "f2",
416 2 => "f3",
417 3 => "f4",
418 4 => "f5",
419 5 => "f6",
420 6 => "f7",
421 7 => "f8",
422 8 => "f9",
423 9 => "f10",
424 10 => "f11",
425 11 => "f12",
426 _ => return None,
427 };
428 Some(Key::new(fn_key))
429 }
430 _ => None,
431 }
432}
433
434pub fn matches_key(data: &str, key_id: &str) -> bool {
436 parse_key(data).map(|k| k.matches(key_id)).unwrap_or(false)
437}
438
439pub fn is_key_release(data: &str) -> bool {
441 parse_key(data)
442 .map(|k| k.event_type == KeyEventType::Release)
443 .unwrap_or(false)
444}
445
446pub fn is_key_repeat(data: &str) -> bool {
448 parse_key(data)
449 .map(|k| k.event_type == KeyEventType::Repeat)
450 .unwrap_or(false)
451}
452
453pub fn decode_kitty_printable_from_str(data: &str) -> Option<String> {
456 let bytes = data.as_bytes();
457
458 if data.starts_with("\x1b[") && bytes.last()? == &b'u' {
460 let params_str = &data[2..data.len() - 1];
462 let params: Vec<u32> = params_str
463 .split(';')
464 .filter_map(|s| s.parse().ok())
465 .collect();
466
467 let code = params.first().copied().unwrap_or(0);
468
469 if code >= 32 && code < 127 {
471 let c = code as u8 as char;
472 return Some(c.to_string());
473 }
474 }
475
476 None
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_single_char() {
485 let key = parse_key("a").unwrap();
486 assert_eq!(key.key, "a");
487 assert_eq!(key.to_key_id(), "a");
488 }
489
490 #[test]
491 fn test_ctrl_char() {
492 let key = parse_key("\x01").unwrap(); assert_eq!(key.to_key_id(), "ctrl+a");
494 }
495
496 #[test]
497 fn test_escape() {
498 let key = parse_key("\x1b").unwrap();
499 assert_eq!(key.key, KeyHelper::ESCAPE);
500 }
501
502 #[test]
503 fn test_arrow_keys() {
504 let key = parse_key("\x1b[A").unwrap();
505 assert_eq!(key.key, KeyHelper::UP);
506
507 let key = parse_key("\x1b[B").unwrap();
508 assert_eq!(key.key, KeyHelper::DOWN);
509 }
510
511 #[test]
512 fn test_key_helper() {
513 assert_eq!(KeyHelper::ctrl("c"), "ctrl+c");
514 assert_eq!(KeyHelper::ctrl_shift("p"), "ctrl+shift+p");
515 }
516
517 #[test]
518 fn test_matches_key() {
519 assert!(matches_key("a", "a"));
520 assert!(matches_key("\x01", "ctrl+a"));
521 assert!(matches_key("\x1b[A", "up"));
522 }
523}