Generalize input module to mouse and joystick/gamepad events
Extend the input service beyond the keyboard so mouse and joystick/gamepad drivers can broadcast too, with per-device publish and subscribe methods. - protocol: KeyEvent joins MouseEvent (motion/buttons/scroll) and JoystickEvent (axes/buttons), all carried in a common InputEvent envelope tagged with a DeviceKind. A subscribe request carries a device_mask, so a subscriber names the classes it wants and the service routes each event only to interested subscribers (a mouse-only listener never wakes for keystrokes). - runtime: per-device publish methods (publishKeyboardEvent/publishMouseEvent/ publishJoystickEvent) and subscribe helpers (subscribeKeyboard/Mouse/Joystick, each typed, plus subscribe(mask)/subscribeAll returning the tagged envelope). - service: subscriber table gains a device_mask; broadcast routes by the event's device class. - mouse driver now publishes (synthetic) mouse events like the keyboard driver; input-source cycles all three classes; input-test subscribes to all and only emits its "ok" marker once it has received one of each class — so the passing test proves per-device routing, not just delivery. Real HID decoding stays a follow-up. No kernel changes: ipc_send is generic and the 36-byte InputEvent fits its 64-byte payload. Full QEMU suite 48/48; serial log confirms keyboard, mouse, and joystick all reach one subscription.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
//! system/services/input — the user-space input service. Shipped in the initial_ramdisk,
|
||||
//! spawned as a ring-3 process, and published under the well-known `input` service id. It
|
||||
//! is the fan-out point between **sources** (keyboard drivers) and **subscribers** (any
|
||||
//! program that wants keyboard events): a source `publish`es a `KeyEvent`, and the service
|
||||
//! pushes it to every subscriber.
|
||||
//! is the fan-out point between **sources** (keyboard, mouse, and joystick/gamepad drivers)
|
||||
//! and **subscribers** (any program that wants input): a source `publish`es an
|
||||
//! `InputEvent`, and the service pushes it to every subscriber whose interest mask includes
|
||||
//! that event's device class (keyboard / mouse / joystick).
|
||||
//!
|
||||
//! The delivery discipline is the whole design (see docs/input.md). The kernel's IPC is a
|
||||
//! synchronous rendezvous: a server holds one pending reply, so it cannot park N
|
||||
@@ -31,6 +32,9 @@ const Subscriber = struct {
|
||||
used: bool = false,
|
||||
endpoint: ipc.Handle = 0,
|
||||
task_id: u32 = 0,
|
||||
/// Which device classes this subscriber wants (an OR of protocol.device_*). An event
|
||||
/// is delivered only if its device's bit is set here.
|
||||
device_mask: u32 = 0,
|
||||
};
|
||||
|
||||
var subscribers = [_]Subscriber{.{}} ** 8;
|
||||
@@ -56,24 +60,25 @@ fn pruneDeadSubscribers() void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register `endpoint` (owned by task `task_id`) to receive events. Returns false if the
|
||||
/// subscriber table is full.
|
||||
fn addSubscriber(endpoint: ipc.Handle, task_id: u32) bool {
|
||||
/// Register `endpoint` (owned by task `task_id`) to receive the device classes in
|
||||
/// `device_mask`. Returns false if the subscriber table is full.
|
||||
fn addSubscriber(endpoint: ipc.Handle, task_id: u32, device_mask: u32) bool {
|
||||
for (&subscribers) |*sub| {
|
||||
if (!sub.used) {
|
||||
sub.* = .{ .used = true, .endpoint = endpoint, .task_id = task_id };
|
||||
sub.* = .{ .used = true, .endpoint = endpoint, .task_id = task_id, .device_mask = device_mask };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Push `event` to every registered subscriber. `ipc.send` never blocks, so a slow or
|
||||
/// dead subscriber cannot stall delivery to the others.
|
||||
fn broadcast(event: protocol.KeyEvent) void {
|
||||
/// Push `event` to every subscriber whose interest mask includes its device class.
|
||||
/// `ipc.send` never blocks, so a slow or dead subscriber cannot stall delivery to others.
|
||||
fn broadcast(event: protocol.InputEvent) void {
|
||||
const bytes = std.mem.asBytes(&event);
|
||||
const bit = protocol.deviceBit(event.device);
|
||||
for (&subscribers) |*sub| {
|
||||
if (sub.used) _ = ipc.send(sub.endpoint, bytes);
|
||||
if (sub.used and sub.device_mask & bit != 0) _ = ipc.send(sub.endpoint, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +100,10 @@ fn handle(message: []const u8, got: ipc.Received, out: []u8) usize {
|
||||
switch (@as(protocol.Operation, @enumFromInt(request.operation))) {
|
||||
.subscribe => {
|
||||
const endpoint = got.cap orelse return reply.write(out, -1); // no endpoint passed
|
||||
// A zero mask means "everything" (a subscriber that named no class still wants input).
|
||||
const mask = if (request.device_mask == 0) protocol.device_all else request.device_mask;
|
||||
pruneDeadSubscribers();
|
||||
if (!addSubscriber(endpoint, @intCast(got.badge))) return reply.write(out, -1); // table full
|
||||
if (!addSubscriber(endpoint, @intCast(got.badge), mask)) return reply.write(out, -1); // table full
|
||||
return reply.write(out, 0);
|
||||
},
|
||||
.publish => {
|
||||
|
||||
Reference in New Issue
Block a user