inputd: add raw evdev peer tap (consumer_raw) for libinput/Wayland
Add a new `consumer_raw` handle to scheme:input — a raw, always-on device
tap modelled on the Linux evdev handler. Unlike the console `consumer`, it:
* is NOT bound to a VT, so it receives events regardless of which VT is
foregrounded (a background compositor keeps getting input); and
* receives the *pre-keymap* event stream — the console keymap is now
applied only on the console `consumer` path, never on the raw tap.
The producer write path snapshots the raw event bytes before the keymap
stamping loop and fans them out to every ConsumerRaw handle unconditionally,
in parallel with the existing VT-gated, keymapped console fan-out (which is
byte-for-byte unchanged — no console-login regression).
This is the RedBear equivalent of evdev being a peer handle off the raw input
core: libinput/xkbcommon (via evdevd) require raw scancodes, which the old
wiring — evdevd reading the keymapped, VT-gated console consumer — could not
provide. Adds ConsumerHandle::new_raw() for consumers.
Part of the Linux-aligned input-stack consolidation
(local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md). Compile-checked for
x86_64-unknown-redox; boot-validation pending build-green (acpi-rs).
This commit is contained in:
@@ -73,6 +73,22 @@ impl ConsumerHandle {
|
||||
Ok(Self(file))
|
||||
}
|
||||
|
||||
/// Open a *raw* device tap (the Linux `evdev` role): the pre-keymap event
|
||||
/// stream, delivered for every producer event regardless of the active VT.
|
||||
///
|
||||
/// This is what a libinput/xkbcommon consumer (evdevd, and in turn a Wayland
|
||||
/// compositor) must read — the console keymap is applied only on the plain
|
||||
/// [`Self::new_vt`] console path, never here. The display/handoff helpers on
|
||||
/// `ConsumerHandle` do not apply to a raw tap (it owns no VT); only
|
||||
/// [`Self::read_events`] / [`Self::event_handle`] are meaningful.
|
||||
pub fn new_raw() -> io::Result<Self> {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(O_NONBLOCK as i32)
|
||||
.open(format!("/scheme/input/consumer_raw"))?;
|
||||
Ok(Self(file))
|
||||
}
|
||||
|
||||
pub fn event_handle(&self) -> BorrowedFd<'_> {
|
||||
self.0.as_fd()
|
||||
}
|
||||
|
||||
@@ -46,6 +46,18 @@ enum Handle {
|
||||
notified: bool,
|
||||
vt: usize,
|
||||
},
|
||||
/// A raw, always-on device tap (the Linux `evdev` role). Unlike `Consumer`,
|
||||
/// it is NOT bound to a VT and receives the *pre-keymap* event stream for
|
||||
/// every producer event regardless of which VT is active. This is what a
|
||||
/// libinput/xkbcommon (Wayland) consumer such as evdevd must read: the
|
||||
/// console keymap is applied only on the `Consumer` (console) path, so a raw
|
||||
/// tap here never sees a keymapped `character`, and a background compositor
|
||||
/// still receives input while a text VT is foregrounded.
|
||||
ConsumerRaw {
|
||||
events: EventFlags,
|
||||
pending: Vec<u8>,
|
||||
notified: bool,
|
||||
},
|
||||
Display {
|
||||
events: EventFlags,
|
||||
pending: Vec<VtEvent>,
|
||||
@@ -186,6 +198,16 @@ impl SchemeSync for InputScheme {
|
||||
vt,
|
||||
}
|
||||
}
|
||||
"consumer_raw" => {
|
||||
// A raw device tap (evdev role). No VT is allocated: it observes
|
||||
// the whole pre-keymap stream, always, independent of the active
|
||||
// VT — exactly what libinput/xkbcommon needs.
|
||||
Handle::ConsumerRaw {
|
||||
events: EventFlags::empty(),
|
||||
pending: Vec::new(),
|
||||
notified: false,
|
||||
}
|
||||
}
|
||||
"consumer_bootlog" => {
|
||||
if !self.vts.insert(1) {
|
||||
return Err(SysError::new(EEXIST));
|
||||
@@ -313,6 +335,16 @@ impl SchemeSync for InputScheme {
|
||||
Ok(copy)
|
||||
}
|
||||
|
||||
Handle::ConsumerRaw { pending, .. } => {
|
||||
// Raw taps never trigger a display handoff (they own no VT); just
|
||||
// drain whatever pre-keymap event bytes are queued.
|
||||
let copy = core::cmp::min(pending.len(), buf.len());
|
||||
for (i, byte) in pending.drain(..copy).enumerate() {
|
||||
buf[i] = byte;
|
||||
}
|
||||
Ok(copy)
|
||||
}
|
||||
|
||||
Handle::Display { pending, .. } => {
|
||||
if buf.len() % size_of::<VtEvent>() == 0 {
|
||||
let copy = core::cmp::min(pending.len(), buf.len() / size_of::<VtEvent>());
|
||||
@@ -379,6 +411,10 @@ impl SchemeSync for InputScheme {
|
||||
log::error!("consumer tried to write");
|
||||
return Err(SysError::new(EINVAL));
|
||||
}
|
||||
Handle::ConsumerRaw { .. } => {
|
||||
log::error!("raw consumer tried to write");
|
||||
return Err(SysError::new(EINVAL));
|
||||
}
|
||||
Handle::Display { .. } => {
|
||||
log::error!("display tried to write");
|
||||
return Err(SysError::new(EINVAL));
|
||||
@@ -424,6 +460,19 @@ impl SchemeSync for InputScheme {
|
||||
v
|
||||
});
|
||||
|
||||
// Snapshot the RAW event bytes *before* the console keymap is applied
|
||||
// below. Raw taps (evdevd → libinput → compositor) must never see a
|
||||
// keymapped `character`; they consume scancodes directly. Taking the
|
||||
// copy here — after the aligned `Vec<Event>` is built but before the
|
||||
// stamping loop — gives every raw consumer the exact producer stream.
|
||||
let raw_buf: Vec<u8> = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
events.as_ptr() as *const u8,
|
||||
events.len() * size_of::<Event>(),
|
||||
)
|
||||
}
|
||||
.to_vec();
|
||||
|
||||
for i in 0..events.len() {
|
||||
let mut new_active_opt = None;
|
||||
match events[i].to_option() {
|
||||
@@ -494,6 +543,19 @@ impl SchemeSync for InputScheme {
|
||||
}
|
||||
}
|
||||
|
||||
// Raw taps get the pre-keymap stream unconditionally — no VT gate, and
|
||||
// even when no VT is active — so a Wayland compositor keeps receiving
|
||||
// input regardless of which text VT (if any) is foregrounded.
|
||||
for handle in self.handles.values_mut() {
|
||||
if let Handle::ConsumerRaw {
|
||||
pending, notified, ..
|
||||
} = handle
|
||||
{
|
||||
pending.extend_from_slice(&raw_buf);
|
||||
*notified = false;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
@@ -513,6 +575,15 @@ impl SchemeSync for InputScheme {
|
||||
*notified = false;
|
||||
Ok(EventFlags::empty())
|
||||
}
|
||||
Handle::ConsumerRaw {
|
||||
ref mut events,
|
||||
ref mut notified,
|
||||
..
|
||||
} => {
|
||||
*events = flags;
|
||||
*notified = false;
|
||||
Ok(EventFlags::empty())
|
||||
}
|
||||
Handle::Display {
|
||||
ref mut events,
|
||||
ref mut notified,
|
||||
@@ -592,6 +663,22 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> {
|
||||
|
||||
*notified = true;
|
||||
}
|
||||
Handle::ConsumerRaw {
|
||||
events,
|
||||
pending,
|
||||
ref mut notified,
|
||||
} => {
|
||||
if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
socket_file.write_response(
|
||||
Response::post_fevent(*id, EventFlags::EVENT_READ.bits()),
|
||||
SignalBehavior::Restart,
|
||||
)?;
|
||||
|
||||
*notified = true;
|
||||
}
|
||||
Handle::Display {
|
||||
events,
|
||||
pending,
|
||||
|
||||
Reference in New Issue
Block a user