From a916a6612c4ee741a851228f3c0f59f433e771d9 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Fri, 24 Jul 2026 12:01:03 +0900 Subject: [PATCH] inputd: add VT graphics-mode control op (console suppress) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- drivers/inputd/src/lib.rs | 14 ++++++++ drivers/inputd/src/main.rs | 69 ++++++++++++++++++++++++++++++-------- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs index efe4a9cbc1..797521e9a9 100644 --- a/drivers/inputd/src/lib.rs +++ b/drivers/inputd/src/lib.rs @@ -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 { + let cmd = ControlEvent { + kind: if graphics { 3 } else { 4 }, + data: vt, + }; + self.0.write(unsafe { any_as_u8_slice(&cmd) }) + } } #[derive(Debug)] diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs index 05a6d8b291..facf057cb9 100644 --- a/drivers/inputd/src/main.rs +++ b/drivers/inputd/src/main.rs @@ -77,6 +77,13 @@ struct InputScheme { display: Option, vts: BTreeSet, + /// 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, super_key: bool, active_vt: Option, 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, } } }