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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user