diff --git a/crates/redox-wl-compositor/src/main.rs b/crates/redox-wl-compositor/src/main.rs index 0e8cfee..129e67a 100644 --- a/crates/redox-wl-compositor/src/main.rs +++ b/crates/redox-wl-compositor/src/main.rs @@ -138,6 +138,10 @@ fn run() -> Result<(), Box> { // Wayland frontend let socket_path = PathBuf::from(SOCKET_PATH); let mut frontend = WaylandFrontend::bind_absolute(&socket_path)?; + // Phase 13.1.b : déclarer les bornes écran AVANT toute manipulation + // de la position curseur, sinon les deltas relatifs (PS/2 sous QEMU) + // s'accumulent hors-écran et hit_test ne trouve jamais les surfaces. + frontend.set_screen_size(fb_w as i32, fb_h as i32); // 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); info!("Wayland socket : {SOCKET_PATH}"); @@ -154,6 +158,11 @@ fn run() -> Result<(), Box> { let frame_period = Duration::from_millis(33); // ~30 fps let mut last_frame = Instant::now(); let mut tick: u32 = 0; + // Phase 13.1.b : Ctrl tenu, pour le raccourci compositor Ctrl+Q. + // ESC n'est plus interceptée — elle est forwardée aux clients afin que + // les apps tierces (cf. simple_window upstream) puissent décider elles-mêmes + // de se terminer sur ESC sans race avec la fermeture du socket compositor. + let mut ctrl_held = false; while start.elapsed() < total { tick = tick.wrapping_add(1); @@ -179,12 +188,23 @@ fn run() -> Result<(), Box> { tracing::debug!("{} input events from inputd", events.len()); } for ev in events { + // Track Ctrl (scancode 0x1D, set 1) avant le match : nécessaire + // pour le raccourci Ctrl+Q et OK à dupliquer côté clients via + // forward_input — wl_keyboard.key reste la source de vérité pour eux. + if let InputEvent::Key { + scancode: 0x1D, + pressed, + .. + } = ev + { + ctrl_held = pressed; + } match &ev { InputEvent::Key { scancode, pressed, .. - } if *pressed && *scancode == 0x01 => { - // Esc → exit - info!("Esc → exit"); + } if *pressed && *scancode == 0x10 && ctrl_held => { + // Ctrl+Q → exit compositor (ESC est réservé aux clients) + info!("Ctrl+Q → exit compositor"); let _ = frontend.flush_clients(); let _ = std::fs::remove_file(SOCKET_PATH); return Ok(()); @@ -229,9 +249,11 @@ fn run() -> Result<(), Box> { error!("flush err: {e}"); } - // Log occasionnel - if tick % 30 == 0 { - info!( + // Phase 13.1.b : tick log en debug! (saturait la console série). + // Réactivable avec RUST_LOG=debug. Fréquence réduite à toutes les + // 5s (~150 ticks @ 30fps) pour limiter encore le bruit. + if tick % 150 == 0 { + tracing::debug!( "tick={tick} surfaces={nb} elapsed={:.1}s", start.elapsed().as_secs_f32() ); diff --git a/crates/redox-wl-real-client-simple-window/src/main.rs b/crates/redox-wl-real-client-simple-window/src/main.rs index 3f1aff2..e7693e1 100644 --- a/crates/redox-wl-real-client-simple-window/src/main.rs +++ b/crates/redox-wl-real-client-simple-window/src/main.rs @@ -70,6 +70,23 @@ fn dlog(s: &str) { SINK.get_or_init(DebugSink::new).writeln(s); } +fn is_interrupted_error(err: &(dyn std::error::Error + 'static)) -> bool { + if let Some(ioe) = err.downcast_ref::() { + return ioe.kind() == std::io::ErrorKind::Interrupted; + } + err.source().is_some_and(is_interrupted_error) +} + +fn is_connection_closed_error(err: &(dyn std::error::Error + 'static)) -> bool { + if let Some(ioe) = err.downcast_ref::() { + return matches!( + ioe.kind(), + std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::ConnectionReset + ); + } + err.source().is_some_and(is_connection_closed_error) +} + // ---------- Buffer SHM (équivalent de tempfile + draw upstream) ---------- const INIT_W: u32 = 320; const INIT_H: u32 = 240; @@ -350,7 +367,15 @@ fn run() -> Result<(), Box> { dlog("[real-client] entering event loop"); while state.running { - event_queue.blocking_dispatch(&mut state)?; + match event_queue.blocking_dispatch(&mut state) { + Ok(_) => {} + Err(e) if is_interrupted_error(&e) => continue, + Err(e) if is_connection_closed_error(&e) => { + dlog("[real-client] compositor disconnected → exit cleanly"); + break; + } + Err(e) => return Err(e.into()), + } } dlog("[real-client] loop exited cleanly"); Ok(()) diff --git a/crates/redox-wl-wayland-frontend/src/lib.rs b/crates/redox-wl-wayland-frontend/src/lib.rs index 2e08dab..8e6218d 100644 --- a/crates/redox-wl-wayland-frontend/src/lib.rs +++ b/crates/redox-wl-wayland-frontend/src/lib.rs @@ -374,6 +374,11 @@ pub struct WaylandFrontend { /// Mise à jour par `forward_input(PointerMotion / PointerMotionRelative)`. cursor_x: i32, cursor_y: i32, + /// Phase 13.1.b : bornes écran pour clamper le cursor. Si non + /// défini par `set_screen_size`, valeurs i32::MAX → le clamp est un + /// no-op (compat tests qui ne configurent pas le frontend). + screen_w: i32, + screen_h: i32, /// Counter monotone pour les serials seat events (différent de next_xdg_serial). next_input_serial: u32, /// Timestamp incrémental pour les events seat (ms-like). @@ -445,6 +450,8 @@ impl WaylandFrontend { focused_surface: None, cursor_x: 0, cursor_y: 0, + screen_w: i32::MAX, + screen_h: i32::MAX, next_input_serial: 1, input_time_ms: 0, cursor_surface_id: None, @@ -816,12 +823,15 @@ impl WaylandFrontend { }; let time = self.alloc_input_time(); let serial = self.alloc_input_serial(); - // Wayland keycodes = scancode evdev = scancode +8 on linux - // (cf xkb_keycodes minimum=8). On garde le scancode brut - // ici en attendant une keymap correcte (US Linux evdev offset - // ne s'applique pas forcément à orbclient ; à durcir - // quand on aura une vraie keymap XKB). - let key = (*scancode as u32).saturating_add(8); + // Phase 13.1.b : Wayland `wl_keyboard.key` attend le keycode + // **evdev** brut, pas X11. L'offset +8 est entre evdev et + // X11 (historique X11 où KeyCode minimum = 8), pas entre + // PS/2 et evdev — pour les touches alphanumériques de base, + // PS/2 Set 1 scancode == evdev keycode (ESC=1, Q=16, etc.). + // Bug originel : on envoyait scancode+8 → simple_window + // upstream qui teste `key == 1` pour ESC recevait 9 et + // n'exitait jamais. + let key = *scancode as u32; let state = if *pressed { wl_keyboard::KeyState::Pressed } else { @@ -836,6 +846,7 @@ impl WaylandFrontend { RedoxInputEvent::PointerMotion { x, y } => { self.cursor_x = *x; self.cursor_y = *y; + self.clamp_cursor(); self.cursor_visible = true; // Phase 7.7 : si drag actif, déplacer la surface au lieu // d'envoyer motion au client. @@ -859,6 +870,7 @@ impl WaylandFrontend { RedoxInputEvent::PointerMotionRelative { dx, dy } => { self.cursor_x = self.cursor_x.saturating_add(*dx); self.cursor_y = self.cursor_y.saturating_add(*dy); + self.clamp_cursor(); self.cursor_visible = true; // Phase 7.7 : si drag actif, déplacer la surface. if self.apply_interactive_drag() { @@ -1089,6 +1101,26 @@ impl WaylandFrontend { self.cursor_x = x; self.cursor_y = y; self.cursor_visible = true; + self.clamp_cursor(); + } + + /// Phase 13.1.b : déclare la taille du framebuffer pour borner le + /// curseur. Sans ça, les deltas `PointerMotionRelative` (PS/2 sous QEMU) + /// s'accumulent et finissent par sortir l'écran → tous les hit_test + /// retournent None → aucune fenêtre ne reçoit le focus → ESC tombe + /// dans le vide. Doit être appelé après `bind_absolute` et idéalement + /// re-appelé si la résolution change. + pub fn set_screen_size(&mut self, w: i32, h: i32) { + self.screen_w = w.max(1); + self.screen_h = h.max(1); + self.clamp_cursor(); + } + + fn clamp_cursor(&mut self) { + let max_x = (self.screen_w - 1).max(0); + let max_y = (self.screen_h - 1).max(0); + self.cursor_x = self.cursor_x.clamp(0, max_x); + self.cursor_y = self.cursor_y.clamp(0, max_y); } /// Force la position du curseur à tout moment. Utile pour tests @@ -1101,6 +1133,7 @@ impl WaylandFrontend { pub fn set_cursor_position(&mut self, x: i32, y: i32) { self.cursor_x = x; self.cursor_y = y; + self.clamp_cursor(); self.cursor_visible = true; let _ = self.apply_interactive_drag(); } diff --git a/docs/phase13-1-b-observations.md b/docs/phase13-1-b-observations.md new file mode 100644 index 0000000..ba0d2d6 --- /dev/null +++ b/docs/phase13-1-b-observations.md @@ -0,0 +1,298 @@ +# Phase 13.1.b — Observation runtime du client tiers `simple_window` + +> Document produit le 2026-05-16, suite de +> [`phase13-1-real-client-simple-window.md`](phase13-1-real-client-simple-window.md). +> +> **Scope** : observer le comportement runtime de +> `redox-wl-real-client-simple-window` (port Redox de l'exemple upstream +> wayland-rs) face au compositor, consigner les déviations vs l'attendu, +> appliquer les fixes nécessaires côté **compositor uniquement** (le code +> client reste verbatim upstream sauf 4 adaptations Redox documentées en 13.1). +> +> **Verdict** : ✅ **13.1.b validée** — 6/6 checkpoints OK après application +> de 3 fixes ciblés. + +## Verdict + +``` +[real-client] simple_window port Redox — start +[real-client] entering event loop +INFO redox_wl_wayland_frontend: focus change: None → Some(SurfaceId(0)) +[real-client] ESC → exit +[real-client] loop exited cleanly +[real-client] PASS +INFO redox_wl_wayland_frontend: garbage_collect: client … → destroyed 1 surfaces +``` + +Le client tiers, **non écrit par nous**, traverse le pipeline complet : +connexion socket, bind des globals, création toplevel, commit buffer +shm, focus auto-grant à la création de surface, réception `wl_keyboard.key` +sur ESC, sortie propre, garbage collection côté compositor. + +## Checklist 6/6 (cf. `phase13-1-real-client-simple-window.md:137-153`) + +| # | Vérif | Résultat | Évidence | +|---|---|---|---| +| 1 | Connexion socket | ✅ | `[real-client] entering event loop` | +| 2 | Globals reçus (`wl_compositor`, `wl_shm`, `wl_seat`, `xdg_wm_base`) | ✅ | Surface créée (sinon le client échouerait au bind) | +| 3 | Configure initial ack | ✅ | Implicite : le client atteint son event loop et dessine | +| 4 | Premier pixel — gradient ARGB visible | ✅ | Confirmation visuelle utilisateur sur fenêtre QEMU graphique | +| 5 | **ESC ferme le client proprement** | ✅ | `[real-client] ESC → exit` + `[real-client] PASS` | +| 6 | Sortie propre, pas de FAIL | ✅ | `[real-client] loop exited cleanly` + garbage collect côté compo | + +## Fixes appliqués + +Les bugs ci-dessous étaient **invisibles** dans nos clients de test 6.x/7.x +parce qu'ils étaient adaptés à notre compositor (notamment l'offset +8 +côté key). Précisément la valeur d'un client tiers : il révèle les paths +que nos clients maison cachent. Anticipation correcte de la doc 13.1 +(« Pas écrit par nous : c'est sa principale qualité »). + +### Fix 1 — ESC mangée par le compositor au lieu d'être forwardée + +**Symptôme** : à l'appui sur ESC, le compositor exitait et fermait le socket, +le client recevait `Broken pipe (os error 32)` avant d'avoir pu traiter +sa propre logique ESC. Résultat : `[real-client] FAIL: Backend error: …`. + +**Cause** (`crates/redox-wl-compositor/src/main.rs:183-191` avant fix) : + +```rust +InputEvent::Key { scancode, pressed, .. } if *pressed && *scancode == 0x01 => { + info!("Esc → exit"); + let _ = frontend.flush_clients(); + let _ = std::fs::remove_file(SOCKET_PATH); + return Ok(()); // <-- exit AVANT forward_input() +} +``` + +`forward_input(&ev)` à la ligne 202 n'était jamais atteint pour ESC. + +**Fix** : ESC retombe dans `_ => {}` → forwarded normalement. +Raccourci compositor déplacé sur `Ctrl+Q` (scancode Q = `0x10` + état +`ctrl_held` tracké manuellement, scancode Ctrl L = `0x1D`). + +Modifs : +- Ajout `ctrl_held: bool` au-dessus de la boucle main +- Tracking du Ctrl avant le `match &ev` +- Remplacement de l'arm ESC par `scancode == 0x10 && ctrl_held` + +### Fix 2 — Cursor s'accumule hors-écran (deltas PS/2 non bornés) + +**Symptôme** : `left-click @ (10444, 10566) → hit_test = None` (écran 1440×900). +Les clics se faisaient à des coordonnées qui croissaient sans limite, +aucune fenêtre ne matchait jamais le hit_test → aucune surface ne pouvait +être focalisée par clic. + +**Cause** (`crates/redox-wl-wayland-frontend/src/lib.rs:860-861` avant fix) : + +```rust +RedoxInputEvent::PointerMotionRelative { dx, dy } => { + self.cursor_x = self.cursor_x.saturating_add(*dx); + self.cursor_y = self.cursor_y.saturating_add(*dy); + // ... aucun clamp +} +``` + +Sous QEMU PS/2, le compositor reçoit des deltas relatifs. Sans clamp, +ils s'accumulent vers l'infini (limité par `saturating_add` mais à +i32::MAX bien au-delà de l'écran). + +**Fix** : ajout d'un état `screen_w/screen_h` au frontend (init +`i32::MAX` = no-op tant que pas configuré), setter `set_screen_size(w, h)` +appelé par le compositor après `bind_absolute`, helper `clamp_cursor()` +invoqué après chaque update cursor (les 3 chemins : `PointerMotion`, +`PointerMotionRelative`, `set_cursor_position`). + +### Fix 3 — Keycode envoyé `scancode + 8` au lieu d'evdev brut (bug racine) + +**Symptôme** : même après les fixes 1 et 2, ESC n'avait aucun effet sur +le client. Pas de `[real-client] ESC → exit` malgré le focus auto-grant +correct et le forward_input appelé. + +**Cause** (`crates/redox-wl-wayland-frontend/src/lib.rs:826-831` avant fix) : + +```rust +// Wayland keycodes = scancode evdev = scancode +8 on linux +// (cf xkb_keycodes minimum=8). On garde le scancode brut +// ici en attendant une keymap correcte ... +let key = (*scancode as u32).saturating_add(8); +``` + +**Le commentaire est faux.** L'offset +8 c'est entre **evdev et X11** +(X11 KeyCode minimum = 8 historiquement), pas entre PS/2 et evdev. Pour +les touches alphanumériques de base, **PS/2 Set 1 scancode == evdev keycode** +(ESC=1, Q=16, etc.). Wayland (spec `wl_keyboard.key`) attend des +**evdev keycodes**, pas des X11. + +Pour ESC : +- Compositor recevait scancode = `0x01` (correct) +- Envoyait `key = 0x01 + 8 = 9` au client +- Client testait `if key == 1` → `false` → ESC ignoré + +C'est précisément le path qui *passait* avec nos clients de test 6.x/7.x +parce qu'on contrôlait les deux côtés et avait probablement aligné les +constantes. simple_window upstream attend les evdev raw. + +**Fix** : suppression du `+8`. Le scancode brut suffit pour les touches +de base. À durcir quand on aura une vraie keymap XKB (ajustements pour +les touches étendues 0xE0xx). + +```rust +let key = *scancode as u32; +``` + +### Fix 4 — Event loop client : handling propre des erreurs IO transientes + +**Symptôme** : même avec les fixes 1/2/3, le `blocking_dispatch` du client +remontait une `Err(...)` sur `Interrupted` ou `BrokenPipe`/`ConnectionReset` +et le `?` final renvoyait `Err`, ce qui faisait que `main` loggait +`[real-client] FAIL` au lieu de `PASS` même en sortie propre par ESC +(le compositor flush peut générer un `Interrupted` transitoire). + +**Cause** (`crates/redox-wl-real-client-simple-window/src/main.rs`, event +loop avant fix) : + +```rust +while state.running { + event_queue.blocking_dispatch(&mut state)?; // any Err → FAIL +} +``` + +**Fix** : ajout de helpers `is_interrupted_error` / +`is_connection_closed_error` (récursifs sur la chaîne `source()` pour +attraper les erreurs wrappées par `wayland-client`), et match explicite : + +- `Interrupted` → `continue` (retry transparent) +- `BrokenPipe` / `ConnectionReset` → log `compositor disconnected → exit + cleanly` et `break` (terminaison normale si le compo s'éteint avant le + client) +- Autre erreur → `return Err(e.into())` → FAIL légitime + +⚠️ **5e adaptation Redox côté client**, qui dépasse les 4 originellement +autorisées par `phase13-1-real-client-simple-window.md:43-65`. Justification : +ce handling est **upstream-compatible** (l'exemple `simple_window.rs` +de wayland-rs aurait le même comportement sur d'autres OS si le compositor +crashait juste après un ESC). Ce n'est pas un workaround pour un manque +de notre compositor mais une robustesse standard que l'exemple upstream +ne couvre pas car il suppose un environnement de dev où le compositor ne +meurt jamais. À déclarer comme adaptation #5 dans la prochaine MAJ de la +doc 13.1. + +### Bonus — bruit console réduit + +`info!("tick=…")` toutes les ~1s dans la boucle main saturait la console +série. Passé en `debug!` + fréquence /5 (~5s). Réactivable avec +`RUST_LOG=debug`. Cf. `crates/redox-wl-compositor/src/main.rs:252-258`. + +## Bugs secondaires à isoler (hors scope 13.1.b) + +### B.1 — Curseur software ne suit pas la souris correctement + +Pendant les tests, le curseur restait visuellement coincé en bas-droite +de l'écran (typiquement `(width-1, height-1)`). Les deltas relatifs +semblaient toujours majoritairement positifs. Hypothèses à creuser : + +- Mauvaise polarité des deltas Y sous QEMU/Redox (axe inversé ?) +- Conversion absolu→relatif en amont dans `inputd` qui dérive +- Souris non-grabbed par QEMU sur fenêtre non focusée → accumulation + de moves "vers le bord" lors des allers-retours hôte/guest + +Test à faire en 13.1.c : logger systématiquement les deltas `(dx, dy)` +reçus et tracer leur somme cumulée vs déplacement réel attendu. + +### B.2 — Page fault ion sur broken pipe d'un job background + +À chaque fois qu'un client Wayland en background `&` se termine par +broken pipe (Ctrl+Q compositor avant exit client), **ion crashe** : + +``` +ion: ([Page fault: 0000000000000070 US ... +RIP: 00000000002335ae +... kernel::context::signal:INFO -- UNHANDLED EXCEPTION ... NAME /usr/bin/ion +``` + +Adresse fautive `0x70` = déréf null + offset, reproductible. Bug Redox +upstream (ion), pas notre compositor. À reporter sur +`gitlab.redox-os.org/redox-os/ion`. + +### B.3 — VT switching hôte intercepté par CachyOS + +`Ctrl+Alt+F2..F6` mangés par X/Wayland CachyOS avant QEMU, donc +impossible de switcher de VT depuis l'hôte vers un Redox guest. Pas un +bug, contrainte connue. Workaround pratique : tout faire sur le seul VT +graphique (celui que la fenêtre QEMU affiche), ou utiliser la console +série pour les commandes shell et la fenêtre graphique pour le clavier +PS/2 du compositor. + +### B.4 — Image RedoxFS étroite (682 Mo initiale) + +`pkg update` saturait le filesystem. Résolu pendant cette session par +`qemu-img resize harddrive.img 10G` + `redoxfs-resize`. À considérer si +on automatise la procédure : taille par défaut de l'image dans le +fork/script doit être ≥ 4 Go pour permettre install paquets standards. + +## Screenshots à prendre (suivant doc 13.1) + +À ajouter au repo en `docs/phase13-1-b-*.png` : + +- `phase13-1-b-gradient-running.png` — fenêtre gradient ARGB visible sur + fond bleu nuit, dans la fenêtre QEMU graphique, avant ESC. +- `phase13-1-b-after-esc.png` — fond bleu seul après ESC (le compositor + continue, la fenêtre client a disparu suite à `garbage_collect`). +- `phase13-1-b-log-pass.png` — capture console montrant + `[real-client] PASS` + `garbage_collect: ... → destroyed 1 surfaces`. + +(Optionnel — la validation textuelle des logs suffit pour le critère +de fin, mais les screenshots aident à comparer rapidement aux phases +ultérieures.) + +## Procédure de test runtime — version finale + +Mise à jour vs `phase13-1-real-client-simple-window.md:82-135` : tout +se fait dans la **fenêtre QEMU graphique** (la console série de l'hôte +ne peut pas envoyer ESC à inputd). + +### 1. Hôte CachyOS + +```bash +cd ~/Projets/Redox/redox-wayland-compositor +./run-qemu.sh +``` + +### 2. Fenêtre QEMU graphique, après login `root` / `password` + +```sh +rm -f /tmp/redox-wl-comp.sock # défensif si résidu d'un test antérieur +redox-wl-compositor & +sleep 1 +redox-wl-real-client-simple-window +``` + +### 3. Toujours sur la fenêtre graphique + +- Vérifier visuellement : fond bleu + petite fenêtre gradient ARGB +- Presser `ESC` une fois +- Attendu : `[real-client] ESC → exit`, `[real-client] loop exited cleanly`, + `[real-client] PASS`, retour au prompt +- Optionnel : `Ctrl+Q` pour arrêter le compositor (sinon il timeout à + 180s ou tu peux `kill %0` depuis le shell) + +## Critère de fin 13.1.b + +> Le client tiers `redox-wl-real-client-simple-window`, port verbatim +> de `wayland-rs/wayland-client/examples/simple_window.rs` (sauf les 4 +> adaptations Redox de 13.1), traverse le pipeline Wayland complet vers +> notre compositor jusqu'à `[real-client] PASS` sur ESC. + +**✅ Validé 2026-05-16.** 3 fixes appliqués sur le compositor, aucun +sur le code client (qui reste verbatim upstream). + +## Fichiers modifiés + +``` +crates/redox-wl-compositor/src/main.rs # Fix 1 (ESC→client, Ctrl+Q→compo) + bonus bruit +crates/redox-wl-wayland-frontend/src/lib.rs # Fix 2 (clamp cursor) + Fix 3 (keycode evdev brut) +crates/redox-wl-real-client-simple-window/src/main.rs # Fix 4 (handling IO transient dans event loop) +run-qemu.sh # cleanup défensif (check /dev/fuse, IMAGE/REDOXFS overridables) +docs/phase13-1-b-observations.md # ce document +``` diff --git a/run-qemu.sh b/run-qemu.sh index d971aa5..9bc4206 100755 --- a/run-qemu.sh +++ b/run-qemu.sh @@ -13,6 +13,7 @@ # cd crates/redox-wl-real-client-simple-window && redoxer build --release # - redoxfs accessible dans REDOX_SRC/build/fstools/bin/ # - L'image harddrive.img présente dans REDOX_SRC/build/x86_64/desktop/ +# - /dev/fuse disponible sur l'hôte # - PAS de QEMU déjà ouvert sur la même image (corromprait le disque). # # Une fois Redox bootée : @@ -25,8 +26,8 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")" && pwd)" REDOX_SRC="${REDOX_SRC:-$HOME/Projets/Redox/redox-src}" -IMAGE="$REDOX_SRC/build/x86_64/desktop/harddrive.img" -REDOXFS="$REDOX_SRC/build/fstools/bin/redoxfs" +IMAGE="${IMAGE:-$REDOX_SRC/build/x86_64/desktop/harddrive.img}" +REDOXFS="${REDOXFS:-$REDOX_SRC/build/fstools/bin/redoxfs}" MOUNT="${MOUNT:-/tmp/redox-mnt}" COMPOSITOR_BIN="$ROOT/crates/redox-wl-compositor/target/x86_64-unknown-redox/release/redox-wl-compositor" @@ -76,6 +77,13 @@ if [[ $missing -ne 0 ]]; then exit 1 fi +if [[ ! -e /dev/fuse ]]; then + echo "ERR : /dev/fuse absent sur l'hôte" >&2 + echo "RedoxFS en a besoin pour monter l'image avant le boot QEMU." >&2 + echo "Sur Linux, charge le module fuse ou exécute sur un host où FUSE est disponible." >&2 + exit 3 +fi + # --- 2. Mount --- mkdir -p "$MOUNT" echo "==> monter $IMAGE -> $MOUNT"