diff --git a/local/recipes/system/evdevd/source/src/main.rs b/local/recipes/system/evdevd/source/src/main.rs index 373ceabeee..8c7899198e 100644 --- a/local/recipes/system/evdevd/source/src/main.rs +++ b/local/recipes/system/evdevd/source/src/main.rs @@ -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 { 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 { - let event_size = size_of::(); + 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::::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")] { diff --git a/local/recipes/system/evdevd/source/src/scheme.rs b/local/recipes/system/evdevd/source/src/scheme.rs index 57a8bbb411..77fe9097a0 100644 --- a/local/recipes/system/evdevd/source/src/scheme.rs +++ b/local/recipes/system/evdevd/source/src/scheme.rs @@ -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 {