gardesk/garlock / 5367521

Browse files

Add XKB keyboard input handling

- Keycode to character mapping via xkbcommon
- Special key detection: Enter, Backspace, Escape, Ctrl+U
- Modifier key filtering (Shift, Ctrl, Alt, Caps Lock)
- Caps Lock state detection
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5367521f4023668b18e0f5102627af1be32d01e3
Parents
f3eb3e6
Tree
8acf9a2

1 changed file

StatusFile+-
A garlock/src/keyboard.rs 209 0
garlock/src/keyboard.rsadded
@@ -0,0 +1,209 @@
1
+//! Keyboard input handling for garlock
2
+//!
3
+//! Uses xkbcommon for proper keycode-to-character mapping and
4
+//! modifier state tracking (Caps Lock, etc.).
5
+
6
+use anyhow::{Context, Result};
7
+use xkbcommon::xkb::{
8
+    self,
9
+    keysyms::{
10
+        KEY_Alt_L, KEY_Alt_R, KEY_BackSpace, KEY_Caps_Lock, KEY_Control_L, KEY_Control_R,
11
+        KEY_Delete, KEY_Down, KEY_End, KEY_Escape, KEY_F1, KEY_F12, KEY_Home, KEY_Insert,
12
+        KEY_KP_Enter, KEY_Left, KEY_Menu, KEY_Meta_L, KEY_Meta_R, KEY_Num_Lock, KEY_Page_Down,
13
+        KEY_Page_Up, KEY_Pause, KEY_Print, KEY_Return, KEY_Right, KEY_Scroll_Lock, KEY_Shift_L,
14
+        KEY_Shift_R, KEY_Super_L, KEY_Super_R, KEY_Up, KEY_c, KEY_u,
15
+    },
16
+    Keycode, Keysym, MOD_NAME_CAPS, MOD_NAME_CTRL, MOD_NAME_SHIFT, STATE_MODS_EFFECTIVE,
17
+    STATE_MODS_LOCKED,
18
+};
19
+
20
+/// Result of processing a key event
21
+#[derive(Debug, Clone, PartialEq, Eq)]
22
+pub enum KeyResult {
23
+    /// A printable character was typed
24
+    Char(char),
25
+    /// Backspace was pressed
26
+    Backspace,
27
+    /// Enter/Return was pressed (submit password)
28
+    Enter,
29
+    /// Escape was pressed (dev mode exit)
30
+    Escape,
31
+    /// Clear buffer (Ctrl+U)
32
+    Clear,
33
+    /// A modifier key was pressed (no visual feedback)
34
+    Modifier,
35
+    /// Key should be ignored (function keys, etc.)
36
+    Ignored,
37
+}
38
+
39
+/// Keyboard state handler using XKB
40
+pub struct Keyboard {
41
+    #[allow(dead_code)]
42
+    context: xkb::Context,
43
+    #[allow(dead_code)]
44
+    keymap: xkb::Keymap,
45
+    state: xkb::State,
46
+}
47
+
48
+impl Keyboard {
49
+    /// Create a new keyboard handler with default keymap
50
+    pub fn new() -> Result<Self> {
51
+        let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
52
+
53
+        // Load keymap from system defaults (respects XKBLAYOUT, etc.)
54
+        let keymap = xkb::Keymap::new_from_names(
55
+            &context,
56
+            "", // rules (empty = default)
57
+            "", // model
58
+            "", // layout
59
+            "", // variant
60
+            None, // options
61
+            xkb::KEYMAP_COMPILE_NO_FLAGS,
62
+        )
63
+        .context("Failed to create XKB keymap")?;
64
+
65
+        let state = xkb::State::new(&keymap);
66
+
67
+        tracing::debug!("XKB keyboard initialized");
68
+        Ok(Self {
69
+            context,
70
+            keymap,
71
+            state,
72
+        })
73
+    }
74
+
75
+    /// Process a key event and return the result
76
+    ///
77
+    /// `keycode` is the X11 keycode (8-based).
78
+    /// `pressed` is true for key press, false for key release.
79
+    pub fn process_key(&mut self, keycode: u8, pressed: bool) -> KeyResult {
80
+        // X11 keycodes map directly to XKB keycodes
81
+        let xkb_keycode: Keycode = keycode.into();
82
+
83
+        // Update XKB state
84
+        let direction = if pressed {
85
+            xkb::KeyDirection::Down
86
+        } else {
87
+            xkb::KeyDirection::Up
88
+        };
89
+        self.state.update_key(xkb_keycode, direction);
90
+
91
+        // Only process key presses, not releases
92
+        if !pressed {
93
+            return KeyResult::Ignored;
94
+        }
95
+
96
+        // Get the keysym for this key
97
+        let keysym: Keysym = self.state.key_get_one_sym(xkb_keycode);
98
+        let keysym_raw = keysym.raw();
99
+
100
+        // Check for special keys first
101
+        if keysym_raw == KEY_Escape {
102
+            return KeyResult::Escape;
103
+        }
104
+
105
+        if keysym_raw == KEY_Return || keysym_raw == KEY_KP_Enter {
106
+            return KeyResult::Enter;
107
+        }
108
+
109
+        if keysym_raw == KEY_BackSpace {
110
+            return KeyResult::Backspace;
111
+        }
112
+
113
+        // Modifier keys - no visual feedback
114
+        if matches!(
115
+            keysym_raw,
116
+            KEY_Shift_L
117
+                | KEY_Shift_R
118
+                | KEY_Control_L
119
+                | KEY_Control_R
120
+                | KEY_Alt_L
121
+                | KEY_Alt_R
122
+                | KEY_Super_L
123
+                | KEY_Super_R
124
+                | KEY_Meta_L
125
+                | KEY_Meta_R
126
+                | KEY_Caps_Lock
127
+                | KEY_Num_Lock
128
+                | KEY_Scroll_Lock
129
+        ) {
130
+            return KeyResult::Modifier;
131
+        }
132
+
133
+        // Function keys, navigation, etc. - ignore
134
+        if (KEY_F1..=KEY_F12).contains(&keysym_raw)
135
+            || matches!(
136
+                keysym_raw,
137
+                KEY_Home
138
+                    | KEY_End
139
+                    | KEY_Page_Up
140
+                    | KEY_Page_Down
141
+                    | KEY_Left
142
+                    | KEY_Right
143
+                    | KEY_Up
144
+                    | KEY_Down
145
+                    | KEY_Insert
146
+                    | KEY_Delete
147
+                    | KEY_Print
148
+                    | KEY_Pause
149
+                    | KEY_Menu
150
+            )
151
+        {
152
+            return KeyResult::Ignored;
153
+        }
154
+
155
+        // Check for Ctrl+U (clear buffer)
156
+        if self.ctrl_active() && keysym_raw == KEY_u {
157
+            return KeyResult::Clear;
158
+        }
159
+
160
+        // Check for Ctrl+C (also clear, common convention)
161
+        if self.ctrl_active() && keysym_raw == KEY_c {
162
+            return KeyResult::Clear;
163
+        }
164
+
165
+        // Ignore other Ctrl combinations
166
+        if self.ctrl_active() {
167
+            return KeyResult::Ignored;
168
+        }
169
+
170
+        // Try to get a character from the key
171
+        let utf8_string = self.state.key_get_utf8(xkb_keycode);
172
+
173
+        if !utf8_string.is_empty() {
174
+            if let Some(c) = utf8_string.chars().next() {
175
+                // Filter out control characters
176
+                if c.is_control() {
177
+                    return KeyResult::Ignored;
178
+                }
179
+                return KeyResult::Char(c);
180
+            }
181
+        }
182
+
183
+        KeyResult::Ignored
184
+    }
185
+
186
+    /// Check if Caps Lock is currently active
187
+    pub fn caps_lock_active(&self) -> bool {
188
+        self.state.mod_name_is_active(MOD_NAME_CAPS, STATE_MODS_LOCKED)
189
+    }
190
+
191
+    /// Check if Ctrl is currently held
192
+    pub fn ctrl_active(&self) -> bool {
193
+        self.state
194
+            .mod_name_is_active(MOD_NAME_CTRL, STATE_MODS_EFFECTIVE)
195
+    }
196
+
197
+    /// Check if Shift is currently held
198
+    #[allow(dead_code)]
199
+    pub fn shift_active(&self) -> bool {
200
+        self.state
201
+            .mod_name_is_active(MOD_NAME_SHIFT, STATE_MODS_EFFECTIVE)
202
+    }
203
+}
204
+
205
+impl Default for Keyboard {
206
+    fn default() -> Self {
207
+        Self::new().expect("Failed to initialize keyboard")
208
+    }
209
+}