//! 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 (`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 ipc = @import("ipc"); const process = @import("process"); const service = @import("service"); const input = @import("input-client"); const device_manager = @import("driver"); const logging = @import("logging"); const usb = @import("usb"); const usb_abi = @import("usb-abi"); const hid = @import("hid-report.zig"); const input_protocol = @import("input-protocol"); // 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: process.Init) void { const argument = init.arguments.get(1) orelse { _ = logging.write("/system/drivers/usb-hid/mouse: missing device id (argv[1])\n"); return; }; const device_id = std.fmt.parseInt(u64, argument, 10) catch { std.log.info("malformed device id '{s}'", .{argument}); return; }; if (device_manager.hello(.device, device_id) == null) return; var device = usb.open(device_id) orelse { std.log.info("could not open device {d}", .{device_id}); return; }; const endpoint = device.findEndpoint(usb.transfer_type_interrupt, true) orelse { _ = logging.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)) { _ = logging.write("/system/drivers/usb-hid/mouse: interrupt subscribe failed\n"); return; } var source = input.connectSource() orelse { _ = logging.write("/system/drivers/usb-hid/mouse: input service unavailable\n"); return; }; _ = process.bindSignals(device.endpoint); std.log.info("ok (device {d}, interface {d})", .{ 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()) continue; // An `interrupt_report` event packet: the verb in its folded header, the // report after it. Anything else on this endpoint is not ours. const message = usb.reportOf(receive[0..got.len]) orelse continue; 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, }); } } }