rush_sync_server/input/
keyboard.rs

1// =====================================================
2// FILE: src/input/keyboard.rs - MIT ESCAPE-CODE FILTER
3// =====================================================
4
5use 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), // 100ms timeout fรผr Sequenzen
50            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    /// ๐Ÿ›ก๏ธ NEUE FUNKTION: Filter Terminal Escape Sequences
80    fn is_escape_sequence_char(&self, c: char) -> bool {
81        // Filter bekannte problematische Zeichen/Sequenzen
82        match c {
83            // Control characters (auรŸer Tab, Enter, etc.) - ESC ist schon in \x1B enthalten
84            '\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            // Problematische Zeichen die in Terminal-Antworten vorkommen
93            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    /// ๐Ÿ›ก๏ธ NEUE FUNKTION: Detect sequence patterns
102    fn detect_terminal_sequence(&mut self, c: char) -> bool {
103        let now = Instant::now();
104
105        // Reset buffer if too old
106        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        // Add to buffer
115        if let Ok(mut buffer) = ESCAPE_SEQUENCE_BUFFER.lock() {
116            buffer.push(c);
117
118            // Check for known bad sequences
119            let sequence: String = buffer.iter().collect();
120
121            // Pattern 1: "tmux" in any form
122            if sequence.to_lowercase().contains("tmux") {
123                log::warn!("๐Ÿ›ก๏ธ BLOCKED TMUX SEQUENCE: '{}'", sequence.escape_debug());
124                buffer.clear();
125                return true;
126            }
127
128            // Pattern 2: Long sequences of digits/semicolons
129            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            // Pattern 3: Control sequence indicators
136            if sequence.contains("///") || sequence.contains(";;;") {
137                log::warn!("๐Ÿ›ก๏ธ BLOCKED CONTROL SEQUENCE: '{}'", sequence);
138                buffer.clear();
139                return true;
140            }
141
142            // Limit buffer size
143            if buffer.len() > 20 {
144                buffer.drain(0..10); // Keep last 10 chars
145            }
146        }
147
148        false
149    }
150
151    pub fn get_action(&mut self, key: &KeyEvent) -> KeyAction {
152        // ๐Ÿ›ก๏ธ ERSTE VERTEIDIGUNG: Debug und Filter
153        self.debug_key_event(key);
154
155        // โœ… SPEZIAL-DEBUG fรผr SHIFT+ARROWS
156        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        // ๐Ÿ›ก๏ธ ZWEITE VERTEIDIGUNG: Char-Level filtering
177        if let KeyCode::Char(c) = key.code {
178            // Prรผfe auf Escape-Sequenz-Zeichen
179            if self.is_escape_sequence_char(c) {
180                return KeyAction::NoAction;
181            }
182
183            // Prรผfe auf Terminal-Sequenz-Pattern
184            if self.detect_terminal_sequence(c) {
185                return KeyAction::NoAction;
186            }
187        }
188
189        // ESC Behandlung
190        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            // ========== BEWEGUNG ==========
211            (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            // ========== SUBMIT ==========
217            (KeyCode::Enter, KeyModifiers::NONE) => KeyAction::Submit,
218
219            // ========== SCROLLING ==========
220            (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            // ========== TEXT-BEARBEITUNG ==========
232            (KeyCode::Backspace, KeyModifiers::NONE) => KeyAction::Backspace,
233            (KeyCode::Delete, KeyModifiers::NONE) => KeyAction::Delete,
234
235            // ========== ๐ŸŽ MAC: CMD-SHORTCUTS (SUPER) ==========
236            (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            // ========== ๐ŸŽ MAC: ALT/OPTION-SHORTCUTS ==========
254            (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            // ========== ๐Ÿ–ฅ๏ธ WINDOWS/LINUX: CTRL-SHORTCUTS ==========
267            (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            // ========== BACKSPACE-KOMBINATIONEN ==========
280            (KeyCode::Backspace, KeyModifiers::SUPER) => KeyAction::ClearLine,
281            (KeyCode::Backspace, KeyModifiers::ALT) => KeyAction::ClearLine,
282            (KeyCode::Backspace, KeyModifiers::CONTROL) => KeyAction::ClearLine,
283
284            // ========== PFEILTASTEN MIT MODIFIERS ==========
285            (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            // ========== ๐Ÿ›ก๏ธ SICHERE ZEICHEN-EINGABE ==========
293            (KeyCode::Char(c), KeyModifiers::NONE) => {
294                // Extra check fรผr normale Zeichen
295                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                // Extra check fรผr Shift-Zeichen
304                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            // ========== FALLBACK ==========
313            (_code, _modifiers) => {
314                log::warn!(
315                    "โ“ UNBEKANNTE KEY-KOMBINATION: {:?} + {:?}",
316                    _code,
317                    _modifiers
318                );
319
320                // โœ… EXTRA DEBUG fรผr SHIFT combinations
321                if _modifiers.contains(KeyModifiers::SHIFT) {
322                    log::error!("๐Ÿšจ SHIFT combination not handled: {:?} + SHIFT", _code);
323                }
324
325                KeyAction::NoAction
326            }
327        }
328    }
329
330    // โœ… DETAILLIERTER KEY-EVENT DEBUGGER
331    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        // ๐Ÿ›ก๏ธ SPECIAL: Check for suspicious patterns
336        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        // CMD-Debugging
353        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        // Test control characters
431        let ctrl_char = KeyEvent::new(KeyCode::Char('\x1B'), KeyModifiers::NONE);
432        assert_eq!(manager.get_action(&ctrl_char), KeyAction::NoAction);
433
434        // Test normal characters
435        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}