reorg: move input-protocol to library/protocol + direct import
input-protocol -> library/protocol/input/input-protocol.zig. Its five consumers (usb-hid keyboard/mouse, ps2 keyboard/mouse, the input service) now import the module directly and alias it input_protocol, replacing the runtime.input_protocol re-export (deleted) and the inconsistent local `protocol` aliases. runtime.input (the client) still imports the module by name. zig build + test green; input, acpi-ps2, usb-hid pass.
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const protocol = runtime.input_protocol;
|
||||
const input_protocol = @import("input-protocol");
|
||||
const ipc = runtime.ipc;
|
||||
const system = runtime.system;
|
||||
|
||||
@@ -32,7 +32,7 @@ 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
|
||||
/// Which device classes this subscriber wants (an OR of input_protocol.device_*). An event
|
||||
/// is delivered only if its device's bit is set here.
|
||||
device_mask: u32 = 0,
|
||||
};
|
||||
@@ -74,9 +74,9 @@ fn addSubscriber(endpoint: ipc.Handle, task_id: u32, device_mask: u32) bool {
|
||||
|
||||
/// 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 {
|
||||
fn broadcast(event: input_protocol.InputEvent) void {
|
||||
const bytes = std.mem.asBytes(&event);
|
||||
const bit = protocol.deviceBit(event.device);
|
||||
const bit = input_protocol.deviceBit(event.device);
|
||||
for (&subscribers) |*sub| {
|
||||
if (sub.used and sub.device_mask & bit != 0) _ = ipc.send(sub.endpoint, bytes);
|
||||
}
|
||||
@@ -88,20 +88,20 @@ fn broadcast(event: protocol.InputEvent) void {
|
||||
fn handle(message: []const u8, got: ipc.Received, out: []u8) usize {
|
||||
const reply = struct {
|
||||
fn write(buffer: []u8, status: i32) usize {
|
||||
const header = protocol.Reply{ .status = status };
|
||||
@memcpy(buffer[0..protocol.reply_size], std.mem.asBytes(&header));
|
||||
return protocol.reply_size;
|
||||
const header = input_protocol.Reply{ .status = status };
|
||||
@memcpy(buffer[0..input_protocol.reply_size], std.mem.asBytes(&header));
|
||||
return input_protocol.reply_size;
|
||||
}
|
||||
};
|
||||
|
||||
if (message.len < protocol.request_size) return reply.write(out, -1);
|
||||
const request = std.mem.bytesToValue(protocol.Request, message[0..protocol.request_size]);
|
||||
if (message.len < input_protocol.request_size) return reply.write(out, -1);
|
||||
const request = std.mem.bytesToValue(input_protocol.Request, message[0..input_protocol.request_size]);
|
||||
|
||||
switch (@as(protocol.Operation, @enumFromInt(request.operation))) {
|
||||
switch (@as(input_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;
|
||||
const mask = if (request.device_mask == 0) input_protocol.device_all else request.device_mask;
|
||||
pruneDeadSubscribers();
|
||||
if (!addSubscriber(endpoint, @intCast(got.badge), mask)) return reply.write(out, -1); // table full
|
||||
return reply.write(out, 0);
|
||||
@@ -124,9 +124,9 @@ pub fn main() void {
|
||||
}
|
||||
_ = system.write("/system/services/input: ready\n");
|
||||
|
||||
var reply_buffer: [protocol.reply_size]u8 = undefined;
|
||||
var reply_buffer: [input_protocol.reply_size]u8 = undefined;
|
||||
var reply_len: usize = 0;
|
||||
var receive: [protocol.request_size]u8 = undefined;
|
||||
var receive: [input_protocol.request_size]u8 = undefined;
|
||||
while (true) {
|
||||
const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null);
|
||||
// Only synchronous client requests (subscribe/publish) arrive here; nothing sends
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
//! 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`); they all travel in a common
|
||||
//! `InputEvent` envelope tagged with a `DeviceKind`, so the fan-out path is one code path
|
||||
//! and a subscriber can take a mix of devices on a single stream. A subscriber declares
|
||||
//! which classes it wants with a `device_mask`, and the service routes accordingly.
|
||||
//!
|
||||
//! Two message shapes ride over the endpoint, tagged by `Operation`, like the
|
||||
//! [VFS protocol](../vfs/protocol.zig):
|
||||
//!
|
||||
//! - **subscribe / publish**: a synchronous `ipc_call` carrying a `Request`. `subscribe`
|
||||
//! hands the service the subscriber's own endpoint as a capability (`send_cap`) and a
|
||||
//! `device_mask`; `publish` carries an `InputEvent`. The reply is a `Reply`.
|
||||
//! - **delivery**: the service pushes each `InputEvent` to every interested subscriber with
|
||||
//! the asynchronous `ipc_send` — no reply owed, and a dead subscriber can never stall the
|
||||
//! broadcast. Received in the subscriber's buffer with `Received.isMessage()` set.
|
||||
//!
|
||||
//! This is a danos-native contract, shared by the input service, the `runtime.input`
|
||||
//! client helpers, and every source/subscriber. Everything fits one IPC message.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// 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 common envelope ----------------------------------------------------
|
||||
|
||||
/// 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)));
|
||||
|
||||
/// The tagged envelope broadcast to subscribers: a `DeviceKind` plus the raw bytes of the
|
||||
/// matching per-device event. 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;
|
||||
}
|
||||
};
|
||||
|
||||
// --- request / reply --------------------------------------------------------
|
||||
|
||||
/// Which side of a request this is.
|
||||
pub const Operation = enum(u32) {
|
||||
subscribe = 0, // register the caller's endpoint (send_cap) for the classes in device_mask
|
||||
publish = 1, // a source submits `event` to broadcast to interested subscribers
|
||||
};
|
||||
|
||||
/// Request header. For `subscribe`, `device_mask` is the OR of `device_*` bits the caller
|
||||
/// wants (0 means all) and the caller's receive endpoint travels as the call's capability;
|
||||
/// `event` is ignored. For `publish`, `event` is the event to broadcast.
|
||||
pub const Request = extern struct {
|
||||
operation: u32, // an Operation
|
||||
device_mask: u32 = 0, // subscribe: interested device classes (0 => all)
|
||||
event: InputEvent = .{ .device = 0 },
|
||||
};
|
||||
|
||||
/// Reply header. `status` is 0 on success or a negative errno.
|
||||
pub const Reply = extern struct {
|
||||
status: i32,
|
||||
_padding: u32 = 0,
|
||||
};
|
||||
|
||||
pub const request_size: usize = @sizeOf(Request);
|
||||
pub const reply_size: usize = @sizeOf(Reply);
|
||||
pub const event_size: usize = @sizeOf(InputEvent);
|
||||
|
||||
comptime {
|
||||
// The delivery path posts a bare InputEvent through ipc_send, so it must fit an
|
||||
// endpoint's async payload slot (POST_MAXIMUM is 64).
|
||||
if (event_size > 64) @compileError("InputEvent must fit the ipc_send payload (POST_MAXIMUM)");
|
||||
}
|
||||
Reference in New Issue
Block a user