1use crate::core::constants::DOUBLE_ESC_THRESHOLD;
6use crate::core::prelude::*;
7use crossterm::event::KeyModifiers;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum KeyAction {
11 MoveLeft,
12 MoveRight,
13 MoveToStart,
14 MoveToEnd,
15 InsertChar(char),
16 Backspace,
17 Delete,
18 Submit,
19 Cancel,
20 Quit,
21 ClearLine,
22 CopySelection,
23 PasteBuffer,
24 NoAction,
25 ScrollUp,
26 ScrollDown,
27 PageUp,
28 PageDown,
29}
30
31use lazy_static::lazy_static;
32use std::sync::Mutex;
33
34lazy_static! {
35 static ref LAST_ESC_PRESS: Mutex<Option<Instant>> = Mutex::new(None);
36 static ref ESCAPE_SEQUENCE_BUFFER: Mutex<Vec<char>> = Mutex::new(Vec::new());
37}
38
39pub struct KeyboardManager {
40 double_press_threshold: Duration,
41 sequence_timeout: Duration,
42 last_key_time: Instant,
43}
44
45impl KeyboardManager {
46 pub fn new() -> Self {
47 let manager = Self {
48 double_press_threshold: Duration::from_millis(DOUBLE_ESC_THRESHOLD),
49 sequence_timeout: Duration::from_millis(100), last_key_time: Instant::now(),
51 };
52
53 manager.log_system_info();
54 manager
55 }
56
57 fn log_system_info(&self) {
58 log::info!("๐ SYSTEM DEBUG INFO:");
59 log::info!(" OS: {}", std::env::consts::OS);
60 log::info!(" Arch: {}", std::env::consts::ARCH);
61
62 if let Ok(term) = std::env::var("TERM") {
63 log::info!(" Terminal: {}", term);
64 }
65
66 if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
67 log::info!(" Terminal Program: {}", term_program);
68 }
69
70 #[cfg(target_os = "macos")]
71 {
72 log::info!("๐ MAC DETECTED - CMD-Taste sollte als SUPER erkannt werden");
73 log::info!("๐ก๏ธ Escape sequence filtering enabled");
74 }
75
76 log::info!("๐ Drรผcke jetzt CMD+C um zu testen...");
77 }
78
79 fn is_escape_sequence_char(&self, c: char) -> bool {
81 match c {
83 '\x00'..='\x08' | '\x0B'..='\x0C' | '\x0E'..='\x1F' | '\x7F' => {
85 if c == '\x1B' {
86 log::warn!("๐ก๏ธ Blocked escape character");
87 } else {
88 log::warn!("๐ก๏ธ Blocked control character: {:?}", c);
89 }
90 true
91 }
92 c if !c.is_ascii() && !c.is_alphabetic() && !"รครถรผรรรรโฌ".contains(c) => {
94 log::warn!("๐ก๏ธ Blocked suspicious character: {:?}", c);
95 true
96 }
97 _ => false,
98 }
99 }
100
101 fn detect_terminal_sequence(&mut self, c: char) -> bool {
103 let now = Instant::now();
104
105 if now.duration_since(self.last_key_time) > self.sequence_timeout {
107 if let Ok(mut buffer) = ESCAPE_SEQUENCE_BUFFER.lock() {
108 buffer.clear();
109 }
110 }
111
112 self.last_key_time = now;
113
114 if let Ok(mut buffer) = ESCAPE_SEQUENCE_BUFFER.lock() {
116 buffer.push(c);
117
118 let sequence: String = buffer.iter().collect();
120
121 if sequence.to_lowercase().contains("tmux") {
123 log::warn!("๐ก๏ธ BLOCKED TMUX SEQUENCE: '{}'", sequence.escape_debug());
124 buffer.clear();
125 return true;
126 }
127
128 if sequence.len() > 3 && sequence.chars().all(|ch| ch.is_ascii_digit() || ch == ';') {
130 log::warn!("๐ก๏ธ BLOCKED DIGIT SEQUENCE: '{}'", sequence);
131 buffer.clear();
132 return true;
133 }
134
135 if sequence.contains("///") || sequence.contains(";;;") {
137 log::warn!("๐ก๏ธ BLOCKED CONTROL SEQUENCE: '{}'", sequence);
138 buffer.clear();
139 return true;
140 }
141
142 if buffer.len() > 20 {
144 buffer.drain(0..10); }
146 }
147
148 false
149 }
150
151 pub fn get_action(&mut self, key: &KeyEvent) -> KeyAction {
152 self.debug_key_event(key);
154
155 if key.modifiers.contains(KeyModifiers::SHIFT) {
157 match key.code {
158 KeyCode::Up => {
159 log::info!("๐ผ SHIFT+UP detected - should return ScrollUp");
160 return KeyAction::ScrollUp;
161 }
162 KeyCode::Down => {
163 log::info!("๐ฝ SHIFT+DOWN detected - should return ScrollDown");
164 return KeyAction::ScrollDown;
165 }
166 KeyCode::Left => {
167 log::info!("โฌ
๏ธ SHIFT+LEFT detected - currently unhandled");
168 }
169 KeyCode::Right => {
170 log::info!("โก๏ธ SHIFT+RIGHT detected - currently unhandled");
171 }
172 _ => {}
173 }
174 }
175
176 if let KeyCode::Char(c) = key.code {
178 if self.is_escape_sequence_char(c) {
180 return KeyAction::NoAction;
181 }
182
183 if self.detect_terminal_sequence(c) {
185 return KeyAction::NoAction;
186 }
187 }
188
189 if key.code == KeyCode::Esc {
191 let now = Instant::now();
192 let mut last_press = LAST_ESC_PRESS.lock().unwrap_or_else(|poisoned| {
193 log::warn!("Recovered from poisoned mutex");
194 poisoned.into_inner()
195 });
196
197 if let Some(prev_press) = *last_press {
198 if now.duration_since(prev_press) <= self.double_press_threshold {
199 *last_press = None;
200 log::info!("๐ช Double ESC detected - Quit requested");
201 return KeyAction::Quit;
202 }
203 }
204
205 *last_press = Some(now);
206 return KeyAction::NoAction;
207 }
208
209 match (key.code, key.modifiers) {
210 (KeyCode::Left, KeyModifiers::NONE) => KeyAction::MoveLeft,
212 (KeyCode::Right, KeyModifiers::NONE) => KeyAction::MoveRight,
213 (KeyCode::Home, KeyModifiers::NONE) => KeyAction::MoveToStart,
214 (KeyCode::End, KeyModifiers::NONE) => KeyAction::MoveToEnd,
215
216 (KeyCode::Enter, KeyModifiers::NONE) => KeyAction::Submit,
218
219 (KeyCode::Up, KeyModifiers::SHIFT) => {
221 log::info!("๐ผ Matched SHIFT+UP pattern");
222 KeyAction::ScrollUp
223 }
224 (KeyCode::Down, KeyModifiers::SHIFT) => {
225 log::info!("๐ฝ Matched SHIFT+DOWN pattern");
226 KeyAction::ScrollDown
227 }
228 (KeyCode::PageUp, KeyModifiers::NONE) => KeyAction::PageUp,
229 (KeyCode::PageDown, KeyModifiers::NONE) => KeyAction::PageDown,
230
231 (KeyCode::Backspace, KeyModifiers::NONE) => KeyAction::Backspace,
233 (KeyCode::Delete, KeyModifiers::NONE) => KeyAction::Delete,
234
235 (KeyCode::Char('c'), KeyModifiers::SUPER) => {
237 log::info!("โ
Mac Cmd+C ERFOLGREICH erkannt โ Copy");
238 KeyAction::CopySelection
239 }
240 (KeyCode::Char('v'), KeyModifiers::SUPER) => {
241 log::info!("โ
Mac Cmd+V ERFOLGREICH erkannt โ Paste");
242 KeyAction::PasteBuffer
243 }
244 (KeyCode::Char('x'), KeyModifiers::SUPER) => {
245 log::info!("โ
Mac Cmd+X ERFOLGREICH erkannt โ Cut");
246 KeyAction::ClearLine
247 }
248 (KeyCode::Char('a'), KeyModifiers::SUPER) => {
249 log::info!("โ
Mac Cmd+A ERFOLGREICH erkannt โ Select All");
250 KeyAction::MoveToStart
251 }
252
253 (KeyCode::Char('a'), KeyModifiers::ALT) => KeyAction::MoveToStart,
255 (KeyCode::Char('e'), KeyModifiers::ALT) => KeyAction::MoveToEnd,
256 (KeyCode::Char('u'), KeyModifiers::ALT) => KeyAction::ClearLine,
257 (KeyCode::Char('c'), KeyModifiers::ALT) => {
258 log::info!("๐ Mac Alt+C detected โ Copy");
259 KeyAction::CopySelection
260 }
261 (KeyCode::Char('v'), KeyModifiers::ALT) => {
262 log::info!("๐ Mac Alt+V detected โ Paste");
263 KeyAction::PasteBuffer
264 }
265
266 (KeyCode::Char('a'), KeyModifiers::CONTROL) => KeyAction::MoveToStart,
268 (KeyCode::Char('e'), KeyModifiers::CONTROL) => KeyAction::MoveToEnd,
269 (KeyCode::Char('u'), KeyModifiers::CONTROL) => KeyAction::ClearLine,
270 (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
271 log::info!("๐ฅ๏ธ Ctrl+C detected โ Copy");
272 KeyAction::CopySelection
273 }
274 (KeyCode::Char('v'), KeyModifiers::CONTROL) => {
275 log::info!("๐ฅ๏ธ Ctrl+V detected โ Paste");
276 KeyAction::PasteBuffer
277 }
278
279 (KeyCode::Backspace, KeyModifiers::SUPER) => KeyAction::ClearLine,
281 (KeyCode::Backspace, KeyModifiers::ALT) => KeyAction::ClearLine,
282 (KeyCode::Backspace, KeyModifiers::CONTROL) => KeyAction::ClearLine,
283
284 (KeyCode::Left, KeyModifiers::SUPER) => KeyAction::MoveToStart,
286 (KeyCode::Right, KeyModifiers::SUPER) => KeyAction::MoveToEnd,
287 (KeyCode::Left, KeyModifiers::CONTROL) => KeyAction::MoveToStart,
288 (KeyCode::Right, KeyModifiers::CONTROL) => KeyAction::MoveToEnd,
289 (KeyCode::Left, KeyModifiers::ALT) => KeyAction::MoveToStart,
290 (KeyCode::Right, KeyModifiers::ALT) => KeyAction::MoveToEnd,
291
292 (KeyCode::Char(c), KeyModifiers::NONE) => {
294 if c.is_ascii_control() && c != '\t' {
296 log::warn!("๐ก๏ธ Blocked control char in normal input: {:?}", c);
297 KeyAction::NoAction
298 } else {
299 KeyAction::InsertChar(c)
300 }
301 }
302 (KeyCode::Char(c), KeyModifiers::SHIFT) => {
303 if c.is_ascii_control() {
305 log::warn!("๐ก๏ธ Blocked control char in shift input: {:?}", c);
306 KeyAction::NoAction
307 } else {
308 KeyAction::InsertChar(c)
309 }
310 }
311
312 (_code, _modifiers) => {
314 log::warn!(
315 "โ UNBEKANNTE KEY-KOMBINATION: {:?} + {:?}",
316 _code,
317 _modifiers
318 );
319
320 if _modifiers.contains(KeyModifiers::SHIFT) {
322 log::error!("๐จ SHIFT combination not handled: {:?} + SHIFT", _code);
323 }
324
325 KeyAction::NoAction
326 }
327 }
328 }
329
330 fn debug_key_event(&self, key: &KeyEvent) {
332 let modifier_debug = self.format_modifiers(key.modifiers);
333 let key_debug = self.format_key_code(key.code);
334
335 if let KeyCode::Char(c) = key.code {
337 if c.is_ascii_control() || !c.is_ascii() {
338 log::warn!(
339 "๐ก๏ธ SUSPICIOUS KEY: {} + {} (char: {:?} = U+{:04X})",
340 key_debug,
341 modifier_debug,
342 c,
343 c as u32
344 );
345 } else {
346 log::info!("๐ KEY EVENT: {} + {}", key_debug, modifier_debug);
347 }
348 } else {
349 log::info!("๐ KEY EVENT: {} + {}", key_debug, modifier_debug);
350 }
351
352 if key.modifiers.contains(KeyModifiers::SUPER) {
354 log::info!("๐ CMD-TASTE ERKANNT! Modifier enthรคlt SUPER flag");
355
356 match key.code {
357 KeyCode::Char(c) => {
358 log::info!("๐ CMD+{} Event empfangen", c.to_uppercase());
359 match c {
360 'c' => log::info!("๐ Das sollte COPY werden!"),
361 'v' => log::info!("๐ Das sollte PASTE werden!"),
362 'x' => log::info!("๐ Das sollte CUT werden!"),
363 'a' => log::info!("๐ Das sollte SELECT ALL werden!"),
364 _ => log::info!("๐ CMD+{} ist kein bekannter Shortcut", c),
365 }
366 }
367 other => log::info!("๐ CMD+{:?} (non-char key)", other),
368 }
369 }
370 }
371
372 fn format_modifiers(&self, modifiers: KeyModifiers) -> String {
373 let mut parts = Vec::new();
374
375 if modifiers.contains(KeyModifiers::SHIFT) {
376 parts.push("SHIFT");
377 }
378 if modifiers.contains(KeyModifiers::CONTROL) {
379 parts.push("CTRL");
380 }
381 if modifiers.contains(KeyModifiers::ALT) {
382 parts.push("ALT");
383 }
384 if modifiers.contains(KeyModifiers::SUPER) {
385 parts.push("CMD");
386 }
387
388 if parts.is_empty() {
389 "NONE".to_string()
390 } else {
391 parts.join("+")
392 }
393 }
394
395 fn format_key_code(&self, code: KeyCode) -> String {
396 match code {
397 KeyCode::Char(c) => format!("'{}'", c),
398 KeyCode::Enter => "ENTER".to_string(),
399 KeyCode::Backspace => "BACKSPACE".to_string(),
400 KeyCode::Delete => "DELETE".to_string(),
401 KeyCode::Left => "LEFT".to_string(),
402 KeyCode::Right => "RIGHT".to_string(),
403 KeyCode::Up => "UP".to_string(),
404 KeyCode::Down => "DOWN".to_string(),
405 KeyCode::Home => "HOME".to_string(),
406 KeyCode::End => "END".to_string(),
407 KeyCode::PageUp => "PAGEUP".to_string(),
408 KeyCode::PageDown => "PAGEDOWN".to_string(),
409 KeyCode::Esc => "ESC".to_string(),
410 other => format!("{:?}", other),
411 }
412 }
413}
414
415impl Default for KeyboardManager {
416 fn default() -> Self {
417 Self::new()
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
425
426 #[test]
427 fn test_escape_sequence_filtering() {
428 let mut manager = KeyboardManager::new();
429
430 let ctrl_char = KeyEvent::new(KeyCode::Char('\x1B'), KeyModifiers::NONE);
432 assert_eq!(manager.get_action(&ctrl_char), KeyAction::NoAction);
433
434 let normal_char = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
436 assert_eq!(manager.get_action(&normal_char), KeyAction::InsertChar('a'));
437 }
438
439 #[test]
440 fn test_cmd_shortcuts() {
441 let mut manager = KeyboardManager::new();
442
443 let cmd_c = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::SUPER);
444 assert_eq!(manager.get_action(&cmd_c), KeyAction::CopySelection);
445
446 let cmd_v = KeyEvent::new(KeyCode::Char('v'), KeyModifiers::SUPER);
447 assert_eq!(manager.get_action(&cmd_v), KeyAction::PasteBuffer);
448 }
449}