inputd: exclusive input grab (EVIOCGRAB equivalent)
Complete the Linux-aligned seat-takeover primitive set: a raw consumer can take
an EXCLUSIVE grab of the input stream by writing a single control byte to its
own consumer_raw handle (1=grab, 0=ungrab) — self-identifying, unlike the
shared control fd. While a grab is held:
* events route ONLY to the grabbing handle;
* the text console AND every other raw tap are suspended; and
* the grab is released automatically when the handle closes, so a compositor
that crashes cannot wedge input away from the console (Linux
__input_release_device).
A second grab attempt by a different handle returns EBUSY. Additive and
behaviour-preserving: grab defaults to None, so nothing changes until a client
grabs. new_raw() now opens read+write and ConsumerHandle::set_grab() drives it.
Together with set_vt_mode (KDSKBMODE) this gives seatd/a compositor the full
grab + VT-mode takeover surface. Compile-checked for x86_64-unknown-redox;
boot-validation pending build-green.
This commit is contained in:
@@ -82,8 +82,11 @@ impl ConsumerHandle {
|
||||
/// `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> {
|
||||
// Opened read+write: reads drain events, and a single control byte is
|
||||
// written to this same handle to grab/ungrab (see `set_grab`).
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(O_NONBLOCK as i32)
|
||||
.open(format!("/scheme/input/consumer_raw"))?;
|
||||
Ok(Self(file))
|
||||
@@ -172,6 +175,18 @@ impl ConsumerHandle {
|
||||
Ok(display_file)
|
||||
}
|
||||
|
||||
/// Request (`grab = true`) or release (`grab = false`) an EXCLUSIVE grab of
|
||||
/// the input stream on a raw tap opened with [`Self::new_raw`] — the RedBear
|
||||
/// `EVIOCGRAB`. While grabbed, this handle is the ONLY consumer fed: the text
|
||||
/// console and every other raw tap are suspended. The grab is dropped
|
||||
/// automatically when the handle closes. Returns `EBUSY` if another handle
|
||||
/// already holds the grab. Meaningless on a console (`new_vt`) handle.
|
||||
pub fn set_grab(&mut self, grab: bool) -> io::Result<()> {
|
||||
let byte = [u8::from(grab)];
|
||||
self.0.write(&byte)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_events<'a>(&self, events: &'a mut [Event]) -> io::Result<ConsumerHandleEvent<'a>> {
|
||||
match read_to_slice(self.0.as_fd(), events) {
|
||||
Ok(count) => Ok(ConsumerHandleEvent::Events(&events[..count])),
|
||||
|
||||
+73
-25
@@ -27,7 +27,7 @@ use redox_scheme::{CallerCtx, OpenResult, Response, SignalBehavior, Socket};
|
||||
use orbclient::{Event, EventOption};
|
||||
use scheme_utils::{Blocking, FpathWriter, HandleMap};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL};
|
||||
use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EBUSY, EEXIST, EINVAL};
|
||||
|
||||
pub mod keymap;
|
||||
|
||||
@@ -84,6 +84,13 @@ struct InputScheme {
|
||||
/// The compositor reads the raw device tap (`consumer_raw`) instead. Empty by
|
||||
/// default, so console behaviour is unchanged until a compositor opts in.
|
||||
graphics_vts: BTreeSet<usize>,
|
||||
/// Handle id of a raw consumer holding an EXCLUSIVE grab of the input stream
|
||||
/// (the Linux `input_grab_device` / `EVIOCGRAB` role). While set, events are
|
||||
/// routed ONLY to this handle — the console consumer and every other raw tap
|
||||
/// are suspended — and it is cleared automatically when that handle closes
|
||||
/// (like `__input_release_device`). `None` by default, so nothing changes
|
||||
/// until a client explicitly grabs.
|
||||
grab: Option<usize>,
|
||||
super_key: bool,
|
||||
active_vt: Option<usize>,
|
||||
active_keymap: KeymapData,
|
||||
@@ -103,6 +110,7 @@ impl InputScheme {
|
||||
display: None,
|
||||
vts: BTreeSet::new(),
|
||||
graphics_vts: BTreeSet::new(),
|
||||
grab: None,
|
||||
super_key: false,
|
||||
active_vt: None,
|
||||
// TODO: configurable init?
|
||||
@@ -414,6 +422,29 @@ impl SchemeSync for InputScheme {
|
||||
) -> syscall::Result<usize> {
|
||||
self.has_new_events = true;
|
||||
|
||||
// A raw consumer manages its exclusive grab by writing a single control
|
||||
// byte to its OWN handle (self-identifying, unlike the shared control
|
||||
// fd): `1` = grab, `0` = ungrab. This is the RedBear `EVIOCGRAB`. Handled
|
||||
// up front so it does not fall into the event-processing path below.
|
||||
// `matches!` ends the immutable borrow before we touch `self.grab`.
|
||||
if matches!(self.handles.get(id), Ok(Handle::ConsumerRaw { .. })) {
|
||||
let want_grab = buf.first().copied().unwrap_or(0) != 0;
|
||||
if want_grab {
|
||||
match self.grab {
|
||||
None => {
|
||||
self.grab = Some(id);
|
||||
log::debug!("raw consumer #{id} grabbed the input stream");
|
||||
}
|
||||
Some(g) if g == id => {} // idempotent re-grab by the owner
|
||||
Some(_) => return Err(SysError::new(EBUSY)),
|
||||
}
|
||||
} else if self.grab == Some(id) {
|
||||
self.grab = None;
|
||||
log::debug!("raw consumer #{id} released the input stream");
|
||||
}
|
||||
return Ok(buf.len());
|
||||
}
|
||||
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
|
||||
match handle {
|
||||
@@ -557,41 +588,52 @@ impl SchemeSync for InputScheme {
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(active_vt) = self.active_vt {
|
||||
// Suppress the text console entirely when the active VT is owned by a
|
||||
// graphical client — the compositor drives it via the raw tap, and
|
||||
// cooked bytes must not also reach a (now hidden) text console.
|
||||
let console_suppressed = self.graphics_vts.contains(&active_vt);
|
||||
if !console_suppressed {
|
||||
for handle in self.handles.values_mut() {
|
||||
match handle {
|
||||
Handle::Consumer {
|
||||
pending,
|
||||
notified,
|
||||
vt,
|
||||
..
|
||||
} => {
|
||||
if *vt != active_vt {
|
||||
continue;
|
||||
}
|
||||
let grab = self.grab;
|
||||
|
||||
pending.extend_from_slice(buf);
|
||||
*notified = false;
|
||||
// The text console is fed only when NObody holds an exclusive grab and
|
||||
// the active VT is not owned by a graphical client. A grab (a fullscreen
|
||||
// compositor) or graphics-mode VT both mean cooked bytes must not reach a
|
||||
// hidden text console.
|
||||
if grab.is_none() {
|
||||
if let Some(active_vt) = self.active_vt {
|
||||
let console_suppressed = self.graphics_vts.contains(&active_vt);
|
||||
if !console_suppressed {
|
||||
for handle in self.handles.values_mut() {
|
||||
match handle {
|
||||
Handle::Consumer {
|
||||
pending,
|
||||
notified,
|
||||
vt,
|
||||
..
|
||||
} => {
|
||||
if *vt != active_vt {
|
||||
continue;
|
||||
}
|
||||
|
||||
pending.extend_from_slice(buf);
|
||||
*notified = false;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
// Raw taps get the pre-keymap stream with no VT gate — so a compositor
|
||||
// keeps receiving input regardless of the active text VT. When a grab is
|
||||
// held, ONLY the grabbing handle is fed (Linux `EVIOCGRAB` exclusivity);
|
||||
// otherwise every raw tap is fed.
|
||||
for (hid, handle) in self.handles.iter_mut() {
|
||||
if let Handle::ConsumerRaw {
|
||||
pending, notified, ..
|
||||
} = handle
|
||||
{
|
||||
if let Some(gid) = grab {
|
||||
if *hid != gid {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pending.extend_from_slice(&raw_buf);
|
||||
*notified = false;
|
||||
}
|
||||
@@ -648,6 +690,12 @@ impl SchemeSync for InputScheme {
|
||||
let Some(handle) = self.handles.remove(id) else {
|
||||
return;
|
||||
};
|
||||
// Release an exclusive grab held by the closing handle, so a compositor
|
||||
// that crashes or exits does not wedge input away from the console.
|
||||
if self.grab == Some(id) {
|
||||
self.grab = None;
|
||||
log::debug!("raw consumer #{id} closed; input grab released");
|
||||
}
|
||||
if let Handle::Consumer { vt, .. } = handle {
|
||||
self.vts.remove(&vt);
|
||||
if self.active_vt == Some(vt) {
|
||||
|
||||
Reference in New Issue
Block a user