redox-wayland-compositor/docs/phase6-4-wayland-frontend.md
Votre Nom 8a897d975d 🎉🎉🎉 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
2026-05-09 13:30:05 +02:00

7.9 KiB
Raw Blame History

Phase 6.4 — Frontend Wayland : un client externe affiche ses pixels

Document produit le 2026-05-09 dans le cadre du plan directeur REDOX_COSMIC_XWAYLAND_RS_PLAN.md.

Scope : un client Wayland externe (process séparé, code Rust pur via wayland-rs) se connecte au compositor binaire, lui envoie un buffer shm peint, et le compositor l'affiche.

Verdict

Pipeline Wayland complet sur Redox, end-to-end, par-dessus notre stack compositor-core + redox-wl-display + redox-wl-input.

Capture preuve : — le pattern ARGB 320x240 visible dans le coin haut-gauche est écrit par le client externe et affiché par le compositor sur le display Redox.

Architecture finale 6.4

┌──────────────────────────────────────┐    ┌─────────────────────────────┐
│  redox-wl-compositor (bin)           │    │  redox-wl-test-client-shm   │
│                                      │    │  (bin séparé)               │
│  ┌────────────────┐                  │    │                             │
│  │ RedoxOutput    │                  │    │  - shm_open + pattern ARGB  │
│  └────┬───────────┘                  │    │  - wl_compositor            │
│       │ Arc<ConsumerHandle>          │    │  - wl_shm                   │
│       ▼                              │    │  - wl_shm_pool.create       │
│  ┌────────────────┐                  │    │  - wl_buffer                │
│  │ InputBackend   │                  │    │  - wl_surface.attach        │
│  └────────────────┘                  │    │  - wl_surface.commit        │
│                                      │    │                             │
│  ┌────────────────┐                  │    └─────────────┬───────────────┘
│  │WaylandFrontend │                  │                  │ Unix socket
│  │ - registry     │ ◄────────────────┼──────────────────┘ + SCM_RIGHTS
│  │   (compositor- │                  │
│  │    core)       │                  │
│  │ - Display<Self>│                  │
│  │ - Listener     │                  │
│  └────────┬───────┘                  │
│           │ compose_into             │
│           ▼                          │
│  framebuffer Redox → écran           │
└──────────────────────────────────────┘

Globaux exposés

Global Version Comportement
wl_compositor 5 create_surfaceregistry.create() ; create_region no-op
wl_shm 1 advertise Argb8888 + Xrgb8888 ; create_pool(fd, size) → mmap immédiat
wl_shm_pool create_bufferBufferData (offset/w/h/stride/format) ; resize no-op (TODO)
wl_buffer (pas de request à traiter, juste destroy implicite)
wl_surface 5 attach, damage/damage_buffer (no-op tracking 6.4), commit, frame, destroy
wl_callback utilisé pour wl_surface.frame ; done envoyé par notify_frame_done
wl_region no-op (pas d'input region utilisée)

Sémantique commit Wayland implémentée

Quand un client envoie wl_surface.commit :

  1. Récupération du BufferData attaché en pending (via wl_surface.attach)
  2. Lecture des pixels du shm via mmap côté serveur
  3. Création d'un SurfaceBuffer (Arc<Vec>) côté compositor-core
  4. registry.modify_pending(id, |s| s.buffer = Some(...))
  5. registry.commit(id) — pending → current
  6. registry.raise(id) — politique simple : dernière surface commitée passe au top
  7. Frame callbacks pending → queue globale, traité au notify_frame_done après le prochain present

Approche "copy on commit" : on copie les pixels du shm vers un Vec owned. Plus simple que de garder une référence vivante au mmap qui peut être unmappé par le client à tout moment. Coût ≈ 320×240×4 = 300 KiB par commit pour notre client de test, négligeable.

Validation runtime

Configuration :

# init.d/20_orbital → nowait VT=2 redox-wl-compositor
# init.d/30_console → nowait redox-wl-test-client-shm

Le compositor démarre, expose le socket /tmp/redox-wl-comp.sock, le client a une boucle de connexion qui retry 50× × 100 ms et finit par se connecter quand le socket apparaît.

Logs capturés via /scheme/debug → serial QEMU stdio :

[comp]  Phase 6.4 — compositor Wayland démarrage
[comp]  display 1280x800, VT=3
[comp]  CRTC pris
[comp]  Wayland socket : /tmp/redox-wl-comp.sock
[client] connect to compositor
[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 20s

Limitations / hors scope

Pas de placement (xdg-shell absent)

La surface du client est affichée à (0, 0) parce qu'aucun protocole de placement n'est implémenté. Pour des fenêtres positionnables / redimensionnables, il faudra xdg_wm_base + xdg_toplevel (phase 7 ou plus tard).

Pas de damage tracking effectif

wl_surface.damage et damage_buffer sont reçus mais ignorés. Chaque frame recompose tout. Pour 1-3 surfaces de petite taille c'est imperceptible ; à optimiser quand on aura beaucoup de surfaces.

Pas de wl_buffer.release explicite

Le release Wayland indique au client qu'il peut réutiliser un buffer. Notre approche copy-on-commit rend ce protocole inutile (on n'a plus besoin du shm après commit). Mais des clients sophistiqués pourraient attendre release avant d'écrire à nouveau — à vérifier au cas par cas.

Pas de seat / input vers les clients

wl_seat, wl_keyboard, wl_pointer ne sont pas exposés. Les events input sont seulement consommés côté compositor (pour Esc=quit). Phase 7 ajoutera la propagation des events vers la surface focalisée.

Pas de subcompositor

wl_subcompositor non exposé. Pas critique pour des clients simples.

Code source

crates/redox-wl-wayland-frontend/    # lib (~430 lignes)
├── Cargo.toml
└── src/lib.rs                       # WaylandFrontend, Dispatch impls

crates/redox-wl-compositor/          # bin (~150 lignes)
├── Cargo.toml
└── src/main.rs                      # boucle main display+input+frontend

crates/redox-wl-test-client-shm/     # bin (~170 lignes)
├── Cargo.toml
└── src/main.rs                      # client wayland-rs qui peint + commit

Suite phase 7

Si on veut un compositor utilisable au quotidien, il manque (par ordre de priorité) :

  1. xdg-shell (xdg_wm_base + xdg_toplevel + xdg_surface) → placement, redimensionnement, fermeture propre, titres de fenêtres
  2. wl_seat + wl_keyboard + wl_pointer → propager les events input vers la surface focalisée. Décision XKB à prendre (porter libxkbcommon / impl Rust pur / strings minimales).
  3. Curseur software → afficher le pointeur souris à l'écran (le compositor en a déjà la position via InputBackend, mais ne le dessine pas)
  4. Gestion focus + raise on click → utiliser hit_test + wl_seat.keyboard_enter/leave events
  5. Damage tracking effectif → réduire le coût de composition
  6. Clipboard → wl_data_device_manager
  7. Multiple clients simultanés, fermeture propre, recover sur crash client

Estimé phase 7 complète : 5-8 sessions.


Fin du document de phase 6.4.