//! The input wire protocol — the message format spoken between the user-space input //! service ([input.zig](input.zig)) and the two kinds of process that reach it: a //! **source** (a keyboard, mouse, or joystick/gamepad driver) that publishes events, and a //! **subscriber** (any program) that subscribes and is then pushed each event. //! //! The service handles several device classes over one endpoint. Each class has its own //! typed event (`KeyEvent`, `MouseEvent`, `JoystickEvent`); a subscriber declares which //! classes it wants with a `device_mask`, and the service routes accordingly. //! //! Three shapes ride over the channel, and the envelope names all three //! (docs/os-development/protocol-namespace.md): //! //! - **subscribe** is the *reserved* verb, not one of this protocol's own: its shape — a //! synchronous call whose attached capability is the subscriber's endpoint — is exactly //! what `envelope.operation_subscribe` means everywhere. The interest mask travels as the //! packet's tail (`Subscribe`), because a reserved verb carries no typed request. //! - **publish** is this protocol's one verb: a source sends one `InputEvent` and the //! service answers at once, so publishing never blocks on a slow subscriber. //! - **delivery** is an event push: the service `ipc_send`s each event to every interested //! subscriber — no reply owed, so a dead subscriber can never stall the broadcast. The //! packet is the folded header plus the typed event, and **the device class is the //! header's operation**: one event per class, so a subscriber reads the kind from the //! packet rather than from a tag inside the payload. //! //! `Header.target` is unused (0) in both directions: the service is the only object either //! side addresses. const std = @import("std"); const envelope = @import("envelope"); /// The classes of input device the service fans out. Each names a typed event and a bit in /// the subscription mask. pub const DeviceKind = enum(u32) { keyboard = 0, mouse = 1, joystick = 2, // joysticks and gamepads/controllers }; /// Subscription-interest bits (`Request.device_mask`) — which device classes a subscriber /// wants. OR them together, or use `device_all`. pub const device_keyboard: u32 = 1 << 0; pub const device_mouse: u32 = 1 << 1; pub const device_joystick: u32 = 1 << 2; pub const device_all: u32 = device_keyboard | device_mouse | device_joystick; /// The subscription bit for a `DeviceKind` value (as it appears in `InputEvent.device`). /// An unknown device maps to 0, so it matches no subscriber. pub fn deviceBit(device: u32) u32 { return switch (device) { @intFromEnum(DeviceKind.keyboard) => device_keyboard, @intFromEnum(DeviceKind.mouse) => device_mouse, @intFromEnum(DeviceKind.joystick) => device_joystick, else => 0, }; } // --- keyboard --------------------------------------------------------------- /// What happened to a key. `key_down`/`key_up` are the physical make/break; `key_press` /// is the higher-level "a character was produced", carrying it in `KeyEvent.character`. pub const EventKind = enum(u32) { key_down = 0, key_up = 1, key_press = 2, }; /// One keyboard event. `keycode` names the physical key (layout-independent); `character` /// is the Unicode scalar for `key_press` (else 0); `modifiers` is an OR of `modifier_*`. pub const KeyEvent = extern struct { kind: u32, // an EventKind keycode: u32, // a Keycode character: u32, // Unicode scalar for key_press, else 0 modifiers: u32, // OR of modifier_* }; pub const modifier_shift: u32 = 1 << 0; pub const modifier_control: u32 = 1 << 1; pub const modifier_alt: u32 = 1 << 2; /// The danos-native keycode namespace: USB HID keyboard-page usages (page 0x07), the /// numbering the PS/2 scancode decoder emits and the xkeyboard-config layout tables are /// indexed by. Non-exhaustive, so an unnamed usage still travels as a valid value. pub const Keycode = enum(u32) { unknown = 0, // letters a = 4, b = 5, c = 6, d = 7, e = 8, f = 9, g = 10, h = 11, i = 12, j = 13, k = 14, l = 15, m = 16, n = 17, o = 18, p = 19, q = 20, r = 21, s = 22, t = 23, u = 24, v = 25, w = 26, x = 27, y = 28, z = 29, // digit row one = 30, two = 31, three = 32, four = 33, five = 34, six = 35, seven = 36, eight = 37, nine = 38, zero = 39, // control and whitespace enter = 40, escape = 41, backspace = 42, tab = 43, spacebar = 44, // punctuation minus = 45, equal = 46, left_bracket = 47, right_bracket = 48, backslash = 49, non_us_hash = 50, semicolon = 51, apostrophe = 52, grave = 53, comma = 54, period = 55, slash = 56, caps_lock = 57, // function row f1 = 58, f2 = 59, f3 = 60, f4 = 61, f5 = 62, f6 = 63, f7 = 64, f8 = 65, f9 = 66, f10 = 67, f11 = 68, f12 = 69, print_screen = 70, scroll_lock = 71, pause = 72, // navigation insert = 73, home = 74, page_up = 75, delete = 76, end = 77, page_down = 78, right_arrow = 79, left_arrow = 80, down_arrow = 81, up_arrow = 82, // keypad num_lock = 83, keypad_slash = 84, keypad_asterisk = 85, keypad_minus = 86, keypad_plus = 87, keypad_enter = 88, keypad_one = 89, keypad_two = 90, keypad_three = 91, keypad_four = 92, keypad_five = 93, keypad_six = 94, keypad_seven = 95, keypad_eight = 96, keypad_nine = 97, keypad_zero = 98, keypad_period = 99, non_us_backslash = 100, application = 101, // modifiers left_control = 224, left_shift = 225, left_alt = 226, left_gui = 227, right_control = 228, right_shift = 229, right_alt = 230, right_gui = 231, _, }; // --- mouse ------------------------------------------------------------------ /// What a mouse event reports. `motion` carries relative `dx`/`dy`; `button_down`/`up` /// name a button in `button`; `scroll` carries `scroll_x`/`scroll_y`. pub const MouseEventKind = enum(u32) { motion = 0, button_down = 1, button_up = 2, scroll = 3, }; pub const mouse_button_left: u32 = 1 << 0; pub const mouse_button_right: u32 = 1 << 1; pub const mouse_button_middle: u32 = 1 << 2; /// One mouse event. Relative motion (`dx`/`dy`) and wheel (`scroll_*`) are signed; /// `buttons` is the current pressed-button bitmask (`mouse_button_*`). pub const MouseEvent = extern struct { kind: u32, // a MouseEventKind button: u32, // the mouse_button_* bit for button_down/up, else 0 dx: i32, // relative X motion (.motion) dy: i32, // relative Y motion (.motion) scroll_x: i32, // horizontal wheel (.scroll) scroll_y: i32, // vertical wheel (.scroll) buttons: u32, // current pressed-button bitmask }; // --- joystick / gamepad ----------------------------------------------------- /// What a joystick/gamepad event reports. `axis` carries a signed `value` on axis /// `control`; `button_down`/`up` name a button index in `control`. pub const JoystickEventKind = enum(u32) { axis = 0, button_down = 1, button_up = 2, }; /// One joystick/gamepad event. `control` is the axis index (`.axis`) or button index /// (button events); `value` is the axis position (signed, e.g. -32768..32767) for `.axis`; /// `buttons` is the current pressed-button bitmask. pub const JoystickEvent = extern struct { kind: u32, // a JoystickEventKind control: u32, // axis index (.axis) or button index (button events) value: i32, // axis value for .axis, else 0 buttons: u32, // current pressed-button bitmask }; // --- the tagged union of the three ------------------------------------------ /// The largest per-device event, so `InputEvent` can hold any of them inline. pub const max_event_size: usize = @max(@sizeOf(KeyEvent), @max(@sizeOf(MouseEvent), @sizeOf(JoystickEvent))); /// One event of any class: a `DeviceKind` plus the raw bytes of the matching per-device /// event. This is what a source `publish`es (one verb for all three classes) and what a /// subscriber's helper hands back after decoding a delivery — on the *delivery* wire the /// class is the packet header's operation instead, so this tag never travels there. Decode /// it with `asKeyboard`/`asMouse`/`asJoystick` (each returns null unless `device` matches), /// or build one with the `from*` constructors. pub const InputEvent = extern struct { device: u32, // a DeviceKind _padding: u32 = 0, data: [max_event_size]u8 = [_]u8{0} ** max_event_size, pub fn asKeyboard(self: InputEvent) ?KeyEvent { if (self.device != @intFromEnum(DeviceKind.keyboard)) return null; return std.mem.bytesToValue(KeyEvent, self.data[0..@sizeOf(KeyEvent)]); } pub fn asMouse(self: InputEvent) ?MouseEvent { if (self.device != @intFromEnum(DeviceKind.mouse)) return null; return std.mem.bytesToValue(MouseEvent, self.data[0..@sizeOf(MouseEvent)]); } pub fn asJoystick(self: InputEvent) ?JoystickEvent { if (self.device != @intFromEnum(DeviceKind.joystick)) return null; return std.mem.bytesToValue(JoystickEvent, self.data[0..@sizeOf(JoystickEvent)]); } pub fn fromKeyboard(event: KeyEvent) InputEvent { return pack(.keyboard, std.mem.asBytes(&event)); } pub fn fromMouse(event: MouseEvent) InputEvent { return pack(.mouse, std.mem.asBytes(&event)); } pub fn fromJoystick(event: JoystickEvent) InputEvent { return pack(.joystick, std.mem.asBytes(&event)); } fn pack(device: DeviceKind, bytes: []const u8) InputEvent { var self = InputEvent{ .device = @intFromEnum(device) }; @memcpy(self.data[0..bytes.len], bytes); return self; } }; // --- the contract ----------------------------------------------------------- /// The body of a `subscribe` — the envelope's reserved verb 2, whose shape (a call whose /// capability is the subscriber's own endpoint) this protocol adopts wholesale. A reserved /// verb has no typed request, so the mask travels as the packet's tail and `encodeSubscribe` /// is how a client lays it down. Zero means every class. pub const Subscribe = extern struct { device_mask: u32 = 0 }; pub const Protocol = envelope.Define(.{ .name = "input", .version = 1, .operations = &.{ // A source submits one event; the service broadcasts it to whoever wants that class. .{ .name = "publish", .request = InputEvent }, }, .events = &.{ // One per device class: the class is the packet's operation, the typed event its // payload. The push floor is 64 bytes and the header spends 16 of them, so the // widest of these — the 28-byte mouse event — leaves the budget with room to spare. .{ .name = "keyboard", .payload = KeyEvent }, .{ .name = "mouse", .payload = MouseEvent }, .{ .name = "joystick", .payload = JoystickEvent }, }, }); pub const Operation = Protocol.Operation; pub const Event = Protocol.Event; pub const message_maximum: usize = Protocol.message_maximum; pub const event_size: usize = @sizeOf(InputEvent); /// The event class a `DeviceKind` value (as it appears in `InputEvent.device`) is delivered /// as. Null for a value no class claims, which is delivered to nobody. pub fn eventOfDevice(device: u32) ?Event { return switch (device) { @intFromEnum(DeviceKind.keyboard) => .keyboard, @intFromEnum(DeviceKind.mouse) => .mouse, @intFromEnum(DeviceKind.joystick) => .joystick, else => null, }; } /// Frame a `subscribe` request: the reserved verb's header, then the interest mask. Null if /// the buffer is too small. Spelled here rather than at each caller so the one place that /// knows a reserved verb carries its body in the tail is the protocol module. pub fn encodeSubscribe(device_mask: u32, buffer: []u8) ?[]u8 { const total = envelope.prefix_size + @sizeOf(Subscribe); if (buffer.len < total) return null; const header = envelope.Header{ .operation = envelope.operation_subscribe }; const body = Subscribe{ .device_mask = device_mask }; @memcpy(buffer[0..envelope.prefix_size], std.mem.asBytes(&header)); @memcpy(buffer[envelope.prefix_size..][0..@sizeOf(Subscribe)], std.mem.asBytes(&body)); return buffer[0..total]; } /// The interest mask out of a `subscribe` packet's tail, on the provider's side. A caller /// that sent no mask at all means every class, which is what a zero mask means anyway. pub fn decodeSubscribe(tail: []const u8) Subscribe { if (tail.len < @sizeOf(Subscribe)) return .{}; return std.mem.bytesToValue(Subscribe, tail[0..@sizeOf(Subscribe)]); } test "an event of every class fits the push floor, header included" { // What the hand-rolled comptime assert used to say about `InputEvent`, now // said by `Define` about each typed event — and counting the header, which // the old check did not. try std.testing.expectEqual(envelope.prefix_size + @sizeOf(MouseEvent), Protocol.event_maximum); try std.testing.expect(Protocol.event_maximum <= envelope.post_maximum); } test "the verb numbering, and the class an event carries" { try std.testing.expectEqual(@as(u32, 16), @intFromEnum(Operation.publish)); // Events number in their own space, so the three classes start at 16 too. try std.testing.expectEqual(@as(u32, 16), @intFromEnum(Event.keyboard)); try std.testing.expectEqual(@as(u32, 17), @intFromEnum(Event.mouse)); try std.testing.expectEqual(@as(u32, 18), @intFromEnum(Event.joystick)); // subscribe is the RESERVED verb, below the protocol range entirely. try std.testing.expectEqual(@as(u32, 2), envelope.operation_subscribe); var buffer: [envelope.post_maximum]u8 = undefined; const packet = Protocol.encodeEvent(.mouse, 0, .{ .kind = @intFromEnum(MouseEventKind.motion), .button = 0, .dx = 3, .dy = -4, .scroll_x = 0, .scroll_y = 0, .buttons = 0, }, &buffer).?; try std.testing.expectEqual(Event.mouse, Protocol.eventOf(packet).?); try std.testing.expectEqual(@as(i32, -4), Protocol.decodeEvent(.mouse, packet).?.dy); } test "a subscribe carries its mask in the tail of the reserved verb" { var buffer: [envelope.packet_maximum]u8 = undefined; const packet = encodeSubscribe(device_mouse, &buffer).?; try std.testing.expectEqual(envelope.operation_subscribe, envelope.headerOf(packet).?.operation); try std.testing.expectEqual(device_mouse, decodeSubscribe(packet[envelope.prefix_size..]).device_mask); // A caller that sent nothing at all reads as the every-class mask. try std.testing.expectEqual(@as(u32, 0), decodeSubscribe(&.{}).device_mask); }