🎉 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

@ -63,18 +63,23 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut output = RedoxOutput::open().map_err(|e| format!("RedoxOutput::open: {e}"))?;
dlog(&format!(
"[paint] display ouvert : {}x{}",
"[paint] display ouvert : {}x{}, VT alloué par inputd = {}",
output.width(),
output.height()
output.height(),
output.vt(),
));
if let Some(v) = &vt {
match Command::new("inputd").arg("-A").arg(v).status() {
Ok(s) if s.success() => dlog(&format!("[paint] inputd -A {v} OK")),
Ok(s) => dlog(&format!("[paint] inputd -A {v} exit {:?}", s)),
Err(e) => dlog(&format!("[paint] inputd -A {v} err: {e}")),
}
}
// Activer notre VT (celui que inputd nous a réellement alloué via fpath)
let our_vt = output.vt();
let r = Command::new("inputd")
.arg("-A")
.arg(our_vt.to_string())
.status();
dlog(&format!("[paint] inputd -A {our_vt} → {:?}", r));
// Laisser inputd-daemon traiter le ControlEvent.
thread::sleep(Duration::from_millis(300));
dlog("[paint] sleep 300ms pour propagation active_vt");
output.take_crtc().map_err(|e| format!("take_crtc: {e}"))?;
dlog("[paint] CRTC pris : on tient l'écran");
@ -82,8 +87,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let w = output.width();
let h = output.height();
// 30 frames espacées d'une seconde pour qu'on voie le dégradé bouger
for t in 0..30u32 {
// 600 frames @ 30 fps = 20 secondes d'animation visible.
// set_crtc à chaque frame (via present_with_takeover) pour reprendre le
// CRTC à fbbootlogd s'il essaie de le récupérer en arrière-plan.
let total_frames: u32 = 600;
let frame_period = Duration::from_millis(33); // ~30 fps
for t in 0..total_frames {
{
let pixels = output.pixels_mut()?;
for y in 0..h {
@ -96,13 +106,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
output
.present_with_takeover()
.map_err(|e| format!("present {t}: {e}"))?;
if t % 5 == 0 {
dlog(&format!("[paint] frame {t}/30 présentée"));
if t % 30 == 0 {
dlog(&format!("[paint] frame {t}/{total_frames} présentée"));
}
thread::sleep(Duration::from_millis(1000));
thread::sleep(frame_period);
}
dlog("[paint] 30 frames terminées, exit propre");
dlog(&format!("[paint] {total_frames} frames terminées, exit propre"));
drop(output); // libère framebuffer + buffer explicitement
thread::sleep(Duration::from_millis(500)); // laisser le serial flush
Ok(())