Skip to main content

sdl_keybridge/
layout.rs

1//! Keyboard layouts.
2//!
3//! A [`Layout`] maps physical [`Scancode`]s to the glyphs they produce at
4//! each modifier level (Base / Shift / AltGr / Shift+AltGr), plus the
5//! [`NamedKey`] identity for non-printable keys.
6//!
7//! The layouts shipped here are hand-curated for v0.1. The long-term
8//! plan is to generate them at build time from the Unicode CLDR
9//! keyboard data (see `PLAN.md` and `CONTRIBUTING.md`).
10
11use crate::keycode::Keycode;
12use crate::localizer::Platform;
13use crate::named_key::NamedKey;
14use crate::scancode::Scancode;
15
16/// One physical key within a [`Layout`].
17#[derive(Copy, Clone, Debug)]
18pub struct LayoutKey {
19    pub scancode: Scancode,
20    /// Glyph produced at the base (unshifted) level. `None` means the key
21    /// has no printable output at that level.
22    pub base: Option<char>,
23    /// Glyph produced with Shift held.
24    pub shift: Option<char>,
25    /// Glyph produced with AltGr (`KMOD_MODE` or `RALT`) held.
26    pub altgr: Option<char>,
27    /// Glyph produced with Shift + AltGr held.
28    pub shift_altgr: Option<char>,
29    /// Named-key identity for keys that are not character-producing
30    /// (Escape, arrows, F-keys, …). `None` means the key is printable.
31    pub named: Option<NamedKey>,
32}
33
34impl LayoutKey {
35    /// Short helper — build a pure-named-key entry (no glyphs).
36    pub const fn named(sc: Scancode, nk: NamedKey) -> Self {
37        Self {
38            scancode: sc,
39            base: None,
40            shift: None,
41            altgr: None,
42            shift_altgr: None,
43            named: Some(nk),
44        }
45    }
46
47    /// Build a printable key entry with base + shift glyphs only.
48    pub const fn printable(sc: Scancode, base: char, shift: char) -> Self {
49        Self {
50            scancode: sc,
51            base: Some(base),
52            shift: Some(shift),
53            altgr: None,
54            shift_altgr: None,
55            named: None,
56        }
57    }
58
59    /// Build a printable key entry with all 4 modifier levels.
60    pub const fn printable4(
61        sc: Scancode,
62        base: char,
63        shift: char,
64        altgr: char,
65        shift_altgr: char,
66    ) -> Self {
67        Self {
68            scancode: sc,
69            base: Some(base),
70            shift: Some(shift),
71            altgr: Some(altgr),
72            shift_altgr: Some(shift_altgr),
73            named: None,
74        }
75    }
76}
77
78/// A complete keyboard layout.
79#[derive(Copy, Clone, Debug)]
80pub struct Layout {
81    /// Layout identifier — `"<platform>/<BCP 47>-t-k0-<variant>"`.
82    pub id: &'static str,
83    /// Display name suitable for UIs (e.g. `"French (AZERTY)"`).
84    pub display_name: &'static str,
85    /// Host platform this layout was designed for.
86    pub platform: Platform,
87    /// Primary language subtag — helps match a UI locale to this layout.
88    pub language: &'static str,
89    /// Non-printable named keys — usually the `STD_NAMED_KEYS` constant.
90    pub named_keys: &'static [LayoutKey],
91    /// Character-producing keys — layout-specific.
92    pub printable_keys: &'static [LayoutKey],
93}
94
95impl Layout {
96    /// Find a key by scancode.
97    pub fn key(&self, sc: Scancode) -> Option<&'static LayoutKey> {
98        self.printable_keys
99            .iter()
100            .find(|k| k.scancode == sc)
101            .or_else(|| self.named_keys.iter().find(|k| k.scancode == sc))
102    }
103}
104
105/// Named (non-printable) keys shared by every layout — their identity is
106/// defined by the scancode, not by the layout.
107pub const STD_NAMED_KEYS: &[LayoutKey] = &[
108    LayoutKey::named(Scancode::ESCAPE, NamedKey::Escape),
109    LayoutKey::named(Scancode::RETURN, NamedKey::Return),
110    LayoutKey::named(Scancode::BACKSPACE, NamedKey::Backspace),
111    LayoutKey::named(Scancode::TAB, NamedKey::Tab),
112    LayoutKey::named(Scancode::CAPSLOCK, NamedKey::CapsLock),
113    LayoutKey::named(Scancode::F1, NamedKey::F1),
114    LayoutKey::named(Scancode::F2, NamedKey::F2),
115    LayoutKey::named(Scancode::F3, NamedKey::F3),
116    LayoutKey::named(Scancode::F4, NamedKey::F4),
117    LayoutKey::named(Scancode::F5, NamedKey::F5),
118    LayoutKey::named(Scancode::F6, NamedKey::F6),
119    LayoutKey::named(Scancode::F7, NamedKey::F7),
120    LayoutKey::named(Scancode::F8, NamedKey::F8),
121    LayoutKey::named(Scancode::F9, NamedKey::F9),
122    LayoutKey::named(Scancode::F10, NamedKey::F10),
123    LayoutKey::named(Scancode::F11, NamedKey::F11),
124    LayoutKey::named(Scancode::F12, NamedKey::F12),
125    LayoutKey::named(Scancode::F13, NamedKey::F13),
126    LayoutKey::named(Scancode::F14, NamedKey::F14),
127    LayoutKey::named(Scancode::F15, NamedKey::F15),
128    LayoutKey::named(Scancode::F16, NamedKey::F16),
129    LayoutKey::named(Scancode::F17, NamedKey::F17),
130    LayoutKey::named(Scancode::F18, NamedKey::F18),
131    LayoutKey::named(Scancode::F19, NamedKey::F19),
132    LayoutKey::named(Scancode::F20, NamedKey::F20),
133    LayoutKey::named(Scancode::F21, NamedKey::F21),
134    LayoutKey::named(Scancode::F22, NamedKey::F22),
135    LayoutKey::named(Scancode::F23, NamedKey::F23),
136    LayoutKey::named(Scancode::F24, NamedKey::F24),
137    LayoutKey::named(Scancode::PRINT_SCREEN, NamedKey::PrintScreen),
138    LayoutKey::named(Scancode::SCROLL_LOCK, NamedKey::ScrollLock),
139    LayoutKey::named(Scancode::PAUSE, NamedKey::Pause),
140    LayoutKey::named(Scancode::INSERT, NamedKey::Insert),
141    LayoutKey::named(Scancode::HOME, NamedKey::Home),
142    LayoutKey::named(Scancode::PAGE_UP, NamedKey::PageUp),
143    LayoutKey::named(Scancode::DELETE, NamedKey::Delete),
144    LayoutKey::named(Scancode::END, NamedKey::End),
145    LayoutKey::named(Scancode::PAGE_DOWN, NamedKey::PageDown),
146    LayoutKey::named(Scancode::RIGHT, NamedKey::ArrowRight),
147    LayoutKey::named(Scancode::LEFT, NamedKey::ArrowLeft),
148    LayoutKey::named(Scancode::DOWN, NamedKey::ArrowDown),
149    LayoutKey::named(Scancode::UP, NamedKey::ArrowUp),
150    LayoutKey::named(Scancode::NUM_LOCK_CLEAR, NamedKey::NumLock),
151    LayoutKey::named(Scancode::KP_DIVIDE, NamedKey::KeypadDivide),
152    LayoutKey::named(Scancode::KP_MULTIPLY, NamedKey::KeypadMultiply),
153    LayoutKey::named(Scancode::KP_MINUS, NamedKey::KeypadMinus),
154    LayoutKey::named(Scancode::KP_PLUS, NamedKey::KeypadPlus),
155    LayoutKey::named(Scancode::KP_ENTER, NamedKey::KeypadEnter),
156    LayoutKey::named(Scancode::KP_1, NamedKey::Keypad1),
157    LayoutKey::named(Scancode::KP_2, NamedKey::Keypad2),
158    LayoutKey::named(Scancode::KP_3, NamedKey::Keypad3),
159    LayoutKey::named(Scancode::KP_4, NamedKey::Keypad4),
160    LayoutKey::named(Scancode::KP_5, NamedKey::Keypad5),
161    LayoutKey::named(Scancode::KP_6, NamedKey::Keypad6),
162    LayoutKey::named(Scancode::KP_7, NamedKey::Keypad7),
163    LayoutKey::named(Scancode::KP_8, NamedKey::Keypad8),
164    LayoutKey::named(Scancode::KP_9, NamedKey::Keypad9),
165    LayoutKey::named(Scancode::KP_0, NamedKey::Keypad0),
166    LayoutKey::named(Scancode::KP_PERIOD, NamedKey::KeypadPeriod),
167    LayoutKey::named(Scancode::KP_EQUALS, NamedKey::KeypadEquals),
168    LayoutKey::named(Scancode::APPLICATION, NamedKey::Application),
169    LayoutKey::named(Scancode::MENU, NamedKey::Menu),
170    LayoutKey::named(Scancode::SPACE, NamedKey::Space),
171    LayoutKey::named(Scancode::LCTRL, NamedKey::ControlLeft),
172    LayoutKey::named(Scancode::LSHIFT, NamedKey::ShiftLeft),
173    LayoutKey::named(Scancode::LALT, NamedKey::AltLeft),
174    LayoutKey::named(Scancode::LGUI, NamedKey::GuiLeft),
175    LayoutKey::named(Scancode::RCTRL, NamedKey::ControlRight),
176    LayoutKey::named(Scancode::RSHIFT, NamedKey::ShiftRight),
177    LayoutKey::named(Scancode::RALT, NamedKey::AltRight),
178    LayoutKey::named(Scancode::RGUI, NamedKey::GuiRight),
179];
180
181// ==========================================================================
182// Layout registry — populated entirely from CLDR 43 via build.rs.
183//
184// `build.rs` reads every XML under `data/cldr-43/keyboards/` and writes
185// `$OUT_DIR/cldr_layouts.rs` containing:
186//
187// - `CLDR_KEYS_<SYM>`   — one `&[LayoutKey]` per layout
188// - `CLDR_LAYOUT_<SYM>` — one `Layout` per layout
189// - `CLDR_LAYOUTS`      — the flat `&[&Layout]` registry.
190//
191// The crate has no hand-authored layout data — every glyph served by
192// `resolve()` / `scancode_for()` comes from a CLDR-maintained XML.
193// ==========================================================================
194
195include!(concat!(env!("OUT_DIR"), "/cldr_layouts.rs"));
196
197/// Look up a layout by its id — either a CLDR 2.x tag like
198/// `"windows/fr-t-k0-windows"` or a CLDR 3.0 tag like `"cldr3/fr"`.
199pub fn get_layout(id: &str) -> Option<&'static Layout> {
200    CLDR_LAYOUTS
201        .iter()
202        .find(|l| l.id == id)
203        .or_else(|| CLDR3_LAYOUTS.iter().find(|l| l.id == id))
204        .copied()
205}
206
207/// Every layout shipped in this build — concatenates the CLDR 2.x and
208/// CLDR 3.0 registries. The `Vec` is allocated once on first call and
209/// returned as a borrowed slice thereafter.
210pub fn all_layouts() -> &'static [&'static Layout] {
211    use std::sync::OnceLock;
212    static COMBINED: OnceLock<Vec<&'static Layout>> = OnceLock::new();
213    COMBINED
214        .get_or_init(|| {
215            let mut v: Vec<&'static Layout> = CLDR_LAYOUTS.to_vec();
216            v.extend(CLDR3_LAYOUTS.iter().copied());
217            v
218        })
219        .as_slice()
220}
221
222/// Compute the canonical SDL keycode for a [`LayoutKey`] at its base
223/// modifier level.
224pub(crate) fn layout_key_base_keycode(k: &LayoutKey) -> Keycode {
225    if let Some(nk) = k.named {
226        named_key_keycode(nk)
227    } else if let Some(c) = k.base {
228        Keycode::from(c)
229    } else {
230        Keycode::UNKNOWN
231    }
232}
233
234/// The canonical SDL keycode for a [`NamedKey`].
235pub(crate) fn named_key_keycode(nk: NamedKey) -> Keycode {
236    use NamedKey::*;
237    match nk {
238        Escape => Keycode::ESCAPE,
239        Return => Keycode::RETURN,
240        Tab => Keycode::TAB,
241        Space => Keycode::SPACE,
242        Backspace => Keycode::BACKSPACE,
243        Insert => Keycode::INSERT,
244        Delete => Keycode::DELETE,
245        Home => Keycode::HOME,
246        End => Keycode::END,
247        PageUp => Keycode::PAGE_UP,
248        PageDown => Keycode::PAGE_DOWN,
249        ArrowUp => Keycode::UP,
250        ArrowDown => Keycode::DOWN,
251        ArrowLeft => Keycode::LEFT,
252        ArrowRight => Keycode::RIGHT,
253        CapsLock => Keycode::CAPSLOCK,
254        NumLock => Keycode::NUM_LOCK_CLEAR,
255        ScrollLock => Keycode::SCROLL_LOCK,
256        PrintScreen => Keycode::PRINT_SCREEN,
257        Pause => Keycode::PAUSE,
258        Menu => Keycode::MENU,
259        Application => Keycode::APPLICATION,
260        ShiftLeft => Keycode::LSHIFT,
261        ShiftRight => Keycode::RSHIFT,
262        ControlLeft => Keycode::LCTRL,
263        ControlRight => Keycode::RCTRL,
264        AltLeft => Keycode::LALT,
265        AltRight => Keycode::RALT,
266        AltGr => Keycode::RALT,
267        GuiLeft => Keycode::LGUI,
268        GuiRight => Keycode::RGUI,
269        F1 => Keycode::F1,
270        F2 => Keycode::F2,
271        F3 => Keycode::F3,
272        F4 => Keycode::F4,
273        F5 => Keycode::F5,
274        F6 => Keycode::F6,
275        F7 => Keycode::F7,
276        F8 => Keycode::F8,
277        F9 => Keycode::F9,
278        F10 => Keycode::F10,
279        F11 => Keycode::F11,
280        F12 => Keycode::F12,
281        F13 => Keycode::F13,
282        F14 => Keycode::F14,
283        F15 => Keycode::F15,
284        F16 => Keycode::F16,
285        F17 => Keycode::F17,
286        F18 => Keycode::F18,
287        F19 => Keycode::F19,
288        F20 => Keycode::F20,
289        F21 => Keycode::F21,
290        F22 => Keycode::F22,
291        F23 => Keycode::F23,
292        F24 => Keycode::F24,
293        KeypadEnter => Keycode::KP_ENTER,
294        KeypadDivide => Keycode::KP_DIVIDE,
295        KeypadMultiply => Keycode::KP_MULTIPLY,
296        KeypadMinus => Keycode::KP_MINUS,
297        KeypadPlus => Keycode::KP_PLUS,
298        KeypadPeriod => Keycode::KP_PERIOD,
299        KeypadEquals => Keycode::KP_EQUALS,
300        Keypad0 => Keycode::KP_0,
301        Keypad1 => Keycode::KP_1,
302        Keypad2 => Keycode::KP_2,
303        Keypad3 => Keycode::KP_3,
304        Keypad4 => Keycode::KP_4,
305        Keypad5 => Keycode::KP_5,
306        Keypad6 => Keycode::KP_6,
307        Keypad7 => Keycode::KP_7,
308        Keypad8 => Keycode::KP_8,
309        Keypad9 => Keycode::KP_9,
310    }
311}