221 lines
11 KiB
Zig
221 lines
11 KiB
Zig
//! User-space input helpers: the client and publisher sides of the input service, so a
|
|
//! program listening for input events — or a driver broadcasting them — doesn't hand-roll
|
|
//! the IPC. Layered over `ipc` (endpoints, capability passing, `send`) and the shared
|
|
//! `input-protocol` wire format, the way `device.zig` layers over the raw `device_*` calls.
|
|
//! See system/services/input/input.zig.
|
|
//!
|
|
//! The service carries several device classes (keyboard, mouse, joystick/gamepad). A
|
|
//! **source** publishes its class with the matching method:
|
|
//! var source = input.connectSource() orelse return;
|
|
//! _ = source.publishKeyboardEvent(.{ .kind = ..., .keycode = ..., ... });
|
|
//! _ = source.publishMouseEvent(.{ ... });
|
|
//! _ = source.publishJoystickEvent(.{ ... });
|
|
//!
|
|
//! A **subscriber** either takes one class with a typed helper —
|
|
//! var keys = input.subscribeKeyboard() orelse return;
|
|
//! while (true) { const key = keys.next() orelse continue; ... }
|
|
//! — or takes several at once and inspects the tagged envelope:
|
|
//! var listener = input.subscribeAll() orelse return;
|
|
//! while (true) {
|
|
//! const event = listener.next() orelse continue;
|
|
//! if (event.asKeyboard()) |k| { ... } else if (event.asMouse()) |m| { ... }
|
|
//! }
|
|
|
|
const std = @import("std");
|
|
const abi = @import("abi");
|
|
const ipc = @import("ipc.zig");
|
|
const system = @import("system.zig");
|
|
const protocol = @import("input-protocol");
|
|
|
|
pub const DeviceKind = protocol.DeviceKind;
|
|
pub const InputEvent = protocol.InputEvent;
|
|
pub const KeyEvent = protocol.KeyEvent;
|
|
pub const MouseEvent = protocol.MouseEvent;
|
|
pub const JoystickEvent = protocol.JoystickEvent;
|
|
pub const EventKind = protocol.EventKind;
|
|
pub const MouseEventKind = protocol.MouseEventKind;
|
|
pub const JoystickEventKind = protocol.JoystickEventKind;
|
|
pub const Keycode = protocol.Keycode;
|
|
|
|
/// Interest masks re-exported so a caller can `subscribe(input.device_keyboard |
|
|
/// input.device_mouse)`.
|
|
pub const device_keyboard = protocol.device_keyboard;
|
|
pub const device_mouse = protocol.device_mouse;
|
|
pub const device_joystick = protocol.device_joystick;
|
|
pub const device_all = protocol.device_all;
|
|
|
|
/// Look up the input service, retrying while it is still coming up. Both a subscriber and
|
|
/// a source race the service's registration at boot, so both wait for it here rather than
|
|
/// failing. Returns the service endpoint handle, or null if it never appears.
|
|
fn lookupService() ?ipc.Handle {
|
|
var attempts: usize = 0;
|
|
while (attempts < 100) : (attempts += 1) {
|
|
if (ipc.lookup(.input)) |handle| return handle;
|
|
system.sleep(50);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// --- subscribing ------------------------------------------------------------
|
|
|
|
/// A subscription to the input service: our own endpoint, which the service pushes events
|
|
/// to. `next` returns each event as a tagged `InputEvent`; use `asKeyboard`/`asMouse`/
|
|
/// `asJoystick` to decode. Created with `subscribe`/`subscribeAll`; for a single device
|
|
/// class prefer the typed helpers (`subscribeKeyboard`, ...), which return decoded events.
|
|
pub const Subscriber = struct {
|
|
/// The endpoint the service delivers events to (created and owned by us; its handle
|
|
/// was handed to the service as a capability at subscribe time).
|
|
endpoint: ipc.Handle,
|
|
receive: [protocol.event_size]u8 = undefined,
|
|
|
|
/// Block until the next event is pushed, and return it. Events arrive as asynchronous
|
|
/// buffered messages (`ipc_send` from the service), so nothing is owed in reply — the
|
|
/// empty reply this issues is a harmless no-op. Returns null for any non-event wake-up
|
|
/// (there should be none), so callers can loop.
|
|
pub fn next(self: *Subscriber) ?InputEvent {
|
|
const got = ipc.replyWait(self.endpoint, &.{}, &self.receive, null);
|
|
if (!got.isMessage() or got.len < protocol.event_size) return null;
|
|
return std.mem.bytesToValue(InputEvent, self.receive[0..protocol.event_size]);
|
|
}
|
|
};
|
|
|
|
/// Subscribe to the input classes named in `device_mask` (an OR of `device_*`, or
|
|
/// `device_all`). Creates an endpoint for the service to push to and hands it over as a
|
|
/// capability. Returns a `Subscriber` to loop `next` on, or null on failure.
|
|
pub fn subscribe(device_mask: u32) ?Subscriber {
|
|
const service = lookupService() orelse return null;
|
|
const endpoint = ipc.createIpcEndpoint() orelse return null;
|
|
|
|
var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.subscribe), .device_mask = device_mask };
|
|
var reply: [protocol.reply_size]u8 = undefined;
|
|
const result = ipc.callCap(service, std.mem.asBytes(&request), &reply, endpoint) catch return null;
|
|
if (result.len < protocol.reply_size) return null;
|
|
if (std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status != 0) return null;
|
|
return .{ .endpoint = endpoint };
|
|
}
|
|
|
|
/// Subscribe to every input class (keyboard, mouse, joystick) on one stream.
|
|
pub fn subscribeAll() ?Subscriber {
|
|
return subscribe(device_all);
|
|
}
|
|
|
|
/// A subscriber filtered to keyboard events, whose `next` returns a decoded `KeyEvent`.
|
|
pub const KeyboardSubscriber = struct {
|
|
inner: Subscriber,
|
|
pub fn next(self: *KeyboardSubscriber) ?KeyEvent {
|
|
return (self.inner.next() orelse return null).asKeyboard();
|
|
}
|
|
};
|
|
|
|
/// A subscriber filtered to mouse events, whose `next` returns a decoded `MouseEvent`.
|
|
pub const MouseSubscriber = struct {
|
|
inner: Subscriber,
|
|
pub fn next(self: *MouseSubscriber) ?MouseEvent {
|
|
return (self.inner.next() orelse return null).asMouse();
|
|
}
|
|
};
|
|
|
|
/// A subscriber filtered to joystick/gamepad events, whose `next` returns a decoded
|
|
/// `JoystickEvent`.
|
|
pub const JoystickSubscriber = struct {
|
|
inner: Subscriber,
|
|
pub fn next(self: *JoystickSubscriber) ?JoystickEvent {
|
|
return (self.inner.next() orelse return null).asJoystick();
|
|
}
|
|
};
|
|
|
|
/// Subscribe to keyboard events only; `next` returns decoded `KeyEvent`s.
|
|
pub fn subscribeKeyboard() ?KeyboardSubscriber {
|
|
return .{ .inner = subscribe(device_keyboard) orelse return null };
|
|
}
|
|
|
|
/// Subscribe to mouse events only; `next` returns decoded `MouseEvent`s.
|
|
pub fn subscribeMouse() ?MouseSubscriber {
|
|
return .{ .inner = subscribe(device_mouse) orelse return null };
|
|
}
|
|
|
|
/// Subscribe to joystick/gamepad events only; `next` returns decoded `JoystickEvent`s.
|
|
pub fn subscribeJoystick() ?JoystickSubscriber {
|
|
return .{ .inner = subscribe(device_joystick) orelse return null };
|
|
}
|
|
|
|
// --- publishing -------------------------------------------------------------
|
|
|
|
/// A connection to the input service for a source (a keyboard/mouse/joystick driver) that
|
|
/// publishes events. Each `publish*Event` is a short synchronous call the service answers
|
|
/// at once; its own fan-out to subscribers is asynchronous, so publishing never blocks on
|
|
/// a slow subscriber.
|
|
pub const Publisher = struct {
|
|
service: ipc.Handle,
|
|
|
|
fn publish(self: Publisher, event: InputEvent) bool {
|
|
var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.publish), .event = event };
|
|
var reply: [protocol.reply_size]u8 = undefined;
|
|
const len = ipc.call(self.service, std.mem.asBytes(&request), &reply) catch return false;
|
|
if (len < protocol.reply_size) return false;
|
|
return std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status == 0;
|
|
}
|
|
|
|
/// Broadcast a keyboard event to every subscriber that took keyboard events.
|
|
pub fn publishKeyboardEvent(self: Publisher, event: KeyEvent) bool {
|
|
return self.publish(InputEvent.fromKeyboard(event));
|
|
}
|
|
/// Broadcast a mouse event to every subscriber that took mouse events.
|
|
pub fn publishMouseEvent(self: Publisher, event: MouseEvent) bool {
|
|
return self.publish(InputEvent.fromMouse(event));
|
|
}
|
|
/// Broadcast a joystick/gamepad event to every subscriber that took joystick events.
|
|
pub fn publishJoystickEvent(self: Publisher, event: JoystickEvent) bool {
|
|
return self.publish(InputEvent.fromJoystick(event));
|
|
}
|
|
};
|
|
|
|
/// Connect to the input service as an event source, waiting for it to come up. Returns a
|
|
/// `Publisher`, or null if the service never registered.
|
|
pub fn connectSource() ?Publisher {
|
|
return .{ .service = lookupService() orelse return null };
|
|
}
|
|
|
|
// --- synthetic scaffolding --------------------------------------------------
|
|
|
|
/// Synthetic key events, shared by the demo source and the keyboard driver's placeholder
|
|
/// stream while real scancode decoding is still a follow-up. `step` rolls through A..E,
|
|
/// emitting for each key a `key_down`, then a `key_press` carrying the character, then a
|
|
/// `key_up`. Scaffolding, not wire protocol — hence it lives with the helpers.
|
|
pub fn syntheticKeyEvent(step: usize) KeyEvent {
|
|
const Key = struct { code: Keycode, character: u32 };
|
|
const keys = [_]Key{
|
|
.{ .code = .a, .character = 'A' },
|
|
.{ .code = .b, .character = 'B' },
|
|
.{ .code = .c, .character = 'C' },
|
|
.{ .code = .d, .character = 'D' },
|
|
.{ .code = .e, .character = 'E' },
|
|
};
|
|
const key = keys[(step / 3) % keys.len];
|
|
return switch (step % 3) {
|
|
0 => .{ .kind = @intFromEnum(EventKind.key_down), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
|
|
1 => .{ .kind = @intFromEnum(EventKind.key_press), .keycode = @intFromEnum(key.code), .character = key.character, .modifiers = 0 },
|
|
else => .{ .kind = @intFromEnum(EventKind.key_up), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
|
|
};
|
|
}
|
|
|
|
/// Synthetic mouse events (placeholder until real PS/2 packet decoding). `step` alternates
|
|
/// a small diagonal motion with a left-button click.
|
|
pub fn syntheticMouseEvent(step: usize) MouseEvent {
|
|
return switch (step % 3) {
|
|
0 => .{ .kind = @intFromEnum(MouseEventKind.motion), .button = 0, .dx = 1, .dy = 1, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
|
|
1 => .{ .kind = @intFromEnum(MouseEventKind.button_down), .button = protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = protocol.mouse_button_left },
|
|
else => .{ .kind = @intFromEnum(MouseEventKind.button_up), .button = protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
|
|
};
|
|
}
|
|
|
|
/// Synthetic joystick/gamepad events (placeholder until a real controller driver). `step`
|
|
/// sweeps axis 0 and toggles button 0.
|
|
pub fn syntheticJoystickEvent(step: usize) JoystickEvent {
|
|
return switch (step % 3) {
|
|
0 => .{ .kind = @intFromEnum(JoystickEventKind.axis), .control = 0, .value = 16384, .buttons = 0 },
|
|
1 => .{ .kind = @intFromEnum(JoystickEventKind.button_down), .control = 0, .value = 0, .buttons = 1 },
|
|
else => .{ .kind = @intFromEnum(JoystickEventKind.button_up), .control = 0, .value = 0, .buttons = 0 },
|
|
};
|
|
}
|