131 lines
5.4 KiB
Zig
131 lines
5.4 KiB
Zig
//! 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;
|
|
|
|
// 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 {
|
|
std.log.info("malformed device id '{s}'", .{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 {
|
|
std.log.info("could not open device {d}", .{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);
|
|
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() 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,
|
|
});
|
|
}
|
|
}
|
|
}
|