1use std::fmt;
6
7use serde::Serialize;
8
9#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
11pub struct Key {
12 pub name: String,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub text: Option<String>,
18 #[serde(skip_serializing_if = "std::ops::Not::not")]
20 pub ctrl: bool,
21 #[serde(skip_serializing_if = "std::ops::Not::not")]
23 pub alt: bool,
24 #[serde(skip_serializing_if = "std::ops::Not::not")]
26 pub shift: bool,
27 #[serde(skip_serializing_if = "std::ops::Not::not")]
29 pub meta: bool,
30}
31
32impl Key {
33 pub fn named(name: impl Into<String>) -> Self {
35 Self { name: name.into(), ..Self::default() }
36 }
37
38 pub fn is(&self, chord: &str) -> bool {
51 let (mut ctrl, mut alt, mut shift, mut meta) = (false, false, false, false);
52 let mut name = chord;
53 while let Some((modifier, rest)) = name.split_once('+') {
54 match modifier {
55 "ctrl" => ctrl = true,
56 "alt" => alt = true,
57 "shift" => shift = true,
58 "meta" | "cmd" | "super" => meta = true,
59 _ => break,
60 }
61 name = rest;
62 }
63 self.name == name
64 && self.ctrl == ctrl
65 && self.alt == alt
66 && self.shift == shift
67 && self.meta == meta
68 }
69
70 pub fn typed(&self) -> Option<&str> {
72 if self.ctrl || self.alt || self.meta || self.name == "paste" {
73 return None;
74 }
75 self.text.as_deref()
76 }
77
78 const fn with_mods(mut self, param: u32) -> Self {
80 let bits = param.saturating_sub(1);
81 self.shift |= bits & 1 != 0;
82 self.alt |= bits & 2 != 0;
83 self.ctrl |= bits & 4 != 0;
84 self.meta |= bits & (8 | 32) != 0;
85 self
86 }
87}
88
89impl fmt::Display for Key {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 for (on, name) in
92 [(self.ctrl, "ctrl+"), (self.alt, "alt+"), (self.shift, "shift+"), (self.meta, "meta+")]
93 {
94 if on {
95 f.write_str(name)?;
96 }
97 }
98 f.write_str(&self.name)
99 }
100}
101
102enum Step {
104 Wait,
106 Took(usize, Option<Key>),
108}
109
110#[derive(Clone, Debug, Default)]
121pub struct Decoder {
122 buf: Vec<u8>,
124 paste: Option<Vec<u8>>,
126}
127
128impl Decoder {
129 pub fn new() -> Self {
131 Self::default()
132 }
133
134 pub const fn pending(&self) -> bool {
136 !self.buf.is_empty() && self.paste.is_none()
137 }
138
139 pub fn feed(&mut self, bytes: &[u8]) -> Vec<Key> {
141 self.buf.extend_from_slice(bytes);
142 let mut out = Vec::new();
143 self.run(&mut out, false);
144 out
145 }
146
147 pub fn flush(&mut self) -> Vec<Key> {
150 let mut out = Vec::new();
151 self.run(&mut out, true);
152 out
153 }
154
155 fn run(&mut self, out: &mut Vec<Key>, flush: bool) {
157 let mut at = 0;
158 while at < self.buf.len() {
159 if let Some(paste) = &mut self.paste {
160 let rest = &self.buf[at..];
161 if let Some(end) = rest.array_windows().position(|w| w == b"\x1b[201~") {
162 paste.extend_from_slice(&rest[..end]);
163 at += end + 6;
164 let text = String::from_utf8_lossy_owned(self.paste.take().unwrap_or_default());
165 out.push(Key { text: Some(text), ..Key::named("paste") });
166 continue;
167 }
168 let hold = (1..6)
169 .rev()
170 .find(|&n| rest.ends_with(&b"\x1b[201~"[..n]))
171 .unwrap_or(0);
172 paste.extend_from_slice(&rest[..rest.len() - hold]);
173 at = self.buf.len() - hold;
174 break;
175 }
176 match decode(&self.buf[at..], flush) {
177 Step::Wait => break,
178 Step::Took(n, key) => {
179 if &self.buf[at..at + n] == b"\x1b[200~" {
180 self.paste = Some(Vec::new());
181 }
182 at += n;
183 out.extend(key);
184 },
185 }
186 }
187 self.buf.drain(..at);
188 }
189}
190
191fn decode(b: &[u8], flush: bool) -> Step {
193 match b[0] {
194 0x1b => escape(b, flush),
195 c if c < 0x20 || c == 0x7f => Step::Took(1, Some(control(c))),
196 _ => match utf8(b, flush) {
197 Some((n, Some(ch))) => Step::Took(n, Some(printable(ch))),
198 Some((n, None)) => Step::Took(n, None),
199 None => Step::Wait,
200 },
201 }
202}
203
204fn control(c: u8) -> Key {
206 match c {
207 b'\r' | b'\n' => Key::named("enter"),
208 b'\t' => Key::named("tab"),
209 0x7f => Key::named("backspace"),
210 0x08 => Key { ctrl: true, ..Key::named("backspace") },
211 0x1b => Key::named("escape"),
212 0x00 => Key { ctrl: true, ..Key::named("space") },
213 0x01..=0x1a => Key { ctrl: true, ..Key::named(((b'a' + c - 1) as char).to_string()) },
214 0x1c => Key { ctrl: true, ..Key::named("\\") },
215 0x1d => Key { ctrl: true, ..Key::named("]") },
216 0x1e => Key { ctrl: true, ..Key::named("^") },
217 _ => Key { ctrl: true, ..Key::named("_") },
218 }
219}
220
221fn printable(ch: char) -> Key {
223 if ch == ' ' {
224 return Key { text: Some(" ".into()), ..Key::named("space") };
225 }
226 let lower: String = ch.to_lowercase().collect();
227 let shift = lower.chars().ne(std::iter::once(ch));
228 Key { text: Some(ch.to_string()), shift, ..Key::named(lower) }
229}
230
231fn utf8(b: &[u8], flush: bool) -> Option<(usize, Option<char>)> {
234 let len = match b[0] {
235 0x00..=0x7f => 1,
236 0xc0..=0xdf => 2,
237 0xe0..=0xef => 3,
238 0xf0..=0xf7 => 4,
239 _ => return Some((1, None)),
240 };
241 if b.len() < len {
242 return if flush { Some((b.len(), None)) } else { None };
243 }
244 match std::str::from_utf8(&b[..len]) {
245 Ok(s) => Some((len, s.chars().next())),
246 Err(_) => Some((1, None)),
247 }
248}
249
250fn escape(b: &[u8], flush: bool) -> Step {
252 let Some(&next) = b.get(1) else {
253 return if flush {
254 Step::Took(1, Some(Key::named("escape")))
255 } else {
256 Step::Wait
257 };
258 };
259 match next {
260 b'[' => csi(b, flush),
261 b'O' => match b.get(2) {
262 Some(&c) => Step::Took(3, ss3(c)),
263 None if flush => Step::Took(2, None),
264 None => Step::Wait,
265 },
266 0x1b => match escape(&b[1..], flush) {
267 Step::Wait => Step::Wait,
268 Step::Took(n, key) => Step::Took(n + 1, key.map(|k| Key { alt: true, text: None, ..k })),
269 },
270 _ => match decode(&b[1..], flush) {
271 Step::Wait => Step::Wait,
272 Step::Took(n, key) => Step::Took(n + 1, key.map(|k| Key { alt: true, text: None, ..k })),
273 },
274 }
275}
276
277fn ss3(c: u8) -> Option<Key> {
279 let name = match c {
280 b'A' => "up",
281 b'B' => "down",
282 b'C' => "right",
283 b'D' => "left",
284 b'H' => "home",
285 b'F' => "end",
286 b'E' => "begin",
287 b'P' => "f1",
288 b'Q' => "f2",
289 b'R' => "f3",
290 b'S' => "f4",
291 b'M' => "enter",
292 _ => return None,
293 };
294 Some(Key::named(name))
295}
296
297fn csi(b: &[u8], flush: bool) -> Step {
299 let mut i = 2;
300 while let Some(&c) = b.get(i) {
301 match c {
302 0x20..=0x3f => i += 1,
303 0x40..=0x7e => return Step::Took(i + 1, csi_key(&b[2..i], c)),
304 _ => return Step::Took(i, None),
305 }
306 }
307 if flush {
308 Step::Took(b.len(), None)
309 } else {
310 Step::Wait
311 }
312}
313
314fn fields(params: &[u8]) -> Option<Vec<Vec<u32>>> {
316 let text = std::str::from_utf8(params).ok()?;
317 text
318 .split(';')
319 .map(|f| {
320 f.split(':')
321 .map(|n| {
322 if n.is_empty() {
323 Some(0)
324 } else {
325 n.parse().ok()
326 }
327 })
328 .collect::<Option<Vec<u32>>>()
329 })
330 .collect()
331}
332
333fn csi_key(params: &[u8], fin: u8) -> Option<Key> {
335 if params
336 .first()
337 .is_some_and(|&p| matches!(p, b'?' | b'<' | b'>' | b'='))
338 {
339 return None;
340 }
341 let f = fields(params)?;
342 let num = |i: usize| f.get(i).and_then(|v| v.first()).copied().unwrap_or(0);
343 let mods = num(1).max(1);
344 match fin {
345 b'A' | b'B' | b'C' | b'D' | b'H' | b'F' | b'E' | b'P' | b'Q' | b'R' | b'S' => {
346 let name = match fin {
347 b'A' => "up",
348 b'B' => "down",
349 b'C' => "right",
350 b'D' => "left",
351 b'H' => "home",
352 b'F' => "end",
353 b'E' => "begin",
354 b'P' => "f1",
355 b'Q' => "f2",
356 b'R' => "f3",
357 _ => "f4",
358 };
359 Some(Key::named(name).with_mods(mods))
360 },
361 b'Z' => Some(Key { shift: true, ..Key::named("tab") }.with_mods(mods)),
362 b'~' => tilde(num(0)).map(|name| Key::named(name).with_mods(mods)),
363 b'u' => kitty(&f),
364 _ => None,
365 }
366}
367
368const fn tilde(n: u32) -> Option<&'static str> {
370 Some(match n {
371 1 | 7 => "home",
372 2 => "insert",
373 3 => "delete",
374 4 | 8 => "end",
375 5 => "page_up",
376 6 => "page_down",
377 11 => "f1",
378 12 => "f2",
379 13 => "f3",
380 14 => "f4",
381 15 => "f5",
382 17 => "f6",
383 18 => "f7",
384 19 => "f8",
385 20 => "f9",
386 21 => "f10",
387 23 => "f11",
388 24 => "f12",
389 25 => "f13",
390 26 => "f14",
391 28 => "f15",
392 29 => "menu",
393 31 => "f17",
394 32 => "f18",
395 33 => "f19",
396 34 => "f20",
397 _ => return None,
398 })
399}
400
401fn kitty(f: &[Vec<u32>]) -> Option<Key> {
403 let code = f.first().and_then(|v| v.first()).copied()?;
404 let mods_field = f.get(1);
405 let mods = mods_field
406 .and_then(|v| v.first())
407 .copied()
408 .unwrap_or(1)
409 .max(1);
410 if mods_field.and_then(|v| v.get(1)).copied() == Some(3) {
412 return None;
413 }
414 let named = |name: &str| Some(Key::named(name).with_mods(mods));
415 let key = match code {
416 27 => named("escape"),
417 13 => named("enter"),
418 9 => named("tab"),
419 127 | 8 => named("backspace"),
420 32 => named("space"),
421 57358 => named("caps_lock"),
422 57359 => named("scroll_lock"),
423 57360 => named("num_lock"),
424 57361 => named("print_screen"),
425 57362 => named("pause"),
426 57363 => named("menu"),
427 57364..=57398 => {
428 let name = format!("f{}", code - 57364 + 1);
429 Some(Key::named(name).with_mods(mods))
430 },
431 57399..=57427 => keypad(code).map(|name| Key::named(name).with_mods(mods)),
432 57344..=63743 => None,
435 _ => {
436 let ch = char::from_u32(code)?;
437 let lower: String = ch.to_lowercase().collect();
438 Some(Key::named(lower).with_mods(mods))
439 },
440 }?;
441 Some(with_text(key, f.get(2), code))
442}
443
444fn with_text(mut key: Key, text: Option<&Vec<u32>>, code: u32) -> Key {
447 if key.ctrl || key.alt || key.meta {
448 return key;
449 }
450 if let Some(cps) = text {
451 let s: String = cps.iter().filter_map(|&c| char::from_u32(c)).collect();
452 if !s.is_empty() {
453 key.text = Some(s);
454 }
455 return key;
456 }
457 if key.name == "space" {
458 key.text = Some(" ".into());
459 } else if key.name.chars().count() == 1 && code >= 0x20 {
460 key.text = Some(if key.shift {
461 key.name.to_uppercase()
462 } else {
463 key.name.clone()
464 });
465 }
466 key
467}
468
469const fn keypad(code: u32) -> Option<&'static str> {
471 Some(match code {
472 57399 => "0",
473 57400 => "1",
474 57401 => "2",
475 57402 => "3",
476 57403 => "4",
477 57404 => "5",
478 57405 => "6",
479 57406 => "7",
480 57407 => "8",
481 57408 => "9",
482 57409 => ".",
483 57410 => "/",
484 57411 => "*",
485 57412 => "-",
486 57413 => "+",
487 57414 => "enter",
488 57415 => "=",
489 57416 => ",",
490 57417 => "left",
491 57418 => "right",
492 57419 => "up",
493 57420 => "down",
494 57421 => "page_up",
495 57422 => "page_down",
496 57423 => "home",
497 57424 => "end",
498 57425 => "insert",
499 57426 => "delete",
500 57427 => "begin",
501 _ => return None,
502 })
503}