//! 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 channel = @import("channel"); const envelope = @import("envelope"); const ipc = @import("ipc"); const time = @import("time"); const input_protocol = @import("input-protocol"); const Protocol = input_protocol.Protocol; pub const DeviceKind = input_protocol.DeviceKind; pub const InputEvent = input_protocol.InputEvent; pub const KeyEvent = input_protocol.KeyEvent; pub const MouseEvent = input_protocol.MouseEvent; pub const JoystickEvent = input_protocol.JoystickEvent; pub const EventKind = input_protocol.EventKind; pub const MouseEventKind = input_protocol.MouseEventKind; pub const JoystickEventKind = input_protocol.JoystickEventKind; pub const Keycode = input_protocol.Keycode; /// Interest masks re-exported so a caller can `subscribe(input.device_keyboard | /// input.device_mouse)`. pub const device_keyboard = input_protocol.device_keyboard; pub const device_mouse = input_protocol.device_mouse; pub const device_joystick = input_protocol.device_joystick; pub const device_all = input_protocol.device_all; /// Open `/protocol/input`, retrying while it is still coming up. Both a subscriber and /// a source race the service's bind at boot, so both wait for it here rather than /// failing. Returns the provider's endpoint handle, or null if it never appears. fn lookupService() ?ipc.Handle { var attempts: usize = 0; while (attempts < 100) : (attempts += 1) { if (channel.openEndpoint("input")) |handle| return handle; time.sleepMillis(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, /// A pushed packet is the folded header plus one typed event, so the buffer is /// the push floor rather than any one event's size. receive: [envelope.post_maximum]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. /// /// The device class is the packet's operation, so it is read from the header and /// re-tagged into an `InputEvent` here — one decoded type for a caller that took /// several classes on one stream. pub fn next(self: *Subscriber) ?InputEvent { const got = ipc.replyWait(self.endpoint, &.{}, &self.receive, null); if (!got.isMessage()) return null; const packet = self.receive[0..@min(got.len, self.receive.len)]; return switch (Protocol.eventOf(packet) orelse return null) { .keyboard => InputEvent.fromKeyboard(Protocol.decodeEvent(.keyboard, packet) orelse return null), .mouse => InputEvent.fromMouse(Protocol.decodeEvent(.mouse, packet) orelse return null), .joystick => InputEvent.fromJoystick(Protocol.decodeEvent(.joystick, packet) orelse return null), }; } }; /// 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 — the envelope's reserved `subscribe`, whose shape this is exactly. 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 packet: [input_protocol.message_maximum]u8 = undefined; const framed = input_protocol.encodeSubscribe(device_mask, &packet) orelse return null; var reply: [input_protocol.message_maximum]u8 = undefined; const result = ipc.callCap(service, framed, &reply, endpoint) catch return null; const status = envelope.statusOf(reply[0..result.len]) orelse return null; if (status.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 packet: [input_protocol.message_maximum]u8 = undefined; const framed = Protocol.encodeRequest(.publish, 0, event, &.{}, &packet) orelse return false; var reply: [input_protocol.message_maximum]u8 = undefined; const len = ipc.call(self.service, framed, &reply) catch return false; const status = envelope.statusOf(reply[0..len]) orelse return false; return status.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 = input_protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = input_protocol.mouse_button_left }, else => .{ .kind = @intFromEnum(MouseEventKind.button_up), .button = input_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 }, }; }