diff --git a/README.md b/README.md index e90de09..0f0df94 100644 --- a/README.md +++ b/README.md @@ -76,21 +76,40 @@ Ouvre une fenêtre QEMU, boote une mini-VM Redox avec framebuffer, lance le bina À fermer manuellement quand le test est fini. ### Voie B — Image complète + `make qemu` -Pousser le binaire dans une image Redox bootable via redoxfs-fuse : + +Pousser le binaire dans une image Redox bootable via redoxfs-fuse, puis booter +en mode interactif. Étapes : + ```bash -# Monter l'image -sudo modprobe fuse +# 1. Monter l'image (redoxfs reste en foreground tant que monté) mkdir -p /tmp/redox-mnt -redoxfs ~/Projets/Redox/redox-src/build/x86_64/desktop/harddrive.img /tmp/redox-mnt -# Copier le binaire -sudo cp target/x86_64-unknown-redox/release/redox-wl-test-display-backend /tmp/redox-mnt/usr/bin/ -# Démonter +~/Projets/Redox/redox-src/build/fstools/bin/redoxfs \ + ~/Projets/Redox/redox-src/build/x86_64/desktop/harddrive.img \ + /tmp/redox-mnt & +sleep 2 + +# 2. Copier le binaire dans /usr/bin de l'image +cp ~/Projets/Redox/redox-wayland-compositor/crates/redox-wl-test-display-backend/target/x86_64-unknown-redox/release/redox-wl-test-display-backend \ + /tmp/redox-mnt/usr/bin/ + +# 3. Démonter fusermount -u /tmp/redox-mnt -# Booter -cd ~/Projets/Redox/redox-src && make qemu audio=no -# Dans Redox : login user / password root, puis lancer le binaire +rmdir /tmp/redox-mnt + +# 4. Booter (avec clavier FR si besoin, voir hook QEMU_USER_FLAGS dans mk/qemu.mk) +cd ~/Projets/Redox/redox-src +make qemu audio=no QEMU_USER_FLAGS="-k fr" ``` +Une fois Redox bootée : +- **Ctrl+Alt+F2** pour basculer sur le **VT 2 (console texte)**, sans Orbital +- Login : `root` / mot de passe `password` +- Lancer : `redox-wl-test-display-backend` + +> Le binaire écrit aussi sur `/scheme/debug` (serial), donc même si tu lances +> depuis Orbital (VT 3) la sortie sera visible côté host dans le terminal qui +> a lancé `make qemu`. + ## Roadmap Voir `REDOX_COSMIC_XWAYLAND_RS_PLAN.md` pour le plan complet 14 phases / 5 ans. diff --git a/crates/redox-wl-test-display-backend/src/main.rs b/crates/redox-wl-test-display-backend/src/main.rs index 699ef8c..bfbd2f5 100644 --- a/crates/redox-wl-test-display-backend/src/main.rs +++ b/crates/redox-wl-test-display-backend/src/main.rs @@ -21,43 +21,91 @@ //! parce qu'Orbital tient déjà le display. Ce test reste lecture seule à ce //! stade — phase 4 vraie consiste à *prendre la place* d'Orbital. -use std::io; -use std::process::ExitCode; +use std::env; +use std::fs::OpenOptions; +use std::io::{self, Write}; +use std::process::{Command, ExitCode}; +use std::sync::OnceLock; use drm::Device as _; use drm::control::{Device as _, connector::State}; use graphics_ipc::V2GraphicsHandle; use inputd::ConsumerHandle; +/// Sink that mirrors stdout to /scheme/debug so the host serial console +/// captures every diagnostic line without having to be on the active VT. +struct DebugSink(std::sync::Mutex>); + +impl DebugSink { + fn new() -> Self { + let f = OpenOptions::new().write(true).open("/scheme/debug").ok(); + DebugSink(std::sync::Mutex::new(f)) + } + fn writeln(&self, s: &str) { + println!("{s}"); + if let Ok(mut g) = self.0.lock() { + if let Some(f) = g.as_mut() { + let _ = writeln!(f, "{s}"); + } + } + } +} + +fn dlog(s: &str) { + static SINK: OnceLock = OnceLock::new(); + SINK.get_or_init(DebugSink::new).writeln(s); +} + fn run() -> Result<(), Box> { - println!("[disp] Phase 4 display backend test on Redox"); + dlog("[disp] Phase 4 display backend test on Redox"); + + // VT is set by init when we're started as a VT handler (cf Orbital's main.rs). + let vt = env::var("VT").ok(); + dlog(&format!("[disp] VT env = {:?}", vt)); // Step 1: open consumer handle (talks to inputd) let consumer = ConsumerHandle::new_vt().map_err(|e| { format!( - "ConsumerHandle::new_vt failed (expected in headless redoxer): {e} \ - (errno {})", + "ConsumerHandle::new_vt failed: {e} (errno {})", io::Error::last_os_error().raw_os_error().unwrap_or(0) ) })?; - println!("[disp] inputd consumer handle opened"); + dlog("[disp] inputd consumer handle opened"); // Step 2: open display via inputd's fpath redirection - let display_file = consumer.open_display_v2().map_err(|e| { - format!( - "open_display_v2 failed (expected in headless redoxer): {e}" - ) - })?; - println!("[disp] display file opened from inputd path"); + let display_file = consumer + .open_display_v2() + .map_err(|e| format!("open_display_v2 failed: {e}"))?; + dlog("[disp] display file opened from inputd path"); - // Step 3: wrap as V2GraphicsHandle (subset DRM Linux) + // Step 3: register as VT handler with inputd, exactly like Orbital does + // (cf orbital/src/main.rs ~line 43). This must happen AFTER the display is + // opened so inputd considers us authoritative for that VT. + if let Some(v) = &vt { + match Command::new("inputd").arg("-A").arg(v).status() { + Ok(status) if status.success() => { + dlog(&format!("[disp] registered with inputd -A {v}")); + } + Ok(status) => { + dlog(&format!("[disp] inputd -A {v} exit {:?}", status)); + } + Err(e) => { + dlog(&format!("[disp] inputd -A {v} spawn err: {e}")); + } + } + } + + // Step 4: wrap as V2GraphicsHandle (subset DRM Linux) let handle = V2GraphicsHandle::from_file(display_file)?; - println!("[disp] V2GraphicsHandle created"); + dlog("[disp] V2GraphicsHandle created"); - // Step 4: enumerate connectors + // Step 5: enumerate connectors let resources = handle.resource_handles()?; let connectors = resources.connectors(); - println!("[disp] {} connector(s) reported by KMS subset", connectors.len()); + dlog(&format!( + "[disp] {} connector(s) reported by KMS subset", + connectors.len() + )); let mut connected_count = 0; for (i, &conn) in connectors.iter().enumerate() { @@ -65,22 +113,25 @@ fn run() -> Result<(), Box> { Ok(info) => { let state = info.state(); let modes = info.modes(); - println!( + dlog(&format!( "[disp] #{i} connector {:?}: state={:?}, {} mode(s)", conn, state, modes.len() - ); + )); if state == State::Connected { connected_count += 1; if let Some(mode) = modes.first() { let (w, h) = mode.size(); - println!("[disp] first mode: {w}x{h}"); + dlog(&format!("[disp] first mode: {w}x{h}")); } } } Err(e) => { - eprintln!("[disp] #{i} connector {:?}: get_connector failed: {e}", conn); + dlog(&format!( + "[disp] #{i} connector {:?}: get_connector failed: {e}", + conn + )); } } } @@ -88,54 +139,54 @@ fn run() -> Result<(), Box> { if connected_count == 0 { return Err("no connected display found".into()); } - println!("[disp] {connected_count} connected display(s)"); + dlog(&format!("[disp] {connected_count} connected display(s)")); - // Step 5: capabilities probe + // Step 6: capabilities probe use drm::DriverCapability; let dumb = handle.get_driver_capability(DriverCapability::DumbBuffer); let cursor_w = handle.get_driver_capability(DriverCapability::CursorWidth); let cursor_h = handle.get_driver_capability(DriverCapability::CursorHeight); - println!( + dlog(&format!( "[disp] driver caps: dumb_buffer={:?} cursor={}x{}", dumb, cursor_w.map(|v| v.to_string()).unwrap_or_else(|_| "?".into()), cursor_h.map(|v| v.to_string()).unwrap_or_else(|_| "?".into()), - ); + )); - // Step 6: try allocating a CpuBackedBuffer of a small size, paint a pattern, - // and destroy it. This validates the buffer pipeline of graphics-ipc without - // setting it as scanout (which would require a CRTC takeover from Orbital). + // Step 7: allocate a small CpuBackedBuffer, paint a pattern, destroy. use drm::buffer::DrmFourcc; use graphics_ipc::CpuBackedBuffer; let (test_w, test_h) = (64u32, 64u32); let mut buf = CpuBackedBuffer::new(&handle, (test_w, test_h), DrmFourcc::Argb8888, 32)?; - println!( + dlog(&format!( "[disp] CpuBackedBuffer allocated {}x{} ARGB8888 (shadow={})", test_w, test_h, buf.has_shadow_buf() - ); + )); let bytes = buf.shadow_buf(); for (i, b) in bytes.iter_mut().enumerate() { *b = (i & 0xFF) as u8; } buf.sync_rect(0, 0, test_w, test_h); - println!("[disp] painted test pattern + sync_rect"); + dlog("[disp] painted test pattern + sync_rect"); buf.destroy(&handle)?; - println!("[disp] CpuBackedBuffer destroyed"); + dlog("[disp] CpuBackedBuffer destroyed"); Ok(()) } fn main() -> ExitCode { - match run() { - Ok(()) => { - println!("[disp] PASS: display backend pipeline reachable"); - ExitCode::SUCCESS - } - Err(e) => { - eprintln!("[disp] FAIL: {e}"); - ExitCode::FAILURE - } + let res = run(); + match &res { + Ok(()) => dlog("[disp] PASS: display backend pipeline reachable"), + Err(e) => dlog(&format!("[disp] FAIL: {e}")), + } + // sleep briefly so the serial gets a chance to flush before the OS may shut down + std::thread::sleep(std::time::Duration::from_millis(500)); + if res.is_ok() { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE } }