sdl_keybridge/lib.rs
1//! # sdl-keybridge — universal Rosetta Stone for SDL keyboards
2//!
3//! A static correspondence table that exposes, for every key press, all
4//! of its parallel representations — physical [`Scancode`], logical
5//! [`Keycode`], textual or symbolic glyph, localized label — across
6//! every layout × platform combination supported by SDL.
7//!
8//! Works with both SDL2 and SDL3 (the crate takes primitive `u32` /
9//! `u16` values whose numeric assignments are identical between the two
10//! versions); no `sdl2` or `sdl3` Cargo feature is needed.
11//!
12//! ## Philosophy: Rosette, not Champollion
13//!
14//! This crate is a static lookup table (the **Rosette**). It exposes
15//! parallel data and individual labels. It **does not interpret** and
16//! **does not package** presentation conventions.
17//!
18//! Anything that *interprets* — formatting a `Ctrl+Shift+A` combo,
19//! serializing a binding to disk, rebasing a config from one layout to
20//! another — is **Champollion territory**: the job of consuming
21//! applications, which *use* the Rosette to accomplish their task.
22//!
23//! Concretely, the crate ships:
24//! - the four lookup primitives (forward / reverse / modifier / name parser);
25//! - one extension trait, [`KeyLocalizer`], for plugging in custom labels.
26//!
27//! It does *not* ship `format_combo()`, a binding serializer, or layout
28//! auto-detection. The README documents the canonical patterns the
29//! consumer assembles in 2–3 lines of glue code.
30//!
31//! ## The four public functions
32//!
33//! | Function | Purpose |
34//! |----------|---------|
35//! | [`resolve`][fn@resolve] | Forward lookup — every parallel representation in one pass |
36//! | [`scancode_for`] | Reverse lookup — physical key for a given keycode in a layout |
37//! | [`modifier_label`] | Platform-aware localized label for a held modifier |
38//! | [`keycode_from_name`] | Inverse of `SDL_GetKeyName` — parse a textual name |
39//!
40//! ## The one extension trait
41//!
42//! [`KeyLocalizer`] is the single point of customization. Implementors
43//! map a stable id (`"key_escape"`, `"mod_gui_mac"`, …) to a display
44//! string. The crate already ships a [`MultiLocalizer`] that aggregates
45//! every locale module enabled through Cargo features — most callers
46//! never need to write their own.
47//!
48//! Add your own only when you want to override or extend the default
49//! labels (e.g. brand-specific naming):
50//!
51//! ```
52//! use std::borrow::Cow;
53//! use sdl_keybridge::{KeyLocalizer, LabelStyle};
54//!
55//! /// A localizer that overrides the macOS Command label with "⌘ Cmd"
56//! /// and falls through to the built-in lookup for everything else.
57//! struct BrandedLocalizer;
58//!
59//! impl KeyLocalizer for BrandedLocalizer {
60//! fn translate(&self, key_id: &str, _locale: &str, _style: LabelStyle)
61//! -> Option<Cow<'static, str>>
62//! {
63//! match key_id {
64//! "mod_gui_mac" => Some(Cow::Borrowed("⌘ Cmd")),
65//! _ => None, // fall back to the runtime lookup chain
66//! }
67//! }
68//! }
69//! ```
70//!
71//! ## Cross-layout binding bridge
72//!
73//! Two lines of consumer code translate a binding from one layout to
74//! another through scancode-as-pivot:
75//!
76//! ```no_run
77//! use sdl_keybridge::{resolve, scancode_for, Keycode, KeyMod, LabelStyle, MultiLocalizer};
78//! # let keycode_ru = Keycode::from('ф');
79//! let loc = MultiLocalizer::new();
80//! let sc = scancode_for(keycode_ru, "windows/ru-t-k0-jcuken").unwrap();
81//! let r = resolve(sc, KeyMod::NONE, "windows/fr-t-k0-azerty", "fr", LabelStyle::Textual, &loc);
82//! // r.glyph_local is what the user sees on their French AZERTY keyboard.
83//! ```
84//!
85//! ## Module layout
86//!
87//! - [`scancode`] — [`Scancode`] newtype + USB HID / SDL constants
88//! - [`keycode`] — [`Keycode`] newtype + `SDLK_*` constants + scancode-mask helpers
89//! - [`keymod`] — [`KeyMod`] modifier bitmask (`KMOD_*`) + ergonomic predicates
90//! - [`named_key`] — [`NamedKey`] taxonomy with stable `"key_*"` ids
91//! - [`layout`] — [`Layout`] / [`LayoutKey`] structs + the static layout registry
92//! - [`localizer`] — [`KeyLocalizer`] trait, [`MultiLocalizer`], [`Platform`], [`Modifier`], [`LabelStyle`]
93//! - [`mod@resolve`] — the four public functions
94//!
95//! ## Cargo features
96//!
97//! Locales are feature-gated. The default feature is `en`. Pull only
98//! what you ship for, or use the `all-locales` aggregate.
99//!
100//! ```toml
101//! [dependencies]
102//! sdl-keybridge = { version = "0.1", features = ["fr", "de", "ja"] }
103//! ```
104//!
105//! Available codes: `ar`, `bn`, `cs`, `de`, `en`, `es`, `fi`, `fr`,
106//! `hi`, `id`, `it`, `ja`, `ko`, `nl`, `pl`, `pt`, `ru`, `sk`, `sv`,
107//! `sw`, `th`, `tr`, `ur`, `vi`, `zh-hans`, `zh-hant`.
108//!
109//! ## Bundled examples
110//!
111//! Two `examples/` binaries ship with the crate to validate the API
112//! without writing your own driver:
113//!
114//! - `cargo run --example showcase --all-features` — non-interactive
115//! walk-through of the eleven canonical scenarios (cross-layout char
116//! resolution, locale fallback, Caps/NumLock correctness, platform
117//! modifier glyphs, the bridge pattern, combo formatting).
118//! - `cargo run --example inspect --all-features -- <layout> <kind> <value> [mods] [locale] [platform]`
119//! — interactive inspector. Feed it a layout id and either a
120//! `scancode N`, a `keycode N` (decimal or hex), or a `name "Esc"`
121//! and it dumps the full [`Resolved`] in both styles, the four-level
122//! layout glyph table, every [`modifier_label`] for the chosen
123//! platform, and replays the same physical key on every other layout
124//! (cross-layout bridge demo). Pass `?` as the layout id to list the
125//! layouts compiled into the build.
126//!
127//! ## Non-goals
128//!
129//! Permanent — these will not land in this crate:
130//!
131//! - **Detecting the current OS layout.** No Rust solution reliably
132//! covers all five SDL platforms; the caller supplies the BCP 47 id.
133//! - **Dead keys / text composition.** A scancode + modifiers resolves
134//! to *one* glyph. Composition (`^` + `e` → `ê`) is the OS/IME's job
135//! via `SDL_StartTextInput`, not ours.
136//! - **`format_combo()`.** Presentation conventions vary; the consumer
137//! assembles labels with its preferred separator and ordering.
138//! - **Binding serialization.** Config format is the consumer's choice
139//! (JSON, INI, RON, binary, …).
140//! - **`sdl2` / `sdl3` feature flags.** A single API on primitive types
141//! covers both versions.
142
143#![cfg_attr(docsrs, feature(doc_cfg))]
144
145pub mod keycode;
146pub mod keymod;
147pub mod layout;
148pub mod localizer;
149pub mod named_key;
150pub mod resolve;
151pub mod scancode;
152
153mod locales;
154
155pub use keycode::{Keycode, SCANCODE_MASK};
156pub use keymod::KeyMod;
157pub use layout::{
158 all_layouts, get_layout, Layout, LayoutKey, CLDR3_LAYOUTS, CLDR_LAYOUTS, STD_NAMED_KEYS,
159};
160pub use localizer::{KeyLocalizer, LabelStyle, Modifier, MultiLocalizer, Platform};
161pub use named_key::NamedKey;
162pub use resolve::{keycode_from_name, modifier_label, resolve, scancode_for, Resolved};
163pub use scancode::Scancode;