3 Commits
Author SHA1 Message Date
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
Daniel Samson 1bf91115dd Generalize input module to mouse and joystick/gamepad events
Extend the input service beyond the keyboard so mouse and joystick/gamepad
drivers can broadcast too, with per-device publish and subscribe methods.

- protocol: KeyEvent joins MouseEvent (motion/buttons/scroll) and
  JoystickEvent (axes/buttons), all carried in a common InputEvent envelope
  tagged with a DeviceKind. A subscribe request carries a device_mask, so a
  subscriber names the classes it wants and the service routes each event only
  to interested subscribers (a mouse-only listener never wakes for keystrokes).
- runtime: per-device publish methods (publishKeyboardEvent/publishMouseEvent/
  publishJoystickEvent) and subscribe helpers (subscribeKeyboard/Mouse/Joystick,
  each typed, plus subscribe(mask)/subscribeAll returning the tagged envelope).
- service: subscriber table gains a device_mask; broadcast routes by the
  event's device class.
- mouse driver now publishes (synthetic) mouse events like the keyboard driver;
  input-source cycles all three classes; input-test subscribes to all and only
  emits its "ok" marker once it has received one of each class — so the passing
  test proves per-device routing, not just delivery. Real HID decoding stays a
  follow-up.

No kernel changes: ipc_send is generic and the 36-byte InputEvent fits its
64-byte payload. Full QEMU suite 48/48; serial log confirms keyboard, mouse,
and joystick all reach one subscription.
2026-07-11 15:21:09 +01:00
Daniel Samson 65244e3103 Add input module: broadcast keyboard events over IPC
Programs can now subscribe to keyboard events (key_down/key_up/key_press)
and drivers can broadcast them, through a new user-space input service.

The delivery model is forced by danos IPC: a synchronous rendezvous holds
one pending reply, so a server cannot park N subscribers blocked in a
"wait for next event" call — delivery must be push. But a synchronous push
has no timeout and the kernel never wakes a sender parked on a dead peer's
endpoint, so one dying subscriber would hang all input. So this lands the
roadmap's planned asynchronous buffered send and builds the service on it:

- ipc_send (syscall 26): non-blocking post to an endpoint's bounded payload
  ring, delivered through reply_wait as a buffered message (notify_message_bit).
  A full ring drops the oldest. It can never hang on a dead/slow peer.
- input-protocol + runtime.input helpers (subscribe/next, connectSource/
  publish) — the first real consumer of M13 capability passing: a subscriber
  hands the service its own endpoint as a capability.
- input service (fan-out via ipc_send, dead-subscriber pruning), a synthetic
  input-source, and input-test; the ps2-bus keyboard driver publishes to it.
  Real IRQ1 scancode decoding (which must live in the bus, the PNP0303 owner)
  is a documented follow-up; the source is synthetic for now.
- build/init wiring, an `input` QEMU case, and docs/input.md.

Full QEMU suite 48/48, including the new input case and every IPC/endpoint
regression (ipc, ipc-call, ipc-cap, vfs, hpet, bus, irqfree).
2026-07-11 15:03:24 +01:00
35 changed files with 12066 additions and 6 deletions
+56
View File
@@ -178,6 +178,13 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("system/services/vfs/protocol.zig"), .root_source_file = b.path("system/services/vfs/protocol.zig"),
}); });
// The input wire protocol: the input service's public interface, exposed as its own
// module the same way vfs-protocol is. Shared by the input service, the runtime's
// `input` helper (subscribe/publish), and every source and subscriber.
const input_protocol_module = b.addModule("input-protocol", .{
.root_source_file = b.path("system/services/input/protocol.zig"),
});
// The danos-native user-space runtime: system_call wrappers, the C-convention // The danos-native user-space runtime: system_call wrappers, the C-convention
// heap, IPC helpers, the process start shim, device access. This is the stable // heap, IPC helpers, the process start shim, device access. This is the stable
// application ABI; POSIX compatibility is a separate library on top (see below). // application ABI; POSIX compatibility is a separate library on top (see below).
@@ -193,6 +200,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "abi", .module = abi_module }, .{ .name = "abi", .module = abi_module },
.{ .name = "device-abi", .module = device_abi_module }, .{ .name = "device-abi", .module = device_abi_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module }, .{ .name = "vfs-protocol", .module = vfs_protocol_module },
.{ .name = "input-protocol", .module = input_protocol_module },
}, },
}); });
@@ -203,6 +211,21 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("library/mmio/mmio.zig"), .root_source_file = b.path("library/mmio/mmio.zig"),
}); });
// Keyboard layouts compiled from the X11 xkeyboard-config database into native Zig
// (keycode + modifiers -> keysym/character). The `layouts` tables are generated by
// tools/make-xkeyboard-config.py; `xkeyboard-config` is the hand-written API over them.
// No target set, so each inherits its importer's. See library/xkeyboard-config/.
const xkb_layouts_module = b.addModule("layouts", .{
.root_source_file = b.path("library/xkeyboard-config/generated/layouts.zig"),
});
const xkeyboard_config_module = b.addModule("xkeyboard-config", .{
.root_source_file = b.path("library/xkeyboard-config/xkeyboard-config.zig"),
.imports = &.{
.{ .name = "layouts", .module = xkb_layouts_module },
},
});
_ = xkeyboard_config_module;
// The POSIX / C compatibility layer, a separate library layered strictly over the // The POSIX / C compatibility layer, a separate library layered strictly over the
// runtime (it calls the runtime's IPC/heap, never system calls directly). This is // runtime (it calls the runtime's IPC/heap, never system calls directly). This is
// the one place POSIX/C spellings are allowed verbatim — see docs/coding-standards.md // the one place POSIX/C spellings are allowed verbatim — see docs/coding-standards.md
@@ -302,6 +325,11 @@ pub fn build(b: *std.Build) void {
const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig"); const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig");
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig"); const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "device-manager", "system/services/device-manager/device-manager.zig"); const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "device-manager", "system/services/device-manager/device-manager.zig");
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "input", "system/services/input/input.zig");
const input_source_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "input-source", "system/services/input-source/input-source.zig");
const input_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "input-test", "system/services/input-test/input-test.zig");
const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "args-echo", "system/services/args-echo/args-echo.zig"); const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "args-echo", "system/services/args-echo/args-echo.zig");
const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "process-test", "system/services/process-test/process-test.zig"); const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "process-test", "system/services/process-test/process-test.zig");
@@ -327,6 +355,12 @@ pub fn build(b: *std.Build) void {
mk_run.addFileArg(ps2_mouse_exe.getEmittedBin()); mk_run.addFileArg(ps2_mouse_exe.getEmittedBin());
mk_run.addArg("device-manager"); mk_run.addArg("device-manager");
mk_run.addFileArg(device_manager_exe.getEmittedBin()); mk_run.addFileArg(device_manager_exe.getEmittedBin());
mk_run.addArg("input");
mk_run.addFileArg(input_exe.getEmittedBin());
mk_run.addArg("input-source");
mk_run.addFileArg(input_source_exe.getEmittedBin());
mk_run.addArg("input-test");
mk_run.addFileArg(input_test_exe.getEmittedBin());
mk_run.addArg("args-echo"); mk_run.addArg("args-echo");
mk_run.addFileArg(args_echo_exe.getEmittedBin()); mk_run.addFileArg(args_echo_exe.getEmittedBin());
mk_run.addArg("process-test"); mk_run.addArg("process-test");
@@ -337,6 +371,7 @@ pub fn build(b: *std.Build) void {
for ([_]struct { *std.Build.Step.Compile, []const u8 }{ for ([_]struct { *std.Build.Step.Compile, []const u8 }{
.{ vfs_exe, "system/services" }, .{ vfs_exe, "system/services" },
.{ device_manager_exe, "system/services" }, .{ device_manager_exe, "system/services" },
.{ input_exe, "system/services" },
.{ hpet_exe, "system/drivers" }, .{ hpet_exe, "system/drivers" },
.{ bus_exe, "system/drivers" }, .{ bus_exe, "system/drivers" },
.{ ps2_bus_exe, "system/drivers" }, .{ ps2_bus_exe, "system/drivers" },
@@ -482,4 +517,25 @@ pub fn build(b: *std.Build) void {
}); });
test_step.dependOn(&b.addRunArtifact(mod_tests).step); test_step.dependOn(&b.addRunArtifact(mod_tests).step);
} }
// The xkeyboard-config keymap tests need its generated `layouts` import wired, so they
// don't fit the plain loop above. Its keycode->character assertions are the end-to-end
// proof that the xkb-data -> generator -> Zig-lookup pipeline is correct.
const xkb_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("library/xkeyboard-config/xkeyboard-config.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "layouts", .module = xkb_layouts_module },
},
}),
});
test_step.dependOn(&b.addRunArtifact(xkb_tests).step);
// Convenience: `zig build gen-xkeyboard-config` regenerates the layout tables from the
// vendored data (offline). `fetch` (the network step) stays a manual script run.
const gen_xkb = b.addSystemCommand(&.{ "python3", "tools/make-xkeyboard-config.py", "generate" });
const gen_xkb_step = b.step("gen-xkeyboard-config", "Regenerate library/xkeyboard-config/generated from the vendored data");
gen_xkb_step.dependOn(&gen_xkb.step);
} }
+5 -1
View File
@@ -52,7 +52,11 @@ rather than restate it. Roughly in the order things happen at runtime:
microkernel's `ps`/`kill`/SIGCHLD: enumerate as a table snapshot, the microkernel's `ps`/`kill`/SIGCHLD: enumerate as a table snapshot, the
supervision link as the kill authority, and child-exit notifications over the supervision link as the kill authority, and child-exit notifications over the
same endpoints IRQs arrive on. same endpoints IRQs arrive on.
16. **[halting.md](halting.md) — halting.** Why a kernel can't just "exit", and 16. **[input.md](input.md) — the input module.** Broadcasting input events (keyboard,
mouse, joystick): why a synchronous rendezvous can't fan out to many listeners, the
asynchronous `ipc_send` primitive built to fix it, and the per-device subscribe/publish
service layered on top.
17. **[halting.md](halting.md) — halting.** Why a kernel can't just "exit", and
how `while (true) hlt` parks the CPU safely once there's nothing left to do. how `while (true) hlt` parks the CPU safely once there's nothing left to do.
Start with the north star: Start with the north star:
+147
View File
@@ -0,0 +1,147 @@
# The input module: broadcasting input events
A keyboard driver has one keystroke and *many* programs that might want it — a shell, a
window server, a logger. None of them owns the hardware, and the driver should not know
who is listening. So between the drivers and the listeners sits the **input service**
(`system/services/input/`): drivers **publish** events to it, programs **subscribe**, and
it fans each event out to every interested subscriber. It is an ordinary ring-3 process
reached over IPC, like the [VFS server](../system/services/vfs/vfs.zig) — no kernel knows
what a key is.
## One service, several device classes
The service carries three device classes today — **keyboard**, **mouse**, and
**joystick/gamepad** — and is built to take more
([protocol.zig](../system/services/input/protocol.zig)). Each class has its own typed
event:
- `KeyEvent` — `key_down`/`key_up` (physical make/break) and `key_press` (a character was
produced, carrying the Unicode scalar); plus a layout-independent `keycode` and a
`modifiers` bitmask.
- `MouseEvent` — relative `motion` (`dx`/`dy`), `button_down`/`button_up`, and `scroll`.
- `JoystickEvent` — `axis` moves (a signed value on a `control` index) and
`button_down`/`button_up`.
All three travel in one **`InputEvent` envelope** tagged with a `DeviceKind`, so the
fan-out is a single code path and a subscriber can take a mix of classes on one stream.
Decode an envelope with `asKeyboard()` / `asMouse()` / `asJoystick()` (each returns null
unless the tag matches). A subscriber names the classes it wants with a **`device_mask`**,
and the service routes each event only to subscribers whose mask includes its class — so a
mouse-only listener never wakes for keystrokes.
## Why this needed a new kernel primitive
The interesting part is delivery, and it runs straight into the shape of danos IPC.
[ipc.md](ipc.md) describes a **synchronous rendezvous**: a server holds exactly one
pending reply (`Task.ipc_client`) and *must* answer it on its next `replyWait`. Two
consequences decide the whole design:
1. **You cannot block N subscribers waiting for "the next event".** A server can hold only
one caller at a time, so the natural "subscriber calls `next_event()` and blocks" API
is impossible for more than one subscriber. Delivery therefore has to be **push** — the
service reaching out to subscribers — not pull.
2. **A synchronous push can hang the whole service.** If the service delivered with
`ipc_call`, it would block until each subscriber replied. `ipc_call` has no timeout, and
the kernel does **not** wake a caller parked on a *dead* peer's endpoint (it only fails a
peer that was mid-reply — see [process.zig](../system/kernel/process.zig)
`releaseTaskResourcesLocked`). One subscriber that exits mid-delivery would wedge input
for everyone. That is the opposite of the resilience the microkernel is for.
The fix is the asynchronous send that [ipc.md](ipc.md) had already earmarked as future
work ("asynchronous / buffered send … for notifications between servers"):
```
ipc_send(handle, message_ptr, message_len) -> 0 / -errno
```
`ipc_send` copies a small payload into the endpoint's **bounded queue** and wakes a
receiver, then returns immediately — it never blocks and so can never hang on a dead or
slow subscriber. The receiver picks it up through the same `replyWait` it already runs:
the wake arrives as a **buffered message** — `notify_badge_bit | notify_message_bit` set in
the badge (distinguishing it from a bare IRQ/child-exit notification), the sender's task id
in the low bits, and the payload in the receive buffer, with no reply owed. The queue holds
16 messages per endpoint; a full queue **drops the oldest**, because a buffered message is
discrete data, not a coalescing "level" like an interrupt. See
[ipc-synchronous.zig](../system/kernel/ipc-synchronous.zig) (`sendLocked`, `popPost`, and
the `replyWait` receive loop).
This is the async counterpart of `ipc_call`, and the input service is its first consumer.
## How the pieces fit
```
keyboard/mouse driver, input-source input service subscriber(s)
----------------------------------- ------------- -------------
connectSource(); loop: replyWait: subscribeKeyboard()/…All:
publishKeyboardEvent(k) ─ ipc_call ─▶ publish → broadcast: createIpcEndpoint()
publishMouseEvent(m) for each sub whose callCap(subscribe,
publishJoystickEvent(j) mask matches event.device: send_cap = ep,
ipc_send(sub_ep) ──────▶ device_mask)
reply ok loop: next()
subscribe → store {ep cap, └─ replyWait(ep)
task id, device_mask} → InputEvent
```
- A **subscriber** calls `input.subscribe(mask)` — or a typed helper: `subscribeKeyboard()`,
`subscribeMouse()`, `subscribeJoystick()` (one class, `next()` returns the decoded event),
or `subscribeAll()` (every class, `next()` returns a tagged `InputEvent`)
([library/runtime/input.zig](../library/runtime/input.zig)). It creates its own endpoint
and hands it to the service as a **capability** (M13 capability passing — the input
service is that feature's first real user), along with its `device_mask`. Then it loops on
`next()`, a `replyWait` on that endpoint returning each pushed event.
- A **source** (a keyboard, mouse, or joystick driver) calls `input.connectSource()` and the
method for its class: `publishKeyboardEvent`, `publishMouseEvent`, or
`publishJoystickEvent`. Publishing is a short synchronous `ipc_call` the service answers at
once; the service's own fan-out is asynchronous, so publishing never blocks on a slow
subscriber.
- The **service** ([input.zig](../system/services/input/input.zig)) keeps a small subscriber
table (endpoint handle + owning task id + `device_mask`). On `publish` it `ipc_send`s the
event to every subscriber whose mask includes the event's device class. On `subscribe` it
stores the passed capability and mask and, as housekeeping, prunes any slot whose owning
process has exited (checked against `process_enumerate`) — not for correctness (an async
send to an orphaned endpoint is harmless) but to reclaim the slot.
Publisher and subscriber must be **separate processes**: a single thread that both
published and serviced its own subscription would deadlock (its `publish` call blocks until
the service delivers to its endpoint, which only the same thread could receive).
## Status and follow-ups
- **Synthetic sources, for now.** The `ps2-bus` driver owns PNP0303, which carries *both*
the 0x60/0x64 ports and IRQ1, so reading real scancodes/packets has to live in the bus,
not in [keyboard.zig](../system/drivers/ps2-bus/keyboard.zig) /
[mouse.zig](../system/drivers/ps2-bus/mouse.zig). Until that lands, the keyboard driver
publishes a synthetic key stream, the mouse driver a synthetic motion/click stream, and
the hardware-free `input-source` rotates through all three classes (including a synthetic
joystick, which has no driver yet) — all via the `input.synthetic*Event` helpers. The
fan-out and per-device routing are real; only the bytes are placeholder. **Follow-up:** the
bus binds IRQ1/IRQ12, reads port 0x60, and `ps2-library` decodes scan-set-1 →
keycodes and mouse packets; the drivers publish decoded events.
- **Keycode → character** is a keymap, and danos has one:
[`library/xkeyboard-config`](../library/xkeyboard-config/README.md) compiles the X11
xkeyboard-config layouts (us, gb, de, fr, …) to native Zig —
`xkb.map(layout, keycode, mods)` → keysym + Unicode character. Wiring it in to fill a
`key_press` event's `character` (in the keyboard driver, or a small keymap service) is the
natural next step.
- **Drop-oldest under overflow** is a defined loss; the 16-slot ring absorbs normal bursts.
Real backpressure/flow-control is future work.
- **`publish` is unauthenticated** — any process may publish, consistent with the current
bring-up trust model (see [driver-model.md](driver-model.md)). A source capability is
future work.
## Verifying it
The `input` case (`python3 test/qemu_test.py input`, in
[tests.zig](../system/kernel/tests.zig) `inputTest`) boots the real kernel and spawns the
service, the synthetic source (which cycles keyboard, mouse, and joystick events), and a
subscriber that took all three classes. It passes only when the subscriber heartbeats
`input-test: ok` — proof that an event travelled source → service → subscriber over IPC,
exercising `ipc_send`, capability-passing subscription, and per-device routing. Each
serial line names the class received, so the log shows all three arriving on one stream.
## See also
- [ipc.md](ipc.md) — the synchronous rendezvous and the notification path `ipc_send` extends.
- [syscall.md](syscall.md) — the system-call surface, including `ipc_send`.
- [driver-model.md](driver-model.md) — class drivers, capability passing (M13), the trust model.
+6 -1
View File
@@ -95,6 +95,11 @@ This is what makes a user-space driver possible at all, and it's the subject of
every capability is either well-known (the registry) or inherited — there's no way every capability is either well-known (the registry) or inherited — there's no way
to delegate one. to delegate one.
- **Asynchronous / buffered send** for the cases where a rendezvous is the wrong - **Asynchronous / buffered send** for the cases where a rendezvous is the wrong
shape (logging, notifications between servers). shape (logging, notifications between servers). *Landed as `ipc_send`* — a
non-blocking post to an endpoint's bounded payload queue, delivered through
`reply_wait` as a buffered message (badge bit `notify_message_bit`). Built for, and
first used by, the [input service](input.md)'s keyboard-event broadcast, where a
synchronous push would let one dead subscriber hang the fan-out. A full queue drops
the oldest (discrete messages, not a coalescing level like the notification ring).
- **A bounded reply.** `MSG_MAX` is 256 bytes and the copy runs under the big kernel - **A bounded reply.** `MSG_MAX` is 256 bytes and the copy runs under the big kernel
lock; a bulk transfer wants shared pages, not a copy. lock; a bulk transfer wants shared pages, not a copy.
+2
View File
@@ -47,6 +47,8 @@ Everything else---including`read()`,`write()`,`malloc()`, and`fork()`---will run
- **What it does:**Used strictly by your background user-space servers (like your disk driver or filesystem). It sends a reply to the last client that called it, and immediately puts the server to sleep until the next request arrives.[[1](https://news.ycombinator.com/item?id=33078441)] - **What it does:**Used strictly by your background user-space servers (like your disk driver or filesystem). It sends a reply to the last client that called it, and immediately puts the server to sleep until the next request arrives.[[1](https://news.ycombinator.com/item?id=33078441)]
3. **`Yield()`/`Thread_Ctrl()`** 3. **`Yield()`/`Thread_Ctrl()`**
- **What it does:**Allows a thread to voluntarily give up its CPU time slice, or allows a root task to spawn/kill threads. - **What it does:**Allows a thread to voluntarily give up its CPU time slice, or allows a root task to spawn/kill threads.
4. **`ipc_send(endpoint, message_buffer)`(Asynchronous Send)**
- **What it does:**Posts a small payload to an endpoint's bounded queue and returns *without* blocking — no rendezvous, no reply. The receiver picks it up through the same `IPC_ReplyWait`, as a buffered message. It is the async counterpart of `IPC_Call`, for one-to-many broadcasts where a synchronous rendezvous would let one dead or slow receiver hang the sender. The [input service](input.md) — keyboard-event fan-out — is its first user. A full queue drops the oldest message (a buffered message is discrete data, unlike a coalescing interrupt notification).
* * * * * * * * * *
+219
View File
@@ -0,0 +1,219 @@
//! User-space input helpers: the client and publisher sides of the input service, so a
//! program listening for input events — or a driver broadcasting them — doesn't hand-roll
//! the IPC. Layered over `ipc` (endpoints, capability passing, `send`) and the shared
//! `input-protocol` wire format, the way `device.zig` layers over the raw `device_*` calls.
//! See system/services/input/input.zig.
//!
//! The service carries several device classes (keyboard, mouse, joystick/gamepad). A
//! **source** publishes its class with the matching method:
//! var source = input.connectSource() orelse return;
//! _ = source.publishKeyboardEvent(.{ .kind = ..., .keycode = ..., ... });
//! _ = source.publishMouseEvent(.{ ... });
//! _ = source.publishJoystickEvent(.{ ... });
//!
//! A **subscriber** either takes one class with a typed helper —
//! var keys = input.subscribeKeyboard() orelse return;
//! while (true) { const key = keys.next() orelse continue; ... }
//! — or takes several at once and inspects the tagged envelope:
//! var listener = input.subscribeAll() orelse return;
//! while (true) {
//! const event = listener.next() orelse continue;
//! if (event.asKeyboard()) |k| { ... } else if (event.asMouse()) |m| { ... }
//! }
const std = @import("std");
const abi = @import("abi");
const ipc = @import("ipc.zig");
const system = @import("system.zig");
const protocol = @import("input-protocol");
pub const DeviceKind = protocol.DeviceKind;
pub const InputEvent = protocol.InputEvent;
pub const KeyEvent = protocol.KeyEvent;
pub const MouseEvent = protocol.MouseEvent;
pub const JoystickEvent = protocol.JoystickEvent;
pub const EventKind = protocol.EventKind;
pub const MouseEventKind = protocol.MouseEventKind;
pub const JoystickEventKind = protocol.JoystickEventKind;
pub const Keycode = protocol.Keycode;
/// Interest masks re-exported so a caller can `subscribe(input.device_keyboard | input.device_mouse)`.
pub const device_keyboard = protocol.device_keyboard;
pub const device_mouse = protocol.device_mouse;
pub const device_joystick = protocol.device_joystick;
pub const device_all = protocol.device_all;
/// Look up the input service, retrying while it is still coming up. Both a subscriber and
/// a source race the service's registration at boot, so both wait for it here rather than
/// failing. Returns the service endpoint handle, or null if it never appears.
fn lookupService() ?ipc.Handle {
var attempts: usize = 0;
while (attempts < 100) : (attempts += 1) {
if (ipc.lookup(.input)) |handle| return handle;
system.sleep(50);
}
return null;
}
// --- subscribing ------------------------------------------------------------
/// A subscription to the input service: our own endpoint, which the service pushes events
/// to. `next` returns each event as a tagged `InputEvent`; use `asKeyboard`/`asMouse`/
/// `asJoystick` to decode. Created with `subscribe`/`subscribeAll`; for a single device
/// class prefer the typed helpers (`subscribeKeyboard`, ...), which return decoded events.
pub const Subscriber = struct {
/// The endpoint the service delivers events to (created and owned by us; its handle
/// was handed to the service as a capability at subscribe time).
endpoint: ipc.Handle,
receive: [protocol.event_size]u8 = undefined,
/// Block until the next event is pushed, and return it. Events arrive as asynchronous
/// buffered messages (`ipc_send` from the service), so nothing is owed in reply — the
/// empty reply this issues is a harmless no-op. Returns null for any non-event wake-up
/// (there should be none), so callers can loop.
pub fn next(self: *Subscriber) ?InputEvent {
const got = ipc.replyWait(self.endpoint, &.{}, &self.receive, null);
if (!got.isMessage() or got.len < protocol.event_size) return null;
return std.mem.bytesToValue(InputEvent, self.receive[0..protocol.event_size]);
}
};
/// Subscribe to the input classes named in `device_mask` (an OR of `device_*`, or
/// `device_all`). Creates an endpoint for the service to push to and hands it over as a
/// capability. Returns a `Subscriber` to loop `next` on, or null on failure.
pub fn subscribe(device_mask: u32) ?Subscriber {
const service = lookupService() orelse return null;
const endpoint = ipc.createIpcEndpoint() orelse return null;
var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.subscribe), .device_mask = device_mask };
var reply: [protocol.reply_size]u8 = undefined;
const result = ipc.callCap(service, std.mem.asBytes(&request), &reply, endpoint) catch return null;
if (result.len < protocol.reply_size) return null;
if (std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status != 0) return null;
return .{ .endpoint = endpoint };
}
/// Subscribe to every input class (keyboard, mouse, joystick) on one stream.
pub fn subscribeAll() ?Subscriber {
return subscribe(device_all);
}
/// A subscriber filtered to keyboard events, whose `next` returns a decoded `KeyEvent`.
pub const KeyboardSubscriber = struct {
inner: Subscriber,
pub fn next(self: *KeyboardSubscriber) ?KeyEvent {
return (self.inner.next() orelse return null).asKeyboard();
}
};
/// A subscriber filtered to mouse events, whose `next` returns a decoded `MouseEvent`.
pub const MouseSubscriber = struct {
inner: Subscriber,
pub fn next(self: *MouseSubscriber) ?MouseEvent {
return (self.inner.next() orelse return null).asMouse();
}
};
/// A subscriber filtered to joystick/gamepad events, whose `next` returns a decoded
/// `JoystickEvent`.
pub const JoystickSubscriber = struct {
inner: Subscriber,
pub fn next(self: *JoystickSubscriber) ?JoystickEvent {
return (self.inner.next() orelse return null).asJoystick();
}
};
/// Subscribe to keyboard events only; `next` returns decoded `KeyEvent`s.
pub fn subscribeKeyboard() ?KeyboardSubscriber {
return .{ .inner = subscribe(device_keyboard) orelse return null };
}
/// Subscribe to mouse events only; `next` returns decoded `MouseEvent`s.
pub fn subscribeMouse() ?MouseSubscriber {
return .{ .inner = subscribe(device_mouse) orelse return null };
}
/// Subscribe to joystick/gamepad events only; `next` returns decoded `JoystickEvent`s.
pub fn subscribeJoystick() ?JoystickSubscriber {
return .{ .inner = subscribe(device_joystick) orelse return null };
}
// --- publishing -------------------------------------------------------------
/// A connection to the input service for a source (a keyboard/mouse/joystick driver) that
/// publishes events. Each `publish*Event` is a short synchronous call the service answers
/// at once; its own fan-out to subscribers is asynchronous, so publishing never blocks on
/// a slow subscriber.
pub const Publisher = struct {
service: ipc.Handle,
fn publish(self: Publisher, event: InputEvent) bool {
var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.publish), .event = event };
var reply: [protocol.reply_size]u8 = undefined;
const len = ipc.call(self.service, std.mem.asBytes(&request), &reply) catch return false;
if (len < protocol.reply_size) return false;
return std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status == 0;
}
/// Broadcast a keyboard event to every subscriber that took keyboard events.
pub fn publishKeyboardEvent(self: Publisher, event: KeyEvent) bool {
return self.publish(InputEvent.fromKeyboard(event));
}
/// Broadcast a mouse event to every subscriber that took mouse events.
pub fn publishMouseEvent(self: Publisher, event: MouseEvent) bool {
return self.publish(InputEvent.fromMouse(event));
}
/// Broadcast a joystick/gamepad event to every subscriber that took joystick events.
pub fn publishJoystickEvent(self: Publisher, event: JoystickEvent) bool {
return self.publish(InputEvent.fromJoystick(event));
}
};
/// Connect to the input service as an event source, waiting for it to come up. Returns a
/// `Publisher`, or null if the service never registered.
pub fn connectSource() ?Publisher {
return .{ .service = lookupService() orelse return null };
}
// --- synthetic scaffolding --------------------------------------------------
/// Synthetic key events, shared by the demo source and the keyboard driver's placeholder
/// stream while real scancode decoding is still a follow-up. `step` rolls through A..E,
/// emitting for each key a `key_down`, then a `key_press` carrying the character, then a
/// `key_up`. Scaffolding, not wire protocol — hence it lives with the helpers.
pub fn syntheticKeyEvent(step: usize) KeyEvent {
const Key = struct { code: Keycode, character: u32 };
const keys = [_]Key{
.{ .code = .a, .character = 'A' },
.{ .code = .b, .character = 'B' },
.{ .code = .c, .character = 'C' },
.{ .code = .d, .character = 'D' },
.{ .code = .e, .character = 'E' },
};
const key = keys[(step / 3) % keys.len];
return switch (step % 3) {
0 => .{ .kind = @intFromEnum(EventKind.key_down), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
1 => .{ .kind = @intFromEnum(EventKind.key_press), .keycode = @intFromEnum(key.code), .character = key.character, .modifiers = 0 },
else => .{ .kind = @intFromEnum(EventKind.key_up), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
};
}
/// Synthetic mouse events (placeholder until real PS/2 packet decoding). `step` alternates
/// a small diagonal motion with a left-button click.
pub fn syntheticMouseEvent(step: usize) MouseEvent {
return switch (step % 3) {
0 => .{ .kind = @intFromEnum(MouseEventKind.motion), .button = 0, .dx = 1, .dy = 1, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
1 => .{ .kind = @intFromEnum(MouseEventKind.button_down), .button = protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = protocol.mouse_button_left },
else => .{ .kind = @intFromEnum(MouseEventKind.button_up), .button = protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
};
}
/// Synthetic joystick/gamepad events (placeholder until a real controller driver). `step`
/// sweeps axis 0 and toggles button 0.
pub fn syntheticJoystickEvent(step: usize) JoystickEvent {
return switch (step % 3) {
0 => .{ .kind = @intFromEnum(JoystickEventKind.axis), .control = 0, .value = 16384, .buttons = 0 },
1 => .{ .kind = @intFromEnum(JoystickEventKind.button_down), .control = 0, .value = 0, .buttons = 1 },
else => .{ .kind = @intFromEnum(JoystickEventKind.button_up), .control = 0, .value = 0, .buttons = 0 },
};
}
+29
View File
@@ -79,6 +79,16 @@ pub fn call(h: Handle, message: []const u8, reply: []u8) CallError!usize {
return (try callCap(h, message, reply, null)).len; return (try callCap(h, message, reply, null)).len;
} }
/// Post `message` to endpoint `h`'s asynchronous queue and return immediately — no
/// rendezvous, no reply, no blocking. The receiver picks it up through `replyWait` as a
/// buffered message (`Received.isMessage`). Unlike `call`, this **cannot hang on a dead
/// or slow peer**, which is why a broadcaster (the input service) delivers events this
/// way. The payload must fit an endpoint slot (64 bytes); a full queue drops the oldest
/// message. Returns false on failure (bad handle, oversized payload, bad buffer).
pub fn send(h: Handle, message: []const u8) bool {
return !failed(sc.systemCall3(.ipc_send, h, @intFromPtr(message.ptr), message.len));
}
/// Set in `Received.badge` when what arrived is an asynchronous notification — a /// Set in `Received.badge` when what arrived is an asynchronous notification — a
/// bound device interrupt — rather than a client's message. The low bits carry the /// bound device interrupt — rather than a client's message. The low bits carry the
/// GSI. See `isNotification`. /// GSI. See `isNotification`.
@@ -89,6 +99,12 @@ pub const notify_badge_bit: u64 = abi.notify_badge_bit;
/// rather than a device interrupt. The low bits carry the child's process id. /// rather than a device interrupt. The low bits carry the child's process id.
pub const notify_exit_bit: u64 = abi.notify_exit_bit; pub const notify_exit_bit: u64 = abi.notify_exit_bit;
/// Set alongside `notify_badge_bit` when the wake-up is a **buffered message** — a payload
/// posted with `send` (`ipc_send`) — rather than a bare device interrupt or child-exit
/// notice. The payload is in the `replyWait` receive buffer (`Received.len` bytes); the
/// low bits of the badge carry the sender's task id. See `Received.isMessage`.
pub const notify_message_bit: u64 = abi.notify_message_bit;
/// The result of a `replyWait`: the request length, the sender's badge (a task id, or /// The result of a `replyWait`: the request length, the sender's badge (a task id, or
/// an IRQ notification if the high bit is set), and any capability the request carried. /// an IRQ notification if the high bit is set), and any capability the request carried.
pub const Received = struct { pub const Received = struct {
@@ -109,6 +125,19 @@ pub const Received = struct {
return self.isNotification() and self.badge & notify_exit_bit != 0; return self.isNotification() and self.badge & notify_exit_bit != 0;
} }
/// True if this wake-up is a **buffered message** posted with `send` (`ipc_send`):
/// there is a payload in the receive buffer (`self.len` bytes) and no reply is owed.
/// The subscriber side of a broadcast branches on this.
pub fn isMessage(self: Received) bool {
return self.isNotification() and self.badge & notify_message_bit != 0;
}
/// The task id of whoever posted a buffered message, meaningful only when
/// `isMessage`. (The badge's low bits, with the three high marker bits masked off.)
pub fn senderTaskId(self: Received) u32 {
return @intCast(self.badge & ~(notify_badge_bit | notify_exit_bit | notify_message_bit));
}
/// The interrupt source (a GSI), meaningful only when `isNotification` and /// The interrupt source (a GSI), meaningful only when `isNotification` and
/// not `isChildExit`. /// not `isChildExit`.
pub fn source(self: Received) u64 { pub fn source(self: Received) u64 {
+5
View File
@@ -16,6 +16,11 @@ pub const ipc = @import("ipc.zig");
pub const start = @import("start.zig"); pub const start = @import("start.zig");
/// The VFS wire protocol (shared with the VFS server). /// The VFS wire protocol (shared with the VFS server).
pub const vfs_protocol = @import("vfs-protocol"); pub const vfs_protocol = @import("vfs-protocol");
/// Keyboard-event listening (subscribe/next) and broadcasting (publish), over the input
/// service. See library/runtime/input.zig and system/services/input/.
pub const input = @import("input.zig");
/// The input wire protocol (shared with the input service and its clients).
pub const input_protocol = @import("input-protocol");
/// POSIX-style file API: open/read/write/lseek/stat/close. /// POSIX-style file API: open/read/write/lseek/stat/close.
/// C stdio: fopen/fread/fwrite/fseek/ftell/fclose over unistd. /// C stdio: fopen/fread/fwrite/fseek/ftell/fclose over unistd.
/// Device access for drivers: enumerate/claim/mmioMap. /// Device access for drivers: enumerate/claim/mmioMap.
+65
View File
@@ -0,0 +1,65 @@
# 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.
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
Copyright 1996 by Joseph Moss
Copyright (C) 2002-2007 Free Software Foundation, Inc.
Copyright (C) Dmitry Golubev <lastguru@mail.ru>, 2003-2004
Copyright (C) 2004, Gregory Mokhin <mokhin@bog.msu.ru>
Copyright (C) 2006 Erdal Ronahî
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation, and that the name of the copyright holder(s) not be used in
advertising or publicity pertaining to distribution of the software without
specific, written prior permission. The copyright holder(s) makes no
representations about the suitability of this software for any purpose. It
is provided "as is" without express or implied warranty.
THE COPYRIGHT HOLDER(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
Copyright (c) 1996 Digital Equipment Corporation
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL DIGITAL EQUIPMENT CORPORATION BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the Digital Equipment
Corporation shall not be used in advertising or otherwise to promote
the sale, use or other dealings in this Software without prior written
authorization from Digital Equipment Corporation.
Copyright 1996, 1998 The Open Group
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of The Open Group shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from The Open Group.
Copyright 2004-2005 Sun Microsystems, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
Permission to use, copy, modify, and distribute this
software and its documentation for any purpose and without
fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright
notice and this permission notice appear in supporting
documentation, and that the name of Silicon Graphics not be
used in advertising or publicity pertaining to distribution
of the software without specific prior written permission.
Silicon Graphics makes no representation about the suitability
of this software for any purpose. It is provided "as is"
without any express or implied warranty.
SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
THE USE OR PERFORMANCE OF THIS SOFTWARE.
Copyright (c) 1996 X Consortium
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the X Consortium shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from the X Consortium.
Copyright (C) 2004, 2006 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization of
the copyright holder.
Copyright (C) 1999, 2000 by Anton Zinoviev <anton@lml.bas.bg>
This software may be used, modified, copied, distributed, and sold,
in both source and binary form provided that the above copyright
and these terms are retained. Under no circumstances is the author
responsible for the proper functioning of this software, nor does
the author assume any responsibility for damages incurred with its
use.
Permission is granted to anyone to use, distribute and modify
this file in any way, provided that the above copyright notice
is left intact and the author of the modification summarizes
the changes in this header.
This file is distributed without any expressed or implied warranty.
+21
View File
@@ -0,0 +1,21 @@
# Vendored xkeyboard-config subset
- **Package**: xkeyboard-config 2.44
- **Source**: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/archive/xkeyboard-config-2.44/xkeyboard-config-2.44.tar.gz
- **sha256**: `35e34edeaf4e8da8d0696ff6b241ee11ddb1b8c6730bac7252d4d0a88ea5f05b`
- **keysymdef.h**: xorgproto, copied from `/opt/homebrew/include/X11/keysymdef.h`
- **License**: MIT/X11 (see COPYING)
Only the symbols files reachable from the generated layouts (tools/make-xkeyboard-config.py `TARGETS`) are vendored; regenerate with
`python3 tools/make-xkeyboard-config.py fetch` then `... generate`.
Vendored symbols files:
- `symbols/de`
- `symbols/es`
- `symbols/fr`
- `symbols/gb`
- `symbols/kpdl`
- `symbols/latin`
- `symbols/level3`
- `symbols/us`
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+250
View File
@@ -0,0 +1,250 @@
// Keyboard layouts for Spain.
// Modified for a real Spanish keyboard by Jon Tombs.
default partial alphanumeric_keys
xkb_symbols "basic" {
include "latin(type4)"
name[Group1]="Spanish";
key <TLDE> { [ masculine, ordfeminine, backslash, backslash ] };
key <AE01> { [ 1, exclam, bar, exclamdown ] };
key <AE03> { [ 3, periodcentered, numbersign, sterling ] };
key <AE04> { [ 4, dollar, asciitilde, dollar ] };
key <AE11> { [apostrophe, question, backslash, questiondown ] };
key <AE12> { [exclamdown, questiondown, dead_cedilla, dead_ogonek] };
key <AD11> { [dead_grave, dead_circumflex, bracketleft, dead_abovering ] };
key <AD12> { [ plus, asterisk, bracketright, dead_macron ] };
key <AC10> { [ ntilde, Ntilde, dead_tilde, dead_doubleacute ] };
key <AC11> { [dead_acute, dead_diaeresis, braceleft, dead_caron ] };
key <BKSL> { [ ccedilla, Ccedilla, braceright, dead_breve ] };
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "winkeys" {
include "es(basic)"
name[Group1]="Spanish (Windows)";
include "eurosign(5)"
};
partial alphanumeric_keys
xkb_symbols "nodeadkeys" {
include "es(basic)"
name[Group1]="Spanish (no dead keys)";
key <AE12> { [exclamdown, questiondown, cedilla, ogonek ] };
key <AD11> { [ grave, asciicircum, bracketleft, degree ] };
key <AD12> { [ plus, asterisk, bracketright, macron ] };
key <AC07> { [ j, J, ezh, EZH ] };
key <AC10> { [ ntilde, Ntilde, asciitilde, doubleacute ] };
key <AC11> { [ acute, diaeresis, braceleft, caron ] };
key <BKSL> { [ ccedilla, Ccedilla, braceright, breve ] };
key <AB10> { [ minus, underscore, ellipsis, abovedot ] };
};
// Spanish Dvorak mapping (note R-H exchange)
partial alphanumeric_keys
xkb_symbols "dvorak" {
name[Group1]="Spanish (Dvorak)";
key <TLDE> {[ masculine, ordfeminine, backslash, degree ]};
key <AE01> {[ 1, exclam, bar, onesuperior ]};
key <AE02> {[ 2, quotedbl, at, twosuperior ]};
key <AE03> {[ 3, periodcentered, numbersign, threesuperior ]};
key <AE04> {[ 4, dollar, asciitilde, onequarter ]};
key <AE05> {[ 5, percent, brokenbar, fiveeighths ]};
key <AE06> {[ 6, ampersand, notsign, threequarters ]};
key <AE07> {[ 7, slash, onehalf, seveneighths ]};
key <AE08> {[ 8, parenleft, oneeighth, threeeighths ]};
key <AE09> {[ 9, parenright, asciicircum ]};
key <AE10> {[ 0, equal, grave, dead_doubleacute ]};
key <AE11> {[ apostrophe, question, dead_macron, dead_ogonek ]};
key <AE12> {[ exclamdown, questiondown, dead_breve, dead_abovedot ]};
key <AD01> {[ period, colon, less, guillemotleft ]};
key <AD02> {[ comma, semicolon, greater, guillemotright ]};
key <AD03> {[ ntilde, Ntilde, lstroke, Lstroke ]};
key <AD04> {[ p, P, paragraph ]};
key <AD05> {[ y, Y, yen ]};
key <AD06> {[ f, F, tslash, Tslash ]};
key <AD07> {[ g, G, dstroke, Dstroke ]};
key <AD08> {[ c, C, cent, copyright ]};
key <AD09> {[ h, H, hstroke, Hstroke ]};
key <AD10> {[ l, L, sterling ]};
key <AD11> {[ dead_grave, dead_circumflex, bracketleft, dead_caron ]};
key <AD12> {[ plus, asterisk, bracketright, plusminus ]};
key <AC01> {[ a, A, ae, AE ]};
key <AC02> {[ o, O, oslash, Oslash ]};
key <AC03> {[ e, E, EuroSign ]};
key <AC04> {[ u, U, aring, Aring ]};
key <AC05> {[ i, I, oe, OE ]};
key <AC06> {[ d, D, eth, ETH ]};
key <AC07> {[ r, R, registered, trademark ]};
key <AC08> {[ t, T, thorn, THORN ]};
key <AC09> {[ n, N, eng, ENG ]};
key <AC10> {[ s, S, ssharp, section ]};
key <AC11> {[ dead_acute, dead_diaeresis, braceleft, dead_tilde ]};
key <BKSL> {[ ccedilla, Ccedilla, braceright, dead_cedilla ]};
key <LSGT> {[ less, greater, guillemotleft, guillemotright ]};
key <AB01> {[ minus, underscore, hyphen, macron ]};
key <AB02> {[ q, Q, currency ]};
key <AB03> {[ j, J ]};
key <AB04> {[ k, K, kra ]};
key <AB05> {[ x, X, multiply, division ]};
key <AB06> {[ b, B ]};
key <AB07> {[ m, M, mu ]};
key <AB08> {[ w, W ]};
key <AB09> {[ v, V ]};
key <AB10> {[ z, Z ]};
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "cat" {
include "es(basic)"
name[Group1]="Catalan (Spain, with middle-dot L)";
key <AC09> { [ l, L, 0x1000140, 0x100013F ] };
};
partial alphanumeric_keys
xkb_symbols "ast" {
include "es(basic)"
name[Group1]="Asturian (Spain, with bottom-dot H and L)";
key <AC06> { [ h, H, 0x1001E25, 0x1001E24 ] };
key <AC09> { [ l, L, 0x1001E37, 0x1001E36 ] };
};
partial alphanumeric_keys
xkb_symbols "olpc" {
// #HW-SPECIFIC
// http://wiki.laptop.org/go/OLPC_Spanish_Keyboard
include "us(basic)"
name[Group1]="Spanish";
key <AE00> { [ masculine, ordfeminine ] };
key <AE01> { [ 1, exclam, bar ] };
key <AE02> { [ 2, quotedbl, at ] };
key <AE03> { [ 3, dead_grave, numbersign, grave ] };
key <AE05> { [ 5, percent, asciicircum, dead_circumflex ] };
key <AE06> { [ 6, ampersand, notsign ] };
key <AE07> { [ 7, slash, backslash ] };
key <AE08> { [ 8, parenleft ] };
key <AE09> { [ 9, parenright ] };
key <AE10> { [ 0, equal ] };
key <AE11> { [ apostrophe, question ] };
key <AE12> { [ exclamdown, questiondown ] };
key <AD03> { [ e, E, EuroSign ] };
key <AD11> { [ dead_acute, dead_diaeresis, acute, dead_abovering ] };
key <AD12> { [ bracketleft, braceleft ] };
key <AC10> { [ ntilde, Ntilde ] };
key <AC11> { [ plus, asterisk, dead_tilde ] };
key <AC12> { [ bracketright, braceright, section ] };
key <AB08> { [ comma, semicolon ] };
key <AB09> { [ period, colon ] };
key <AB10> { [ minus, underscore ] };
key <I219> { [ less, greater, ISO_Next_Group ] };
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "olpcm" {
// #HW-SPECIFIC
// Mechanical (non-membrane) OLPC Spanish keyboard layout.
// See: http://wiki.laptop.org/go/OLPC_Spanish_Non-membrane_Keyboard
include "us(basic)"
name[Group1]="Spanish";
key <AE00> { [ questiondown, exclamdown, backslash ] };
key <AE01> { [ 1, exclam, bar ] };
key <AE02> { [ 2, quotedbl, at ] };
key <AE03> { [ 3, dead_grave, numbersign, grave ] };
key <AE04> { [ 4, dollar, asciitilde, dead_tilde ] };
key <AE05> { [ 5, percent, asciicircum, dead_circumflex ] };
key <AE06> { [ 6, ampersand, notsign ] };
key <AE07> { [ 7, slash, backslash ] }; // no '\' label on olpcm, leave for compatibility
key <AE08> { [ 8, parenleft, masculine ] };
key <AE09> { [ 9, parenright, ordfeminine ] };
key <AE10> { [ 0, equal ] };
key <AE11> { [ apostrophe, question ] };
key <AD03> { [ e, E, EuroSign ] };
key <AD11> { [ dead_acute, dead_diaeresis, dead_abovering, acute ] };
key <AD12> { [ plus, asterisk ] };
key <AC10> { [ ntilde, Ntilde ] };
// no AC11 or AC12 on olpcm
key <AB08> { [ comma, semicolon ] };
key <AB09> { [ period, colon ] };
key <AB10> { [ minus, underscore ] };
key <AA02> { [ less, greater ] };
key <AA06> { [ bracketleft, braceleft, ccedilla, Ccedilla ] };
key <AA07> { [ bracketright, braceright ] };
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "deadtilde" {
include "es(basic)"
name[Group1]="Spanish (dead tilde)";
key <AE04> { [ 4, dollar, dead_tilde, dollar ] };
key <AC10> { [ ntilde, Ntilde, asciitilde, dead_doubleacute ] };
};
partial alphanumeric_keys
xkb_symbols "olpc2" {
// #HW-SPECIFIC
// Modified variant of US International layout, specifically for Peru
// Contact: Sayamindu Dasgupta <sayamindu@laptop.org>
include "us(olpc)"
name[Group1]="Spanish";
key <AE03> { [ 3, numbersign, dead_grave, dead_grave] }; // combining grave
key <I236> { [ XF86Start ] };
include "level3(ralt_switch)"
};
// EXTRAS:
partial alphanumeric_keys
xkb_symbols "sun_type6" {
include "sun_vndr/es(sun_type6)"
};
File diff suppressed because it is too large Load Diff
+249
View File
@@ -0,0 +1,249 @@
// Keyboard layouts for Great Britain.
default partial alphanumeric_keys
xkb_symbols "basic" {
// The basic UK layout, also known as the IBM 166 layout,
// but with the useless brokenbar pushed two levels up.
include "latin"
name[Group1]="English (UK)";
key <TLDE> { [ grave, notsign, bar, bar ] };
key <AE02> { [ 2, quotedbl, twosuperior, oneeighth ] };
key <AE03> { [ 3, sterling, threesuperior, sterling ] };
key <AE04> { [ 4, dollar, EuroSign, onequarter ] };
key <AC11> { [apostrophe, at, dead_circumflex, dead_caron] };
key <BKSL> { [numbersign, asciitilde, dead_grave, dead_breve ] };
key <LSGT> { [ backslash, bar, bar, brokenbar ] };
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "intl" {
// A UK layout but with five accents made into dead keys:
// grave, diaeresis, circumflex, acute, and tilde.
// By Phil Jones <philjones1 at blueyonder.co.uk>.
include "latin"
name[Group1]="English (UK, intl., with dead keys)";
key <TLDE> { [ dead_grave, notsign, bar, bar ] };
key <AE02> { [ 2, dead_diaeresis, twosuperior, onehalf ] };
key <AE03> { [ 3, sterling, threesuperior, onethird ] };
key <AE04> { [ 4, dollar, EuroSign, onequarter ] };
key <AE06> { [ 6, dead_circumflex, threequarters, onesixth ] };
key <AC11> { [ dead_acute, at, apostrophe, bar ] };
key <BKSL> { [ numbersign, dead_tilde, bar, bar ] };
key <LSGT> { [ backslash, bar, bar, bar ] };
key <AB08> { [ comma, less, ccedilla, Ccedilla ] };
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "extd" {
// Clone of the Microsoft "United Kingdom Extended" layout, which
// includes dead keys for: grave; diaeresis; circumflex; tilde; and
// accute. It also enables direct access to accute characters using
// the Multi_key (Alt Gr).
//
// Taken from...
// "Windows Keyboard Layouts"
// https://docs.microsoft.com/en-gb/globalization/windows-keyboard-layouts#U
//
// -- Jonathan Miles <jon@cybah.co.uk>
include "latin"
name[Group1]="English (UK, extended, Windows)";
key <TLDE> { [ dead_grave, notsign, brokenbar, NoSymbol ] };
key <AE02> { [ 2, quotedbl, dead_diaeresis, onehalf ] };
key <AE03> { [ 3, sterling, threesuperior, onethird ] };
key <AE04> { [ 4, dollar, EuroSign, onequarter ] };
key <AE06> { [ 6, asciicircum, dead_circumflex, NoSymbol ] };
key <AD02> { [ w, W, wacute, Wacute ] };
key <AD03> { [ e, E, eacute, Eacute ] };
key <AD06> { [ y, Y, yacute, Yacute ] };
key <AD07> { [ u, U, uacute, Uacute ] };
key <AD08> { [ i, I, iacute, Iacute ] };
key <AD09> { [ o, O, oacute, Oacute ] };
key <AD12> { [ bracketright, braceright, NoSymbol, bar ] };
key <AC01> { [ a, A, aacute, Aacute ] };
key <AC11> { [ apostrophe, at, dead_acute, grave ] };
key <BKSL> { [ numbersign, asciitilde, dead_tilde, backslash ] };
key <LSGT> { [ backslash, bar, NoSymbol, NoSymbol ] };
key <AB03> { [ c, C, ccedilla, Ccedilla ] };
include "level3(ralt_switch)"
};
// Describe the differences between the US Colemak layout
// and a UK variant. By Andy Buckley (andy@insectnation.org)
partial alphanumeric_keys
xkb_symbols "colemak" {
include "us(colemak)"
name[Group1]="English (UK, Colemak)";
key <TLDE> { [ grave, notsign, bar, asciitilde ] };
key <AE02> { [ 2, quotedbl, twosuperior, oneeighth ] };
key <AE03> { [ 3, sterling, threesuperior, sterling ] };
key <AE04> { [ 4, dollar, EuroSign, onequarter ] };
key <AC11> { [apostrophe, at, dead_circumflex, dead_caron] };
key <BKSL> { [numbersign, asciitilde, dead_grave, dead_breve ] };
key <LSGT> { [ backslash, bar, asciitilde, brokenbar ] };
};
// Colemak-DH (ISO) layout, UK Variant, https://colemakmods.github.io/mod-dh/
partial alphanumeric_keys
xkb_symbols "colemak_dh" {
include "us(colemak_dh)"
name[Group1]="English (UK, Colemak-DH)";
key <TLDE> { [ grave, notsign, bar, asciitilde ] };
key <AE02> { [ 2, quotedbl, twosuperior, oneeighth ] };
key <AE03> { [ 3, sterling, threesuperior, sterling ] };
key <AE04> { [ 4, dollar, EuroSign, onequarter ] };
key <AC11> { [apostrophe, at, dead_circumflex, dead_caron] };
key <BKSL> { [numbersign, asciitilde, dead_grave, dead_breve ] };
key <AB05> { [ backslash, bar, asciitilde, brokenbar ] };
};
// Dvorak (UK) keymap (by odaen) allowing the usage of
// the £ and ? key and swapping the @ and " keys.
partial alphanumeric_keys
xkb_symbols "dvorak" {
include "us(dvorak-alt-intl)"
name[Group1]="English (UK, Dvorak)";
key <TLDE> { [ grave, notsign, bar, bar ] };
key <AE02> { [ 2, quotedbl, twosuperior, NoSymbol ] };
key <AE03> { [ 3, sterling, threesuperior, NoSymbol ] };
key <AD01> { [ apostrophe, at ] };
key <BKSL> { [ numbersign, asciitilde ] };
key <LSGT> { [ backslash, bar ] };
};
// Dvorak letter positions, but punctuation all in the normal UK positions.
partial alphanumeric_keys
xkb_symbols "dvorakukp" {
include "gb(dvorak)"
name[Group1]="English (UK, Dvorak, with UK punctuation)";
key <AE11> { [ minus, underscore ] };
key <AE12> { [ equal, plus ] };
key <AD11> { [ bracketleft, braceleft ] };
key <AD12> { [ bracketright, braceright ] };
key <AD01> { [ slash, question ] };
key <AC11> { [apostrophe, at, dead_circumflex, dead_caron] };
};
partial alphanumeric_keys
xkb_symbols "mac" {
include "latin"
name[Group1]= "English (UK, Macintosh)";
key <TLDE> { [ section, plusminus ] };
key <AE02> { [ 2, at, EuroSign ] };
key <AE03> { [ 3, sterling, numbersign ] };
key <LSGT> { [ grave, asciitilde ] };
include "level3(ralt_switch)"
include "level3(enter_switch)"
};
partial alphanumeric_keys
xkb_symbols "mac_intl" {
include "latin"
name[Group1]="English (UK, Macintosh, intl.)";
key <TLDE> { [ section, plusminus, notsign, notsign ] }; //dead_grave
key <AE02> { [ 2, at, EuroSign, onehalf ] };
key <AE03> { [ 3, sterling, twosuperior, onethird ] };
key <AE04> { [ 4, dollar, threesuperior, onequarter ] };
key <AE06> { [ 6, dead_circumflex, NoSymbol, onesixth ] };
key <AD09> { [ o, O, oe, OE ] };
key <AC11> { [ dead_acute, dead_diaeresis, dead_diaeresis, bar ] }; //dead_doubleacute
key <BKSL> { [ backslash, bar, numbersign, bar ] };
key <LSGT> { [ dead_grave, dead_tilde, brokenbar, bar ] };
include "level3(ralt_switch)"
};
partial alphanumeric_keys
xkb_symbols "pl" {
// Polish accented letters on upper levels of corresponding base letters.
// Idea from Wawrzyniec Niewodniczański, adapted by Aleksander Kowalski.
include "gb(basic)"
name[Group1]="Polish (British keyboard)";
key <AD03> { [ e, E, eogonek, Eogonek ] };
key <AD09> { [ o, O, oacute, Oacute ] };
key <AC01> { [ a, A, aogonek, Aogonek ] };
key <AC02> { [ s, S, sacute, Sacute ] };
key <AB01> { [ z, Z, zabovedot, Zabovedot ] };
key <AB02> { [ x, X, zacute, Zacute ] };
key <AB03> { [ c, C, cacute, Cacute ] };
key <AB06> { [ n, N, nacute, Nacute ] };
};
partial alphanumeric_keys
xkb_symbols "gla" {
// Grave-accented letters on the upper levels of the relevant vowels.
include "gb(basic)"
name[Group1]="Scottish Gaelic";
key <AD03> { [ e, E, egrave, Egrave ] };
key <AD07> { [ u, U, ugrave, Ugrave ] };
key <AD08> { [ i, I, igrave, Igrave ] };
key <AD09> { [ o, O, ograve, Ograve ] };
key <AC01> { [ a, A, agrave, Agrave ] };
};
// EXTRAS:
partial alphanumeric_keys
xkb_symbols "sun_type6" {
include "sun_vndr/gb(sun_type6)"
};
+102
View File
@@ -0,0 +1,102 @@
// The <KPDL> key is a mess.
// It was probably originally meant to be a decimal separator.
// Except since it was declared by USA people it didn't use the original
// SI separator "," but a "." (since then the USA managed to f-up the SI
// by making "." an accepted alternative, but standards still use "," as
// default)
// As a result users of SI-abiding countries expect either a "." or a ","
// or a "decimal_separator" which may or may not be translated in one of the
// above depending on applications.
// It's not possible to define a default per-country since user expectations
// depend on the conflicting choices of their most-used applications,
// operating system, etc. Therefore it needs to be a configuration setting
// Copyright © 2007 Nicolas Mailhot <nicolas.mailhot @ laposte.net>
// Legacy <KPDL> #1
// This assumes KP_Decimal will be translated in a dot
partial keypad_keys
xkb_symbols "dot" {
key.type[Group1]="KEYPAD" ;
key <KPDL> { [ KP_Delete, KP_Decimal ] }; // <delete> <separator>
};
// Legacy <KPDL> #2
// This assumes KP_Separator will be translated in a comma
partial keypad_keys
xkb_symbols "comma" {
key.type[Group1]="KEYPAD" ;
key <KPDL> { [ KP_Delete, KP_Separator ] }; // <delete> <separator>
};
// Period <KPDL>, usual keyboard serigraphy in most countries
partial keypad_keys
xkb_symbols "dotoss" {
key.type[Group1]="FOUR_LEVEL_MIXED_KEYPAD" ;
key <KPDL> { [ KP_Delete, period, comma, 0x100202F ] }; // <delete> . , ⍽ (narrow no-break space)
};
// Period <KPDL>, usual keyboard serigraphy in most countries, latin-9 restriction
partial keypad_keys
xkb_symbols "dotoss_latin9" {
key.type[Group1]="FOUR_LEVEL_MIXED_KEYPAD" ;
key <KPDL> { [ KP_Delete, period, comma, nobreakspace ] }; // <delete> . , ⍽ (no-break space)
};
// Comma <KPDL>, what most non anglo-saxon people consider the real separator
partial keypad_keys
xkb_symbols "commaoss" {
key.type[Group1]="FOUR_LEVEL_MIXED_KEYPAD" ;
key <KPDL> { [ KP_Delete, comma, period, 0x100202F ] }; // <delete> , . ⍽ (narrow no-break space)
};
// Momayyez <KPDL>: Bahrain, Iran, Iraq, Kuwait, Oman, Qatar, Saudi Arabia, Syria, UAE
partial keypad_keys
xkb_symbols "momayyezoss" {
key.type[Group1]="FOUR_LEVEL_MIXED_KEYPAD" ;
key <KPDL> { [ KP_Delete, 0x100066B, comma, 0x100202F ] }; // <delete> ? , ⍽ (narrow no-break space)
};
// Abstracted <KPDL>, pray everything will work out (it usually does not)
partial keypad_keys
xkb_symbols "kposs" {
key.type[Group1]="FOUR_LEVEL_MIXED_KEYPAD" ;
key <KPDL> { [ KP_Delete, KP_Decimal, KP_Separator, 0x100202F ] }; // <delete> ? ? ⍽ (narrow no-break space)
};
// Spreadsheets may be configured to use the dot as decimal
// punctuation, comma as a thousands separator and then semi-colon as
// the list separator. Of these, dot and semi-colon is most important
// when entering data by the keyboard; the comma can then be inferred
// and added to the presentation afterwards. Using semi-colon as a
// general separator may in fact be preferred to avoid ambiguities
// in data files. Most times a decimal separator is hard-coded, it
// seems to be period, probably since this is the syntax used in
// (most) programming languages.
partial keypad_keys
xkb_symbols "semi" {
key.type[Group1]="FOUR_LEVEL_MIXED_KEYPAD" ;
key <KPDL> { [ NoSymbol, NoSymbol, semicolon ] };
};
+255
View File
@@ -0,0 +1,255 @@
// Common Latin alphabet layout
default partial
xkb_symbols "basic" {
key <AE01> { [ 1, exclam, onesuperior, exclamdown ] };
key <AE02> { [ 2, at, twosuperior, oneeighth ] };
key <AE03> { [ 3, numbersign, threesuperior, sterling ] };
key <AE04> { [ 4, dollar, onequarter, dollar ] };
key <AE05> { [ 5, percent, onehalf, threeeighths ] };
key <AE06> { [ 6, asciicircum, threequarters, fiveeighths ] };
key <AE07> { [ 7, ampersand, braceleft, seveneighths ] };
key <AE08> { [ 8, asterisk, bracketleft, trademark ] };
key <AE09> { [ 9, parenleft, bracketright, plusminus ] };
key <AE10> { [ 0, parenright, braceright, degree ] };
key <AE11> { [ minus, underscore, backslash, questiondown ] };
key <AE12> { [ equal, plus, dead_cedilla, dead_ogonek ] };
key <AD01> { [ q, Q, at, Greek_OMEGA ] };
key <AD02> { [ w, W, U017F, section ] };
key <AD03> { [ e, E, e, E ] };
key <AD04> { [ r, R, paragraph, registered ] };
key <AD05> { [ t, T, tslash, Tslash ] };
key <AD06> { [ y, Y, leftarrow, yen ] };
key <AD07> { [ u, U, downarrow, uparrow ] };
key <AD08> { [ i, I, rightarrow, idotless ] };
key <AD09> { [ o, O, oslash, Oslash ] };
key <AD10> { [ p, P, thorn, THORN ] };
key <AD11> { [bracketleft, braceleft, dead_diaeresis, dead_abovering ] };
key <AD12> { [bracketright, braceright, dead_tilde, dead_macron ] };
key <AC01> { [ a, A, ae, AE ] };
key <AC02> { [ s, S, ssharp, U1E9E ] };
key <AC03> { [ d, D, eth, ETH ] };
key <AC04> { [ f, F, dstroke, ordfeminine ] };
key <AC05> { [ g, G, eng, ENG ] };
key <AC06> { [ h, H, hstroke, Hstroke ] };
key <AC07> { [ j, J, dead_hook, dead_horn ] };
key <AC08> { [ k, K, kra, ampersand ] };
key <AC09> { [ l, L, lstroke, Lstroke ] };
key <AC10> { [ semicolon, colon, dead_acute, dead_doubleacute ] };
key <AC11> { [apostrophe, quotedbl, dead_circumflex, dead_caron ] };
key <TLDE> { [ grave, asciitilde, notsign, notsign ] };
key <BKSL> { [ backslash, bar, dead_grave, dead_breve ] };
key <AB01> { [ z, Z, guillemotleft, less ] };
key <AB02> { [ x, X, guillemotright, greater ] };
key <AB03> { [ c, C, cent, copyright ] };
key <AB04> { [ v, V, doublelowquotemark, singlelowquotemark ] };
key <AB05> { [ b, B, leftdoublequotemark, leftsinglequotemark ] };
key <AB06> { [ n, N, rightdoublequotemark, rightsinglequotemark ] };
key <AB07> { [ m, M, mu, masculine ] };
key <AB08> { [ comma, less, U2022, multiply ] }; // bullet
key <AB09> { [ period, greater, periodcentered, division ] };
key <AB10> { [ slash, question, dead_belowdot, dead_abovedot ] };
};
// Northern Europe ( Danish, Finnish, Norwegian, Swedish) common layout
partial
xkb_symbols "type2" {
include "latin"
key <AE01> { [ 1, exclam, exclamdown, onesuperior ] };
key <AE02> { [ 2, quotedbl, at, twosuperior ] };
key <AE03> { [ 3, numbersign, sterling, threesuperior] };
key <AE04> { [ 4, currency, dollar, onequarter ] };
key <AE05> { [ 5, percent, onehalf, cent ] };
key <AE06> { [ 6, ampersand, yen, fiveeighths ] };
key <AE07> { [ 7, slash, braceleft, division ] };
key <AE08> { [ 8, parenleft, bracketleft, guillemotleft] };
key <AE09> { [ 9, parenright, bracketright, guillemotright] };
key <AE10> { [ 0, equal, braceright, degree ] };
key <AD03> { [ e, E, EuroSign, cent ] };
key <AD04> { [ r, R, registered, registered ] };
key <AD05> { [ t, T, thorn, THORN ] };
key <AD09> { [ o, O, oe, OE ] };
key <AD11> { [ aring, Aring, dead_diaeresis, dead_abovering ] };
key <AD12> { [dead_diaeresis, dead_circumflex, dead_tilde, dead_caron ] };
key <AC01> { [ a, A, ordfeminine, masculine ] };
key <AB03> { [ c, C, copyright, copyright ] };
key <AB08> { [ comma, semicolon, dead_cedilla, dead_ogonek ] };
key <AB09> { [ period, colon, periodcentered, dead_abovedot ] };
key <AB10> { [ minus, underscore, dead_belowdot, dead_abovedot ] };
};
// Slavic Latin ( Albanian, Croatian, Polish, Slovene, Yugoslav)
// common layout
partial
xkb_symbols "type3" {
include "latin"
key <AD01> { [ q, Q, backslash, Greek_OMEGA ] };
key <AD02> { [ w, W, bar, section ] };
key <AD06> { [ z, Z, leftarrow, yen ] };
key <AC04> { [ f, F, bracketleft, ordfeminine ] };
key <AC05> { [ g, G, bracketright, ENG ] };
key <AC08> { [ k, K, lstroke, ampersand ] };
key <AB01> { [ y, Y, guillemotleft, less ] };
key <AB04> { [ v, V, at, grave ] };
key <AB05> { [ b, B, braceleft, apostrophe ] };
key <AB06> { [ n, N, braceright, acute ] };
key <AB07> { [ m, M, section, masculine ] };
key <AB08> { [ comma, semicolon, less, multiply ] };
key <AB09> { [ period, colon, greater, division ] };
};
// Another common Latin layout
// (German, Estonian, Spanish, Icelandic, Italian, Latin American, Portuguese)
partial
xkb_symbols "type4" {
include "latin"
key <AE02> { [ 2, quotedbl, at, oneeighth ] };
key <AE06> { [ 6, ampersand, notsign, fiveeighths ] };
key <AE07> { [ 7, slash, braceleft, seveneighths ] };
key <AE08> { [ 8, parenleft, bracketleft, trademark ] };
key <AE09> { [ 9, parenright, bracketright, plusminus ] };
key <AE10> { [ 0, equal, braceright, degree ] };
key <AD03> { [ e, E, EuroSign, cent ] };
key <AB08> { [ comma, semicolon, U2022, multiply ] }; // bullet
key <AB09> { [ period, colon, periodcentered, division ] };
key <AB10> { [ minus, underscore, dead_belowdot, dead_abovedot ] };
};
partial
xkb_symbols "nodeadkeys" {
key <AE12> { [ equal, plus, cedilla, ogonek ] };
key <AD11> { [bracketleft, braceleft, diaeresis, degree ] };
key <AD12> { [bracketright, braceright, asciitilde, macron ] };
key <AC07> { [ j, J, ezh, EZH ] };
key <AC10> { [ semicolon, colon, acute, doubleacute ] };
key <AC11> { [apostrophe, quotedbl, asciicircum, caron ] };
key <BKSL> { [ backslash, bar, grave, breve ] };
key <AB10> { [ slash, question, ellipsis, abovedot ] };
};
partial
xkb_symbols "type2_nodeadkeys" {
include "latin(nodeadkeys)"
key <AD11> { [ aring, Aring, diaeresis, degree ] };
key <AD12> { [ diaeresis, asciicircum, asciitilde, caron ] };
key <AB08> { [ comma, semicolon, cedilla, ogonek ] };
key <AB09> { [ period, colon, periodcentered, abovedot ] };
key <AB10> { [ minus, underscore, ellipsis, abovedot ] };
};
partial
xkb_symbols "type3_nodeadkeys" {
include "latin(nodeadkeys)"
};
partial
xkb_symbols "type4_nodeadkeys" {
include "latin(nodeadkeys)"
key <AB10> { [ minus, underscore, ellipsis, abovedot ] };
};
// Added 2008.03.05 by Marcin Woliński
// See http://marcinwolinski.pl/keyboard/ for a description.
// Used by pl(intl)
//
// ┌─────┐
// │ 2 4 │ 2 = Shift, 4 = Level3 + Shift
// │ 1 3 │ 1 = Normal, 3 = Level3
// └─────┘
// ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┲━━━━━━━━━┓
// │ ~ ~ │ ! ' │ @ " │ # ˝ │ $ ¸ │ % ˇ │ ^ ^ │ & ˘ │ * ̇ │ ( ̣ │ ) ° │ _ ¯ │ + ˛ ┃ ⌫ Back- ┃
// │ ` ` │ 1 ¡ │ 2 © │ 3 • │ 4 § │ 5 € │ 6 ¢ │ 7 − │ 8 × │ 9 ÷ │ 0 ° │ - – │ = — ┃ space ┃
// ┢━━━━━┷━┱───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┴─┬───┺━┳━━━━━━━┫
// ┃ ┃ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ { « │ } » ┃ Enter ┃
// ┃Tab ↹ ┃ q │ w │ e │ r │ t │ y │ u │ i │ o │ p │ [ ‹ │ ] › ┃ ⏎ ┃
// ┣━━━━━━━┻┱────┴┬────┴┬────┴┬────┴┬────┴┬────┴┬────┴┬────┴┬────┴┬────┴┬────┴┬────┺┓ ┃
// ┃ ┃ A │ S │ D │ F │ G │ H │ J │ K │ L │ : “ │ " ” │ | ¶ ┃ ┃
// ┃Caps ⇬ ┃ a │ s │ d │ f │ g │ h │ j │ k │ l │ ; ‘ │ ' ’ │ \ ┃ ┃
// ┣━━━━━━━━┹────┬┴────┬┴────┬┴────┬┴────┬┴────┬┴────┬┴────┬┴────┬┴────┬┴────┲┷━━━━━┻━━━━━━┫
// ┃ │ Z │ X │ C │ V │ B │ N │ M │ < „ │ > · │ ? ¿ ┃ ┃
// ┃Shift ⇧ │ z │ x │ c │ v │ b │ n │ m │ , ‚ │ . … │ / ⁄ ┃Shift ⇧ ┃
// ┣━━━━━━━┳━━━━━┷━┳━━━┷━━━┱─┴─────┴─────┴─────┴─────┴─────┴───┲━┷━━━━━╈━━━━━┻━┳━━━━━━━┳━━━┛
// ┃ ┃ ┃ ┃ ␣ ⍽ ┃ ┃ ┃ ┃
// ┃Ctrl ┃Meta ┃Alt ┃ ␣ Space ⍽ ┃AltGr ⇮┃Menu ┃Ctrl ┃
// ┗━━━━━━━┻━━━━━━━┻━━━━━━━┹───────────────────────────────────┺━━━━━━━┻━━━━━━━┻━━━━━━━┛
partial
xkb_symbols "intl" {
key <TLDE> { [ grave, asciitilde, dead_grave, dead_tilde ] };
key <AE01> { [ 1, exclam, exclamdown, dead_acute ] };
key <AE02> { [ 2, at, copyright, dead_diaeresis ] };
key <AE03> { [ 3, numbersign, U2022, dead_doubleacute ] }; // U+2022 is bullet (the name bullet does not work)
key <AE04> { [ 4, dollar, section, dead_cedilla ] };
key <AE05> { [ 5, percent, EuroSign, dead_caron ] };
key <AE06> { [ 6, asciicircum, cent, dead_circumflex ] };
key <AE07> { [ 7, ampersand, U2212, dead_breve ] }; // U+2212 is MINUS SIGN
key <AE08> { [ 8, asterisk, multiply, dead_abovedot ] };
key <AE09> { [ 9, parenleft, division, dead_belowdot ] };
key <AE10> { [ 0, parenright, degree, dead_abovering ] };
key <AE11> { [ minus, underscore, endash, dead_macron ] };
key <AE12> { [ equal, plus, emdash, dead_ogonek ] };
key <AD01> { [ q, Q ] };
key <AD02> { [ w, W ] };
key <AD03> { [ e, E ] };
key <AD04> { [ r, R ] };
key <AD05> { [ t, T ] };
key <AD06> { [ y, Y ] };
key <AD07> { [ u, U ] };
key <AD08> { [ i, I ] };
key <AD09> { [ o, O ] };
key <AD10> { [ p, P ] };
key <AD11> { [bracketleft, braceleft, U2039, guillemotleft ] };
key <AD12> { [bracketright, braceright, U203A, guillemotright ] };
key <AC01> { [ a, A ] };
key <AC02> { [ s, S ] };
key <AC03> { [ d, D ] };
key <AC04> { [ f, F ] };
key <AC05> { [ g, G ] };
key <AC06> { [ h, H ] };
key <AC07> { [ j, J ] };
key <AC08> { [ k, K ] };
key <AC09> { [ l, L ] };
key <AC10> { [ semicolon, colon, leftsinglequotemark, leftdoublequotemark ] };
key <AC11> { [apostrophe, quotedbl, rightsinglequotemark, rightdoublequotemark ] };
key <BKSL> { [ backslash, bar, NoSymbol, paragraph ] };
key <AB01> { [ z, Z ] };
key <AB02> { [ x, X ] };
key <AB03> { [ c, C ] };
key <AB04> { [ v, V ] };
key <AB05> { [ b, B ] };
key <AB06> { [ n, N ] };
key <AB07> { [ m, M ] };
key <AB08> { [ comma, less, singlelowquotemark, doublelowquotemark ] };
key <AB09> { [ period, greater, ellipsis, periodcentered ] };
key <AB10> { [ slash, question, U2044, questiondown ] }; // U+2044 is FRACTION SLASH
};
+156
View File
@@ -0,0 +1,156 @@
// These variants assign ISO_Level3_Shift to various keys
// so that levels 3 and 4 can be reached.
// The default behaviour:
// the right Alt key (AltGr) chooses the third symbol engraved on a key.
default partial modifier_keys
xkb_symbols "ralt_switch" {
key <RALT> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The right Alt key never chooses the third level.
// This option attempts to undo the effect of a layout's inclusion of
// 'ralt_switch'. You may want to also select another level3 option
// to map the level3 shift to some other key.
partial modifier_keys
xkb_symbols "ralt_alt" {
key <RALT> {[ Alt_R, Meta_R ], type[group1]="TWO_LEVEL" };
modifier_map Mod1 { <RALT> };
};
// The right Alt key (while pressed) chooses the third shift level,
// and Compose is mapped to its second level.
partial modifier_keys
xkb_symbols "ralt_switch_multikey" {
key <RALT> {[ ISO_Level3_Shift, Multi_key ], type[group1]="TWO_LEVEL" };
};
// Either Alt key (while pressed) chooses the third shift level.
// (To be used mostly to imitate Mac OS functionality.)
partial modifier_keys
xkb_symbols "alt_switch" {
include "level3(lalt_switch)"
include "level3(ralt_switch)"
};
// The left Alt key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "lalt_switch" {
key <LALT> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The right Ctrl key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "switch" {
key <RCTL> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The Menu key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "menu_switch" {
key <MENU> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// Either Win key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "win_switch" {
include "level3(lwin_switch)"
include "level3(rwin_switch)"
};
// The left Win key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "lwin_switch" {
key <LWIN> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The right Win key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "rwin_switch" {
key <RWIN> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The Enter key on the kepypad (while pressed) chooses the third shift level.
// (This is especially useful for Mac laptops which miss the right Alt key.)
partial modifier_keys
xkb_symbols "enter_switch" {
key <KPEN> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The CapsLock key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "caps_switch" {
key <CAPS> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The CapsLock key (while pressed) chooses the third shift level and
// Ctrl + CapsLock has the original CapsLock function.
// The 2023 DIN standard for German keyboards recommends it as an option:
// - https://de.wikipedia.org/wiki/E1_(Tastaturbelegung)#Feststelltaste/Umschaltsperre
// - https://en.wikipedia.org/wiki/Caps_Lock#Abolition
partial modifier_keys
xkb_symbols "caps_switch_capslock_with_ctrl" {
virtual_modifiers LevelThree;
key <CAPS> {
type[Group1] = "PC_CONTROL_LEVEL2",
symbols[Group1] = [ ISO_Level3_Shift, Caps_Lock ],
// Explicit actions are preferred over modMap None/Mod5 { Caps_Lock }
// because they have no side effect
actions[Group1] = [ SetMods(modifiers = LevelThree), LockMods(modifiers = Lock) ]
};
};
// The Backslash key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "bksl_switch" {
key <BKSL> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The AC11 key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "ac11_switch" {
key <AC11> {[ ISO_Level3_Shift ], type[Group1]="ONE_LEVEL" };
};
// The Less/Greater key (while pressed) chooses the third shift level.
partial modifier_keys
xkb_symbols "lsgt_switch" {
key <LSGT> {[ ISO_Level3_Shift ], type[group1]="ONE_LEVEL" };
};
// The CapsLock key (while pressed) chooses the third shift level,
// and latches when pressed together with another third-level chooser.
partial modifier_keys
xkb_symbols "caps_switch_latch" {
key <CAPS> {[ ISO_Level3_Shift, ISO_Level3_Shift, ISO_Level3_Latch ],
type[group1]="THREE_LEVEL" };
};
// The Backslash key (while pressed) chooses the third shift level,
// and latches when pressed together with another third-level chooser.
partial modifier_keys
xkb_symbols "bksl_switch_latch" {
key <BKSL> {[ ISO_Level3_Shift, ISO_Level3_Shift, ISO_Level3_Latch ],
type[group1]="THREE_LEVEL" };
};
// The Less/Greater key (while pressed) chooses the third shift level,
// and latches when pressed together with another third-level chooser.
partial modifier_keys
xkb_symbols "lsgt_switch_latch" {
key <LSGT> {[ ISO_Level3_Shift, ISO_Level3_Shift, ISO_Level3_Latch ],
type[group1]="THREE_LEVEL" };
};
// Top-row digit key 4 chooses third shift level when pressed alone.
partial modifier_keys
xkb_symbols "4_switch_isolated" {
override key <AE04> {[ ISO_Level3_Shift ]};
};
// Top-row digit key 9 chooses third shift level when pressed alone.
partial modifier_keys
xkb_symbols "9_switch_isolated" {
override key <AE09> {[ ISO_Level3_Shift ]};
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
//! xkeyboard-config — keyboard layouts, compiled from the X11 xkeyboard-config database
//! into native Zig. It turns a physical key (a USB HID usage, as the input module delivers)
//! plus a modifier state into a **keysym** and, when the key produces one, a **character**
//! (a Unicode scalar). This is the piece that lets a `KeyEvent.keycode` become a
//! `KeyEvent.character`, without shipping an X11 runtime.
//!
//! The layout tables in `generated/layouts.zig` are produced by
//! `tools/make-xkeyboard-config.py` (see ./README.md to regenerate). Those tables are
//! deliberately pure data — each key carries its up-to-four levels and an XKB *type*. The
//! type -> level selection semantics (which modifier picks which level) live here, so the
//! data and the policy are separable.
//!
//! Scope (documented in README.md): group 1 only, no dead-key/compose composition (a dead
//! key returns its keysym with no character), and a curated set of key types. Layouts:
//! us, gb, de, fr, es, dvorak.
//!
//! Upstream xkeyboard-config and keysymdef.h are MIT/X11 licensed; see vendor/COPYING and
//! vendor/PROVENANCE.md.
const std = @import("std");
const generated = @import("layouts");
pub const Level = generated.Level;
pub const KeyType = generated.KeyType;
pub const Key = generated.Key;
pub const Layout = generated.Layout;
/// The generated layouts, by name — as pointers, so they share identity with `all` and
/// `byName` (and match the `*const Layout` that `map` takes).
pub const us: *const Layout = &generated.us;
pub const gb: *const Layout = &generated.gb;
pub const de: *const Layout = &generated.de;
pub const fr: *const Layout = &generated.fr;
pub const es: *const Layout = &generated.es;
pub const dvorak: *const Layout = &generated.dvorak;
/// Every generated layout, for enumeration (e.g. a settings UI).
pub const all = generated.all;
/// The modifier state that selects a key's level. `level3` is AltGr (ISO Level3 Shift);
/// `control` is accepted for completeness but does not affect level selection here.
pub const Modifiers = struct {
shift: bool = false,
caps_lock: bool = false,
level3: bool = false,
control: bool = false,
};
/// The result of a lookup: the X11 `keysym`, and the `character` it produces (a Unicode
/// scalar) when it is a printable key — null for keys that produce none (Return, F1, a
/// bare dead key, an unmapped key).
pub const Mapping = struct {
keysym: u32,
character: ?u21,
};
/// Which level (0..3) a key of `kind` selects under `mods`. XKB's canonical semantics:
/// Shift picks the odd level, AltGr (level3) adds 2, and Caps acts like Shift for the
/// alphabetic types. See the XKB "key types" — this covers the ones the vendored layouts
/// use; anything else falls back to shift-or-not.
fn selectLevel(kind: KeyType, mods: Modifiers) usize {
const shift_or_caps = mods.shift != mods.caps_lock; // XOR: Caps behaves like Shift
const low: usize = if (mods.shift) 1 else 0;
const high: usize = if (mods.level3) 2 else 0;
return switch (kind) {
.one_level => 0,
.two_level, .keypad, .other => low,
.alphabetic => if (shift_or_caps) 1 else 0,
.four_level => low + high,
.four_level_alphabetic => (if (shift_or_caps) @as(usize, 1) else 0) + high,
// Caps affects only the base pair, not the AltGr pair.
.four_level_semialphabetic => if (mods.level3) 2 + low else (if (shift_or_caps) @as(usize, 1) else 0),
};
}
/// Map a physical key (`hid_usage`, a USB HID keyboard-page usage) under `mods` on
/// `layout` to its keysym and character. Falls back gracefully when the selected level is
/// undefined for the key: it drops the AltGr component, then the shift component, so a key
/// with only a base/shift pair still yields something sensible under AltGr.
pub fn map(layout: *const Layout, hid_usage: u8, mods: Modifiers) Mapping {
const key = &layout.keys[hid_usage];
var level = selectLevel(key.kind, mods);
// Fall back to a defined level: full -> without AltGr -> base.
if (key.levels[level].keysym == 0 and key.levels[level].unicode == 0) {
const candidates = [_]usize{ level & 1, 0 };
for (candidates) |candidate| {
if (key.levels[candidate].keysym != 0 or key.levels[candidate].unicode != 0) {
level = candidate;
break;
}
}
}
const chosen = key.levels[level];
return .{
.keysym = chosen.keysym,
.character = if (chosen.unicode != 0) @intCast(chosen.unicode) else null,
};
}
/// Look up a layout by its name (`"us"`, `"gb"`, ...), or null if unknown.
pub fn byName(name: []const u8) ?*const Layout {
for (all) |layout| {
if (std.mem.eql(u8, layout.name, name)) return layout;
}
return null;
}
// --- tests (host-run via `zig build test`) ---------------------------------
const testing = std.testing;
// USB HID usages used in the tests (keyboard page 0x07).
const hid_a: u8 = 0x04;
const hid_1: u8 = 0x1e;
const hid_3: u8 = 0x20;
test "us: letters obey shift and caps" {
try testing.expectEqual(@as(?u21, 'a'), map(us, hid_a, .{}).character);
try testing.expectEqual(@as(?u21, 'A'), map(us, hid_a, .{ .shift = true }).character);
try testing.expectEqual(@as(?u21, 'A'), map(us, hid_a, .{ .caps_lock = true }).character);
// Shift + Caps cancels for an alphabetic key.
try testing.expectEqual(@as(?u21, 'a'), map(us, hid_a, .{ .shift = true, .caps_lock = true }).character);
}
test "us: digits and their shifted symbols" {
try testing.expectEqual(@as(?u21, '1'), map(us, hid_1, .{}).character);
try testing.expectEqual(@as(?u21, '!'), map(us, hid_1, .{ .shift = true }).character);
try testing.expectEqual(@as(?u21, '3'), map(us, hid_3, .{}).character);
try testing.expectEqual(@as(?u21, '#'), map(us, hid_3, .{ .shift = true }).character);
// A digit is not alphabetic: Caps alone must not shift it.
try testing.expectEqual(@as(?u21, '3'), map(us, hid_3, .{ .caps_lock = true }).character);
}
test "layouts differ: GB pound vs US hash on shift+3" {
try testing.expectEqual(@as(?u21, '#'), map(us, hid_3, .{ .shift = true }).character);
try testing.expectEqual(@as(?u21, '£'), map(gb, hid_3, .{ .shift = true }).character);
}
test "french azerty places q where us has a" {
try testing.expectEqual(@as(?u21, 'q'), map(fr, hid_a, .{}).character);
try testing.expectEqual(@as(?u21, 'Q'), map(fr, hid_a, .{ .shift = true }).character);
}
test "byName resolves and rejects" {
try testing.expect(byName("us") == us);
try testing.expect(byName("gb") == gb);
try testing.expect(byName("nonsense") == null);
}
test "unmapped key yields no character" {
// HID 0x00 is not a key; every level is empty.
try testing.expectEqual(@as(?u21, null), map(us, 0x00, .{}).character);
}
+11
View File
@@ -52,6 +52,7 @@ pub const SystemCall = enum(u64) {
clock = 23, // clock() -> nanoseconds since boot: a monotonic time source (for timeouts/delays) clock = 23, // clock() -> nanoseconds since boot: a monotonic time source (for timeouts/delays)
process_enumerate = 24, // process_enumerate(buffer, maximum) -> total: snapshot the task table process_enumerate = 24, // process_enumerate(buffer, maximum) -> total: snapshot the task table
process_kill = 25, // process_kill(id) -> 0/-errno: end a process this process spawned process_kill = 25, // process_kill(id) -> 0/-errno: end a process this process spawned
ipc_send = 26, // ipc_send(handle, message_ptr, message_len) -> 0/-errno: post a payload to an endpoint's async queue without blocking
_, _,
}; };
@@ -83,6 +84,15 @@ pub const notify_badge_bit: u64 = 1 << 63;
/// notifications, which never set this bit). The microkernel's SIGCHLD. /// notifications, which never set this bit). The microkernel's SIGCHLD.
pub const notify_exit_bit: u64 = 1 << 62; pub const notify_exit_bit: u64 = 1 << 62;
/// Set (alongside `notify_badge_bit`) in the badge of a **buffered message** — a payload
/// posted to an endpoint's async queue by `ipc_send`, delivered through `ipc_reply_wait`
/// like a notification (no reply owed) but carrying bytes in the receive buffer, not just
/// a badge. This is what distinguishes a payload-bearing async message from a bare IRQ /
/// child-exit notification (which sets neither this nor `notify_exit_bit`). The low bits
/// carry the sender's task id. The async counterpart of the synchronous `ipc_call`, for
/// broadcasts where a rendezvous is the wrong shape (the input service is the first user).
pub const notify_message_bit: u64 = 1 << 61;
/// Capacity of `ProcessDescriptor.name` — matches the longest name `system_spawn` /// Capacity of `ProcessDescriptor.name` — matches the longest name `system_spawn`
/// accepts, so a process's recorded name (its argv[0]) is never truncated. /// accepts, so a process's recorded name (its argv[0]) is never truncated.
pub const maximum_process_name = 64; pub const maximum_process_name = 64;
@@ -113,6 +123,7 @@ pub const ProcessDescriptor = extern struct {
/// during bring-up. The VFS server registers under `vfs`; clients look it up. /// during bring-up. The VFS server registers under `vfs`; clients look it up.
pub const ServiceId = enum(u32) { pub const ServiceId = enum(u32) {
vfs = 1, vfs = 1,
input = 2,
_, _,
}; };
+14 -1
View File
@@ -36,8 +36,21 @@ pub fn main() void {
// served through the bus and does not claim the controller itself. // served through the bus and does not claim the controller itself.
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: served by ps2-bus (controller owned by bus)\n"); _ = runtime.system.write("system/drivers/ps2-bus/keyboard: served by ps2-bus (controller owned by bus)\n");
// Broadcast keyboard events through the input service so programs can listen for them
// (docs/input.md). Until the bus reads real IRQ1 scancodes and hands them here (a
// follow-up), we publish the same synthetic stand-in stream the demo source uses — the
// fan-out path is real, only the source of the bytes is placeholder.
var source = runtime.input.connectSource() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: input service unavailable\n");
return;
};
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n"); _ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n");
while (true) runtime.system.sleep(1000);
var step: usize = 0;
while (true) : (step +%= 1) {
_ = source.publishKeyboardEvent(runtime.input.syntheticKeyEvent(step));
runtime.system.sleep(200);
}
} }
pub const panic = runtime.panic; pub const panic = runtime.panic;
+14 -1
View File
@@ -40,8 +40,21 @@ pub fn main() void {
} }
writeLine("system/drivers/ps2-bus/mouse: claimed device for hid {s}\n", .{hid}); writeLine("system/drivers/ps2-bus/mouse: claimed device for hid {s}\n", .{hid});
// Broadcast mouse events through the input service so programs can listen for them
// (docs/input.md). As with the keyboard, decoding real PS/2 mouse packets is a
// follow-up; for now we publish the synthetic stand-in stream. The fan-out path is
// real, only the source of the movement is placeholder.
var source = runtime.input.connectSource() orelse {
_ = runtime.system.write("system/drivers/ps2-bus/mouse: input service unavailable\n");
return;
};
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n"); _ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n");
while (true) runtime.system.sleep(1000);
var step: usize = 0;
while (true) : (step +%= 1) {
_ = source.publishMouseEvent(runtime.input.syntheticMouseEvent(step));
runtime.system.sleep(200);
}
} }
pub const panic = runtime.panic; pub const panic = runtime.panic;
+79 -1
View File
@@ -57,6 +57,29 @@ pub const EPERM: i64 = 9; // not permitted (process_kill by anyone but the super
/// shared kernel↔user ABI (system/abi.zig), because ring 3 has to test the same bit. /// shared kernel↔user ABI (system/abi.zig), because ring 3 has to test the same bit.
pub const notify_badge_bit: u64 = abi.notify_badge_bit; pub const notify_badge_bit: u64 = abi.notify_badge_bit;
/// Set (with `notify_badge_bit`) when a `replyWait` wake carries a buffered payload
/// posted by `send` (`ipc_send`), rather than a bare IRQ/exit notification. Shared with
/// ring 3 through the ABI so the receiver can tell "a message arrived" from "the hardware
/// spoke".
pub const notify_message_bit: u64 = abi.notify_message_bit;
/// Largest payload a single `send` (`ipc_send`) may post. Kept small — the payload rides
/// inline in every `Endpoint`, and the async path is for events (a `KeyEvent` is 16
/// bytes), not bulk transfer, which is what `call` and future shared pages are for.
pub const POST_MAXIMUM: usize = 64;
/// Depth of an endpoint's async payload ring. Absorbs a burst while a receiver is briefly
/// busy; a full ring drops the *oldest* message (see `send`).
const post_capacity: usize = 16;
/// One buffered message: a length-prefixed payload plus the sender's task id (delivered
/// in the low bits of the receiver's badge).
const PostSlot = struct {
length: u16 = 0,
sender_id: u64 = 0,
bytes: [POST_MAXIMUM]u8 = undefined,
};
/// End of the user (low) canonical half — user buffers must lie below it. /// End of the user (low) canonical half — user buffers must lie below it.
const user_half_end: u64 = 0x0000_8000_0000_0000; const user_half_end: u64 = 0x0000_8000_0000_0000;
@@ -74,6 +97,12 @@ pub const Endpoint = struct {
notify_buffer: [8]u64 = undefined, notify_buffer: [8]u64 = undefined,
notify_head: u8 = 0, notify_head: u8 = 0,
notify_tail: u8 = 0, notify_tail: u8 = 0,
// Pending buffered messages (payloads posted by `send`), a small FIFO ring. Unlike
// notifications — which are a level and coalesce — these are discrete messages, so a
// full ring drops the oldest rather than merging.
post_buffer: [post_capacity]PostSlot = undefined,
post_head: u16 = 0,
post_tail: u16 = 0,
}; };
pub fn createIpcEndpoint() ?*Endpoint { pub fn createIpcEndpoint() ?*Endpoint {
@@ -265,12 +294,23 @@ pub fn replyWait(endpoint: *Endpoint, reply_ptr: u64, reply_len: u64, receive_pt
scheduler.readyLocked(client); // its `call` now returns scheduler.readyLocked(client); // its `call` now returns
} }
// (2) Receive the next request (or notification), blocking until one is ready. // (2) Receive the next request (or notification / buffered message), blocking until
// one is ready. Bare notifications (IRQ/exit) come first — they're latency-sensitive
// and carry no payload — then buffered messages, then synchronous client requests.
while (true) { while (true) {
if (popNotify(endpoint)) |badge| { if (popNotify(endpoint)) |badge| {
out_badge.* = badge | notify_badge_bit; out_badge.* = badge | notify_badge_bit;
return 0; // notification: no payload, no reply owed, no cap return 0; // notification: no payload, no reply owed, no cap
} }
if (popPost(endpoint)) |slot| {
const n = @min(@as(usize, slot.length), receive_cap);
// Copy from the kernel-resident ring slot (source aspace 0) into the receiver.
if (!copyAcross(0, @intFromPtr(&slot.bytes), me.aspace, receive_ptr, n)) {
continue; // bad receive buffer: drop this message, keep serving
}
out_badge.* = slot.sender_id | notify_badge_bit | notify_message_bit;
return @intCast(n); // async message: payload delivered, no reply owed, no cap
}
if (dequeueSender(endpoint)) |caller| { if (dequeueSender(endpoint)) |caller| {
const n = @min(caller.ipc_send_len, receive_cap); const n = @min(caller.ipc_send_len, receive_cap);
if (!copyAcross(caller.aspace, caller.ipc_send_ptr, me.aspace, receive_ptr, n)) { if (!copyAcross(caller.aspace, caller.ipc_send_ptr, me.aspace, receive_ptr, n)) {
@@ -306,6 +346,44 @@ fn popNotify(endpoint: *Endpoint) ?u64 {
return badge; return badge;
} }
/// Take the oldest buffered message from the post ring, or null if empty. Returns a
/// pointer into the endpoint's own storage — valid until the next `send`/`popPost` under
/// the same lock region, which is all the copy-out in `replyWait` needs.
fn popPost(endpoint: *Endpoint) ?*const PostSlot {
if (endpoint.post_head == endpoint.post_tail) return null;
const slot = &endpoint.post_buffer[endpoint.post_head % post_capacity];
endpoint.post_head +%= 1;
return slot;
}
/// Client-free side of async IPC (`ipc_send`): copy `[source_va, len)` from address space
/// `source_as` into `endpoint`'s post ring and wake a waiting receiver — **without
/// blocking the sender** and with no reply owed. `sender_id` rides along, delivered in the
/// low bits of the receiver's badge. Returns 0, or a negative errno (`-E2BIG` if the
/// payload exceeds `POST_MAXIMUM`, `-EFAULT` if the source buffer is unmapped / out of the
/// user half). A full ring drops the *oldest* message (advancing `post_head`), because a
/// buffered message is discrete, not a level: keeping the newest keeps input responsive.
/// Precondition: the big kernel lock is held.
pub fn sendLocked(endpoint: *Endpoint, source_as: u64, source_va: u64, len: u64, sender_id: u64) i64 {
if (len > POST_MAXIMUM) return -E2BIG;
// Drop the oldest if the ring is full, so this newest message always lands.
if (endpoint.post_tail -% endpoint.post_head >= post_capacity) endpoint.post_head +%= 1;
const slot = &endpoint.post_buffer[endpoint.post_tail % post_capacity];
if (!copyFromUser(source_as, source_va, slot.bytes[0..@intCast(len)])) return -EFAULT;
slot.length = @intCast(len);
slot.sender_id = sender_id;
endpoint.post_tail +%= 1;
scheduler.wakeLocked(&endpoint.receive_wait_queue);
return 0;
}
/// `sendLocked` wrapped in its own critical section, for the `ipc_send` syscall path.
pub fn send(endpoint: *Endpoint, source_as: u64, source_va: u64, len: u64, sender_id: u64) i64 {
const flags = sync.enter();
defer sync.leave(flags);
return sendLocked(endpoint, source_as, source_va, len, sender_id);
}
/// Post an asynchronous notification carrying `badge` to `endpoint` and wake a waiting /// Post an asynchronous notification carrying `badge` to `endpoint` and wake a waiting
/// receiver. Precondition: the big kernel lock is held. /// receiver. Precondition: the big kernel lock is held.
/// ///
+13
View File
@@ -183,6 +183,7 @@ fn system_call(state: *architecture.CpuState) void {
.ipc_lookup => systemIpcLookup(state), .ipc_lookup => systemIpcLookup(state),
.ipc_call => systemIpcCall(state), .ipc_call => systemIpcCall(state),
.ipc_reply_wait => systemIpcReplyWait(state), .ipc_reply_wait => systemIpcReplyWait(state),
.ipc_send => systemIpcSend(state),
.device_enumerate => systemDeviceEnumerate(state), .device_enumerate => systemDeviceEnumerate(state),
.device_claim => systemDeviceClaim(state), .device_claim => systemDeviceClaim(state),
.mmio_map => systemMmioMap(state), .mmio_map => systemMmioMap(state),
@@ -263,6 +264,18 @@ fn systemIpcReplyWait(state: *architecture.CpuState) void {
architecture.setSystemCallResult3(state, received_cap); architecture.setSystemCallResult3(state, received_cap);
} }
/// ipc_send(handle, message_ptr, message_len) -> 0/-errno: post a payload to an
/// endpoint's async queue and wake a receiver, without blocking the caller. The async
/// counterpart of ipc_call — for broadcasts (the input service) where a rendezvous would
/// let one dead subscriber hang the sender. Delivered through ipc_reply_wait as a
/// buffered message (badge carries notify_message_bit and the caller's task id).
fn systemIpcSend(state: *architecture.CpuState) void {
const me = scheduler.current();
const endpoint = ipc.resolveHandle(me, architecture.systemCallArg(state, 0)) orelse return failErr(state, ipc.EBADF);
const r = ipc.send(endpoint, me.aspace, architecture.systemCallArg(state, 1), architecture.systemCallArg(state, 2), me.id);
architecture.setSystemCallResult(state, @bitCast(r));
}
/// device_enumerate(buffer, maximum) -> total: snapshot the device table into the caller's /// device_enumerate(buffer, maximum) -> total: snapshot the device table into the caller's
/// buffer (up to `maximum` entries), returning the total device count. /// buffer (up to `maximum` entries), returning the total device count.
fn systemDeviceEnumerate(state: *architecture.CpuState) void { fn systemDeviceEnumerate(state: *architecture.CpuState) void {
+47
View File
@@ -136,6 +136,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
initialRamdiskTest(boot_information); initialRamdiskTest(boot_information);
} else if (eql(case, "vfs")) { } else if (eql(case, "vfs")) {
vfsTest(boot_information); vfsTest(boot_information);
} else if (eql(case, "input")) {
inputTest(boot_information);
} else if (eql(case, "hpet")) { } else if (eql(case, "hpet")) {
hpetTest(boot_information); hpetTest(boot_information);
} else if (eql(case, "iopass")) { } else if (eql(case, "iopass")) {
@@ -1585,6 +1587,51 @@ fn vfsTest(boot_information: *const BootInformation) void {
result(); result();
} }
/// The full input path: spawn the input service, a synthetic source, and a subscriber from
/// the initial_ramdisk. The source publishes keyboard, mouse, and joystick events in turn;
/// the service routes them (with the asynchronous ipc_send) to the subscriber, which took
/// all three classes and — only once it has received one — heartbeats "input-test: ok".
/// Seeing that marker proves an event travelled source -> service -> subscriber over IPC,
/// exercising the async buffered-send primitive, capability-passing subscription, and
/// per-device routing. The source and service stay silent after startup so the subscriber's
/// line is the one left in the shared evidence buffer.
fn inputTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: input\n", .{});
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over an initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false);
result();
return;
};
process.write_count = 0;
process.write_from_user = false;
_ = spawnNamed(rd, "input"); // the fan-out service
_ = spawnNamed(rd, "input-source"); // a synthetic keyboard publishing events
_ = spawnNamed(rd, "input-test"); // the subscriber whose "ok" line is the marker
// Wait for the subscriber's success heartbeat (it beats once per received event).
const prefix = "input-test: ok";
scheduler.setPriority(1);
const deadline = architecture.millis() + 12000;
while (architecture.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break;
scheduler.yield();
}
scheduler.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok);
check("events kept flowing (service + async send stay up)", process.write_count >= 2);
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
result();
}
/// Process arguments, end to end: spawn args-echo bare (its argv[0] is the /// Process arguments, end to end: spawn args-echo bare (its argv[0] is the
/// initial-ramdisk name). Instance 1 sees argc == 1 and respawns itself through /// initial-ramdisk name). Instance 1 sees argc == 1 and respawns itself through
/// `system_spawn` with the extra arguments "alpha beta-42" — the syscall argument /// `system_spawn` with the extra arguments "alpha beta-42" — the syscall argument
+1 -1
View File
@@ -17,7 +17,7 @@ const runtime = @import("runtime");
/// microkernel keeps such choices in user space, not the kernel. Drivers are absent /// microkernel keeps such choices in user space, not the kernel. Drivers are absent
/// on purpose: the device manager owns those. (A future init reads this from a /// on purpose: the device manager owns those. (A future init reads this from a
/// manifest under /system/services instead of a hardcoded list.) /// manifest under /system/services instead of a hardcoded list.)
const boot_services = [_][]const u8{ "vfs", "device-manager" }; const boot_services = [_][]const u8{ "vfs", "input", "device-manager" };
pub fn main() void { pub fn main() void {
// Prove the heap end to end: allocate through the runtime allocator (which // Prove the heap end to end: allocate through the runtime allocator (which
@@ -0,0 +1,40 @@
//! system/services/input-source — a hardware-free synthetic input source, used to exercise
//! the input service end to end without a real PS/2 controller (the `input` test case, and
//! any bring-up where there is no hardware). It stands in for a driver: it connects to the
//! input service and publishes a rolling stream that cycles through all device classes —
//! keyboard, mouse, and joystick/gamepad — which the service routes to interested
//! subscribers.
//!
//! It stays silent after startup (no per-event logging) so it can share the boot serial
//! transcript with a subscriber whose output is the test's success marker. The real
//! keyboard and mouse drivers publish their own synthetic streams today; swapping in
//! decoded hardware is a follow-up (see docs/input.md).
const runtime = @import("runtime");
const input = runtime.input;
const system = runtime.system;
pub fn main() void {
var source = input.connectSource() orelse {
_ = system.write("input-source: input service unavailable\n");
return;
};
_ = system.write("input-source: publishing synthetic input events\n");
var step: usize = 0;
while (true) : (step +%= 1) {
// Rotate across the device classes so every publish path (and the service's
// per-device routing) is exercised.
switch (step % 3) {
0 => _ = source.publishKeyboardEvent(input.syntheticKeyEvent(step)),
1 => _ = source.publishMouseEvent(input.syntheticMouseEvent(step)),
else => _ = source.publishJoystickEvent(input.syntheticJoystickEvent(step)),
}
system.sleep(200);
}
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start;
}
+56
View File
@@ -0,0 +1,56 @@
//! system/services/input-test — the input service's client and test oracle, the input
//! counterpart of vfs-test. It subscribes to *all* device classes and loops receiving the
//! events a source broadcasts, logging each with its class. It emits the success marker
//! `"input-test: ok"` only **after it has received at least one of each class** (keyboard,
//! mouse, and joystick), then heartbeats it. So the in-kernel `input` test case seeing that
//! marker proves not just that IPC delivery works but that the service *routed* all three
//! device classes to one subscription — source -> service -> subscriber, per device.
const std = @import("std");
const runtime = @import("runtime");
const input = runtime.input;
const system = runtime.system;
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
var line: [128]u8 = undefined;
_ = system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
}
pub fn main() void {
var listener = input.subscribeAll() orelse {
_ = system.write("input-test: could not subscribe\n");
return;
};
_ = system.write("input-test: subscribed\n");
var seen_keyboard = false;
var seen_mouse = false;
var seen_joystick = false;
while (true) {
const event = listener.next() orelse continue;
// Decode the class-specific payload from the tagged envelope and note the class.
if (event.asKeyboard()) |key| {
seen_keyboard = true;
writeLine("input-test: got keyboard code={d} char={d}\n", .{ key.keycode, key.character });
} else if (event.asMouse()) |mouse| {
seen_mouse = true;
writeLine("input-test: got mouse dx={d} dy={d} buttons={d}\n", .{ mouse.dx, mouse.dy, mouse.buttons });
} else if (event.asJoystick()) |joystick| {
seen_joystick = true;
writeLine("input-test: got joystick control={d} value={d}\n", .{ joystick.control, joystick.value });
} else {
writeLine("input-test: got device={d}\n", .{event.device});
}
// The success marker: only once every class has been routed here does this appear,
// and then it heartbeats. Seeing "input-test: ok" proves per-device fan-out works.
if (seen_keyboard and seen_mouse and seen_joystick) {
_ = system.write("input-test: ok all classes received (keyboard, mouse, joystick)\n");
}
}
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start;
}
+145
View File
@@ -0,0 +1,145 @@
//! system/services/input — the user-space input service. Shipped in the initial_ramdisk,
//! spawned as a ring-3 process, and published under the well-known `input` service id. It
//! is the fan-out point between **sources** (keyboard, mouse, and joystick/gamepad drivers)
//! and **subscribers** (any program that wants input): a source `publish`es an
//! `InputEvent`, and the service pushes it to every subscriber whose interest mask includes
//! that event's device class (keyboard / mouse / joystick).
//!
//! The delivery discipline is the whole design (see docs/input.md). The kernel's IPC is a
//! synchronous rendezvous: a server holds one pending reply, so it cannot park N
//! subscribers blocked in a "wait for next event" call. Broadcasting therefore has to be
//! *push* — the service delivering to subscribers. But a synchronous push (`ipc_call`)
//! would let one dead or wedged subscriber hang the whole broadcast, since the kernel
//! never wakes a sender parked on a dead peer's endpoint. So delivery uses the
//! asynchronous `ipc.send`: it posts the event to each subscriber's endpoint queue and
//! returns at once, and can never block on a subscriber. That primitive exists for exactly
//! this ([ipc.md](../../../docs/ipc.md), "asynchronous / buffered send").
//!
//! A subscriber registers by handing the service its own endpoint as a capability (M13
//! capability passing — this service is its first real consumer). The service keeps that
//! handle and `ipc.send`s each event to it.
const std = @import("std");
const runtime = @import("runtime");
const protocol = runtime.input_protocol;
const ipc = runtime.ipc;
const system = runtime.system;
/// One registered subscriber: the endpoint we push events to (a capability it handed us at
/// subscribe time) and the task id that owns it (the subscribe call's badge), so a slot
/// left behind by a subscriber that exited can be reclaimed.
const Subscriber = struct {
used: bool = false,
endpoint: ipc.Handle = 0,
task_id: u32 = 0,
/// Which device classes this subscriber wants (an OR of protocol.device_*). An event
/// is delivered only if its device's bit is set here.
device_mask: u32 = 0,
};
var subscribers = [_]Subscriber{.{}} ** 8;
/// Drop any subscriber whose owning process is no longer alive, so its slot (and the
/// endpoint reference it holds) can be reused. Cheap and only run on subscribe — the async
/// `send` to a dead subscriber's orphaned endpoint is harmless (it just fills a queue no
/// one drains), so this is housekeeping, not correctness.
fn pruneDeadSubscribers() void {
var table: [32]system.ProcessDescriptor = undefined;
const total = system.processes(&table);
const count = @min(total, table.len);
for (&subscribers) |*sub| {
if (!sub.used) continue;
var alive = false;
for (table[0..count]) |descriptor| {
if (descriptor.id == sub.task_id) {
alive = true;
break;
}
}
if (!alive) sub.* = .{};
}
}
/// Register `endpoint` (owned by task `task_id`) to receive the device classes in
/// `device_mask`. Returns false if the subscriber table is full.
fn addSubscriber(endpoint: ipc.Handle, task_id: u32, device_mask: u32) bool {
for (&subscribers) |*sub| {
if (!sub.used) {
sub.* = .{ .used = true, .endpoint = endpoint, .task_id = task_id, .device_mask = device_mask };
return true;
}
}
return false;
}
/// Push `event` to every subscriber whose interest mask includes its device class.
/// `ipc.send` never blocks, so a slow or dead subscriber cannot stall delivery to others.
fn broadcast(event: protocol.InputEvent) void {
const bytes = std.mem.asBytes(&event);
const bit = protocol.deviceBit(event.device);
for (&subscribers) |*sub| {
if (sub.used and sub.device_mask & bit != 0) _ = ipc.send(sub.endpoint, bytes);
}
}
/// Handle one request. `got` carries the sender badge (a task id) and, for subscribe, the
/// subscriber's endpoint capability in `got.cap`. Writes a `Reply` into `out` and returns
/// its length.
fn handle(message: []const u8, got: ipc.Received, out: []u8) usize {
const reply = struct {
fn write(buffer: []u8, status: i32) usize {
const header = protocol.Reply{ .status = status };
@memcpy(buffer[0..protocol.reply_size], std.mem.asBytes(&header));
return protocol.reply_size;
}
};
if (message.len < protocol.request_size) return reply.write(out, -1);
const request = std.mem.bytesToValue(protocol.Request, message[0..protocol.request_size]);
switch (@as(protocol.Operation, @enumFromInt(request.operation))) {
.subscribe => {
const endpoint = got.cap orelse return reply.write(out, -1); // no endpoint passed
// A zero mask means "everything" (a subscriber that named no class still wants input).
const mask = if (request.device_mask == 0) protocol.device_all else request.device_mask;
pruneDeadSubscribers();
if (!addSubscriber(endpoint, @intCast(got.badge), mask)) return reply.write(out, -1); // table full
return reply.write(out, 0);
},
.publish => {
broadcast(request.event);
return reply.write(out, 0);
},
}
}
pub fn main() void {
const endpoint = ipc.createIpcEndpoint() orelse {
_ = system.write("input: no endpoint\n");
return;
};
if (!ipc.register(.input, endpoint)) {
_ = system.write("input: register failed\n");
return;
}
_ = system.write("input: ready\n");
var reply_buffer: [protocol.reply_size]u8 = undefined;
var reply_len: usize = 0;
var receive: [protocol.request_size]u8 = undefined;
while (true) {
const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null);
// Only synchronous client requests (subscribe/publish) arrive here; nothing sends
// this service asynchronous messages, so a notification wake would be spurious.
if (got.isNotification()) {
reply_len = 0;
continue;
}
reply_len = handle(receive[0..got.len], got, &reply_buffer);
}
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start;
}
+210
View File
@@ -0,0 +1,210 @@
//! The input wire protocol — the message format spoken between the user-space input
//! service ([input.zig](input.zig)) and the two kinds of process that reach it: a
//! **source** (a keyboard, mouse, or joystick/gamepad driver) that publishes events, and a
//! **subscriber** (any program) that subscribes and is then pushed each event.
//!
//! The service handles several device classes over one endpoint. Each class has its own
//! typed event (`KeyEvent`, `MouseEvent`, `JoystickEvent`); they all travel in a common
//! `InputEvent` envelope tagged with a `DeviceKind`, so the fan-out path is one code path
//! and a subscriber can take a mix of devices on a single stream. A subscriber declares
//! which classes it wants with a `device_mask`, and the service routes accordingly.
//!
//! Two message shapes ride over the endpoint, tagged by `Operation`, like the
//! [VFS protocol](../vfs/protocol.zig):
//!
//! - **subscribe / publish**: a synchronous `ipc_call` carrying a `Request`. `subscribe`
//! hands the service the subscriber's own endpoint as a capability (`send_cap`) and a
//! `device_mask`; `publish` carries an `InputEvent`. The reply is a `Reply`.
//! - **delivery**: the service pushes each `InputEvent` to every interested subscriber with
//! the asynchronous `ipc_send` — no reply owed, and a dead subscriber can never stall the
//! broadcast. Received in the subscriber's buffer with `Received.isMessage()` set.
//!
//! This is a danos-native contract, shared by the input service, the `runtime.input`
//! client helpers, and every source/subscriber. Everything fits one IPC message.
const std = @import("std");
/// The classes of input device the service fans out. Each names a typed event and a bit in
/// the subscription mask.
pub const DeviceKind = enum(u32) {
keyboard = 0,
mouse = 1,
joystick = 2, // joysticks and gamepads/controllers
};
/// Subscription-interest bits (`Request.device_mask`) — which device classes a subscriber
/// wants. OR them together, or use `device_all`.
pub const device_keyboard: u32 = 1 << 0;
pub const device_mouse: u32 = 1 << 1;
pub const device_joystick: u32 = 1 << 2;
pub const device_all: u32 = device_keyboard | device_mouse | device_joystick;
/// The subscription bit for a `DeviceKind` value (as it appears in `InputEvent.device`).
/// An unknown device maps to 0, so it matches no subscriber.
pub fn deviceBit(device: u32) u32 {
return switch (device) {
@intFromEnum(DeviceKind.keyboard) => device_keyboard,
@intFromEnum(DeviceKind.mouse) => device_mouse,
@intFromEnum(DeviceKind.joystick) => device_joystick,
else => 0,
};
}
// --- keyboard ---------------------------------------------------------------
/// What happened to a key. `key_down`/`key_up` are the physical make/break; `key_press`
/// is the higher-level "a character was produced", carrying it in `KeyEvent.character`.
pub const EventKind = enum(u32) {
key_down = 0,
key_up = 1,
key_press = 2,
};
/// One keyboard event. `keycode` names the physical key (layout-independent); `character`
/// is the Unicode scalar for `key_press` (else 0); `modifiers` is an OR of `modifier_*`.
pub const KeyEvent = extern struct {
kind: u32, // an EventKind
keycode: u32, // a Keycode
character: u32, // Unicode scalar for key_press, else 0
modifiers: u32, // OR of modifier_*
};
pub const modifier_shift: u32 = 1 << 0;
pub const modifier_control: u32 = 1 << 1;
pub const modifier_alt: u32 = 1 << 2;
/// A minimal danos-native keycode namespace — enough for the synthetic source and to show
/// the shape. A real set (USB HID usage-style) fills in with the scancode decoder.
pub const Keycode = enum(u32) {
unknown = 0,
a = 4, // deliberately USB-HID-usage-aligned so a real decoder can extend this
b = 5,
c = 6,
d = 7,
e = 8,
enter = 40,
_,
};
// --- mouse ------------------------------------------------------------------
/// What a mouse event reports. `motion` carries relative `dx`/`dy`; `button_down`/`up`
/// name a button in `button`; `scroll` carries `scroll_x`/`scroll_y`.
pub const MouseEventKind = enum(u32) {
motion = 0,
button_down = 1,
button_up = 2,
scroll = 3,
};
pub const mouse_button_left: u32 = 1 << 0;
pub const mouse_button_right: u32 = 1 << 1;
pub const mouse_button_middle: u32 = 1 << 2;
/// One mouse event. Relative motion (`dx`/`dy`) and wheel (`scroll_*`) are signed;
/// `buttons` is the current pressed-button bitmask (`mouse_button_*`).
pub const MouseEvent = extern struct {
kind: u32, // a MouseEventKind
button: u32, // the mouse_button_* bit for button_down/up, else 0
dx: i32, // relative X motion (.motion)
dy: i32, // relative Y motion (.motion)
scroll_x: i32, // horizontal wheel (.scroll)
scroll_y: i32, // vertical wheel (.scroll)
buttons: u32, // current pressed-button bitmask
};
// --- joystick / gamepad -----------------------------------------------------
/// What a joystick/gamepad event reports. `axis` carries a signed `value` on axis
/// `control`; `button_down`/`up` name a button index in `control`.
pub const JoystickEventKind = enum(u32) {
axis = 0,
button_down = 1,
button_up = 2,
};
/// One joystick/gamepad event. `control` is the axis index (`.axis`) or button index
/// (button events); `value` is the axis position (signed, e.g. -32768..32767) for `.axis`;
/// `buttons` is the current pressed-button bitmask.
pub const JoystickEvent = extern struct {
kind: u32, // a JoystickEventKind
control: u32, // axis index (.axis) or button index (button events)
value: i32, // axis value for .axis, else 0
buttons: u32, // current pressed-button bitmask
};
// --- the common envelope ----------------------------------------------------
/// The largest per-device event, so `InputEvent` can hold any of them inline.
pub const max_event_size: usize = @max(@sizeOf(KeyEvent), @max(@sizeOf(MouseEvent), @sizeOf(JoystickEvent)));
/// The tagged envelope broadcast to subscribers: a `DeviceKind` plus the raw bytes of the
/// matching per-device event. Decode it with `asKeyboard`/`asMouse`/`asJoystick` (each
/// returns null unless `device` matches), or build one with the `from*` constructors.
pub const InputEvent = extern struct {
device: u32, // a DeviceKind
_padding: u32 = 0,
data: [max_event_size]u8 = [_]u8{0} ** max_event_size,
pub fn asKeyboard(self: InputEvent) ?KeyEvent {
if (self.device != @intFromEnum(DeviceKind.keyboard)) return null;
return std.mem.bytesToValue(KeyEvent, self.data[0..@sizeOf(KeyEvent)]);
}
pub fn asMouse(self: InputEvent) ?MouseEvent {
if (self.device != @intFromEnum(DeviceKind.mouse)) return null;
return std.mem.bytesToValue(MouseEvent, self.data[0..@sizeOf(MouseEvent)]);
}
pub fn asJoystick(self: InputEvent) ?JoystickEvent {
if (self.device != @intFromEnum(DeviceKind.joystick)) return null;
return std.mem.bytesToValue(JoystickEvent, self.data[0..@sizeOf(JoystickEvent)]);
}
pub fn fromKeyboard(event: KeyEvent) InputEvent {
return pack(.keyboard, std.mem.asBytes(&event));
}
pub fn fromMouse(event: MouseEvent) InputEvent {
return pack(.mouse, std.mem.asBytes(&event));
}
pub fn fromJoystick(event: JoystickEvent) InputEvent {
return pack(.joystick, std.mem.asBytes(&event));
}
fn pack(device: DeviceKind, bytes: []const u8) InputEvent {
var self = InputEvent{ .device = @intFromEnum(device) };
@memcpy(self.data[0..bytes.len], bytes);
return self;
}
};
// --- request / reply --------------------------------------------------------
/// Which side of a request this is.
pub const Operation = enum(u32) {
subscribe = 0, // register the caller's endpoint (send_cap) for the classes in device_mask
publish = 1, // a source submits `event` to broadcast to interested subscribers
};
/// Request header. For `subscribe`, `device_mask` is the OR of `device_*` bits the caller
/// wants (0 means all) and the caller's receive endpoint travels as the call's capability;
/// `event` is ignored. For `publish`, `event` is the event to broadcast.
pub const Request = extern struct {
operation: u32, // an Operation
device_mask: u32 = 0, // subscribe: interested device classes (0 => all)
event: InputEvent = .{ .device = 0 },
};
/// Reply header. `status` is 0 on success or a negative errno.
pub const Reply = extern struct {
status: i32,
_padding: u32 = 0,
};
pub const request_size: usize = @sizeOf(Request);
pub const reply_size: usize = @sizeOf(Reply);
pub const event_size: usize = @sizeOf(InputEvent);
comptime {
// The delivery path posts a bare InputEvent through ipc_send, so it must fit an
// endpoint's async payload slot (POST_MAXIMUM is 64).
if (event_size > 64) @compileError("InputEvent must fit the ipc_send payload (POST_MAXIMUM)");
}
+6
View File
@@ -250,6 +250,12 @@ CASES = [
{"name": "vfs", {"name": "vfs",
"expect": r"DANOS-TEST-RESULT: PASS", "expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
# The input service: a synthetic keyboard source publishes events, the service
# broadcasts them over the async ipc_send primitive, and a subscriber (which joined by
# passing its endpoint as a capability) receives them — source -> service -> subscriber.
{"name": "input",
"expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# IO passthrough + IRQ-as-IPC: a user-space HPET driver maps device MMIO into # IO passthrough + IRQ-as-IPC: a user-space HPET driver maps device MMIO into
# its own address space, binds the device's interrupt to an IPC endpoint, and # its own address space, binds the device's interrupt to an IPC endpoint, and
# is woken by the hardware five times while blocked (never polling). # is woken by the hardware five times while blocked (never polling).
+468
View File
@@ -0,0 +1,468 @@
#!/usr/bin/env python3
"""Vendor xkeyboard-config and compile it to a native Zig keymap library.
danos does not ship an X11 runtime, but it wants X11's keyboard *data*: the layout
tables (US, UK, German, ...) that turn a physical key + modifiers into a character.
So we compile xkeyboard-config down to Zig at build time, the same way the initial
ramdisk is packed by a host-side Python tool.
Two subcommands:
fetch Download the pinned xkeyboard-config release, verify its sha256, and copy
the transitively-needed data (the symbols files for the configured layouts
plus everything they `include`) into library/xkeyboard-config/vendor/,
alongside a vendored keysymdef.h, the upstream COPYING, and a PROVENANCE.md.
This is the one step that needs the network; run it when bumping the version.
generate Parse the vendored data and emit library/xkeyboard-config/generated/layouts.zig
(deterministic, offline). Run whenever the layout list or emitter changes.
The pipeline per key: our events carry a USB HID usage; HID_TO_NAME maps it to an XKB
key name (<AC01>); the layout's symbols give the keysyms per level; keysymdef.h resolves
each keysym name to its value and Unicode scalar. Level *selection* (which modifier picks
which level) is deliberately left to the Zig side (xkeyboard-config.zig) — we only emit the
per-key type and its up-to-4 keysyms here.
"""
import argparse
import hashlib
import io
import os
import re
import sys
import tarfile
import urllib.request
# --- pinned upstream -------------------------------------------------------
VERSION = "2.44"
TARBALL_URL = (
"https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/archive/"
f"xkeyboard-config-{VERSION}/xkeyboard-config-{VERSION}.tar.gz"
)
TARBALL_SHA256 = "35e34edeaf4e8da8d0696ff6b241ee11ddb1b8c6730bac7252d4d0a88ea5f05b"
# keysymdef.h is xorgproto, not xkeyboard-config; fetch copies it from the build host.
KEYSYMDEF_CANDIDATES = [
"/opt/homebrew/include/X11/keysymdef.h",
"/usr/include/X11/keysymdef.h",
"/usr/X11/include/X11/keysymdef.h",
"/usr/X11R6/include/X11/keysymdef.h",
]
# The layouts we generate: (zig name, symbols file, section or None=default).
TARGETS = [
("us", "us", None),
("gb", "gb", None),
("de", "de", None),
("fr", "fr", None),
("es", "es", None),
("dvorak", "us", "dvorak"),
]
# USB HID usage (keyboard page 0x07) -> XKB key name. The physical keys we can turn into
# characters; positions are the ANSI standard shared by HID usages and XKB names.
HID_TO_NAME = {
0x04: "AC01", 0x05: "AB05", 0x06: "AB03", 0x07: "AC03", 0x08: "AD03",
0x09: "AC04", 0x0A: "AC05", 0x0B: "AC06", 0x0C: "AD08", 0x0D: "AC07",
0x0E: "AC08", 0x0F: "AC09", 0x10: "AB07", 0x11: "AB06", 0x12: "AD09",
0x13: "AD10", 0x14: "AD01", 0x15: "AD04", 0x16: "AC02", 0x17: "AD05",
0x18: "AD07", 0x19: "AB04", 0x1A: "AD02", 0x1B: "AB02", 0x1C: "AD06",
0x1D: "AB01",
0x1E: "AE01", 0x1F: "AE02", 0x20: "AE03", 0x21: "AE04", 0x22: "AE05",
0x23: "AE06", 0x24: "AE07", 0x25: "AE08", 0x26: "AE09", 0x27: "AE10",
0x2C: "SPCE",
0x2D: "AE11", 0x2E: "AE12", 0x2F: "AD11", 0x30: "AD12", 0x31: "BKSL",
0x33: "AC10", 0x34: "AC11", 0x35: "TLDE",
0x36: "AB08", 0x37: "AB09", 0x38: "AB10",
0x64: "LSGT", # the extra key on ISO keyboards (102nd key)
}
# XKB type name -> the KeyType enum tag emitted for the Zig side.
TYPE_TO_TAG = {
"ONE_LEVEL": "one_level",
"TWO_LEVEL": "two_level",
"ALPHABETIC": "alphabetic",
"FOUR_LEVEL": "four_level",
"FOUR_LEVEL_ALPHABETIC": "four_level_alphabetic",
"FOUR_LEVEL_SEMIALPHABETIC": "four_level_semialphabetic",
"FOUR_LEVEL_MIXED_KEYPAD": "keypad",
"FOUR_LEVEL_KEYPAD": "keypad",
"KEYPAD": "keypad",
"PC_SYSRQ": "other",
}
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LIB = os.path.join(REPO, "library", "xkeyboard-config")
VENDOR = os.path.join(LIB, "vendor")
# --- keysymdef.h: keysym name -> (value, unicode scalar or None) ------------
def parse_keysymdef(path):
table = {}
line_re = re.compile(r"#define\s+XK_(\w+)\s+0x([0-9a-fA-F]+)\s*(?:/\*\s*U\+([0-9A-Fa-f]+)|/\*<U\+([0-9A-Fa-f]+))?")
with open(path, encoding="latin-1") as f:
for line in f:
m = line_re.match(line)
if not m:
continue
name = m.group(1)
value = int(m.group(2), 16)
uni = m.group(3) or m.group(4)
unicode_scalar = int(uni, 16) if uni else None
# First definition wins (keysymdef lists the canonical one first).
table.setdefault(name, (value, unicode_scalar))
return table
def resolve_keysym(name, keysymdef):
"""A keysym token from a symbols file -> (keysym value, unicode scalar or 0)."""
if name in ("NoSymbol", "VoidSymbol", ""):
return (0, 0)
# Unicode escape forms: U00E9 / u00E9.
m = re.fullmatch(r"[Uu]([0-9A-Fa-f]{4,6})", name)
if m:
cp = int(m.group(1), 16)
return (0x01000000 + cp, cp)
# Raw hex keysym value: 0x1000161 (a Unicode keysym) or a plain keysym number.
m = re.fullmatch(r"0x([0-9A-Fa-f]+)", name)
if m:
value = int(m.group(1), 16)
if 0x01000100 <= value <= 0x0110FFFF:
return (value, value - 0x01000000)
if 0x20 <= value <= 0x7E or 0xA0 <= value <= 0xFF:
return (value, value)
return (value, 0)
entry = keysymdef.get(name)
if entry is None:
return (0, 0) # unknown named keysym (dead_*, ISO_*, rare) -> no character
value, unicode_scalar = entry
return (value, unicode_scalar or 0)
# --- symbols parsing --------------------------------------------------------
def strip_comments(text):
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
text = re.sub(r"//[^\n]*", "", text)
return text
class KeyDef:
__slots__ = ("levels", "type_name")
def __init__(self, levels, type_name):
self.levels = levels # list of keysym-name strings (group 1)
self.type_name = type_name # explicit XKB type name, or None
class Symbols:
"""Loads and resolves xkb_symbols sections from a directory of symbols files."""
def __init__(self, symbols_dir):
self.dir = symbols_dir
self._files = {} # filename -> {section_name: body, "__default__": name}
self.visited_files = set()
def _load(self, fname):
if fname in self._files:
return self._files[fname]
path = os.path.join(self.dir, fname)
self.visited_files.add(fname)
with open(path, encoding="latin-1") as f:
text = strip_comments(f.read())
sections = {}
default = None
# Find each `[flags] xkb_symbols "NAME" {` and brace-match its body.
for m in re.finditer(r'(\w[\w\s]*?)?\bxkb_symbols\s+"([^"]+)"\s*\{', text):
flags = m.group(1) or ""
name = m.group(2)
body, _ = self._brace_match(text, m.end() - 1)
sections[name] = body
if "default" in flags.split() and default is None:
default = name
if default is None and sections:
default = next(iter(sections))
sections["__default__"] = default
self._files[fname] = sections
return sections
@staticmethod
def _brace_match(text, open_index):
depth = 0
for i in range(open_index, len(text)):
c = text[i]
if c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
return text[open_index + 1:i], i
raise ValueError("unbalanced braces")
def resolve(self, fname, section=None, stack=()):
"""Merged {key name -> KeyDef} for (fname, section), following includes."""
sections = self._load(fname)
if section is None:
section = sections["__default__"]
key = (fname, section)
if key in stack:
return {}
body = sections.get(section)
if body is None:
return {}
keys = {}
default_type = None
for stmt in self._statements(body):
kind = stmt[0]
if kind == "include":
augment, inc_file, inc_section = stmt[1], stmt[2], stmt[3]
inc = self.resolve(inc_file, inc_section, stack + (key,))
for n, kd in inc.items():
if augment and n in keys:
continue
keys[n] = kd
elif kind == "default_type":
default_type = stmt[1]
elif kind == "key":
augment, name, kd = stmt[1], stmt[2], stmt[3]
if augment and name in keys:
continue
keys[name] = kd
if default_type is not None:
for kd in keys.values():
if kd.type_name is None:
kd.type_name = default_type
return keys
def _statements(self, body):
"""Yield ('include', augment, file, section) / ('default_type', name) /
('key', augment, name, KeyDef) in source order."""
# Recognise the three statement heads and step through the body in order.
head = re.compile(
r'(?P<inc>(?:(?P<augi>augment|override|replace)\s+)?include\s+"(?P<incarg>[^"]+)")'
r'|(?P<dtype>key\.type(?:\[[^\]]*\])?\s*=\s*"(?P<dtypearg>[^"]+)")'
r'|(?P<key>(?:(?P<augk>augment|override|replace)\s+)?key\s+<(?P<kname>[^>]+)>\s*\{)'
)
i = 0
while i < len(body):
m = head.search(body, i)
if not m:
break
if m.group("inc"):
inc_file, inc_section = self._parse_include(m.group("incarg"))
yield ("include", m.group("augi") == "augment", inc_file, inc_section)
i = m.end()
elif m.group("dtype"):
yield ("default_type", m.group("dtypearg"))
i = m.end()
else: # a key definition; brace-match its body
kbody, end = self._brace_match(body, m.end() - 1)
kd = self._parse_key_body(kbody)
if kd is not None:
yield ("key", m.group("augk") == "augment", m.group("kname"), kd)
i = end + 1
@staticmethod
def _parse_include(arg):
m = re.fullmatch(r"([^()]+)(?:\(([^)]+)\))?", arg.strip())
return (m.group(1), m.group(2))
@staticmethod
def _parse_key_body(kbody):
type_name = None
mt = re.search(r'type(?:\[[^\]]*\])?\s*=\s*"([^"]+)"', kbody)
if mt:
type_name = mt.group(1)
groups = re.findall(r"\[([^\]]*)\]", kbody)
if not groups:
return None
levels = [tok.strip() for tok in groups[0].split(",")]
levels = [t for t in levels if t != ""]
if not levels:
return None
return KeyDef(levels, type_name)
# --- type inference + emission ---------------------------------------------
def is_case_pair(a, b):
return len(a) == 1 and len(b) == 1 and a.isalpha() and a.islower() and b == a.upper()
def key_type_tag(kd):
if kd.type_name is not None:
return TYPE_TO_TAG.get(kd.type_name, "other")
n = len(kd.levels)
if n <= 1:
return "one_level"
if n == 2:
return "alphabetic" if is_case_pair(kd.levels[0], kd.levels[1]) else "two_level"
return "four_level_alphabetic" if is_case_pair(kd.levels[0], kd.levels[1]) else "four_level"
def build_layout(symbols, keysymdef, fname, section):
keys = symbols.resolve(fname, section)
table = [] # 256 entries: (tag, [(keysym, unicode) x4])
for hid in range(256):
name = HID_TO_NAME.get(hid)
kd = keys.get(name) if name else None
if kd is None:
table.append(("one_level", [(0, 0)] * 4))
continue
levels = [resolve_keysym(kd.levels[i], keysymdef) if i < len(kd.levels) else (0, 0)
for i in range(4)]
table.append((key_type_tag(kd), levels))
return table
def emit_zig(layouts):
out = io.StringIO()
out.write("// GENERATED by tools/make-xkeyboard-config.py from xkeyboard-config "
f"{VERSION}. Do not edit.\n")
out.write("// Keyboard layout tables compiled from the X11 xkeyboard-config database\n")
out.write("// (MIT/X11 licensed; see ../vendor/COPYING and ../vendor/PROVENANCE.md).\n\n")
out.write("pub const Level = struct { keysym: u32 = 0, unicode: u21 = 0 };\n\n")
out.write("pub const KeyType = enum {\n")
out.write(" one_level,\n two_level,\n alphabetic,\n four_level,\n"
" four_level_alphabetic,\n four_level_semialphabetic,\n keypad,\n other,\n};\n\n")
out.write("pub const Key = struct { kind: KeyType = .one_level, levels: [4]Level = [_]Level{.{}} ** 4 };\n\n")
out.write("pub const Layout = struct { name: []const u8, keys: [256]Key };\n\n")
names = []
for zig_name, fname, section in TARGETS:
table = layouts[zig_name]
names.append(zig_name)
out.write(f"pub const {zig_name}: Layout = .{{\n")
out.write(f' .name = "{zig_name}",\n')
out.write(" .keys = .{\n")
for hid, (tag, levels) in enumerate(table):
if tag == "one_level" and all(k == 0 and u == 0 for k, u in levels):
out.write(" .{},\n")
continue
parts = ", ".join(f".{{ .keysym = {k}, .unicode = {u} }}" for k, u in levels)
out.write(f" .{{ .kind = .{tag}, .levels = .{{ {parts} }} }},\n")
out.write(" },\n};\n\n")
out.write("pub const all = [_]*const Layout{ " + ", ".join("&" + n for n in names) + " };\n")
return out.getvalue()
# --- fetch ------------------------------------------------------------------
def collect_needed(symbols):
for _, fname, section in TARGETS:
symbols.resolve(fname, section)
return set(symbols.visited_files)
def find_keysymdef():
env = os.environ.get("KEYSYMDEF")
if env and os.path.isfile(env):
return env
for p in KEYSYMDEF_CANDIDATES:
if os.path.isfile(p):
return p
sys.exit("error: keysymdef.h not found; install xorgproto or set KEYSYMDEF=/path/to/keysymdef.h")
def cmd_fetch(_args):
local = os.environ.get("XKB_TARBALL")
if local:
data = open(local, "rb").read()
else:
print(f"downloading {TARBALL_URL}")
data = urllib.request.urlopen(TARBALL_URL).read()
digest = hashlib.sha256(data).hexdigest()
if digest != TARBALL_SHA256:
sys.exit(f"error: sha256 mismatch\n expected {TARBALL_SHA256}\n got {digest}")
tar = tarfile.open(fileobj=io.BytesIO(data), mode="r:gz")
members = tar.getnames()
root = members[0].split("/")[0]
# Extract symbols/ + COPYING to a temp view, resolve includes, keep only what's needed.
tmp = os.path.join(VENDOR, ".upstream")
for m in tar.getmembers():
if m.name.startswith(f"{root}/symbols/") or m.name == f"{root}/COPYING":
m.name = m.name[len(root) + 1:]
tar.extract(m, tmp)
tar.close()
needed = collect_needed(Symbols(os.path.join(tmp, "symbols")))
os.makedirs(os.path.join(VENDOR, "symbols"), exist_ok=True)
for fname in sorted(needed):
src = os.path.join(tmp, "symbols", fname)
dst = os.path.join(VENDOR, "symbols", fname)
os.makedirs(os.path.dirname(dst), exist_ok=True)
with open(src, "rb") as s, open(dst, "wb") as d:
d.write(s.read())
_copy(os.path.join(tmp, "COPYING"), os.path.join(VENDOR, "COPYING"))
keysymdef = find_keysymdef()
_copy(keysymdef, os.path.join(VENDOR, "keysymdef.h"))
_rmtree(tmp)
with open(os.path.join(VENDOR, "PROVENANCE.md"), "w") as f:
f.write("# Vendored xkeyboard-config subset\n\n")
f.write(f"- **Package**: xkeyboard-config {VERSION}\n")
f.write(f"- **Source**: {TARBALL_URL}\n")
f.write(f"- **sha256**: `{TARBALL_SHA256}`\n")
f.write(f"- **keysymdef.h**: xorgproto, copied from `{keysymdef}`\n")
f.write("- **License**: MIT/X11 (see COPYING)\n\n")
f.write("Only the symbols files reachable from the generated layouts "
"(tools/make-xkeyboard-config.py `TARGETS`) are vendored; regenerate with\n"
"`python3 tools/make-xkeyboard-config.py fetch` then `... generate`.\n\n")
f.write("Vendored symbols files:\n\n")
for fname in sorted(needed):
f.write(f"- `symbols/{fname}`\n")
print(f"vendored {len(needed)} symbols files + keysymdef.h + COPYING into {VENDOR}")
def cmd_generate(args):
symbols_dir = args.symbols or os.path.join(VENDOR, "symbols")
keysymdef_path = args.keysymdef or os.path.join(VENDOR, "keysymdef.h")
keysymdef = parse_keysymdef(keysymdef_path)
symbols = Symbols(symbols_dir)
layouts = {zig_name: build_layout(symbols, keysymdef, fname, section)
for zig_name, fname, section in TARGETS}
out_dir = os.path.join(LIB, "generated")
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, "layouts.zig")
with open(out_path, "w") as f:
f.write(emit_zig(layouts))
print(f"wrote {out_path} ({len(TARGETS)} layouts)")
def _copy(src, dst):
os.makedirs(os.path.dirname(dst), exist_ok=True)
with open(src, "rb") as s, open(dst, "wb") as d:
d.write(s.read())
def _rmtree(path):
for root, dirs, files in os.walk(path, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
if os.path.isdir(path):
os.rmdir(path)
def main():
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("fetch", help="download + vendor the needed xkeyboard-config data")
g = sub.add_parser("generate", help="emit generated/layouts.zig from the vendored data")
g.add_argument("--symbols", help="override the vendored symbols/ dir (for development)")
g.add_argument("--keysymdef", help="override the vendored keysymdef.h (for development)")
args = parser.parse_args()
{"fetch": cmd_fetch, "generate": cmd_generate}[args.command](args)
if __name__ == "__main__":
main()