Turn a keycode + modifiers into a character. The input module delivers HID
usage keycodes but nothing mapped them to characters; rather than hand-maintain
layout tables, vendor the X11 xkeyboard-config database and compile it to native
Zig at build time (no X11 runtime), the way make-initial-ramdisk.py packs the
ramdisk.
- tools/make-xkeyboard-config.py: `fetch` downloads the pinned xkeyboard-config
release (2.44, sha256-verified), resolves the include graph for the configured
layouts, and vendors only the reached symbols files + keysymdef.h + COPYING +
PROVENANCE into library/xkeyboard-config/vendor/. `generate` parses that
(keycodes via a HID->xkb-name table, symbols with include/augment/override and
per-key type, keysymdef for keysym->Unicode) and emits generated/layouts.zig
deterministically.
- library/xkeyboard-config/xkeyboard-config.zig: the API over the generated data
— map(layout, hid_usage, mods) -> { keysym, character }, byName, and the
level-selection semantics (the generated tables stay pure data). Host tests
assert US letters/digits with Shift/Caps, GB £ vs US # on Shift+3, and French
AZERTY q-where-US-has-a — the end-to-end proof of the parse->emit->lookup path.
- build.zig: `xkeyboard-config` + `layouts` modules, the test wired into
`zig build test`, and a `zig build gen-xkeyboard-config` convenience step.
- Layouts: us, gb, de, fr, es, dvorak. Scope (documented): group 1, no dead-key
composition, curated key types. Standalone library; wiring it into the input
path to fill KeyEvent.character is a documented follow-up.
zig build test green (incl. the new keymap tests); regeneration is byte-identical;
full QEMU suite 48/48 (unaffected — no kernel/runtime/service change).
66 lines
3.1 KiB
Markdown
66 lines
3.1 KiB
Markdown
# xkeyboard-config — X11 keyboard layouts, compiled to Zig
|
||
|
||
This module turns a physical key (a **USB HID usage**, as the [input module](../../docs/input.md)
|
||
delivers in `KeyEvent.keycode`) plus a modifier state into a **keysym** and, when the key
|
||
produces one, a **character** (a Unicode scalar). It is what lets a `keycode` become a
|
||
`character` — a keymap — without danos shipping an X11 runtime.
|
||
|
||
The layout data comes from the X11 [xkeyboard-config](https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config)
|
||
database, but it is **compiled to native Zig at build time** rather than parsed at runtime.
|
||
`tools/make-xkeyboard-config.py` reads the vendored xkb data and emits pure-data tables into
|
||
`generated/layouts.zig`; `xkeyboard-config.zig` is the hand-written API over them. This is
|
||
the same build-time-codegen pattern as `tools/make-initial-ramdisk.py`.
|
||
|
||
## Using it
|
||
|
||
```zig
|
||
const xkb = @import("xkeyboard-config");
|
||
|
||
const m = xkb.map(xkb.us, key_event.keycode, .{ .shift = shift_held, .caps_lock = caps });
|
||
if (m.character) |ch| { /* a printable Unicode scalar */ }
|
||
// m.keysym is always set (e.g. an X11 keysym for Return / F1 / a dead key).
|
||
|
||
const layout = xkb.byName("gb") orelse xkb.us; // choose a layout by name
|
||
for (xkb.all) |l| { /* enumerate available layouts */ }
|
||
```
|
||
|
||
`Modifiers` carries `shift`, `caps_lock`, `level3` (AltGr), and `control`. `map` selects the
|
||
level from the key's XKB *type* (the generated data) and those modifiers (the policy, in
|
||
`xkeyboard-config.zig`), so data and semantics stay separable.
|
||
|
||
Layouts: **us, gb, de, fr, es, dvorak**.
|
||
|
||
## Regenerating
|
||
|
||
```sh
|
||
python3 tools/make-xkeyboard-config.py fetch # network: download + vendor the data subset
|
||
python3 tools/make-xkeyboard-config.py generate # offline: emit generated/layouts.zig
|
||
# or, from the build:
|
||
zig build gen-xkeyboard-config
|
||
```
|
||
|
||
- **`fetch`** downloads the pinned xkeyboard-config release (version + sha256 in the script),
|
||
resolves the `include` graph for the configured layouts, and vendors *only* the symbols
|
||
files actually reached (plus `keysymdef.h`, `COPYING`, and `PROVENANCE.md`) into `vendor/`.
|
||
Run it when bumping the version or adding a layout.
|
||
- **`generate`** is deterministic and offline — same vendored input produces byte-identical
|
||
output. To add a layout, extend `TARGETS` (and `HID_TO_NAME` if a new physical key is
|
||
involved), then re-run `fetch` (to vendor any new includes) and `generate`.
|
||
|
||
## Scope
|
||
|
||
A pragmatic subset, enough for real Latin-script typing:
|
||
|
||
- **Group 1 only** — no multi-layout group switching.
|
||
- **No dead-key / compose composition** — a dead key returns its keysym with no `character`
|
||
(composing `´` + `e` → `é` is a higher layer's job).
|
||
- **Curated key types** — the common XKB types (one/two-level, alphabetic, four-level, …);
|
||
unmapped keys and unknown types fall back to level-by-shift.
|
||
- **6 layouts** — extend via `TARGETS` as above.
|
||
|
||
## Licensing
|
||
|
||
xkeyboard-config and `keysymdef.h` (xorgproto) are MIT/X11 licensed. The vendored data
|
||
subset carries the upstream `vendor/COPYING`, and `vendor/PROVENANCE.md` records the exact
|
||
version, source URL, and sha256. The generated tables are a derived work under the same terms.
|