diff --git a/docs/phase13-2-output-subcompositor.md b/docs/phase13-2-output-subcompositor.md new file mode 100644 index 0000000..379d26f --- /dev/null +++ b/docs/phase13-2-output-subcompositor.md @@ -0,0 +1,262 @@ +# Phase 13.2 — `wl_output` v3 + `wl_subcompositor`/`wl_subsurface` + +> Document produit le 2026-05-16, suite de +> [`phase13-1-c-cursor.md`](phase13-1-c-cursor.md). +> +> **Scope** : combler 2 dettes protocolaires bloquantes pour les +> toolkits clients tiers modernes : +> +> - **`wl_output` complet** — annonce le single display avec geometry/ +> mode/scale/done, en respectant le gating par version (v1/v2/v3). +> Plusieurs toolkits clients (sctk, GTK, Qt) considèrent ce global +> implicitement requis — son absence en 13.1.b était une bombe à +> retardement. +> - **`wl_subcompositor` + `wl_subsurface`** — supporte les surfaces +> parent-enfant et les rend visuellement à `parent_pos + offset`. +> Pré-requis pour tout client multi-surface (popups, badges, splash +> screens, decorations CSD, etc.). +> +> **Stratégie de validation hybride** : pour chaque sprint, un test +> natif (cargo test, sans QEMU) valide la couche protocolaire ; un +> test runtime visuel valide le rendu effectif. Le coût de QEMU +> est ainsi réduit aux validations vraiment visuelles. +> +> **Verdict** : ✅ **13.2 entièrement validée** — 6 commits, 5 +> sprints, 2 nouveaux clients de test, 3 scripts ion shortcuts. + +## Sous-phases livrées + +### 13.2.a — `wl_output` v3 complet ([commit `7413745`](../crates/redox-wl-wayland-frontend/src/lib.rs)) + +Avant : `wl_output` n'était PAS déclaré comme global. Les clients qui +le bind-eraient implicitement (sctk, GTK) se retrouvaient sans output. + +Après : + +- Global `wl_output` à `OUTPUT_VERSION = 3` +- `GlobalDispatch::bind` envoie au bind, dans l'ordre : + 1. `geometry(0, 0, 0_mm, 0_mm, Subpixel::Unknown, "Redox", "redox-wl-output:0", Transform::Normal)` + 2. `mode(CURRENT | PREFERRED, screen_w, screen_h, 60_000_mHz)` + 3. `scale(1)` — **uniquement si version bound ≥ 3** (gating conservateur) + 4. `done()` — **uniquement si version bound ≥ 2** +- `Dispatch::request` accepte `Release` (v3+) en no-op + +Taille pixel issue de `screen_w / screen_h` qui sont settés par +`set_screen_size()` (introduit en 13.1.b pour clamp curseur). Refresh +hardcodé à 60 Hz : notre boucle main tourne à ~30 fps mais les apps +modernes ignorent largement ce champ. + +**Test natif** (`crates/redox-wl-test-wl-output/tests/gating_native.rs`) : +serveur + client en-process via `UnixStream::pair()`, 11 assertions +vérifient v1 (geometry+mode), v2 (+done), v3 (+scale). PASS le 2026-05-16. + +**Test runtime** (`redox-wl-test-wl-output`, runnable via `test-out`) : +bind aux 3 versions et reporte PASS/FAIL côté serial. Confirme le +gating sur la vraie cible Redox. + +### 13.2.b.1 — `wl_subcompositor` protocole ([commit `f9c3de1`](../crates/redox-wl-wayland-frontend/src/lib.rs)) + +Implémentation protocole uniquement (sans rendering, repoussé à .b.2) : + +- Global `wl_subcompositor` à `SUBCOMPOSITOR_VERSION = 1` +- `GlobalDispatch::bind` no-op (resource init) +- `Dispatch::request` : + - `Destroy` : no-op + - `GetSubsurface { id, surface, parent }` : crée un `Arc` + avec parent ref, child ref, position pending `(0,0)`, sync mode `true` +- `Dispatch::request` : + - `SetPosition { x, y }` : update SubsurfaceData.position + - `PlaceAbove`, `PlaceBelow` : no-op (single-subsurface use-case) + - `SetSync`, `SetDesync` : update SubsurfaceData.sync + - `Destroy` : no-op (resource cleanup par wayland-server) + +**SubsurfaceData** est une nouvelle struct portée par la wl_subsurface +resource via `data_init.init(id, Arc)`. + +**Test natif** (`subcompositor_native.rs`) : cycle complet bind → +get_subsurface → set_position → set_sync/desync → destroy. Aucune +erreur protocole détectée. + +### 13.2.b.2 — Rendering subsurface ([commit `bba2d7b`](../crates/redox-wl-wayland-frontend/src/lib.rs)) + +Wire la SubsurfaceData de .b.1 au pipeline de composition : + +1. Nouveau champ `subsurface_link: Mutex>>` + sur `SurfaceData` +2. À `GetSubsurface`, on link la SubsurfaceData à la child SurfaceData +3. Au commit du child wl_surface : + - Si `subsurface_link` est Some : récupère le parent SurfaceId via + `sub_data.parent.data::>()` + - Lookup la position actuelle du parent dans le registry + - `modify_pending(child_id, |s| s.x = px + ox; s.y = py + oy)` avant + le `commit(id)` qui applique pending → current + - Skip `raise()` et `set_focus()` pour les subsurfaces (elles ne + grabbent pas le focus indépendamment, et leur z-order est géré + comme cascade au-dessus du parent) + +`compose_into` (côté `redox-wl-compositor-core`) n'est PAS modifié — il +itère le z_order et dessine chaque surface à son `(x, y)` absolu. Notre +prep au commit assure que le child est à la bonne position. + +### 13.2.b.3 — Client de test visuel ([commit `1dab6ff`](../crates/redox-wl-test-client-subcompositor/)) + +Nouveau crate `redox-wl-test-client-subcompositor` qui : + +1. Connecte au socket `/tmp/redox-wl-comp.sock` +2. Bind 5 globals : `wl_compositor`, `wl_shm`, `xdg_wm_base`, `wl_seat`, + `wl_subcompositor` +3. Crée un parent xdg_toplevel 300×200 bleu nuit avec bordure noire 2px, + ack_configure puis attach + commit buffer ARGB +4. Crée une child wl_surface, `get_subsurface(child, parent)`, + `set_position(50, 50)`, `set_desync`, attach + commit buffer 60×60 + rouge avec bordure noire +5. ESC fermes le client proprement (le path keyboard étant routé au + parent toplevel qui a le focus auto-grant) + +Reprend l'event-loop tolérant Interrupted/BrokenPipe de la phase 13.1.b. + +### Fix bug visuel ([commit `dfb5c66`](../crates/redox-wl-wayland-frontend/src/lib.rs)) + +Détecté lors du runtime QEMU 13.2.b.3. Symptôme : le carré rouge est +calculé à la bonne position au commit du child, mais reste invisible. + +Cause : `parent_surface.commit()` final dans le client (refresh post- +subsurface) appelle `raise(parent_id)` dans le compositor, plaçant le +parent **au-dessus** du child dans le z_order. Le parent overdraw le +child à compose_into. + +Z-order observé : `[child, parent]` au lieu de `[parent, child]`. + +**Fix** : après `raise(parent_id)`, scanner `surfaces_by_id` pour +trouver toutes les subsurfaces dont `subsurface_link.parent` pointe +vers cette SurfaceId, et les `raise()` juste après. Le z-order devient +`[parent, child]` (avec child raised en dernier = au-dessus). + +Coût O(N) par raise — N est typiquement petit (1-10 toplevels). À +durcir avec un index parent_id → Vec si N explose. + +### 13.2.b.4 — Scripts ion shortcuts ([commit `6fe3214`](../tools/redox-scripts/)) + +UX : pour éviter de retaper 3 lignes à chaque test runtime dans la +fenêtre QEMU graphique (où le copier-coller depuis CachyOS n'est pas +disponible), 3 scripts ion prêts à l'emploi déployés dans `/usr/bin` : + +- **`test-sw`** : compositor + simple_window upstream (phase 13.1.b) +- **`test-subc`** : compositor + subcompositor visuel (phase 13.2.b.3) +- **`test-out`** : compositor + wl_output gating test (phase 13.2.a) + +Tous suivent le même pattern : + +```ion +#!/usr/bin/ion +rm -f /tmp/redox-wl-comp.sock +redox-wl-compositor & +sleep 1 + +``` + +run-qemu.sh copie maintenant `tools/redox-scripts/*` dans `/usr/bin` de +l'image (avec `cp -p` pour préserver le bit exécutable). + +## Stratégie de tests : hybride natif + runtime + +Innovation méthodologique de cette phase. Pour chaque sprint : + +- **Test natif** (cargo test, sur l'hôte CachyOS, sans QEMU) : + - Spawn un thread serveur avec wayland-server + la logique de gating + identique au prod code + - Connecte un client wayland-client via `UnixStream::pair()` + - Exercise toute la séquence Wayland + - Asserts précis (versions, events reçus, ordre, etc.) + - Tourne en < 6 secondes +- **Test runtime** (QEMU, visuel) : + - Confirme le rendu effectif sur le framebuffer Redox + - Confirme l'interaction utilisateur (clic, ESC, focus, etc.) + - Validation finale "tout le pipeline marche" + +Gain : on n'a QEMU dans la boucle que pour ce qui est vraiment visuel. +La couche protocolaire est testée en CI-friendly time. + +## Limitations connues + +À traiter en phase ultérieure si besoin : + +- **Parent move ≠ propagation aux enfants** : si on drag un parent + toplevel (phase 7.7), ses subsurfaces ne suivent pas tant que le + client ne re-commit pas l'enfant. Pour des use-cases statiques + c'est OK ; pour Wayland conforme strict il faudra walker les enfants + au move du parent. +- **wl_subsurface.Destroy ne unmap pas le child** : spec violation + mineure. Si le client veut vraiment retirer la subsurface, il doit + aussi destroy son wl_surface enfant. +- **Pas de cascade sync** : un commit du parent en mode sync devrait + flusher les pending states des subsurfaces sync. Pas implémenté. + Notre client de test utilise `set_desync` donc le path n'est pas + exercé. +- **Pas de role-tracking** : spec exige `bad_surface` error si une + wl_surface a déjà un rôle (xdg_toplevel) et qu'on appelle + GetSubsurface dessus. Pour l'instant on log debug seulement. +- **PlaceAbove/PlaceBelow no-op** : z-order relatif entre subsurfaces + siblings non géré. Pas observé en pratique avec nos clients de test + single-subsurface. +- **Raise children scan O(N)** : naïf. Avec un index parent_id → + Vec on passe O(1). À faire si N de surfaces visibles + devient grand (> 50 ?). + +## Fichiers livrés en 13.2 + +``` +# Code compositor (frontend Wayland) +crates/redox-wl-wayland-frontend/src/lib.rs # globals + Dispatch + commit hook + +# Clients de test +crates/redox-wl-test-wl-output/ # validation gating wl_output (13.2.a) + ├── src/main.rs # runtime client + └── tests/ + ├── gating_native.rs # test natif v1/v2/v3 + └── subcompositor_native.rs # test natif protocole + +crates/redox-wl-test-client-subcompositor/ # validation visuelle (13.2.b.3) + └── src/main.rs + +# Tooling runtime +tools/redox-scripts/test-sw # shortcut simple_window +tools/redox-scripts/test-subc # shortcut subcompositor +tools/redox-scripts/test-out # shortcut output gating + +# Build/deploy +run-qemu.sh # copie binaires + scripts + +# Doc +docs/phase13-2-output-subcompositor.md # ce document +``` + +## Critère de fin 13.2 + +> 1. Le compositor expose `wl_output` v3 avec gating correct des events +> par version bound (testé v1/v2/v3 en natif et runtime) +> 2. Le compositor expose `wl_subcompositor` v1 et accepte tous les +> requests `wl_subsurface` sans erreur protocole +> 3. Un client multi-surface (parent + 1 subsurface) rend visuellement +> les deux surfaces aux bonnes positions (parent à `cascade_offset`, +> child à `parent + offset`) +> 4. Le z-order reste cohérent même après recommit du parent (le child +> reste au-dessus) +> 5. Le focus clavier reste sur le parent (les subsurfaces ne grabbent +> pas) + +**✅ Tous critères validés 2026-05-16.** + +## Étapes suivantes possibles + +- **13.3** : extraction backend pour Smithay/COSMIC (Phase 13 du master + plan). Auditer notre code pour identifier les primitives réutilisables + comme backend Smithay (display, input, session, renderer logiciel). +- **13.2.c** : retenter sctk simple_window — avec wl_output et + wl_subcompositor maintenant présents, il manque encore xdg-decoration + et un wayland-server qui charge libxkbcommon. À évaluer si le ROI + vaut le coût d'implémentation. +- **Durcissement 13.2** : reprendre les limitations listées ci-dessus + (cascade sync, parent move propagation, role-tracking). +- **Phase 8** : remplacement expérimental d'Orbital avec ce compositor. + Préparé maintenant qu'on couvre les protocoles de base.