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.windows(6).position(|w| w == b"\x1b[201~") {
162 paste.extend_from_slice(&rest[..end]);
163 at += end + 6;
164 let text =
165 String::from_utf8_lossy(&self.paste.take().unwrap_or_default()).into_owned();
166 out.push(Key { text: Some(text), ..Key::named("paste") });
167 continue;
168 }
169 let hold = (1..6)
170 .rev()
171 .find(|&n| rest.ends_with(&b"\x1b[201~"[..n]))
172 .unwrap_or(0);
173 paste.extend_from_slice(&rest[..rest.len() - hold]);
174 at = self.buf.len() - hold;
175 break;
176 }
177 match decode(&self.buf[at..], flush) {
178 Step::Wait => break,
179 Step::Took(n, key) => {
180 if &self.buf[at..at + n] == b"\x1b[200~" {
181 self.paste = Some(Vec::new());
182 }
183 at += n;
184 out.extend(key);
185 },
186 }
187 }
188 self.buf.drain(..at);
189 }
190}
191
192fn decode(b: &[u8], flush: bool) -> Step {
194 match b[0] {
195 0x1b => escape(b, flush),
196 c if c < 0x20 || c == 0x7f => Step::Took(1, Some(control(c))),
197 _ => match utf8(b, flush) {
198 Some((n, Some(ch))) => Step::Took(n, Some(printable(ch))),
199 Some((n, None)) => Step::Took(n, None),
200 None => Step::Wait,
201 },
202 }
203}
204
205fn control(c: u8) -> Key {
207 match c {
208 b'\r' | b'\n' => Key::named("enter"),
209 b'\t' => Key::named("tab"),
210 0x7f => Key::named("backspace"),
211 0x08 => Key { ctrl: true, ..Key::named("backspace") },
212 0x1b => Key::named("escape"),
213 0x00 => Key { ctrl: true, ..Key::named("space") },
214 0x01..=0x1a => Key { ctrl: true, ..Key::named(((b'a' + c - 1) as char).to_string()) },
215 0x1c => Key { ctrl: true, ..Key::named("\\") },
216 0x1d => Key { ctrl: true, ..Key::named("]") },
217 0x1e => Key { ctrl: true, ..Key::named("^") },
218 _ => Key { ctrl: true, ..Key::named("_") },
219 }
220}
221
222fn printable(ch: char) -> Key {
224 if ch == ' ' {
225 return Key { text: Some(" ".into()), ..Key::named("space") };
226 }
227 let lower: String = ch.to_lowercase().collect();
228 let shift = lower.chars().ne(std::iter::once(ch));
229 Key { text: Some(ch.to_string()), shift, ..Key::named(lower) }
230}
231
232fn utf8(b: &[u8], flush: bool) -> Option<(usize, Option<char>)> {
235 let len = match b[0] {
236 0x00..=0x7f => 1,
237 0xc0..=0xdf => 2,
238 0xe0..=0xef => 3,
239 0xf0..=0xf7 => 4,
240 _ => return Some((1, None)),
241 };
242 if b.len() < len {
243 return if flush { Some((b.len(), None)) } else { None };
244 }
245 match std::str::from_utf8(&b[..len]) {
246 Ok(s) => Some((len, s.chars().next())),
247 Err(_) => Some((1, None)),
248 }
249}
250
251fn escape(b: &[u8], flush: bool) -> Step {
253 let Some(&next) = b.get(1) else {
254 return if flush {
255 Step::Took(1, Some(Key::named("escape")))
256 } else {
257 Step::Wait
258 };
259 };
260 match next {
261 b'[' => csi(b, flush),
262 b'O' => match b.get(2) {
263 Some(&c) => Step::Took(3, ss3(c)),
264 None if flush => Step::Took(2, None),
265 None => Step::Wait,
266 },
267 0x1b => match escape(&b[1..], flush) {
268 Step::Wait => Step::Wait,
269 Step::Took(n, key) => Step::Took(n + 1, key.map(|k| Key { alt: true, text: None, ..k })),
270 },
271 _ => match decode(&b[1..], flush) {
272 Step::Wait => Step::Wait,
273 Step::Took(n, key) => Step::Took(n + 1, key.map(|k| Key { alt: true, text: None, ..k })),
274 },
275 }
276}
277
278fn ss3(c: u8) -> Option<Key> {
280 let name = match c {
281 b'A' => "up",
282 b'B' => "down",
283 b'C' => "right",
284 b'D' => "left",
285 b'H' => "home",
286 b'F' => "end",
287 b'E' => "begin",
288 b'P' => "f1",
289 b'Q' => "f2",
290 b'R' => "f3",
291 b'S' => "f4",
292 b'M' => "enter",
293 _ => return None,
294 };
295 Some(Key::named(name))
296}
297
298fn csi(b: &[u8], flush: bool) -> Step {
300 let mut i = 2;
301 while let Some(&c) = b.get(i) {
302 match c {
303 0x20..=0x3f => i += 1,
304 0x40..=0x7e => return Step::Took(i + 1, csi_key(&b[2..i], c)),
305 _ => return Step::Took(i, None),
306 }
307 }
308 if flush {
309 Step::Took(b.len(), None)
310 } else {
311 Step::Wait
312 }
313}
314
315fn fields(params: &[u8]) -> Option<Vec<Vec<u32>>> {
317 let text = std::str::from_utf8(params).ok()?;
318 text
319 .split(';')
320 .map(|f| {
321 f.split(':')
322 .map(|n| {
323 if n.is_empty() {
324 Some(0)
325 } else {
326 n.parse().ok()
327 }
328 })
329 .collect::<Option<Vec<u32>>>()
330 })
331 .collect()
332}
333
334fn csi_key(params: &[u8], fin: u8) -> Option<Key> {
336 if params
337 .first()
338 .is_some_and(|&p| matches!(p, b'?' | b'<' | b'>' | b'='))
339 {
340 return None;
341 }
342 let f = fields(params)?;
343 let num = |i: usize| f.get(i).and_then(|v| v.first()).copied().unwrap_or(0);
344 let mods = num(1).max(1);
345 match fin {
346 b'A' | b'B' | b'C' | b'D' | b'H' | b'F' | b'E' | b'P' | b'Q' | b'R' | b'S' => {
347 let name = match fin {
348 b'A' => "up",
349 b'B' => "down",
350 b'C' => "right",
351 b'D' => "left",
352 b'H' => "home",
353 b'F' => "end",
354 b'E' => "begin",
355 b'P' => "f1",
356 b'Q' => "f2",
357 b'R' => "f3",
358 _ => "f4",
359 };
360 Some(Key::named(name).with_mods(mods))
361 },
362 b'Z' => Some(Key { shift: true, ..Key::named("tab") }.with_mods(mods)),
363 b'~' => tilde(num(0)).map(|name| Key::named(name).with_mods(mods)),
364 b'u' => kitty(&f),
365 _ => None,
366 }
367}
368
369const fn tilde(n: u32) -> Option<&'static str> {
371 Some(match n {
372 1 | 7 => "home",
373 2 => "insert",
374 3 => "delete",
375 4 | 8 => "end",
376 5 => "page_up",
377 6 => "page_down",
378 11 => "f1",
379 12 => "f2",
380 13 => "f3",
381 14 => "f4",
382 15 => "f5",
383 17 => "f6",
384 18 => "f7",
385 19 => "f8",
386 20 => "f9",
387 21 => "f10",
388 23 => "f11",
389 24 => "f12",
390 25 => "f13",
391 26 => "f14",
392 28 => "f15",
393 29 => "menu",
394 31 => "f17",
395 32 => "f18",
396 33 => "f19",
397 34 => "f20",
398 _ => return None,
399 })
400}
401
402fn kitty(f: &[Vec<u32>]) -> Option<Key> {
404 let code = f.first().and_then(|v| v.first()).copied()?;
405 let mods_field = f.get(1);
406 let mods = mods_field
407 .and_then(|v| v.first())
408 .copied()
409 .unwrap_or(1)
410 .max(1);
411 if mods_field.and_then(|v| v.get(1)).copied() == Some(3) {
413 return None;
414 }
415 let named = |name: &str| Some(Key::named(name).with_mods(mods));
416 let key = match code {
417 27 => named("escape"),
418 13 => named("enter"),
419 9 => named("tab"),
420 127 | 8 => named("backspace"),
421 32 => named("space"),
422 57358 => named("caps_lock"),
423 57359 => named("scroll_lock"),
424 57360 => named("num_lock"),
425 57361 => named("print_screen"),
426 57362 => named("pause"),
427 57363 => named("menu"),
428 57364..=57398 => {
429 let name = format!("f{}", code - 57364 + 1);
430 Some(Key::named(name).with_mods(mods))
431 },
432 57399..=57427 => keypad(code).map(|name| Key::named(name).with_mods(mods)),
433 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}