inputd: add VT graphics-mode control op (console suppress)

Add control kinds 3/4 (set VT graphics/text mode) and a graphics_vts set.
When a VT is claimed by a graphical client, the console `Consumer` for that VT
is suspended — cooked bytes stop being delivered to the text console — while
the raw device tap (consumer_raw) keeps feeding the compositor. This is the
RedBear equivalent of Linux KDSKBMODE(K_OFF)/KD_GRAPHICS silencing the console
keyboard + n_tty for a VT owned by a Wayland compositor.

Behaviour-preserving by default: graphics_vts is empty until a compositor
opts in via ControlHandle::set_vt_mode(vt, true), so text-console delivery is
unchanged. Two control kinds are used instead of a bool to keep the
ControlEvent ABI unchanged.

Full device-grab arbitration (exclusive routing / EVIOCGRAB) and the
VT_PROCESS switch-ack handshake remain TODO — the raw broadcast is sufficient
for a single compositor. Part of the Linux-aligned input-stack consolidation.
Compile-checked for x86_64-unknown-redox; boot-validation pending build-green.
This commit is contained in:
Red Bear OS
2026-07-24 12:01:03 +09:00
parent bd5e3db32a
commit a916a6612c
2 changed files with 69 additions and 14 deletions
+14
View File
@@ -271,6 +271,20 @@ impl ControlHandle {
let cmd = ControlEvent::from(KeymapActivate { keymap });
self.0.write(unsafe { any_as_u8_slice(&cmd) })
}
/// Claim (`graphics = true`) or release (`graphics = false`) a VT for a
/// graphical client (a Wayland compositor). Claiming suspends the text
/// console for that VT — the RedBear `KDSKBMODE(K_OFF)` equivalent — so a
/// compositor reading the raw device tap (`consumer_raw`) owns input while
/// the text console stays silent. Releasing restores normal console
/// delivery. See inputd `set_vt_mode`.
pub fn set_vt_mode(&mut self, vt: usize, graphics: bool) -> io::Result<usize> {
let cmd = ControlEvent {
kind: if graphics { 3 } else { 4 },
data: vt,
};
self.0.write(unsafe { any_as_u8_slice(&cmd) })
}
}
#[derive(Debug)]
+55 -14
View File
@@ -77,6 +77,13 @@ struct InputScheme {
display: Option<String>,
vts: BTreeSet<usize>,
/// VTs currently owned by a graphical client (a Wayland compositor). While a
/// VT is in this set the console `Consumer` for it is SUSPENDED — no cooked
/// bytes are delivered to the text console — exactly like Linux
/// `KDSKBMODE(K_OFF)` silences the console keyboard/n_tty for a graphics VT.
/// 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>,
super_key: bool,
active_vt: Option<usize>,
active_keymap: KeymapData,
@@ -95,6 +102,7 @@ impl InputScheme {
display: None,
vts: BTreeSet::new(),
graphics_vts: BTreeSet::new(),
super_key: false,
active_vt: None,
// TODO: configurable init?
@@ -145,6 +153,28 @@ impl InputScheme {
self.active_vt = Some(new_active);
}
/// Set whether a VT is owned by a graphical client (a compositor).
///
/// `graphics = true` suspends the text console for that VT (the RedBear
/// `KDSKBMODE(K_OFF)` equivalent): the compositor now drives the display and
/// reads raw input from `consumer_raw`, so cooked bytes must not also land in
/// the console `Consumer`. `graphics = false` restores normal text-console
/// delivery. Idempotent; unknown VTs are accepted (a compositor may claim a
/// VT it is about to open).
fn set_vt_mode(&mut self, vt: usize, graphics: bool) {
let changed = if graphics {
self.graphics_vts.insert(vt)
} else {
self.graphics_vts.remove(&vt)
};
if changed {
log::debug!(
"VT #{vt} mode -> {}",
if graphics { "graphics" } else { "text" }
);
}
}
fn switch_keymap(&mut self, new_active: usize) {
if new_active == self.active_keymap.get_kind() as usize {
return;
@@ -399,6 +429,11 @@ impl SchemeSync for InputScheme {
match cmd.kind {
1 => self.switch_vt(cmd.data),
2 => self.switch_keymap(cmd.data),
// 3/4: a graphical client (compositor) claims/releases a VT.
// `data` is the VT id. Two kinds instead of a bool keeps the
// ControlEvent ABI unchanged.
3 => self.set_vt_mode(cmd.data, true),
4 => self.set_vt_mode(cmd.data, false),
k => {
log::warn!("unknown control {}", k);
}
@@ -523,22 +558,28 @@ impl SchemeSync for InputScheme {
};
if let Some(active_vt) = self.active_vt {
for handle in self.handles.values_mut() {
match handle {
Handle::Consumer {
pending,
notified,
vt,
..
} => {
if *vt != active_vt {
continue;
}
// 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);
*notified = false;
pending.extend_from_slice(buf);
*notified = false;
}
_ => continue,
}
_ => continue,
}
}
}