danos/library/xkeyboard-config
Daniel Samson d7a154a596
Add xkeyboard-config: X11 keyboard layouts compiled to Zig
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).
2026-07-11 15:57:09 +01:00
..
generated Add xkeyboard-config: X11 keyboard layouts compiled to Zig 2026-07-11 15:57:09 +01:00
vendor Add xkeyboard-config: X11 keyboard layouts compiled to Zig 2026-07-11 15:57:09 +01:00
README.md Add xkeyboard-config: X11 keyboard layouts compiled to Zig 2026-07-11 15:57:09 +01:00
xkeyboard-config.zig Add xkeyboard-config: X11 keyboard layouts compiled to Zig 2026-07-11 15:57:09 +01:00

README.md

xkeyboard-config — X11 keyboard layouts, compiled to Zig

This module turns a physical key (a USB HID usage, as the input module 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 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

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

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.