🎉🎉🎉 Phase 6.4 — Wayland complet : un client externe affiche ses pixels
Capture preuve : docs/phase6-4-wayland-client-surface.png — pattern ARGB 320x240 écrit par un binaire client Wayland externe affiché par notre compositor sur le framebuffer Redox dans QEMU. Crates ajoutés : redox-wl-wayland-frontend (lib, ~430 lignes) : - WaylandFrontend struct avec SurfaceRegistry intégré + Display<Self> + ListeningSocket - bind_absolute(path), accept_pending_clients(), dispatch_clients(), flush_clients(), notify_frame_done() - ShmPool : mmap + munmap on drop - BufferData : Arc<Mutex<ShmPool>> + offset/w/h/stride/format - SurfaceData : Arc<...> qui contient SurfaceId + pending_buffer + pending_frame_callbacks - Dispatch impls : wl_compositor v5, wl_shm v1 (advertise ARGB+XRGB), wl_shm_pool, wl_buffer, wl_surface (attach/damage/commit/frame/destroy), wl_callback, wl_region (no-op) Sémantique commit : copy-on-commit (lit pixels via mmap, copie dans SurfaceBuffer owned). Plus simple que de garder le mmap vivant. Au commit, raise auto la surface (politique simple). redox-wl-compositor (bin, ~150 lignes) : - ouvre RedoxOutput + InputBackend partagé - bind WaylandFrontend sur /tmp/redox-wl-comp.sock - export WAYLAND_DISPLAY env var - boucle main 30 fps : accept clients → dispatch → input → render → notify_frame_done → flush - Esc = exit propre redox-wl-test-client-shm (bin, ~170 lignes) : - attente du socket compositor (50 retries × 100ms) - Connection::from_backend après UnixStream::connect - Dispatch handlers minimal pour wl_registry, compositor, shm, pool, buffer, surface - shm_open + ftruncate + mmap + pattern ARGB déterministe (orange + bandes diagonales) - shm.create_pool(fd) + pool.create_buffer + compositor.create_surface - surface.attach + damage_buffer + commit - reste connecté 25s pour qu'on capture l'écran Validation runtime : compositor en init VT=2, client lancé en parallèle via 30_console. Logs serial montrent toute la séquence : [client] globals : compositor=true shm=true [client] shm créé, peint 320x240 ARGB [client] surface attach + damage + commit envoyés [comp] tick=30 surfaces=1 elapsed=1.2s [comp] tick=510 surfaces=1 elapsed=20.7s ← surface persiste 20+s PNG capturée à T+12s montre la surface du client visible sur le framebuffer. Position (0,0) parce que xdg-shell absent (placement absent). Reportable phase 7. Image Redox restaurée à boot Orbital normal. docs/phase6-4-wayland-frontend.md : compte-rendu complet, archi, sémantique commit, limitations, plan phase 7. Phase 6 entièrement close. Le compositor naissant fonctionne avec un vrai client Wayland externe sur Redox. Leyoda 2026 – GPLv3
This commit is contained in:
parent
509aae7769
commit
8a897d975d
8 changed files with 1098 additions and 0 deletions
192
crates/redox-wl-compositor/src/main.rs
Normal file
192
crates/redox-wl-compositor/src/main.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
//! Phase 6.4 — Compositor binaire complet.
|
||||
//!
|
||||
//! Boucle main d'un mini compositor Wayland :
|
||||
//! 1. Ouvre RedoxOutput (display) et take CRTC
|
||||
//! 2. Ouvre InputBackend partageant le ConsumerHandle
|
||||
//! 3. Bind un ListeningSocket Wayland sur `/tmp/redox-wl-comp.sock`
|
||||
//! 4. Loop :
|
||||
//! - accept_pending_clients()
|
||||
//! - dispatch_clients() (lit les requests, appelle nos Dispatch impls)
|
||||
//! - poll() input → log + raise on click éventuel
|
||||
//! - clear bg + compose_into(output) + present
|
||||
//! - notify_frame_done() pour les wl_callback en attente
|
||||
//! - flush_clients()
|
||||
//! - sleep ~16ms
|
||||
//!
|
||||
//! Tourne 60 secondes max, exit propre.
|
||||
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, ExitCode};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use redox_wl_compositor_core::Framebuffer;
|
||||
use redox_wl_display::RedoxOutput;
|
||||
use redox_wl_input::{InputBackend, InputEvent};
|
||||
use redox_wl_wayland_frontend::WaylandFrontend;
|
||||
|
||||
const SOCKET_PATH: &str = "/tmp/redox-wl-comp.sock";
|
||||
const BG_COLOR: u32 = 0xFF101820;
|
||||
|
||||
struct DebugSink(Mutex<Option<std::fs::File>>);
|
||||
impl DebugSink {
|
||||
fn new() -> Self {
|
||||
Self(Mutex::new(
|
||||
OpenOptions::new().write(true).open("/scheme/debug").ok(),
|
||||
))
|
||||
}
|
||||
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<DebugSink> = OnceLock::new();
|
||||
SINK.get_or_init(DebugSink::new).writeln(s);
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dlog("[comp] Phase 6.4 — compositor Wayland démarrage");
|
||||
|
||||
// Display
|
||||
let mut output = RedoxOutput::open()?;
|
||||
let our_vt = output.vt();
|
||||
let fb_w = output.width();
|
||||
let fb_h = output.height();
|
||||
dlog(&format!("[comp] display {fb_w}x{fb_h}, VT={our_vt}"));
|
||||
|
||||
let _ = Command::new("inputd")
|
||||
.arg("-A")
|
||||
.arg(our_vt.to_string())
|
||||
.status();
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
output.take_crtc()?;
|
||||
dlog("[comp] CRTC pris");
|
||||
|
||||
// Clear initial → fond bleu nuit pour signaler "compositor up"
|
||||
{
|
||||
let pixels = <RedoxOutput as Framebuffer>::pixels_mut(&mut output);
|
||||
for p in pixels.iter_mut() {
|
||||
*p = BG_COLOR;
|
||||
}
|
||||
}
|
||||
output.present_with_takeover()?;
|
||||
|
||||
// Input
|
||||
let input = InputBackend::new(output.consumer());
|
||||
|
||||
// Wayland frontend
|
||||
let socket_path = PathBuf::from(SOCKET_PATH);
|
||||
let mut frontend = WaylandFrontend::bind_absolute(&socket_path)?;
|
||||
dlog(&format!("[comp] Wayland socket : {SOCKET_PATH}"));
|
||||
|
||||
// Exporter WAYLAND_DISPLAY pour les clients lancés par l'OS qui regarderaient
|
||||
// l'env. (Notre client de test va connecter explicitement au path.)
|
||||
unsafe {
|
||||
env::set_var("WAYLAND_DISPLAY", SOCKET_PATH);
|
||||
}
|
||||
|
||||
// Boucle principale
|
||||
let start = Instant::now();
|
||||
let total = Duration::from_secs(60);
|
||||
let frame_period = Duration::from_millis(33); // ~30 fps
|
||||
let mut last_frame = Instant::now();
|
||||
let mut tick: u32 = 0;
|
||||
|
||||
while start.elapsed() < total {
|
||||
tick = tick.wrapping_add(1);
|
||||
|
||||
// 1. Accepter nouveaux clients Wayland
|
||||
if let Err(e) = frontend.accept_pending_clients() {
|
||||
dlog(&format!("[comp] accept err: {e}"));
|
||||
}
|
||||
|
||||
// 2. Dispatch des requêtes Wayland en attente
|
||||
if let Err(e) = frontend.dispatch_clients() {
|
||||
dlog(&format!("[comp] dispatch err: {e}"));
|
||||
}
|
||||
|
||||
// 3. Input
|
||||
if let Ok(events) = input.poll() {
|
||||
for ev in events {
|
||||
match ev {
|
||||
InputEvent::Key {
|
||||
scancode, pressed, ..
|
||||
} if pressed && scancode == 0x01 => {
|
||||
// Esc → exit
|
||||
dlog("[comp] Esc → exit");
|
||||
let _ = frontend.flush_clients();
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
return Ok(());
|
||||
}
|
||||
InputEvent::Quit => {
|
||||
dlog("[comp] Quit reçu");
|
||||
let _ = frontend.flush_clients();
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Render
|
||||
let nb = frontend.registry.len();
|
||||
// Recompose tout à chaque frame pour 6.4 (pas de damage tracking)
|
||||
{
|
||||
let pixels = <RedoxOutput as Framebuffer>::pixels_mut(&mut output);
|
||||
for p in pixels.iter_mut() {
|
||||
*p = BG_COLOR;
|
||||
}
|
||||
}
|
||||
frontend.registry.compose_into(&mut output);
|
||||
if let Err(e) = output.present_with_takeover() {
|
||||
dlog(&format!("[comp] present err: {e}"));
|
||||
}
|
||||
|
||||
// 5. Frame callbacks done après le present
|
||||
let elapsed_ms = last_frame.elapsed().as_millis() as u32;
|
||||
last_frame = Instant::now();
|
||||
frontend.notify_frame_done(elapsed_ms);
|
||||
|
||||
// 6. Flush vers les clients
|
||||
if let Err(e) = frontend.flush_clients() {
|
||||
dlog(&format!("[comp] flush err: {e}"));
|
||||
}
|
||||
|
||||
// Log occasionnel
|
||||
if tick % 30 == 0 {
|
||||
dlog(&format!(
|
||||
"[comp] tick={tick} surfaces={nb} elapsed={:.1}s",
|
||||
start.elapsed().as_secs_f32()
|
||||
));
|
||||
}
|
||||
|
||||
thread::sleep(frame_period);
|
||||
}
|
||||
|
||||
dlog("[comp] timeout 60s atteint, exit");
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
dlog("[comp] PASS");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
dlog(&format!("[comp] FAIL: {e}"));
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue