//! USB HID boot keyboard driver. //! //! Spawned by the device manager when the xHCI bus driver reports a HID / boot / //! keyboard interface (class 3, subclass 1, protocol 1); its assigned device id //! arrives as argv[1] and an optional layout name ("us", "gb", ...) as argv[2]. //! It owns no hardware: it opens its device through the USB transfer protocol //! (`runtime.usb`), asks the device for the boot protocol, subscribes to its //! interrupt-IN endpoint, and turns each 8-byte boot report into input-protocol //! events, published to the input service — the USB analogue of ps2-bus/keyboard. //! //! interrupt report -> hid-report diff -> key_down / key_up //! -> xkeyboard-config -> character -> key_press //! //! Because a USB keyboard's usages ARE the input protocol's keycodes (both are //! HID keyboard page 0x07), the decode is nearly 1:1 — no scancode translation. const std = @import("std"); const runtime = @import("runtime"); const usb_abi = @import("usb-abi"); const xkb = @import("xkeyboard-config"); const hid = @import("hid-report.zig"); const ipc = runtime.ipc; const process = runtime.process; const input_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); } // The modifier state a character lookup needs — derived from the report's // modifier byte, plus the driver-tracked caps-lock toggle. const ModifierSnapshot = struct { shift: bool, control: bool, right_alt: bool, caps_lock: bool, }; /// The character a key produces under `modifiers`, or 0 for none — the layout /// lookup for printable keys, with ASCII control characters for the keys every /// consumer expects (Enter, Tab, Backspace, Escape), exactly as ps2-bus/keyboard. fn characterFor(layout: *const xkb.Layout, usage: u8, modifiers: 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(input_protocol.Keycode, @enumFromInt(usage))) { .enter, .keypad_enter => '\n', .tab => '\t', .backspace => 0x08, .escape => 0x1B, else => 0, }; } fn modifierWord(modifiers: u8) u32 { var word: u32 = 0; if (modifiers & (hid.modifier_left_shift | hid.modifier_right_shift) != 0) word |= input_protocol.modifier_shift; if (modifiers & (hid.modifier_left_control | hid.modifier_right_control) != 0) word |= input_protocol.modifier_control; if (modifiers & (hid.modifier_left_alt | hid.modifier_right_alt) != 0) word |= input_protocol.modifier_alt; return word; } pub fn main(init: runtime.process.Init) void { const argument = init.arguments.get(1) orelse { _ = runtime.system.write("/system/drivers/usb-hid/keyboard: missing device id (argv[1])\n"); return; }; const device_id = std.fmt.parseInt(u64, argument, 10) catch { writeLine("/system/drivers/usb-hid/keyboard: malformed device id '{s}'\n", .{argument}); return; }; const layout = xkb.byName(init.arguments.get(2) orelse "us") orelse xkb.us; // Hello the manager first (meet the spawn deadline), then open the device. if (!runtime.usb.helloManager(device_id)) { _ = runtime.system.write("/system/drivers/usb-hid/keyboard: hello to device manager failed\n"); return; } var device = runtime.usb.open(device_id) orelse { writeLine("/system/drivers/usb-hid/keyboard: could not open device {d}\n", .{device_id}); return; }; const endpoint = device.findEndpoint(runtime.usb.transfer_type_interrupt, true) orelse { _ = runtime.system.write("/system/drivers/usb-hid/keyboard: no interrupt-IN endpoint\n"); return; }; // Ask for the boot protocol and an indefinite idle (report only on change). _ = device.controlOut(@bitCast(usb_abi.setProtocol(@enumFromInt(device.interface_number), .boot))); _ = device.controlOut(@bitCast(usb_abi.setIdle(@enumFromInt(device.interface_number), 0, 0))); if (!device.subscribeInterrupt(endpoint.address, endpoint.max_packet_size)) { _ = runtime.system.write("/system/drivers/usb-hid/keyboard: interrupt subscribe failed\n"); return; } var source = runtime.input.connectSource() orelse { _ = runtime.system.write("/system/drivers/usb-hid/keyboard: input service unavailable\n"); return; }; _ = process.bindSignals(device.endpoint); writeLine("/system/drivers/usb-hid/keyboard: ok (device {d}, interface {d}, layout {s})\n", .{ device_id, device.interface_number, layout.name }); var decoder = hid.KeyboardDecoder{}; var caps_lock = false; var receive: [64]u8 = undefined; while (true) { const got = ipc.replyWait(device.endpoint, &.{}, &receive, null); if (!got.isNotification()) continue; if (process.signalsFrom(got.badge)) |signals| { if (signals.has(.terminate)) return; continue; } if (!got.isMessage() or got.len < @sizeOf(runtime.usb.InterruptReport)) continue; const message = std.mem.bytesToValue(runtime.usb.InterruptReport, receive[0..@sizeOf(runtime.usb.InterruptReport)]); if (message.length < @sizeOf(hid.KeyboardReport)) continue; const report = std.mem.bytesToValue(hid.KeyboardReport, message.data[0..@sizeOf(hid.KeyboardReport)]); const transitions = decoder.feed(report); // Caps Lock toggles on its own key-down (a stateful lock, not a modifier). for (transitions.slice()) |transition| { if (transition.kind == .pressed and @as(input_protocol.Keycode, @enumFromInt(transition.usage)) == .caps_lock) caps_lock = !caps_lock; } const modifiers = ModifierSnapshot{ .shift = report.modifiers & (hid.modifier_left_shift | hid.modifier_right_shift) != 0, .control = report.modifiers & (hid.modifier_left_control | hid.modifier_right_control) != 0, .right_alt = report.modifiers & hid.modifier_right_alt != 0, .caps_lock = caps_lock, }; const modifier_word = modifierWord(report.modifiers); for (transitions.slice()) |transition| { switch (transition.kind) { .pressed => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_down), .keycode = transition.usage, .character = 0, .modifiers = modifier_word, }); const character = characterFor(layout, transition.usage, modifiers); if (character != 0) { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_press), .keycode = transition.usage, .character = character, .modifiers = modifier_word, }); } }, .released => { _ = source.publishKeyboardEvent(.{ .kind = @intFromEnum(input_protocol.EventKind.key_up), .keycode = transition.usage, .character = 0, .modifiers = modifier_word, }); }, } } } } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; }