Skip to main content

wkb/
lib.rs

1//! # wkb — Wayland Keyboard
2//!
3//! A lightweight, pure Rust keyboard handling library for Wayland.
4//! WKB compiles XKB keymaps, tracks modifier and compose state, and maps
5//! evdev key codes to characters — all without C dependencies.
6//!
7//! ## Quick Start
8//!
9//! ```rust,no_run
10//! use wkb::{WKB, KeyDirection};
11//!
12//! // Build from an XKB keymap string (e.g. received from a Wayland compositor)
13//! let keymap_string = std::fs::read_to_string("/path/to/keymap").unwrap();
14//! let mut wkb = WKB::new_from_string(keymap_string);
15//!
16//! // Process a key press (evdev code 38 = 'a' on US layout)
17//! let (ch, is_modifier) = wkb.key(38, KeyDirection::Down);
18//! ```
19//!
20//! ## Feature Flags
21//!
22//! - **`xkb`** (default) — XKB keymap compilation via the `xkb-core` crate.
23//! - **`compose`** (default) — Compose-key / dead-key sequence support.
24//! - **`testing`** — Exposes internal helpers for integration tests. Not part of the public API.
25
26use composer::{ComposeState, Composer, ListComposer, Token};
27mod composer;
28pub use modifiers::KeyDirection;
29use modifiers::ModType;
30use modifiers::{level_index, Modifiers, *};
31mod modifiers;
32/// Test-only utilities. Not part of the public API.
33#[cfg(feature = "testing")]
34pub mod testing;
35#[cfg(feature = "xkb")]
36mod xkb;
37
38/// Maximum number of shift levels.
39const MAX_LEVELS: usize = 8;
40
41/// Maximum evdev key code we support (768 covers all standard keycodes).
42const BITSET_WORDS: usize = 12; // 12 * 64 = 768 bits
43
44/// Compact bitset for tracking key states. Covers evdev codes 0..767.
45#[derive(Debug, Clone)]
46pub(crate) struct KeyBitSet {
47    bits: [u64; BITSET_WORDS],
48}
49
50impl KeyBitSet {
51    #[inline]
52    pub(crate) const fn new() -> Self {
53        Self {
54            bits: [0; BITSET_WORDS],
55        }
56    }
57
58    #[inline(always)]
59    pub(crate) fn contains(&self, key: u32) -> bool {
60        let k = key as usize;
61        if k < BITSET_WORDS * 64 {
62            self.bits[k >> 6] & (1u64 << (k & 63)) != 0
63        } else {
64            false
65        }
66    }
67
68    #[inline(always)]
69    pub(crate) fn insert(&mut self, key: u32) {
70        let k = key as usize;
71        if k < BITSET_WORDS * 64 {
72            self.bits[k >> 6] |= 1u64 << (k & 63);
73        }
74    }
75
76    #[inline(always)]
77    pub(crate) fn remove(&mut self, key: u32) {
78        let k = key as usize;
79        if k < BITSET_WORDS * 64 {
80            self.bits[k >> 6] &= !(1u64 << (k & 63));
81        }
82    }
83}
84
85/// Flat keymap: `MAX_LEVELS` planes of `num_keys` slots.
86/// Index: `level * num_keys + evdev_code`.
87#[derive(Debug, Clone)]
88pub(crate) struct FlatKeymap {
89    pub(crate) data: Vec<Option<char>>,
90    pub(crate) num_keys: usize,
91}
92
93impl FlatKeymap {
94    pub(crate) fn new(num_keys: usize) -> Self {
95        Self {
96            data: vec![None; MAX_LEVELS * num_keys],
97            num_keys,
98        }
99    }
100
101    #[inline]
102    pub(crate) fn num_levels(&self) -> usize {
103        MAX_LEVELS
104    }
105
106    #[inline(always)]
107    pub(crate) fn get(&self, level: usize, evdev_code: u32) -> Option<char> {
108        let k = evdev_code as usize;
109        if k < self.num_keys {
110            let idx = level * self.num_keys + k;
111            self.data[idx]
112        } else {
113            None
114        }
115    }
116
117    #[inline]
118    pub(crate) fn set(&mut self, level: usize, evdev_code: u32, ch: char) {
119        let k = evdev_code as usize;
120        if k < self.num_keys {
121            let idx = level * self.num_keys + k;
122            self.data[idx] = Some(ch);
123        }
124    }
125}
126
127const MODIFIER_MAPPING: [(u32, u32); 9] = [
128    (LEFT_SHIFT, 1),
129    (RIGHT_SHIFT, 1),
130    (CAPS_LOCK, 2),
131    (LEFT_CTRL, 4),
132    (RIGHT_CTRL, 4),
133    (ALT, 8),
134    (NUM_LOCK, 16),
135    (LOGO, 64),
136    (ALTGR, 128),
137];
138
139/// Core keyboard state machine. Tracks modifier state, key presses, and compose sequences.
140///
141/// `C` is the compose backend — typically [`ListComposer`] when using the `xkb` feature.
142#[derive(Debug, Clone)]
143pub struct WKB<C: Composer> {
144    pub(crate) layouts: Vec<String>,
145    pub(crate) layout: String,
146    // pub(crate) locale: Option<String>,
147    pub(crate) pressed_keys: KeyBitSet,
148    pub(crate) repeat_keys: KeyBitSet,
149    pub(crate) composer: C,
150    pub(crate) modifiers: Modifiers,
151    pub(crate) state_keymap: FlatKeymap,
152    pub(crate) num_lock_keys: FlatKeymap,
153    pub(crate) caps_lock_keymap: FlatKeymap,
154    pub(crate) level_exceptions_keymap: FlatKeymap,
155    #[cfg(feature = "xkb")]
156    pub(crate) xkb_keymap: Option<xkb_core::rust_types::Keymap>,
157}
158
159#[cfg(feature = "xkb")]
160impl WKB<ListComposer> {
161    /// Create WKB instance from RMLVO names (Rules, Model, Layout, Variant, Options)
162    pub fn new_from_names(locale: String, layout: Option<String>) -> Self {
163        xkb::new_from_names(locale, layout)
164    }
165
166    /// Create WKB instance from XKB keymap string
167    pub fn new_from_string(string: String) -> Self {
168        xkb::new_from_string(string)
169    }
170}
171
172impl<C: Composer> WKB<C> {
173    /// Reset all transient input state: compose sequence and pressed keys.
174    /// Call on wl_keyboard.leave or when focus changes.
175    pub fn reset_state(&mut self) {
176        self.composer.reset();
177        self.pressed_keys = KeyBitSet::new();
178    }
179
180    /// Return the current modifier state as `(depressed, latched, locked, group)` bitmasks.
181    pub fn modifiers_state(&self) -> (u32, u32, u32, u32) {
182        let mut depressed = 0;
183        let mut latched = 0;
184        let mut locked = 0;
185        let group = 0;
186        for (code, bit) in MODIFIER_MAPPING {
187            if let Some(Modifier::Single(mk)) = self.modifiers.get(code) {
188                match mk {
189                    ModKind::Pressed { pressed: true, .. } => depressed |= bit,
190                    ModKind::Lock {
191                        pressed, locked: l, ..
192                    } => {
193                        if *pressed {
194                            depressed |= bit;
195                        }
196                        if *l > 0 {
197                            locked |= bit;
198                        }
199                    }
200                    ModKind::Latch {
201                        pressed,
202                        latched: is_latched,
203                        ..
204                    } => {
205                        if *pressed {
206                            depressed |= bit;
207                        }
208                        if *is_latched {
209                            latched |= bit;
210                        }
211                    }
212                    _ => {}
213                }
214            }
215        }
216        (depressed, latched, locked, group)
217    }
218
219    /// Return the LED indicator state as a bitmask (bit 0 = NumLock, bit 1 = CapsLock, bit 2 = ScrollLock).
220    pub fn leds_state(&self) -> u32 {
221        let mut leds = 0;
222        if self.modifiers.locked_with_type(NUM_LOCK, ModType::Num) {
223            leds |= 1;
224        }
225        if self.modifiers.locked_with_type(CAPS_LOCK, ModType::Caps) {
226            leds |= 2;
227        }
228        if self
229            .modifiers
230            .locked_with_type(SCROLL_LOCK, ModType::Scroll)
231        {
232            leds |= 4;
233        }
234        leds
235    }
236
237    /// Apply modifier state received from `wl_keyboard.modifiers`. Updates depressed, latched, locked masks and active layout group.
238    pub fn update_modifiers(&mut self, depressed: u32, latched: u32, locked: u32, group: u32) {
239        if let Some(l) = self.layouts.get(group as usize) {
240            self.layout = l.clone();
241        }
242        for (code, bit) in MODIFIER_MAPPING {
243            let is_depressed = (depressed & bit) != 0;
244            let is_locked = (locked & bit) != 0;
245            let is_latched = (latched & bit) != 0;
246
247            if let Some(m) = self.modifiers.get_mut(code) {
248                if let Modifier::Single(mk) = m {
249                    match mk {
250                        ModKind::Pressed { pressed, .. } => *pressed = is_depressed,
251                        ModKind::Lock {
252                            pressed, locked, ..
253                        } => {
254                            *pressed = is_depressed;
255                            *locked = if is_locked { 1 } else { 0 };
256                        }
257                        ModKind::Latch {
258                            pressed, latched, ..
259                        } => {
260                            *pressed = is_depressed;
261                            *latched = is_latched;
262                        }
263                        _ => {}
264                    }
265                }
266            }
267        }
268    }
269
270    /// Look up the character at a specific shift level for the given evdev keycode.
271    #[inline]
272    pub fn level_key(&self, evdev_code: u32, level_index: usize) -> Option<char> {
273        self.level_exceptions_keymap
274            .get(level_index, evdev_code)
275            .or_else(|| self.state_keymap.get(level_index, evdev_code))
276    }
277
278    /// Return the number of shift levels supported by this keymap.
279    #[inline]
280    pub fn num_levels(&self) -> usize {
281        self.state_keymap.num_levels()
282    }
283
284    /// Return whether the given evdev keycode is a repeating key.
285    pub fn key_repeats(&self, evdev_code: u32) -> bool {
286        self.repeat_keys.contains(evdev_code)
287    }
288
289    /// Resolve the character for the given evdev keycode under the current modifier state.
290    #[inline]
291    pub fn utf8(&mut self, evdev_code: u32) -> Option<char> {
292        let (none_active, level2, level3, level5) = self.modifiers.active_none_and_levels();
293        if none_active {
294            return None;
295        }
296        let nk = self.state_keymap.num_keys;
297        let level5 = level5 && self.state_keymap.data.len() > 4 * nk;
298        let level3 = level3 && self.state_keymap.data.len() > 2 * nk;
299        let level2 = level2 && self.state_keymap.data.len() > 1 * nk;
300        let base_level = level_index(level5, level3, level2);
301
302        if self.modifiers.locked(NUM_LOCK) {
303            if let Some(key) = self.num_lock_keys.get(base_level, evdev_code) {
304                return Some(key);
305            }
306        }
307
308        if self.modifiers.locked(CAPS_LOCK) {
309            if let Some(c) = self.caps_lock_keymap.get(base_level, evdev_code) {
310                return Some(c);
311            }
312        }
313
314        self.state_keymap.get(base_level, evdev_code)
315    }
316
317    /// Update internal modifier/key-press state for a key event. Returns `true` if the key is a modifier.
318    pub(crate) fn update_key(&mut self, evdev_code: u32, key_direction: KeyDirection) -> bool {
319        let is_modifier = self.modifiers.set_state(evdev_code, key_direction);
320        if !is_modifier {
321            if key_direction == KeyDirection::Down {
322                self.modifiers.unlatch();
323            }
324            match key_direction {
325                KeyDirection::Up => {
326                    self.pressed_keys.remove(evdev_code);
327                }
328                KeyDirection::Down => {
329                    self.pressed_keys.insert(evdev_code);
330                }
331            };
332        }
333        is_modifier
334    }
335
336    /// Process a key event: update state and return `(character, is_modifier)`.
337    pub fn key(&mut self, evdev_code: u32, key_direction: KeyDirection) -> (Option<char>, bool) {
338        let is_modifier = self.update_key(evdev_code, key_direction);
339        let utf8 = if key_direction == KeyDirection::Down && !is_modifier {
340            self.utf8(evdev_code)
341        } else {
342            None
343        };
344        (utf8, is_modifier)
345    }
346
347    /// Process a key event with compose support. Returns `(compose_state, is_modifier)`.
348    #[cfg(feature = "compose")]
349    pub fn key_compose(
350        &mut self,
351        evdev_code: u32,
352        key_direction: KeyDirection,
353    ) -> (Option<ComposeState>, bool) {
354        let is_modifier = self.update_key(evdev_code, key_direction);
355        let compose_state = if key_direction == KeyDirection::Down
356            && is_modifier
357            && self.modifiers.active_mod_type(ModType::Compose)
358        {
359            Some(self.composer.feed(Token::Compose))
360        } else if key_direction == KeyDirection::Down && !is_modifier {
361            self.utf8(evdev_code)
362                .map(|c| self.composer.feed(Token::Char(c)))
363        } else {
364            None
365        };
366        (compose_state, is_modifier)
367    }
368
369    /// Return the list of layout names available in this keymap.
370    pub fn layouts(&self) -> Vec<String> {
371        self.layouts.clone()
372    }
373
374    /// Return the name of the currently active layout.
375    pub fn current_layout(&self) -> String {
376        self.layout.clone()
377    }
378
379    /// Serialize the underlying XKB keymap to v1 text format.
380    ///
381    /// Returns `None` if the instance was not built from an XKB keymap.
382    #[cfg(feature = "xkb")]
383    pub fn as_xkb_string(&self) -> Option<String> {
384        self.xkb_keymap.as_ref().map(|km| km.as_xkb_string())
385    }
386}