From 753a30757ba62e0f8ca929ff66df72c9382c2b6a Mon Sep 17 00:00:00 2001 From: Votre Nom Date: Sat, 9 May 2026 10:46:20 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Phase=204=20vraie=20valid=C3=A9e?= =?UTF-8?q?=20visuellement=20:=20pixels=20custom=20plein=20=C3=A9cran?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ` 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 `/`) - Binary lit output.vt() puis fait inputd -A 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 --- .gitignore | 2 +- crates/redox-wl-display/Cargo.toml | 1 + crates/redox-wl-display/src/lib.rs | 34 ++++++++ crates/redox-wl-fullscreen-paint/src/main.rs | 40 +++++---- .../poc-shm-pixels-readback.png | Bin 0 -> 701 bytes docs/phase4-display-backend.md | 80 +++++++++++++++++- docs/phase4-victory-1280x800.png | Bin 0 -> 5230 bytes 7 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 crates/redox-wl-poc-pixels/poc-shm-pixels-readback.png create mode 100644 docs/phase4-victory-1280x800.png diff --git a/.gitignore b/.gitignore index 671099d..a0093aa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ Cargo.lock # Generated test outputs *.ppm -*.png +# *.png # whitelist phase4-victory below *.pcap # Editor / OS diff --git a/crates/redox-wl-display/Cargo.toml b/crates/redox-wl-display/Cargo.toml index 37a4423..9748c08 100644 --- a/crates/redox-wl-display/Cargo.toml +++ b/crates/redox-wl-display/Cargo.toml @@ -8,3 +8,4 @@ description = "Display backend wrapper around graphics-ipc + drm subset for Redo drm = "0.15" graphics-ipc = { git = "https://gitlab.redox-os.org/redox-os/base.git" } inputd = { git = "https://gitlab.redox-os.org/redox-os/base.git" } +libredox = "0.1" diff --git a/crates/redox-wl-display/src/lib.rs b/crates/redox-wl-display/src/lib.rs index 9e3c4cb..719139c 100644 --- a/crates/redox-wl-display/src/lib.rs +++ b/crates/redox-wl-display/src/lib.rs @@ -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 ` 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, buffer: Option, + /// VT alloué par inputd lors de l'open consumer. + /// À utiliser pour `inputd -A ` 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 { let consumer = ConsumerHandle::new_vt()?; + + // Découvrir notre VT alloué par inputd via fpath sur le consumer fd. + // Le fpath retourne `/` (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 ` 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 { + 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() { diff --git a/crates/redox-wl-fullscreen-paint/src/main.rs b/crates/redox-wl-fullscreen-paint/src/main.rs index fdf2edb..e8408e5 100644 --- a/crates/redox-wl-fullscreen-paint/src/main.rs +++ b/crates/redox-wl-fullscreen-paint/src/main.rs @@ -63,18 +63,23 @@ fn run() -> Result<(), Box> { 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> { 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> { 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(()) diff --git a/crates/redox-wl-poc-pixels/poc-shm-pixels-readback.png b/crates/redox-wl-poc-pixels/poc-shm-pixels-readback.png new file mode 100644 index 0000000000000000000000000000000000000000..416747ec709972caa905b3af395735ef6fd8ec59 GIT binary patch literal 701 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)K$^3dtTpz6=aiY77hwEes65fI|H(?D56^88c~vxSdwa$T$Bo= z7>o>z%ybP*bqy>+3=OP|OsovdwG9lcfavy%BPbej^HVa@DsgM@ZVRXYYS4h&P?DLO zT3nKtTY#>|+{(}ZV#&XiU$!tXFmCX4aSW-r_4bk@CxfE^%SJhi|Fg~?spDdrIN^4g qb;ax3ciH+oTO&$3M)9BsgFlnMEqFb` ultérieurs +retournaient warning "switch to non-existent VT". + +Bug 2 — **L'env var `VT=N` posée par init n'a aucun lien avec le VT +réellement alloué par inputd**. inputd auto-incrémente `next_vt_id` à +partir de 2 (VT 1 réservé bootlog). On dénombrait : +- VT 1 = `consumer_bootlog` de fbbootlogd +- VT 2 = consumer de fbcond (lancé par `init.initfs.d/20_fbcond.service`) +- VT 3 = notre consumer + +Bug 3 — **Sans le bon VT activé, `set_crtc` est silencieusement no-op** +côté `driver-graphics` (cf `lib.rs:575` : `if *vt == self.active_vt { +self.adapter.set_crtc(...) }`). + +### Le fix + +```rust +pub struct RedoxOutput { + /// CONSUMER GARDÉ EN VIE : si on le drop, inputd retire notre VT et + /// `inputd -A ` dira "non-existent". + _consumer: ConsumerHandle, + // ... + /// VT alloué par inputd, lu via fpath() sur le consumer fd. + /// (cf `inputd::main.rs:271-281` qui retourne `display.scheme/`) + vt: usize, +} +``` + +Et le binaire qui appelle : +```rust +let our_vt = output.vt(); // VT exact alloué par inputd +Command::new("inputd").arg("-A").arg(our_vt.to_string()).status()?; +thread::sleep(Duration::from_millis(300)); // propagation active_vt +output.take_crtc()?; // set_crtc passe la condition +output.present_with_takeover()?; // pixels visibles à l'écran +``` + +### Validation visuelle automatisée + +Méthode reproductible dans le repo : +```bash +qemu-system-x86_64 \ + ... -vga std -display none \ + -monitor unix:/tmp/qmp.sock,server,nowait \ + -serial file:/tmp/qemu-serial.log ... & +sleep 2; printf "sendkey ret\n" | ncat -U /tmp/qmp.sock # passe bootloader +sleep 14; printf "screendump /tmp/frame.ppm\n" | ncat -U /tmp/qmp.sock +magick /tmp/frame.ppm /tmp/frame.png # convert +# Lire le PNG pour voir le rendu +``` + +### Implications pour la suite + +Phase 4 entièrement validée. La fondation `RedoxOutput` est utilisable telle +quelle pour : +- Phase 5 (input backend) : ajouter `consumer.read_events()` pour récupérer + les events keyboard/mouse de inputd +- Phase 6 (surfaces shm composées) : composer plusieurs `wl_shm` buffers + dans le `pixels_mut()` de `RedoxOutput` +- Phase 7 (compositor utilisable) : ajouter focus, stacking, damage + tracking au-dessus + +La piste 1 du plan (consommer events VT) reste utile à terme pour gérer le +hot-switch propre entre VTs (Ctrl+Alt+Fn), mais n'est plus bloquante pour +afficher. ## Prochaine étape : phase 4 vraie diff --git a/docs/phase4-victory-1280x800.png b/docs/phase4-victory-1280x800.png new file mode 100644 index 0000000000000000000000000000000000000000..cc60333267b7010a5d7c117c987108013dc302ca GIT binary patch literal 5230 zcmeAS@N?(olHy`uVBq!ia0y~yU14Ba#1H&(% zP{RubhEf9thF1v;3|2E37{m+a>7DPV+YS4h& zP?DLOT3nKtTY#y@*aTvUk}9hJ1A}m)r;B4q#jUru4f&cK1XvE*9&!HpUwe*HlE8`B zAk`GUIa|{5&&n9T&iVNz^6#H>`X66j{(pbD|Nr{=c7OlZ{@U}mc=w;*j0_A9^f?oN zP7q=N1_lG??=|1+S%3_ivrIe;3=T~UY{1Yv$Y}zS6H5S+P<4I_fLc8^FfcPPOi*Y5 zu?v`ZK^j;kfMLtR2-Yl|0n(sz07$BEB!Jj+96%Zp7(u2wf;B&2kpgMp2HVbrq}d#- zEdp$|0GMsj2zCse90u{#2R5*ike#P~0q7GIdN~XdE%(4-MIhWpq{AQyR5}3|C#0t| zs)ZONfmSeslRXu(z;JUIBxC1;Gc#=p4PrtJTH^46%Nlx>W|$6x6oq@hh2TLtm1bCq z*+J_tNaZnF{)5Xj%o=mFVnQxpN6UY3GJ!M`Fk5AVq+%K^|M9kCM=K`e2KH$Ae*oB; z#o1#TE&m5!`H!XZJ6bUzRZOGh|7iISEYpV4_|FIC)_