🎉 Phase 4 vraie validée visuellement : pixels custom plein écran

Capture : docs/phase4-victory-1280x800.png — dégradé ARGB animé 1280x800
écrit par redox-wl-fullscreen-paint, occupant tout l'écran QEMU sans
trace de bootlog, fbcond ou Orbital.

Cause racine du verrou (3 bugs en cascade) :

1. ConsumerHandle local à RedoxOutput::open() → droppé en fin de fn →
   inputd::on_close retirait le VT de self.vts → tous les `inputd -A <vt>`
   ultérieurs retournaient warning "switch to non-existent VT"

2. L'env var VT=N posée par init n'a aucun lien avec le VT alloué par
   inputd. inputd auto-incrémente next_vt_id à partir de 2 (VT 1 réservé
   bootlog). Avec fbbootlogd VT 1 + fbcond VT 2, notre paint = VT 3.

3. Sans le bon VT activé, set_crtc est silencieusement no-op côté
   driver-graphics (lib.rs:575 : `if *vt == self.active_vt { ... }`).

Fixes :
- RedoxOutput stocke `_consumer: ConsumerHandle` pour préserver le VT
- RedoxOutput.vt() lu via fpath sur consumer fd (inputd retourne
  `<scheme>/<vt>`)
- Binary lit output.vt() puis fait inputd -A <vt> avec le bon numéro
- 300ms de sleep pour propagation active_vt avant take_crtc

Validation automatisée : qemu -display none + monitor unix socket +
ncat -U pour sendkey ret + screendump à T+14s + ImageMagick.

Image Redox restaurée à boot Orbital normal après la session.

Phase 4 close. La piste 1 (consume events VT) reste utile pour le
hot-switch propre Ctrl+Alt+Fn mais n'est plus bloquante.

Leyoda 2026 – GPLv3
This commit is contained in:
Votre Nom 2026-05-09 10:46:20 +02:00
parent 5b1e038333
commit 753a30757b
7 changed files with 139 additions and 18 deletions

View file

@ -16,6 +16,7 @@
//! le KMS Redox), pas de page flip / double buffer / hotplug.
use std::io;
use std::os::fd::AsRawFd;
use std::slice;
use drm::Device as _;
@ -28,6 +29,10 @@ use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle};
use inputd::ConsumerHandle;
pub struct RedoxOutput {
/// Consumer handle GARDÉ EN VIE pour que le VT alloué par inputd persiste.
/// Si on drop ce handle, inputd retire le VT de sa table et toute opération
/// `inputd -A <vt>` ultérieure dira "non-existent VT".
_consumer: ConsumerHandle,
handle: V2GraphicsHandle,
width: u32,
height: u32,
@ -36,6 +41,9 @@ pub struct RedoxOutput {
mode: Mode,
fb: Option<framebuffer::Handle>,
buffer: Option<CpuBackedBuffer>,
/// VT alloué par inputd lors de l'open consumer.
/// À utiliser pour `inputd -A <vt>` afin de devenir le VT actif.
vt: usize,
}
impl RedoxOutput {
@ -43,6 +51,19 @@ impl RedoxOutput {
/// connecteur connecté + son premier mode + le premier CRTC compatible.
pub fn open() -> io::Result<Self> {
let consumer = ConsumerHandle::new_vt()?;
// Découvrir notre VT alloué par inputd via fpath sur le consumer fd.
// Le fpath retourne `<display_scheme>/<vt>` (cf inputd main.rs:271).
let mut buf = [0u8; 1024];
let n = libredox_call_fpath(consumer.event_handle().as_raw_fd() as usize, &mut buf)?;
let path = std::str::from_utf8(&buf[..n])
.map_err(|e| io::Error::other(format!("fpath utf8: {e}")))?;
let vt: usize = path
.rsplit('/')
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(|| io::Error::other(format!("can't parse VT from fpath {path:?}")))?;
let display_file = consumer.open_display_v2()?;
let handle = V2GraphicsHandle::from_file(display_file)?;
@ -80,6 +101,7 @@ impl RedoxOutput {
.ok_or_else(|| io::Error::other("no crtc compatible with encoder"))?;
Ok(Self {
_consumer: consumer,
handle,
width: w as u32,
height: h as u32,
@ -88,6 +110,7 @@ impl RedoxOutput {
mode,
fb: None,
buffer: None,
vt,
})
}
@ -100,6 +123,11 @@ impl RedoxOutput {
pub fn handle(&self) -> &V2GraphicsHandle {
&self.handle
}
/// Le VT alloué par inputd lors de l'open consumer.
/// Utiliser cette valeur pour `inputd -A <vt>` ou `ControlHandle::activate_vt`.
pub fn vt(&self) -> usize {
self.vt
}
/// Alloue un CpuBackedBuffer ARGB8888 plein écran, l'ajoute comme
/// framebuffer DRM et appelle `set_crtc` pour qu'il soit affiché.
@ -191,6 +219,12 @@ impl RedoxOutput {
}
}
/// Wrapper minimal sur `libredox::call::fpath` (la fn FFI directe n'a pas de
/// signature stable côté `inputd::ConsumerHandle`).
fn libredox_call_fpath(fd: usize, buf: &mut [u8]) -> io::Result<usize> {
libredox::call::fpath(fd, buf).map_err(|e| io::Error::from_raw_os_error(e.errno()))
}
impl Drop for RedoxOutput {
fn drop(&mut self) {
if let Some(fb) = self.fb.take() {