Sprint 0 — industrialisation : CI, tracing, tests xdg-shell, GPLv3
5 livrables d'industrialisation posés avant la phase 13 (client réel). - .gitlab-ci.yml : pipeline minimal (fmt-check + tests core + tests frontend nightly). Pas de cross-compile Redox dans le MVP. - rustfmt.toml + fmt.sh : config formatter racine + boucle sur les 23 crates ; fmt sweep appliqué (d'où les diffs cosmétiques). - tracing dans wayland-frontend : 22 println/eprintln remplacés par debug/info/warn/error selon sévérité. Préfixe "[frontend]" supprimé (le target tracing le fournit). - tracing-subscriber dans le compositor : TeeWriter écrit sur stdout + /scheme/debug, filtre via RUST_LOG (défaut info). DebugSink/dlog supprimés. - 15 tests unitaires xdg-shell après extraction de 2 helpers libres (clamp_to_min_max + should_throttle_configure). Couvre compute_resize_geom, contraintes min/max et throttling configure. - LICENSE (GPLv3 texte officiel FSF) + .editorconfig. Total tests : 27 → 42 automatisés (compositor-core + frontend). Leyoda 2026 – GPLv3
This commit is contained in:
parent
2150f31d82
commit
8795f39f08
30 changed files with 1373 additions and 298 deletions
|
|
@ -14,16 +14,27 @@
|
|||
//! - sleep ~16ms
|
||||
//!
|
||||
//! Tourne 60 secondes max, exit propre.
|
||||
//!
|
||||
//! Logs : `tracing` + `tracing-subscriber`. Configurable via `RUST_LOG`
|
||||
//! (cf. `EnvFilter::from_default_env`). Par défaut : `info`. Sortie
|
||||
//! tee'd sur stdout ET sur `/scheme/debug` (= serial sous Redox/QEMU).
|
||||
//! Exemples :
|
||||
//! RUST_LOG=info → events lifecycle (focus, drag, garbage_collect)
|
||||
//! RUST_LOG=debug → idem + détails protocole (set_min_size, hit_test…)
|
||||
//! RUST_LOG=warn → uniquement anomalies (refused, rejected, mismatch)
|
||||
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, ExitCode};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::{fmt::MakeWriter, EnvFilter};
|
||||
|
||||
use redox_wl_compositor_core::Framebuffer;
|
||||
use redox_wl_display::RedoxOutput;
|
||||
use redox_wl_input::{InputBackend, InputEvent};
|
||||
|
|
@ -32,36 +43,77 @@ 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 {
|
||||
/// MakeWriter qui tee chaque ligne de log vers stdout ET, si disponible,
|
||||
/// vers `/scheme/debug` (serial console côté host quand on tourne sous
|
||||
/// `make qemu`).
|
||||
#[derive(Clone)]
|
||||
struct TeeWriter {
|
||||
debug_scheme: Arc<Mutex<Option<File>>>,
|
||||
}
|
||||
|
||||
impl TeeWriter {
|
||||
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}");
|
||||
}
|
||||
let f = OpenOptions::new().write(true).open("/scheme/debug").ok();
|
||||
Self {
|
||||
debug_scheme: Arc::new(Mutex::new(f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
fn dlog(s: &str) {
|
||||
static SINK: OnceLock<DebugSink> = OnceLock::new();
|
||||
SINK.get_or_init(DebugSink::new).writeln(s);
|
||||
|
||||
struct TeeWriterGuard {
|
||||
debug_scheme: Arc<Mutex<Option<File>>>,
|
||||
}
|
||||
|
||||
impl Write for TeeWriterGuard {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let _ = io::stdout().write_all(buf);
|
||||
if let Ok(mut g) = self.debug_scheme.lock() {
|
||||
if let Some(f) = g.as_mut() {
|
||||
let _ = f.write_all(buf);
|
||||
}
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
let _ = io::stdout().flush();
|
||||
if let Ok(mut g) = self.debug_scheme.lock() {
|
||||
if let Some(f) = g.as_mut() {
|
||||
let _ = f.flush();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MakeWriter<'a> for TeeWriter {
|
||||
type Writer = TeeWriterGuard;
|
||||
fn make_writer(&'a self) -> Self::Writer {
|
||||
TeeWriterGuard {
|
||||
debug_scheme: Arc::clone(&self.debug_scheme),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(filter)
|
||||
.with_writer(TeeWriter::new())
|
||||
.with_target(true)
|
||||
.with_ansi(false)
|
||||
.without_time()
|
||||
.init();
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dlog("[comp] Phase 6.4 — compositor Wayland démarrage");
|
||||
info!("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}"));
|
||||
info!("display {fb_w}x{fb_h}, VT={our_vt}");
|
||||
|
||||
let _ = Command::new("inputd")
|
||||
.arg("-A")
|
||||
|
|
@ -69,7 +121,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.status();
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
output.take_crtc()?;
|
||||
dlog("[comp] CRTC pris");
|
||||
info!("CRTC pris");
|
||||
|
||||
// Clear initial → fond bleu nuit pour signaler "compositor up"
|
||||
{
|
||||
|
|
@ -88,7 +140,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut frontend = WaylandFrontend::bind_absolute(&socket_path)?;
|
||||
// Phase 7.3 : curseur visible dès le démarrage, placé au centre du fb.
|
||||
frontend.set_cursor_initial_position((fb_w as i32) / 2, (fb_h as i32) / 2);
|
||||
dlog(&format!("[comp] Wayland socket : {SOCKET_PATH}"));
|
||||
info!("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.)
|
||||
|
|
@ -108,12 +160,12 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// 1. Accepter nouveaux clients Wayland
|
||||
if let Err(e) = frontend.accept_pending_clients() {
|
||||
dlog(&format!("[comp] accept err: {e}"));
|
||||
error!("accept err: {e}");
|
||||
}
|
||||
|
||||
// 2. Dispatch des requêtes Wayland en attente
|
||||
if let Err(e) = frontend.dispatch_clients() {
|
||||
dlog(&format!("[comp] dispatch err: {e}"));
|
||||
error!("dispatch err: {e}");
|
||||
}
|
||||
|
||||
// 2.5. Phase 7.6 : nettoyer les surfaces des clients déconnectés.
|
||||
|
|
@ -124,7 +176,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// 3. Input
|
||||
if let Ok(events) = input.poll() {
|
||||
if !events.is_empty() {
|
||||
dlog(&format!("[comp] {} input events from inputd", events.len()));
|
||||
tracing::debug!("{} input events from inputd", events.len());
|
||||
}
|
||||
for ev in events {
|
||||
match &ev {
|
||||
|
|
@ -132,13 +184,13 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
scancode, pressed, ..
|
||||
} if *pressed && *scancode == 0x01 => {
|
||||
// Esc → exit
|
||||
dlog("[comp] Esc → exit");
|
||||
info!("Esc → exit");
|
||||
let _ = frontend.flush_clients();
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
return Ok(());
|
||||
}
|
||||
InputEvent::Quit => {
|
||||
dlog("[comp] Quit reçu");
|
||||
info!("Quit reçu");
|
||||
let _ = frontend.flush_clients();
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
return Ok(());
|
||||
|
|
@ -164,7 +216,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// Phase 7.3 : curseur software par-dessus la composition.
|
||||
frontend.draw_cursor(&mut output);
|
||||
if let Err(e) = output.present_with_takeover() {
|
||||
dlog(&format!("[comp] present err: {e}"));
|
||||
error!("present err: {e}");
|
||||
}
|
||||
|
||||
// 5. Frame callbacks done après le present
|
||||
|
|
@ -174,33 +226,34 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// 6. Flush vers les clients
|
||||
if let Err(e) = frontend.flush_clients() {
|
||||
dlog(&format!("[comp] flush err: {e}"));
|
||||
error!("flush err: {e}");
|
||||
}
|
||||
|
||||
// Log occasionnel
|
||||
if tick % 30 == 0 {
|
||||
dlog(&format!(
|
||||
"[comp] tick={tick} surfaces={nb} elapsed={:.1}s",
|
||||
info!(
|
||||
"tick={tick} surfaces={nb} elapsed={:.1}s",
|
||||
start.elapsed().as_secs_f32()
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
thread::sleep(frame_period);
|
||||
}
|
||||
|
||||
dlog("[comp] timeout atteint, exit");
|
||||
info!("timeout atteint, exit");
|
||||
let _ = std::fs::remove_file(SOCKET_PATH);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
init_tracing();
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
dlog("[comp] PASS");
|
||||
info!("PASS");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
dlog(&format!("[comp] FAIL: {e}"));
|
||||
error!("FAIL: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue