diff --git a/build.zig b/build.zig index 4656fc6..227449f 100644 --- a/build.zig +++ b/build.zig @@ -60,6 +60,7 @@ fn addUserBinary( runtime_module: *std.Build.Module, posix_module: *std.Build.Module, mmio_module: *std.Build.Module, + xkeyboard_config_module: *std.Build.Module, name: []const u8, root: []const u8, ) *std.Build.Step.Compile { @@ -81,6 +82,9 @@ fn addUserBinary( .{ .name = "posix", .module = posix_module }, // Typed volatile MMIO + memory barriers, for drivers. See library/mmio/. .{ .name = "mmio", .module = mmio_module }, + // Keyboard layouts (keycode + modifiers -> keysym/character), available + // to any program that wants it. See library/xkeyboard-config/. + .{ .name = "xkeyboard-config", .module = xkeyboard_config_module }, }, }), }); @@ -224,7 +228,6 @@ pub fn build(b: *std.Build) void { .{ .name = "layouts", .module = xkb_layouts_module }, }, }); - _ = xkeyboard_config_module; // 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 @@ -309,7 +312,7 @@ pub fn build(b: *std.Build) void { // Built by the shared user-binary recipe (see addUserBinary): freestanding, // linked into the kernel's user region against the `runtime` runtime library, and // started in ring 3 by the kernel's user-ELF loader. - const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "init", "system/services/init/init.zig"); + const init_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "init", "system/services/init/init.zig"); const init_install = b.addInstallArtifact(init_exe, .{ .dest_dir = .{ .override = .{ .custom = "system/services" } } }); b.getInstallStep().dependOn(&init_install.step); @@ -317,21 +320,21 @@ pub fn build(b: *std.Build) void { // Each is built by the same user-binary recipe, then packed into one image by // the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel, // which unpacks it and spawns each program (system/initial-ramdisk.zig). - const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "vfs", "system/services/vfs/vfs.zig"); - const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "vfs-test", "system/services/vfs/vfs-test.zig"); - const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "hpet", "system/drivers/hpet/hpet.zig"); - const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/bus/bus.zig"); - const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "bus", "system/drivers/ps2-bus/ps2-bus.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 device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "device-manager", "system/services/device-manager/device-manager.zig"); + const vfs_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "vfs", "system/services/vfs/vfs.zig"); + const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "vfs-test", "system/services/vfs/vfs-test.zig"); + const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "hpet", "system/drivers/hpet/hpet.zig"); + const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "bus", "system/drivers/bus/bus.zig"); + const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "bus", "system/drivers/ps2-bus/ps2-bus.zig"); + const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig"); + const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig"); + const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_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 process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, "process-test", "system/services/process-test/process-test.zig"); + const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "input", "system/services/input/input.zig"); + const input_source_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "input-source", "system/services/input-source/input-source.zig"); + const input_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "input-test", "system/services/input-test/input-test.zig"); + const args_echo_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "args-echo", "system/services/args-echo/args-echo.zig"); + const process_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, "process-test", "system/services/process-test/process-test.zig"); // Pack the user binaries into the initial_ramdisk image with the host-side Python tool // (the container format is trivial, and Python sidesteps std API churn). Args: @@ -507,6 +510,7 @@ pub fn build(b: *std.Build) void { "system/devices/pci-class.zig", // class/subclass/prog-IF name decoding "system/devices/acpi-ids.zig", // _HID name decoding "library/mmio/mmio.zig", // barriers assemble + registers round-trip + "system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine }) |root| { const mod_tests = b.addTest(.{ .root_module = b.createModule(.{ diff --git a/docs/input.md b/docs/input.md index bde90be..a0860aa 100644 --- a/docs/input.md +++ b/docs/input.md @@ -108,22 +108,29 @@ 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. +- **The keyboard is real.** The `ps2-bus` driver owns PNP0303, which carries *both* the + 0x60/0x64 ports and IRQ1, so reading the hardware lives in the bus, not in + [keyboard.zig](../system/drivers/ps2-bus/keyboard.zig): the bus binds IRQ1 and, on each + interrupt, drains port 0x60, routing every byte by the status register's + auxiliary-output bit to whichever child driver **attached** for that device (an + `AttachRequest` to the well-known `ps2_bus` service, carrying the child's endpoint as a + capability; the bytes then arrive as asynchronous `ForwardedByte` messages, so the IRQ + path never blocks on a child). The keyboard driver decodes the stream — scancode **set 2**, + what the keyboard sends with the 8042's legacy translation off, decoded by + [scancode.zig](../system/drivers/ps2-bus/scancode.zig) into USB HID usage keycodes with + make/break, typematic-repeat, and modifier tracking (host-tested under `zig build test`) — + and publishes real `key_down`/`key_press`/`key_up` events. +- **Keycode → character** is wired in: the keyboard driver fills a `key_press` event's + `character` through [`library/xkeyboard-config`](../library/xkeyboard-config/README.md) + (`xkb.map(layout, keycode, mods)` → keysym + Unicode character), synthesizing the ASCII + control characters for Enter/Tab/Backspace/Escape, whose keysyms map to no Unicode. The + layout defaults to `us`; the bus can pass another as the driver's argv[2] — the seam for + a future settings source. +- **The mouse is still synthetic.** [mouse.zig](../system/drivers/ps2-bus/mouse.zig) + publishes a placeholder motion/click stream; the hardware-free `input-source` likewise + rotates through all three classes (including a synthetic joystick, which has no driver + yet) via the `input.synthetic*Event` helpers. **Follow-up:** the mouse driver attaches to + the bus the way the keyboard does and decodes 3/4-byte packets into mouse events. - **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 diff --git a/system/abi.zig b/system/abi.zig index 825039e..69ba4df 100644 --- a/system/abi.zig +++ b/system/abi.zig @@ -124,6 +124,7 @@ pub const ProcessDescriptor = extern struct { pub const ServiceId = enum(u32) { vfs = 1, input = 2, + ps2_bus = 3, // the 8042 owner; child device drivers attach here for raw bytes _, }; diff --git a/system/drivers/ps2-bus/keyboard.zig b/system/drivers/ps2-bus/keyboard.zig index 881ab4d..3b2edbd 100644 --- a/system/drivers/ps2-bus/keyboard.zig +++ b/system/drivers/ps2-bus/keyboard.zig @@ -1,19 +1,74 @@ //! PS/2 Keyboard Driver //! -//! Spawned by the ps2-bus driver once the controller is initialized and port 1 +//! Spawned by the ps2-bus driver once the controller is initialized and the port //! has passed its interface test and device reset. The bus driver hands us our -//! device HID as argv[1]; we use it to locate our own device descriptor. +//! device HID as argv[1] and, optionally, a layout name (`"us"`, `"gb"`, ...) as +//! argv[2]. +//! +//! The 8042's ports (0x60/0x64) and IRQ1 live on the PNP0303 node, which the +//! ps2-bus driver exclusively owns — so this driver never touches the hardware. +//! Instead it **attaches** to the bus (handing over its endpoint as a capability) +//! and receives every scancode byte as a forwarded asynchronous message. Each byte +//! feeds the set-2 decoder; a decoded key becomes input-protocol events: +//! +//! scancode byte -> HID usage keycode -> key_down / key_up +//! -> xkeyboard-config -> character -> key_press const std = @import("std"); const runtime = @import("runtime"); +const xkb = @import("xkeyboard-config"); const ps2 = @import("ps2-library.zig"); +const scancode = @import("scancode.zig"); const device = runtime.device; +const ipc = runtime.ipc; +const protocol = runtime.input_protocol; fn writeLine(comptime fmt: []const u8, arguments: anytype) void { var line: [128]u8 = undefined; _ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return); } +/// Look up the ps2-bus service, retrying while the bus (which spawned us before +/// registering) is still coming up. +fn lookupBus() ?ipc.Handle { + var attempts: usize = 0; + while (attempts < 100) : (attempts += 1) { + if (ipc.lookup(.ps2_bus)) |handle| return handle; + runtime.system.sleep(50); + } + return null; +} + +/// The character a pressed key produces under `modifiers`, or 0 for none. The +/// layout lookup answers for printable keys; the keys whose keysym has no Unicode +/// mapping but that every consumer still expects as a character (Enter, Tab, +/// Backspace, Escape) are given their ASCII control characters here. +fn characterFor(layout: *const xkb.Layout, usage: u8, modifiers: scancode.ModifierSnapshot) u32 { + const mapping = xkb.map(layout, usage, .{ + .shift = modifiers.shift, + .caps_lock = modifiers.caps_lock, + .level3 = modifiers.right_alt, + .control = modifiers.control, + }); + if (mapping.character) |character| return character; + return switch (@as(protocol.Keycode, @enumFromInt(usage))) { + .enter, .keypad_enter => '\n', + .tab => '\t', + .backspace => 0x08, + .escape => 0x1B, + else => 0, + }; +} + +/// The input protocol's modifier word for a snapshot. +fn modifierWord(modifiers: scancode.ModifierSnapshot) u32 { + var word: u32 = 0; + if (modifiers.shift) word |= protocol.modifier_shift; + if (modifiers.control) word |= protocol.modifier_control; + if (modifiers.alt) word |= protocol.modifier_alt; + return word; +} + pub fn main(init: runtime.process.Init) void { const hid = init.arguments.get(1).?; if (hid.len == 0) { @@ -31,25 +86,95 @@ pub fn main(init: runtime.process.Init) void { return; } - // The 8042 ports (0x60/0x64) and this keyboard's IRQ1 both live on the same - // PNP0303 node, which the ps2-bus driver exclusively owns — so the keyboard is - // 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"); + // The layout is a spawn argument so a later settings source can choose it; + // absent (as today) it defaults to us. + const layout_name = init.arguments.get(2) orelse "us"; + const layout = xkb.byName(layout_name) orelse xkb.us; + writeLine("system/drivers/ps2-bus/keyboard: layout {s}\n", .{layout.name}); - // 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. + // Attach to the bus: hand it our endpoint, and it forwards every byte the + // keyboard sends (it owns the controller; we own the decoding). + const bus = lookupBus() orelse { + _ = runtime.system.write("system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n"); + return; + }; + const endpoint = ipc.createIpcEndpoint() orelse { + _ = runtime.system.write("system/drivers/ps2-bus/keyboard: no endpoint\n"); + return; + }; + var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) }; + var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined; + const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch { + _ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach call failed\n"); + return; + }; + if (attached.len < @sizeOf(ps2.AttachReply) or + std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != 0) + { + _ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach refused\n"); + return; + } + + // Broadcast keyboard events through the input service so programs can listen + // for them (docs/input.md). 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"); - var step: usize = 0; - while (true) : (step +%= 1) { - _ = source.publishKeyboardEvent(runtime.input.syntheticKeyEvent(step)); - runtime.system.sleep(200); + var decoder = scancode.Decoder{}; + var state = scancode.KeyboardState{}; + var receive: [@sizeOf(ps2.ForwardedByte)]u8 = undefined; + while (true) { + const got = ipc.replyWait(endpoint, &.{}, &receive, null); + if (!got.isMessage() or got.len < @sizeOf(ps2.ForwardedByte)) continue; + const forwarded = std.mem.bytesToValue(ps2.ForwardedByte, receive[0..@sizeOf(ps2.ForwardedByte)]); + + const key = decoder.feed(@intCast(forwarded.byte & 0xFF)) orelse continue; + const transition = state.apply(key); + const modifiers = modifierWord(transition.modifiers); + + switch (transition.action) { + .pressed => { + _ = source.publishKeyboardEvent(.{ + .kind = @intFromEnum(protocol.EventKind.key_down), + .keycode = key.usage, + .character = 0, + .modifiers = modifiers, + }); + const character = characterFor(layout, key.usage, transition.modifiers); + if (character != 0) { + _ = source.publishKeyboardEvent(.{ + .kind = @intFromEnum(protocol.EventKind.key_press), + .keycode = key.usage, + .character = character, + .modifiers = modifiers, + }); + } + }, + // Typematic repeat: the key did not physically go down again, so no + // key_down — but it keeps producing its character. + .repeated => { + const character = characterFor(layout, key.usage, transition.modifiers); + if (character != 0) { + _ = source.publishKeyboardEvent(.{ + .kind = @intFromEnum(protocol.EventKind.key_press), + .keycode = key.usage, + .character = character, + .modifiers = modifiers, + }); + } + }, + .released => { + _ = source.publishKeyboardEvent(.{ + .kind = @intFromEnum(protocol.EventKind.key_up), + .keycode = key.usage, + .character = 0, + .modifiers = modifiers, + }); + }, + } } } diff --git a/system/drivers/ps2-bus/ps2-bus.zig b/system/drivers/ps2-bus/ps2-bus.zig index 98c985e..cab2f38 100644 --- a/system/drivers/ps2-bus/ps2-bus.zig +++ b/system/drivers/ps2-bus/ps2-bus.zig @@ -13,6 +13,7 @@ const std = @import("std"); const runtime = @import("runtime"); const ps2 = @import("ps2-library.zig"); const device = runtime.device; +const ipc = runtime.ipc; /// Format one whole log line and emit it in a single `debug_write`, so output /// from the child drivers (which run concurrently) can never interleave with it. @@ -23,22 +24,68 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void { /// Ask the device on `port` what it is, then spawn the matching driver from the /// initial-ramdisk, handing it the device's HID as argv[1]. The driver is chosen -/// from what the device reports, not from the port number. -fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) void { +/// from what the device reports, not from the port number. Returns the identified +/// type so the forwarding loop can route that port's bytes to the driver once it +/// attaches, or null if nothing was spawned. +fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType { const device_type = controller.identifyDevice(port) orelse { writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)}); - return; + return null; }; const driver_name = device_type.driverName() orelse { writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)}); - return; + return null; }; const hid = device_type.hid() orelse ""; if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) { writeLine("system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name }); - } else { - writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name}); + return device_type; } + writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name}); + return null; +} + +/// Resource index of the controller's IRQ (IRQ1) on the PNP0303 descriptor, found +/// the way the ports are found in `Controller.init`. +fn findInterruptResourceIndex(descriptor: device.DeviceDescriptor) ?u64 { + for (0..descriptor.resource_count) |index| { + if (descriptor.resources[index].kind == @intFromEnum(device.ResourceKind.irq)) return index; + } + return null; +} + +/// Forwarding endpoints of the attached child drivers, indexed by `ps2.Port`. +/// Written when a child's `AttachRequest` arrives, read on every forwarded byte. +var port_endpoints = [_]?ipc.Handle{ null, null }; + +/// Which device type each port identified as, so an attaching child (which knows +/// its type, not its port) can be matched to the right port's byte stream. +var port_device_types = [_]?ps2.DeviceType{ null, null }; + +/// Handle a child driver's `AttachRequest`: record the endpoint capability it +/// passed as the forwarding target for the port whose device matches its type. +/// Writes an `AttachReply` into `out` and returns its length. +fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize { + const reply = struct { + fn write(buffer: []u8, status: i32) usize { + const header = ps2.AttachReply{ .status = status }; + @memcpy(buffer[0..@sizeOf(ps2.AttachReply)], std.mem.asBytes(&header)); + return @sizeOf(ps2.AttachReply); + } + }; + + if (message.len < @sizeOf(ps2.AttachRequest)) return reply.write(out, -1); + const request = std.mem.bytesToValue(ps2.AttachRequest, message[0..@sizeOf(ps2.AttachRequest)]); + const endpoint = got.cap orelse return reply.write(out, -1); // no endpoint passed + + for (&port_device_types, 0..) |maybe_type, port_index| { + const device_type = maybe_type orelse continue; + if (@intFromEnum(device_type) != request.device_type) continue; + port_endpoints[port_index] = endpoint; + writeLine("system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)}); + return reply.write(out, 0); + } + return reply.write(out, -1); // no port identified as that device type } pub fn main() void { @@ -48,6 +95,8 @@ pub fn main() void { }; var has_two_channels = false; + var maybe_controller: ?ps2.Controller = null; + var maybe_interrupt_index: ?u64 = null; // The 8042's IO ports (0x60/0x64) are enumerated under the keyboard ACPI node // (PNP0303), so we init the controller from that descriptor — but which device // is on which port is decided later by identify, not by this HID. @@ -65,6 +114,8 @@ pub fn main() void { _ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n"); return; }; + maybe_controller = controller; + maybe_interrupt_index = findInterruptResourceIndex(controller_device_descriptor); controller.disablePort(.One); controller.disablePort(.Two); @@ -126,18 +177,12 @@ pub fn main() void { return; } - // enable the working ports and, via a read-modify-write, their interrupts + // Enable the working ports. Their interrupts stay off until IRQ1 is bound + // below — reset and identify use polled reads, which must never race the + // interrupt-driven drain loop for bytes. controller.enablePort(.One); if (port_two_works) controller.enablePort(.Two); - var configuration = controller.readConfigurationByte() orelse { - _ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n"); - return; - }; - if (port_one_works) configuration |= ps2.Port.One.interruptBit(); - if (port_two_works) configuration |= ps2.Port.Two.interruptBit(); - _ = controller.writeConfigurationByte(configuration); - // reset each working device; a failing device is logged but does not // abort bring-up of the other one if (port_one_works) { @@ -158,15 +203,77 @@ pub fn main() void { // Identify the device on each working port and hand it off to the driver // that matches what it reported — a port is not assumed to be a keyboard // or a mouse by its number. - if (port_one_works) spawnIdentifiedDriver(controller, .One); - if (port_two_works) spawnIdentifiedDriver(controller, .Two); + if (port_one_works) port_device_types[@intFromEnum(ps2.Port.One)] = spawnIdentifiedDriver(controller, .One); + if (port_two_works) port_device_types[@intFromEnum(ps2.Port.Two)] = spawnIdentifiedDriver(controller, .Two); } else { _ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n"); return; } + const controller = maybe_controller.?; + const interrupt_index = maybe_interrupt_index orelse { + _ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IRQ\n"); + return; + }; + + // The endpoint the child drivers attach to and IRQ1 wakes. Registered under a + // well-known id so the children can find it, the way input subscribers find + // the input service. + const endpoint = ipc.createIpcEndpoint() orelse { + _ = runtime.system.write("system/drivers/ps2-bus: no endpoint\n"); + return; + }; + if (!ipc.register(.ps2_bus, endpoint)) { + _ = runtime.system.write("system/drivers/ps2-bus: register failed\n"); + return; + } + + // From here on, only the interrupt path reads the data port. Drop anything a + // device sent between enable-scanning and now, bind the IRQ, and only then + // let the controller raise it — an interrupt with nobody bound is lost. + controller.drainOutputBuffer(); + if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) { + _ = runtime.system.write("system/drivers/ps2-bus: irq_bind failed\n"); + return; + } + var configuration = controller.readConfigurationByte() orelse { + _ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n"); + return; + }; + if (port_device_types[@intFromEnum(ps2.Port.One)] != null) configuration |= ps2.Port.One.interruptBit(); + if (port_device_types[@intFromEnum(ps2.Port.Two)] != null) configuration |= ps2.Port.Two.interruptBit(); + _ = controller.writeConfigurationByte(configuration); + _ = runtime.system.write("system/drivers/ps2-bus: ok\n"); - while (true) runtime.system.sleep(1000); + + // The forwarding loop: an IRQ1 notification drains the output buffer, routing + // each byte to the attached driver of the port it came from; a client message + // is a child driver's AttachRequest. + var reply_buffer: [@sizeOf(ps2.AttachReply)]u8 = undefined; + var reply_len: usize = 0; + var receive: [@sizeOf(ps2.AttachRequest)]u8 = undefined; + while (true) { + const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null); + if (got.isNotification()) { + reply_len = 0; + if (got.isMessage() or got.isChildExit()) continue; // nothing sends us these + while (true) { + const current_status = ps2.status(controller.device_id, controller.status_index); + if (current_status & ps2.status_output_buffer_full == 0) break; + const byte = device.ioRead(controller.device_id, controller.data_index, 0, 1) orelse break; + const port: ps2.Port = if (current_status & ps2.status_auxiliary_output != 0) .Two else .One; + if (port_endpoints[@intFromEnum(port)]) |child| { + const forwarded = ps2.ForwardedByte{ .port = @intFromEnum(port), .byte = byte }; + _ = ipc.send(child, std.mem.asBytes(&forwarded)); + } + // An unattached port's byte is dropped — e.g. a keystroke before + // the keyboard driver has attached. + } + _ = device.irqAck(controller.device_id, interrupt_index); + continue; + } + reply_len = handleAttach(receive[0..got.len], got, &reply_buffer); + } } pub const panic = runtime.panic; diff --git a/system/drivers/ps2-bus/ps2-library.zig b/system/drivers/ps2-bus/ps2-library.zig index 7b60c06..d256523 100644 --- a/system/drivers/ps2-bus/ps2-library.zig +++ b/system/drivers/ps2-bus/ps2-library.zig @@ -49,12 +49,15 @@ pub const cmd_write_second_port_output: u8 = 0xD3; // write next data byte to th pub const cmd_write_second_port_input: u8 = 0xD4; // write next data byte to the second port input buffer (to the mouse) pub const cmd_pulse_system_reset: u8 = 0xFE; // pulse output line 0 low: resets the CPU -/// PS/2 status register bits (read from the status port, 0x64). Bits 4 and 5 -/// are chipset-specific and intentionally omitted. +/// PS/2 status register bits (read from the status port, 0x64). Bit 4 is +/// chipset-specific and intentionally omitted. pub const status_output_buffer_full: u8 = 1 << 0; // 1 = a byte is waiting to be read from the data port pub const status_input_buffer_full: u8 = 1 << 1; // 1 = the controller has not yet consumed the last write pub const status_system_flag: u8 = 1 << 2; // set once the controller passes POST pub const status_command_or_data: u8 = 1 << 3; // 1 = last write was a command, 0 = data +/// Chipset-specific in the original spec, universal in practice on dual-channel +/// controllers: set = the waiting byte came from the second port (the mouse). +pub const status_auxiliary_output: u8 = 1 << 5; pub const status_timeout_error: u8 = 1 << 6; // 1 = time-out error pub const status_parity_error: u8 = 1 << 7; // 1 = parity error @@ -237,11 +240,12 @@ pub const Port = enum(u2) { }; /// The kind of device attached to a port, as reported by the device itself in -/// response to the identify command — not assumed from the port number. -pub const DeviceType = enum { - keyboard, - mouse, - unknown, +/// response to the identify command — not assumed from the port number. Fixed +/// `u32` values because the type also travels in an `AttachRequest`. +pub const DeviceType = enum(u32) { + keyboard = 0, + mouse = 1, + unknown = 2, /// Initial-ramdisk name of the driver that serves this device type, or null /// if we could not classify it. @@ -264,6 +268,37 @@ pub const DeviceType = enum { } }; +// --- the bus <-> child-driver forwarding protocol ----------------------------- +// +// The 8042's ports and IRQ1 live on the PNP0303 node that only the ps2-bus driver +// claims, so the child device drivers (ps2-keyboard, ps2-mouse) cannot read port +// 0x60 themselves. Instead each child **attaches**: it calls the bus's well-known +// `ps2_bus` endpoint with an `AttachRequest`, handing over its own endpoint as the +// call's capability. From then on the bus forwards every byte the device sends as +// a `ForwardedByte` via the asynchronous `ipc.send` — the IRQ path in the bus can +// never block on a slow child, and the child never touches the controller. + +/// A child driver registering for its device's bytes. `device_type` is a +/// `DeviceType` value; the child's receive endpoint travels as the call's +/// capability (`send_cap`). +pub const AttachRequest = extern struct { + device_type: u32, +}; + +/// Reply to an `AttachRequest`. `status` is 0 on success or a negative errno. +pub const AttachReply = extern struct { + status: i32, + _padding: u32 = 0, +}; + +/// One raw byte read from the data port, forwarded to the attached child whose +/// port it came from (routed by the status register's auxiliary-output bit). +pub const ForwardedByte = extern struct { + /// The `Port` the byte came from, as `@intFromEnum`. + port: u32, + byte: u32, +}; + /// A single PS/2 (8042) controller. Construct one with `Controller.init` and /// drive the controller through its methods; there is only ever one 8042 per /// machine, but holding the resolved resource indices in an instance keeps the diff --git a/system/drivers/ps2-bus/scancode.zig b/system/drivers/ps2-bus/scancode.zig new file mode 100644 index 0000000..235121c --- /dev/null +++ b/system/drivers/ps2-bus/scancode.zig @@ -0,0 +1,389 @@ +//! PS/2 scancode set 2 → USB HID usage decoding, plus the keyboard state a driver +//! needs on top of it (pressed keys, modifier tracking, caps-lock toggle). +//! +//! Set 2 is what a keyboard sends when the 8042's legacy set-1 translation is off — +//! which is how ps2-bus.zig deliberately configures the controller. A key's **make** +//! code is one byte (two with an `E0` prefix for the "extended" keys added after the +//! original AT layout); its **break** code is the same code behind an `F0` prefix. +//! Pause alone is an eight-byte `E1` sequence with no break. +//! +//! The output vocabulary is USB HID keyboard-page usages (a=4, enter=40, ...), the +//! same numbering the input protocol's `Keycode` and the xkeyboard-config layout +//! tables use — so a decoded usage indexes a layout directly. +//! +//! Everything here is pure (no imports beyond `std`, no IO), so it is host-testable: +//! the tests at the bottom run under `zig build test`. + +const std = @import("std"); + +// --- USB HID usages the state machine itself needs to recognize -------------- + +pub const usage_caps_lock: u8 = 0x39; +pub const usage_left_control: u8 = 0xE0; +pub const usage_left_shift: u8 = 0xE1; +pub const usage_left_alt: u8 = 0xE2; +pub const usage_right_control: u8 = 0xE4; +pub const usage_right_shift: u8 = 0xE5; +pub const usage_right_alt: u8 = 0xE6; // AltGr — selects XKB level 3 + +// --- scancode set 2 → HID usage tables --------------------------------------- + +/// Single-byte (non-`E0`) make codes. Zero means "no key" — protocol bytes (ACK, +/// BAT results) and reserved codes land there and decode to nothing. +pub const set2_base: [256]u8 = blk: { + var table = [_]u8{0} ** 256; + // function row + table[0x01] = 0x42; // F9 + table[0x03] = 0x3E; // F5 + table[0x04] = 0x3C; // F3 + table[0x05] = 0x3A; // F1 + table[0x06] = 0x3B; // F2 + table[0x07] = 0x45; // F12 + table[0x09] = 0x43; // F10 + table[0x0A] = 0x41; // F8 + table[0x0B] = 0x3F; // F6 + table[0x0C] = 0x3D; // F4 + table[0x78] = 0x44; // F11 + table[0x83] = 0x40; // F7 + // letters + table[0x1C] = 0x04; // A + table[0x32] = 0x05; // B + table[0x21] = 0x06; // C + table[0x23] = 0x07; // D + table[0x24] = 0x08; // E + table[0x2B] = 0x09; // F + table[0x34] = 0x0A; // G + table[0x33] = 0x0B; // H + table[0x43] = 0x0C; // I + table[0x3B] = 0x0D; // J + table[0x42] = 0x0E; // K + table[0x4B] = 0x0F; // L + table[0x3A] = 0x10; // M + table[0x31] = 0x11; // N + table[0x44] = 0x12; // O + table[0x4D] = 0x13; // P + table[0x15] = 0x14; // Q + table[0x2D] = 0x15; // R + table[0x1B] = 0x16; // S + table[0x2C] = 0x17; // T + table[0x3C] = 0x18; // U + table[0x2A] = 0x19; // V + table[0x1D] = 0x1A; // W + table[0x22] = 0x1B; // X + table[0x35] = 0x1C; // Y + table[0x1A] = 0x1D; // Z + // digit row + table[0x16] = 0x1E; // 1 + table[0x1E] = 0x1F; // 2 + table[0x26] = 0x20; // 3 + table[0x25] = 0x21; // 4 + table[0x2E] = 0x22; // 5 + table[0x36] = 0x23; // 6 + table[0x3D] = 0x24; // 7 + table[0x3E] = 0x25; // 8 + table[0x46] = 0x26; // 9 + table[0x45] = 0x27; // 0 + // control and whitespace + table[0x5A] = 0x28; // Enter + table[0x76] = 0x29; // Escape + table[0x66] = 0x2A; // Backspace + table[0x0D] = 0x2B; // Tab + table[0x29] = 0x2C; // Space + // punctuation + table[0x4E] = 0x2D; // - _ + table[0x55] = 0x2E; // = + + table[0x54] = 0x2F; // [ { + table[0x5B] = 0x30; // ] } + table[0x5D] = 0x31; // \ | (non-US hash on ISO boards, same position) + table[0x4C] = 0x33; // ; : + table[0x52] = 0x34; // ' " + table[0x0E] = 0x35; // ` ~ + table[0x41] = 0x36; // , < + table[0x49] = 0x37; // . > + table[0x4A] = 0x38; // / ? + table[0x61] = 0x64; // non-US backslash (the extra ISO key between shift and Z) + // locks + table[0x58] = usage_caps_lock; + table[0x77] = 0x53; // Num Lock + table[0x7E] = 0x47; // Scroll Lock + // keypad + table[0x7C] = 0x55; // keypad * + table[0x7B] = 0x56; // keypad - + table[0x79] = 0x57; // keypad + + table[0x69] = 0x59; // keypad 1 + table[0x72] = 0x5A; // keypad 2 + table[0x7A] = 0x5B; // keypad 3 + table[0x6B] = 0x5C; // keypad 4 + table[0x73] = 0x5D; // keypad 5 + table[0x74] = 0x5E; // keypad 6 + table[0x6C] = 0x5F; // keypad 7 + table[0x75] = 0x60; // keypad 8 + table[0x7D] = 0x61; // keypad 9 + table[0x70] = 0x62; // keypad 0 + table[0x71] = 0x63; // keypad . + // modifiers + table[0x14] = usage_left_control; + table[0x12] = usage_left_shift; + table[0x11] = usage_left_alt; + table[0x59] = usage_right_shift; + break :blk table; +}; + +/// `E0`-prefixed make codes. `E0 12` is the "fake shift" the keyboard wraps around +/// Print Screen and navigation keys when a real shift is involved; it maps to zero +/// here, so it decodes to nothing and only the real key comes through. +pub const set2_extended: [256]u8 = blk: { + var table = [_]u8{0} ** 256; + table[0x11] = usage_right_alt; + table[0x14] = usage_right_control; + table[0x1F] = 0xE3; // left GUI + table[0x27] = 0xE7; // right GUI + table[0x2F] = 0x65; // application (menu) + table[0x7C] = 0x46; // Print Screen (arrives as E0 12 E0 7C; the E0 12 decodes to nothing) + table[0x4A] = 0x54; // keypad / + table[0x5A] = 0x58; // keypad Enter + table[0x70] = 0x49; // Insert + table[0x6C] = 0x4A; // Home + table[0x7D] = 0x4B; // Page Up + table[0x71] = 0x4C; // Delete + table[0x69] = 0x4D; // End + table[0x7A] = 0x4E; // Page Down + table[0x74] = 0x4F; // right arrow + table[0x6B] = 0x50; // left arrow + table[0x72] = 0x51; // down arrow + table[0x75] = 0x52; // up arrow + break :blk table; +}; + +// --- the byte-stream decoder -------------------------------------------------- + +/// One decoded key transition: which key (as a USB HID usage) and whether this is +/// a make (press or typematic repeat) or a break (release). +pub const DecodedKey = struct { + usage: u8, + make: bool, +}; + +/// Turns the raw set-2 byte stream into `DecodedKey`s. Feed it every byte the +/// keyboard sends; most bytes complete a key and return one, prefix bytes return +/// null and arm the state machine for the next byte. +pub const Decoder = struct { + const State = enum { + idle, + extended, // saw E0 + break_prefix, // saw F0 + extended_break, // saw E0 F0 + pause_skip, // inside the 8-byte E1 Pause sequence + }; + + state: State = .idle, + /// Bytes still to swallow in `pause_skip`. + skip: u8 = 0, + + /// The whole Pause make sequence is `E1 14 77 E1 F0 14 F0 77` — seven bytes + /// after the leading `E1`, and no break sequence ever follows. + const pause_bytes_after_e1: u8 = 7; + + pub fn feed(self: *Decoder, byte: u8) ?DecodedKey { + switch (self.state) { + .idle => switch (byte) { + 0xE0 => self.state = .extended, + 0xF0 => self.state = .break_prefix, + 0xE1 => { + self.state = .pause_skip; + self.skip = pause_bytes_after_e1; + }, + // Anything else is a make code — or a protocol byte (0xFA ACK, + // 0xAA BAT-passed, 0xEE echo, ...), which the tables map to zero. + else => return decoded(set2_base[byte], true), + }, + .extended => switch (byte) { + 0xF0 => self.state = .extended_break, + else => { + self.state = .idle; + return decoded(set2_extended[byte], true); + }, + }, + .break_prefix => { + self.state = .idle; + return decoded(set2_base[byte], false); + }, + .extended_break => { + self.state = .idle; + return decoded(set2_extended[byte], false); + }, + .pause_skip => { + self.skip -= 1; + if (self.skip == 0) self.state = .idle; + }, + } + return null; + } + + fn decoded(usage: u8, make: bool) ?DecodedKey { + if (usage == 0) return null; // unmapped or a protocol byte + return .{ .usage = usage, .make = make }; + } +}; + +// --- driver-side keyboard state ----------------------------------------------- + +/// What a key transition did, plus the modifier state to stamp on the resulting +/// events (snapshotted after the transition was applied). +pub const Transition = struct { + pub const Action = enum { + pressed, // physical make of a key that was up + repeated, // typematic make of a key already down — no new key_down + released, // physical break + }; + action: Action, + modifiers: ModifierSnapshot, +}; + +/// The modifier state at one instant, in both vocabularies a driver needs: the +/// input protocol's coarse bits (shift/control/alt) and the level-selection +/// inputs xkeyboard-config takes (shift, caps_lock, AltGr as level3). +pub const ModifierSnapshot = struct { + shift: bool, // either shift held + control: bool, // either control held + alt: bool, // either alt held (including AltGr) + right_alt: bool, // AltGr specifically — the XKB level-3 selector + caps_lock: bool, // the toggle, not the key +}; + +/// Tracks which keys are physically down and the caps-lock toggle, and classifies +/// each decoded transition. Pure state — no IO — so repeat detection and modifier +/// snapshots are host-testable. +pub const KeyboardState = struct { + /// One bit per HID usage: set while the key is physically down. + pressed: [32]u8 = [_]u8{0} ** 32, + caps_lock: bool = false, + + pub fn apply(self: *KeyboardState, key: DecodedKey) Transition { + const already_down = self.isPressed(key.usage); + if (key.make) { + if (!already_down) { + self.setPressed(key.usage, true); + if (key.usage == usage_caps_lock) self.caps_lock = !self.caps_lock; + } + return .{ + .action = if (already_down) .repeated else .pressed, + .modifiers = self.snapshot(), + }; + } + self.setPressed(key.usage, false); + return .{ .action = .released, .modifiers = self.snapshot() }; + } + + pub fn isPressed(self: *const KeyboardState, usage: u8) bool { + return self.pressed[usage / 8] & (@as(u8, 1) << @intCast(usage % 8)) != 0; + } + + fn setPressed(self: *KeyboardState, usage: u8, down: bool) void { + const bit = @as(u8, 1) << @intCast(usage % 8); + if (down) { + self.pressed[usage / 8] |= bit; + } else { + self.pressed[usage / 8] &= ~bit; + } + } + + fn snapshot(self: *const KeyboardState) ModifierSnapshot { + const right_alt = self.isPressed(usage_right_alt); + return .{ + .shift = self.isPressed(usage_left_shift) or self.isPressed(usage_right_shift), + .control = self.isPressed(usage_left_control) or self.isPressed(usage_right_control), + .alt = self.isPressed(usage_left_alt) or right_alt, + .right_alt = right_alt, + .caps_lock = self.caps_lock, + }; + } +}; + +// --- tests (host-run via `zig build test`) ------------------------------------ + +const testing = std.testing; + +/// Feed `bytes` and return the single DecodedKey they should produce (fails the +/// test if they produce none or more than one). +fn feedOne(decoder: *Decoder, bytes: []const u8) !DecodedKey { + var result: ?DecodedKey = null; + for (bytes) |byte| { + if (decoder.feed(byte)) |key| { + try testing.expect(result == null); + result = key; + } + } + return result orelse error.TestExpectedResult; +} + +fn feedNone(decoder: *Decoder, bytes: []const u8) !void { + for (bytes) |byte| try testing.expectEqual(@as(?DecodedKey, null), decoder.feed(byte)); +} + +test "base make and break: A" { + var decoder = Decoder{}; + try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = true }, try feedOne(&decoder, &.{0x1C})); + try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = false }, try feedOne(&decoder, &.{ 0xF0, 0x1C })); +} + +test "extended make and break: right arrow" { + var decoder = Decoder{}; + try testing.expectEqual(DecodedKey{ .usage = 0x4F, .make = true }, try feedOne(&decoder, &.{ 0xE0, 0x74 })); + try testing.expectEqual(DecodedKey{ .usage = 0x4F, .make = false }, try feedOne(&decoder, &.{ 0xE0, 0xF0, 0x74 })); +} + +test "pause: the E1 sequence is consumed silently" { + var decoder = Decoder{}; + try feedNone(&decoder, &.{ 0xE1, 0x14, 0x77, 0xE1, 0xF0, 0x14, 0xF0, 0x77 }); + // The decoder is back in idle: an ordinary key still decodes. + try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = true }, try feedOne(&decoder, &.{0x1C})); +} + +test "protocol bytes decode to nothing" { + var decoder = Decoder{}; + try feedNone(&decoder, &.{ 0xFA, 0xAA, 0xEE }); // ACK, BAT-passed, echo +} + +test "print screen: the fake-shift E0 12 decodes to nothing" { + var decoder = Decoder{}; + try feedNone(&decoder, &.{ 0xE0, 0x12 }); + try testing.expectEqual(DecodedKey{ .usage = 0x46, .make = true }, try feedOne(&decoder, &.{ 0xE0, 0x7C })); +} + +test "typematic repeat is classified, not re-pressed" { + var state = KeyboardState{}; + const a = DecodedKey{ .usage = 0x04, .make = true }; + try testing.expectEqual(Transition.Action.pressed, state.apply(a).action); + try testing.expectEqual(Transition.Action.repeated, state.apply(a).action); + try testing.expectEqual(Transition.Action.repeated, state.apply(a).action); + try testing.expectEqual(Transition.Action.released, state.apply(.{ .usage = 0x04, .make = false }).action); + try testing.expectEqual(Transition.Action.pressed, state.apply(a).action); +} + +test "shift held shows in the snapshot of other keys" { + var state = KeyboardState{}; + _ = state.apply(.{ .usage = usage_left_shift, .make = true }); + const transition = state.apply(.{ .usage = 0x04, .make = true }); + try testing.expect(transition.modifiers.shift); + try testing.expect(!transition.modifiers.control); + _ = state.apply(.{ .usage = usage_left_shift, .make = false }); + _ = state.apply(.{ .usage = 0x04, .make = false }); + try testing.expect(!state.apply(.{ .usage = 0x04, .make = true }).modifiers.shift); +} + +test "right alt reports both alt and the level-3 selector" { + var state = KeyboardState{}; + _ = state.apply(.{ .usage = usage_right_alt, .make = true }); + const transition = state.apply(.{ .usage = 0x04, .make = true }); + try testing.expect(transition.modifiers.alt); + try testing.expect(transition.modifiers.right_alt); +} + +test "caps lock toggles on make, not on repeat or break" { + var state = KeyboardState{}; + try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); + try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); // repeat + try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = false }).modifiers.caps_lock); + try testing.expect(!state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); // second press: off +} diff --git a/system/services/input/protocol.zig b/system/services/input/protocol.zig index daab82c..885c60e 100644 --- a/system/services/input/protocol.zig +++ b/system/services/input/protocol.zig @@ -73,16 +73,125 @@ 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. +/// The danos-native keycode namespace: USB HID keyboard-page usages (page 0x07), the +/// numbering the PS/2 scancode decoder emits and the xkeyboard-config layout tables are +/// indexed by. Non-exhaustive, so an unnamed usage still travels as a valid value. pub const Keycode = enum(u32) { unknown = 0, - a = 4, // deliberately USB-HID-usage-aligned so a real decoder can extend this + // letters + a = 4, b = 5, c = 6, d = 7, e = 8, + f = 9, + g = 10, + h = 11, + i = 12, + j = 13, + k = 14, + l = 15, + m = 16, + n = 17, + o = 18, + p = 19, + q = 20, + r = 21, + s = 22, + t = 23, + u = 24, + v = 25, + w = 26, + x = 27, + y = 28, + z = 29, + // digit row + one = 30, + two = 31, + three = 32, + four = 33, + five = 34, + six = 35, + seven = 36, + eight = 37, + nine = 38, + zero = 39, + // control and whitespace enter = 40, + escape = 41, + backspace = 42, + tab = 43, + spacebar = 44, + // punctuation + minus = 45, + equal = 46, + left_bracket = 47, + right_bracket = 48, + backslash = 49, + non_us_hash = 50, + semicolon = 51, + apostrophe = 52, + grave = 53, + comma = 54, + period = 55, + slash = 56, + caps_lock = 57, + // function row + f1 = 58, + f2 = 59, + f3 = 60, + f4 = 61, + f5 = 62, + f6 = 63, + f7 = 64, + f8 = 65, + f9 = 66, + f10 = 67, + f11 = 68, + f12 = 69, + print_screen = 70, + scroll_lock = 71, + pause = 72, + // navigation + insert = 73, + home = 74, + page_up = 75, + delete = 76, + end = 77, + page_down = 78, + right_arrow = 79, + left_arrow = 80, + down_arrow = 81, + up_arrow = 82, + // keypad + num_lock = 83, + keypad_slash = 84, + keypad_asterisk = 85, + keypad_minus = 86, + keypad_plus = 87, + keypad_enter = 88, + keypad_one = 89, + keypad_two = 90, + keypad_three = 91, + keypad_four = 92, + keypad_five = 93, + keypad_six = 94, + keypad_seven = 95, + keypad_eight = 96, + keypad_nine = 97, + keypad_zero = 98, + keypad_period = 99, + non_us_backslash = 100, + application = 101, + // modifiers + left_control = 224, + left_shift = 225, + left_alt = 226, + left_gui = 227, + right_control = 228, + right_shift = 229, + right_alt = 230, + right_gui = 231, _, };