263 lines
12 KiB
Markdown
263 lines
12 KiB
Markdown
|
|
# 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<WlSubcompositor>::request` :
|
|||
|
|
- `Destroy` : no-op
|
|||
|
|
- `GetSubsurface { id, surface, parent }` : crée un `Arc<SubsurfaceData>`
|
|||
|
|
avec parent ref, child ref, position pending `(0,0)`, sync mode `true`
|
|||
|
|
- `Dispatch<WlSubsurface>::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<SubsurfaceData>)`.
|
|||
|
|
|
|||
|
|
**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<Option<Arc<SubsurfaceData>>>`
|
|||
|
|
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::<Arc<SurfaceData>>()`
|
|||
|
|
- 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<child_id> 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
|
|||
|
|
<client>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
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<child_id> 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.
|