//! system/services/input — the user-space input service. Shipped in the initial_ramdisk,
//! spawned as a ring-3 process, and bound at `/protocol/input`. It
//! 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
//! subscribers blocked in a "wait for next event" call. Broadcasting therefore has to be
//! *push* — the service delivering to subscribers. But a synchronous push (`ipc_call`)
//! would let one dead or wedged subscriber hang the whole broadcast, since the kernel
//! never wakes a sender parked on a dead peer's endpoint. So delivery uses the
//! asynchronous `ipc.send`: it posts the event to each subscriber's endpoint queue and
//! returns at once, and can never block on a subscriber. That primitive exists for exactly
//! this ([ipc.md](../../../docs/ipc.md), "asynchronous / buffered send").
//!
//! A subscriber registers by handing the service its own endpoint as a capability (M13
//! capability passing — this service is its first real consumer). The service keeps that
//! handle and `ipc.send`s each event to it. That is the envelope's reserved `subscribe`
//! verb, which this protocol adopts rather than defining its own.
//!
//! P4a moved this service onto the shared harness (library/kernel/service.zig) — it was the
//! last hand-rolled receive loop in the tree, and the one service that answered neither the
//! universal ping nor a `terminate` signal. P4c finished the job: the subscriber table, the
//! fan-out, and the dead-subscriber sweep are the harness's now
//! (`service.Subscribers`), so what is left here is what is actually about input — which
//! device class an event belongs to, and which classes a subscriber asked for. The sweep
//! that replaced the old prune is the one idiom the system uses everywhere: published
//! process-exit notifications, not a poll of the process list on every subscribe.

const ipc = @import("ipc");
const service = @import("service");
const logging = @import("logging");
const input_protocol = @import("input-protocol");
const envelope = @import("envelope");

/// The subscriber side of the input contract: the table, the reserved `subscribe`
/// verb, the exit sweep, and the fan-out. One fan-out point per process, so the
/// handler context is empty.
const Subscriptions = service.Subscribers(input_protocol.Protocol, void);

const Invocation = envelope.Invocation;
const Answer = envelope.Answer;

/// Push `event` to every subscriber whose interest mask includes its device class. The
/// class is the packet's operation, so this picks *which event* to publish and the harness
/// frames it once for the whole fan-out — the mapping from device to class is the only part
/// of a broadcast that is this service's own.
fn broadcast(event: input_protocol.InputEvent) void {
    const class = input_protocol.deviceBit(event.device);
    switch (input_protocol.eventOfDevice(event.device) orelse return) { // no class wants it
        .keyboard => Subscriptions.publishClass(.keyboard, 0, event.asKeyboard() orelse return, class),
        .mouse => Subscriptions.publishClass(.mouse, 0, event.asMouse() orelse return, class),
        .joystick => Subscriptions.publishClass(.joystick, 0, event.asJoystick() orelse return, class),
    }
}

fn onPublish(_: void, invocation: Invocation(input_protocol.InputEvent), _: Answer(void)) isize {
    broadcast(invocation.request);
    return 0;
}

/// `subscribe` and `unsubscribe` are absent on purpose: the harness answers both.
const handlers = Subscriptions.Handlers{ .publish = onPublish };

fn onMessage(message: []const u8, out: []u8, sender: u32, arrived: *ipc.Arrival) usize {
    return Subscriptions.dispatch({}, handlers, message, sender, arrived, out);
}

fn initialise(_: ipc.Handle) bool {
    // The harness has already bound `/protocol/input` by the time this runs, so
    // "ready" still means what it always meant: the name is claimed and the loop
    // is about to serve it.
    _ = logging.write("/system/services/input: ready\n");
    return true;
}

pub fn main() void {
    service.run(input_protocol.message_maximum, .{
        .service = "input",
        .init = initialise,
        .on_message = onMessage,
        .subscribers = Subscriptions.hooks,
    });
}
