//! USB HID boot mouse driver. //! //! Spawned by the device manager when the xHCI bus driver reports a HID / boot / //! mouse interface (class 3, subclass 1, protocol 2); its assigned device id //! arrives as argv[1]. Like the keyboard driver it owns no hardware: it opens its //! device through the USB transfer protocol (`runtime.usb`), asks for the boot //! protocol, subscribes to its interrupt-IN endpoint, and turns each 3- or 4-byte //! boot report into input-protocol mouse events published to the input service. //! //! Unlike PS/2, HID reports Y in screen convention (positive = down), so motion //! is passed straight through (the decode in hid-report.zig does not negate it). const std = @import("std"); const runtime = @import("runtime"); const usb_abi = @import("usb-abi"); 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 current pressed-button bitmask in input-protocol terms. fn buttonMask(buttons: u8) u32 { var mask: u32 = 0; if (buttons & hid.mouse_button_left != 0) mask |= input_protocol.mouse_button_left; if (buttons & hid.mouse_button_right != 0) mask |= input_protocol.mouse_button_right; if (buttons & hid.mouse_button_middle != 0) mask |= input_protocol.mouse_button_middle; return mask; } pub fn main(init: runtime.process.Init) void { const argument = init.arguments.get(1) orelse { _ = runtime.system.write("/system/drivers/usb-hid/mouse: missing device id (argv[1])\n"); return; }; const device_id = std.fmt.parseInt(u64, argument, 10) catch { writeLine("/system/drivers/usb-hid/mouse: malformed device id '{s}'\n", .{argument}); return; }; if (!runtime.usb.helloManager(device_id)) { _ = runtime.system.write("/system/drivers/usb-hid/mouse: hello to device manager failed\n"); return; } var device = runtime.usb.open(device_id) orelse { writeLine("/system/drivers/usb-hid/mouse: 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/mouse: no interrupt-IN endpoint\n"); return; }; _ = device.controlOut(@bitCast(usb_abi.setProtocol(@enumFromInt(device.interface_number), .boot))); if (!device.subscribeInterrupt(endpoint.address, endpoint.max_packet_size)) { _ = runtime.system.write("/system/drivers/usb-hid/mouse: interrupt subscribe failed\n"); return; } var source = runtime.input.connectSource() orelse { _ = runtime.system.write("/system/drivers/usb-hid/mouse: input service unavailable\n"); return; }; _ = process.bindSignals(device.endpoint); writeLine("/system/drivers/usb-hid/mouse: ok (device {d}, interface {d})\n", .{ device_id, device.interface_number }); var previous_buttons: u8 = 0; 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)]); const length = @min(message.length, message.data.len); const report = hid.parseMouse(message.data[0..length]) orelse continue; const mask = buttonMask(report.buttons); // Button transitions: one event per changed button bit. const changed = report.buttons ^ previous_buttons; inline for (.{ .{ hid.mouse_button_left, input_protocol.mouse_button_left }, .{ hid.mouse_button_right, input_protocol.mouse_button_right }, .{ hid.mouse_button_middle, input_protocol.mouse_button_middle }, }) |pair| { if (changed & pair[0] != 0) { _ = source.publishMouseEvent(.{ .kind = @intFromEnum(if (report.buttons & pair[0] != 0) input_protocol.MouseEventKind.button_down else input_protocol.MouseEventKind.button_up), .button = pair[1], .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = mask, }); } } previous_buttons = report.buttons; // Relative motion (dy straight through — HID Y is already screen convention). if (report.dx != 0 or report.dy != 0) { _ = source.publishMouseEvent(.{ .kind = @intFromEnum(input_protocol.MouseEventKind.motion), .button = 0, .dx = report.dx, .dy = report.dy, .scroll_x = 0, .scroll_y = 0, .buttons = mask, }); } // Wheel (4-byte reports only): positive = scroll up. if (report.has_wheel and report.wheel != 0) { _ = source.publishMouseEvent(.{ .kind = @intFromEnum(input_protocol.MouseEventKind.scroll), .button = 0, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = report.wheel, .buttons = mask, }); } } } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; }