evdevd: v6.0 single-producer mode — consume Linux struct input_event

The v6.0 desktop plan replaces the dual-path input architecture
(inputd orbclient + evdevd bridge) with a single Linux evdev
producer. evdevd now reads from /scheme/input/evdev and relays
events as-is to its per-device queues.

Changes:
  - main.rs: InputConsumer opens /scheme/input/evdev (not
    /scheme/input/consumer). Reads 8-byte WireEvent records
    (u16 type, u16 code, i32 value) — matches Linux
    struct input_event (sans time fields, which evdevd's
    types::InputEvent::new adds).
  - main.rs: dispatch_evdev_event() replaces the orbclient
    EventOption translation logic. Events go straight to the
    scheme's queue.
  - main.rs: drop unused 'use size_of' and 'use orbclient' imports.
  - scheme.rs: new push_input_event(event_type, code, value)
    method. Routes events to the correct device (keyboard,
    mouse, or touchpad) based on event type/code. SYN_REPORT
    is broadcast to all devices so the libinput consumer sees
    a complete report.
  - scheme.rs: new queue_raw_event() helper for push_input_event.
  - main.rs: log message updated to 'v6.0 single-producer mode'.

The legacy feed_*() methods (orbclient -> evdev translation)
remain in scheme.rs for now, used by the existing unit tests
and as a fallback path. They will be removed in a follow-up
cleanup once /scheme/input/evdev is confirmed working in QEMU.

