170 lines
7.6 KiB
Zig
170 lines
7.6 KiB
Zig
//! 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,
|
|
});
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|