Rust · 6167 bytes Raw Blame History
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 }
210