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
|
/// `ConsumerHandle` do not apply to a raw tap (it owns no VT); only
|
||||||
/// [`Self::read_events`] / [`Self::event_handle`] are meaningful.
|
/// [`Self::read_events`] / [`Self::event_handle`] are meaningful.
|
||||||
pub fn new_raw() -> io::Result<Self> {
|
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()
|
let file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
.write(true)
|
||||||
.custom_flags(O_NONBLOCK as i32)
|
.custom_flags(O_NONBLOCK as i32)
|
||||||
.open(format!("/scheme/input/consumer_raw"))?;
|
.open(format!("/scheme/input/consumer_raw"))?;
|
||||||
Ok(Self(file))
|
Ok(Self(file))
|
||||||
@@ -172,6 +175,18 @@ impl ConsumerHandle {
|
|||||||
Ok(display_file)
|
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>> {
|
pub fn read_events<'a>(&self, events: &'a mut [Event]) -> io::Result<ConsumerHandleEvent<'a>> {
|
||||||
match read_to_slice(self.0.as_fd(), events) {
|
match read_to_slice(self.0.as_fd(), events) {
|
||||||
Ok(count) => Ok(ConsumerHandleEvent::Events(&events[..count])),
|
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 orbclient::{Event, EventOption};
|
||||||
use scheme_utils::{Blocking, FpathWriter, HandleMap};
|
use scheme_utils::{Blocking, FpathWriter, HandleMap};
|
||||||
use syscall::schemev2::NewFdFlags;
|
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;
|
pub mod keymap;
|
||||||
|
|
||||||
@@ -84,6 +84,13 @@ struct InputScheme {
|
|||||||
/// The compositor reads the raw device tap (`consumer_raw`) instead. Empty by
|
/// The compositor reads the raw device tap (`consumer_raw`) instead. Empty by
|
||||||
/// default, so console behaviour is unchanged until a compositor opts in.
|
/// default, so console behaviour is unchanged until a compositor opts in.
|
||||||
graphics_vts: BTreeSet<usize>,
|
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,
|
super_key: bool,
|
||||||
active_vt: Option<usize>,
|
active_vt: Option<usize>,
|
||||||
active_keymap: KeymapData,
|
active_keymap: KeymapData,
|
||||||
@@ -103,6 +110,7 @@ impl InputScheme {
|
|||||||
display: None,
|
display: None,
|
||||||
vts: BTreeSet::new(),
|
vts: BTreeSet::new(),
|
||||||
graphics_vts: BTreeSet::new(),
|
graphics_vts: BTreeSet::new(),
|
||||||
|
grab: None,
|
||||||
super_key: false,
|
super_key: false,
|
||||||
active_vt: None,
|
active_vt: None,
|
||||||
// TODO: configurable init?
|
// TODO: configurable init?
|
||||||
@@ -414,6 +422,29 @@ impl SchemeSync for InputScheme {
|
|||||||
) -> syscall::Result<usize> {
|
) -> syscall::Result<usize> {
|
||||||
self.has_new_events = true;
|
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)?;
|
let handle = self.handles.get_mut(id)?;
|
||||||
|
|
||||||
match handle {
|
match handle {
|
||||||
@@ -557,41 +588,52 @@ impl SchemeSync for InputScheme {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(active_vt) = self.active_vt {
|
let grab = self.grab;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
pending.extend_from_slice(buf);
|
// The text console is fed only when NObody holds an exclusive grab and
|
||||||
*notified = false;
|
// 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
|
// Raw taps get the pre-keymap stream with no VT gate — so a compositor
|
||||||
// even when no VT is active — so a Wayland compositor keeps receiving
|
// keeps receiving input regardless of the active text VT. When a grab is
|
||||||
// input regardless of which text VT (if any) is foregrounded.
|
// held, ONLY the grabbing handle is fed (Linux `EVIOCGRAB` exclusivity);
|
||||||
for handle in self.handles.values_mut() {
|
// otherwise every raw tap is fed.
|
||||||
|
for (hid, handle) in self.handles.iter_mut() {
|
||||||
if let Handle::ConsumerRaw {
|
if let Handle::ConsumerRaw {
|
||||||
pending, notified, ..
|
pending, notified, ..
|
||||||
} = handle
|
} = handle
|
||||||
{
|
{
|
||||||
|
if let Some(gid) = grab {
|
||||||
|
if *hid != gid {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
pending.extend_from_slice(&raw_buf);
|
pending.extend_from_slice(&raw_buf);
|
||||||
*notified = false;
|
*notified = false;
|
||||||
}
|
}
|
||||||
@@ -648,6 +690,12 @@ impl SchemeSync for InputScheme {
|
|||||||
let Some(handle) = self.handles.remove(id) else {
|
let Some(handle) = self.handles.remove(id) else {
|
||||||
return;
|
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 {
|
if let Handle::Consumer { vt, .. } = handle {
|
||||||
self.vts.remove(&vt);
|
self.vts.remove(&vt);
|
||||||
if self.active_vt == Some(vt) {
|
if self.active_vt == Some(vt) {
|
||||||
|
|||||||
Reference in New Issue
Block a user