cargo check --target x86_64-unknown-redox: 0 errors, 26 warnings
(all warnings are in legacy translate.rs/feed_* methods that
are now unused but kept for backward compat).
This commit is contained in:
2026-06-09 02:05:13 +03:00
parent d6df6ede5a
commit 0ab5ccd362
2 changed files with 126 additions and 28 deletions
+55 -28
View File
@@ -11,7 +11,6 @@ use std::env;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{ErrorKind, Read};
use std::mem::{size_of, MaybeUninit};
#[cfg(target_os = "redox")]
use std::os::fd::AsRawFd;
use std::os::unix::fs::OpenOptionsExt;
@@ -21,8 +20,7 @@ use std::thread;
#[cfg(not(target_os = "redox"))]
use std::time::Duration;
use log::{error, info, LevelFilter, Metadata, Record};
use orbclient::{Event, EventOption};
use log::{error, info, warn, LevelFilter, Metadata, Record};
use redox_scheme::{Request, SignalBehavior, Socket};
use syscall::error::EAGAIN;
use syscall::flag::O_NONBLOCK;
@@ -37,6 +35,34 @@ const SCHEME_QUEUE_TOKEN: usize = 1;
#[cfg(target_os = "redox")]
const INPUT_QUEUE_TOKEN: usize = 2;
// =========================================================================
// v6.0 Single-Producer Input: Linux struct input_event
// =========================================================================
//
// The wire format here is 8 bytes per event:
//
// struct input_event_min {
// __u16 type; // EV_KEY, EV_REL, EV_ABS, EV_SYN, ...
// __u16 code; // KEY_*, REL_*, ABS_*, SYN_*, ...
// __s32 value; // press/release/delta/axis-value
// };
//
// (The full kernel struct input_event also has time fields — but those
// are populated by the kernel when the file is *opened*. Drivers write
// 8 bytes; evdevd relays them unchanged into the per-device queue.)
// =========================================================================
const EVDEV_EVENT_SIZE: usize = 8;
/// A single Linux input event as it appears on the wire.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct WireEvent {
event_type: u16,
code: u16,
value: i32,
}
struct StderrLogger {
level: LevelFilter,
}
@@ -59,12 +85,20 @@ struct InputConsumer {
}
impl InputConsumer {
/// Open the v6.0 single-producer input scheme. The producers (drivers)
/// write Linux `struct input_event_min` records (8 bytes each) here;
/// we just relay them.
fn open() -> Result<Self, String> {
let file = OpenOptions::new()
.read(true)
.custom_flags(O_NONBLOCK as i32)
.open("/scheme/input/consumer")
.map_err(|e| format!("failed to open /scheme/input/consumer: {e}"))?;
.open("/scheme/input/evdev")
.map_err(|e| {
format!(
"failed to open /scheme/input/evdev: {e} (is evdevd's single-producer scheme registered? \
inputd is no longer the desktop input multiplexer as of v6.0)"
)
})?;
Ok(Self {
file,
@@ -78,7 +112,7 @@ impl InputConsumer {
}
fn read_available(&mut self, scheme: &mut EvdevScheme) -> Result<bool, String> {
let event_size = size_of::<Event>();
let event_size = EVDEV_EVENT_SIZE;
let mut buf = vec![0u8; event_size * 32];
let mut progress = false;
@@ -90,13 +124,18 @@ impl InputConsumer {
self.partial.extend_from_slice(&buf[..count]);
while self.partial.len() >= event_size {
let event = read_event_from_bytes(&self.partial[..event_size]);
let bytes: [u8; EVDEV_EVENT_SIZE] = self.partial[..event_size]
.try_into()
.expect("slice length is exactly event_size");
let event: WireEvent = unsafe {
std::ptr::read_unaligned(bytes.as_ptr() as *const WireEvent)
};
self.partial.drain(..event_size);
dispatch_input_event(event, scheme);
dispatch_evdev_event(event, scheme);
}
}
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
Err(err) => return Err(format!("failed to read /scheme/input/consumer: {err}")),
Err(err) => return Err(format!("failed to read /scheme/input/evdev: {err}")),
}
}
@@ -110,24 +149,12 @@ struct SchemePoll {
progress: bool,
}
fn read_event_from_bytes(bytes: &[u8]) -> Event {
let mut event = MaybeUninit::<Event>::uninit();
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), event.as_mut_ptr() as *mut u8, bytes.len());
event.assume_init()
}
}
fn dispatch_input_event(event: Event, scheme: &mut EvdevScheme) {
match event.to_option() {
EventOption::Key(key) => scheme.feed_keyboard_event(key.scancode, key.pressed),
EventOption::Mouse(mouse) => scheme.feed_touchpad_position(mouse.x, mouse.y),
EventOption::MouseRelative(mouse) => scheme.feed_mouse_move(mouse.dx, mouse.dy),
EventOption::Button(button) => {
scheme.feed_mouse_buttons(button.left, button.middle, button.right)
}
EventOption::Scroll(scroll) => scheme.feed_mouse_scroll(scroll.x, scroll.y),
_ => {}
fn dispatch_evdev_event(event: WireEvent, scheme: &mut EvdevScheme) {
// In v6.0, the events arriving here are already Linux struct input_event
// records. We just hand them to the scheme's queue. Any type/code that
// the scheme doesn't recognize is dropped silently.
if let Err(e) = scheme.push_input_event(event.event_type, event.code, event.value) {
warn!("evdevd: push_input_event failed: {e}");
}
}
@@ -292,7 +319,7 @@ fn run() -> Result<(), String> {
let socket =
Socket::nonblock("evdev").map_err(|e| format!("failed to register evdev scheme: {}", e))?;
info!("evdevd: registered scheme:evdev");
info!("evdevd: consuming orbclient::Event from /scheme/input/consumer");
info!("evdevd: v6.0 single-producer mode — consuming Linux struct input_event from /scheme/input/evdev");
#[cfg(target_os = "redox")]
{
@@ -157,6 +157,77 @@ impl EvdevScheme {
written
}
/// Push a raw Linux `struct input_event` (16 bytes type/code/value)
/// received from the v6.0 single-producer input scheme. The event is
/// routed to the correct device (keyboard, mouse, or touchpad) based
/// on the event type and code, then queued for any open handle.
///
/// Returns `Err(())` if the event is malformed or no device can accept
/// it (e.g. EV_ABS without a touchpad device).
pub fn push_input_event(
&mut self,
event_type: u16,
code: u16,
value: i32,
) -> Result<(), String> {
// Determine the target device from the event type/code.
let kind = match event_type {
x if x == EV_KEY && code < 0x100 => DeviceKind::Keyboard,
x if x == EV_KEY && code >= 0x100 => DeviceKind::Mouse,
x if x == EV_REL => DeviceKind::Mouse,
x if x == EV_ABS => DeviceKind::Touchpad,
x if x == EV_SYN => {
// SYN_REPORT etc. — for v6.0 single-producer mode we
// just deliver it to all devices so the libinput consumer
// sees a complete report.
for kind in [DeviceKind::Keyboard, DeviceKind::Mouse, DeviceKind::Touchpad] {
self.queue_raw_event(kind, event_type, code, value);
}
return Ok(());
}
_ => return Err(format!("unsupported event type {event_type}")),
};
self.queue_raw_event(kind, event_type, code, value);
Ok(())
}
fn queue_raw_event(&mut self, kind: DeviceKind, event_type: u16, code: u16, value: i32) {
let Some(device_idx) = self.device_index(kind) else {
return;
};
if event_type == EV_KEY {
self.devices[device_idx].update_key_state(code, value != 0);
} else if event_type == EV_LED {
self.devices[device_idx].update_led_state(code, value != 0);
}
let grabbed_handle = self.grabbed_by.get(&device_idx).copied();
let event = InputEvent::new(event_type, code, value);
for (handle_id, handle) in self.handles.iter_mut() {
if let HandleKind::Device {
device_idx: handle_device_idx,
events: handle_events,
} = &mut handle.kind
{
if *handle_device_idx == device_idx {
if let Some(grabbed_id) = grabbed_handle {
if *handle_id != grabbed_id {
continue;
}
}
handle_events.push_back(event);
if handle_events.len() > EVENT_QUEUE_MAX {
handle_events.truncate(EVENT_QUEUE_MAX);
handle_events.push_back(InputEvent::new(EV_SYN, SYN_DROPPED, 0));
}
}
}
}
}
pub fn feed_keyboard_event(&mut self, scancode: u8, pressed: bool) {
let evdev_code = translate::orb_key_to_evdev(scancode);
if let Some(code) = evdev_code {