Initial commit: phases 1-3 du portage Wayland Rust pour Redox OS

Plan directeur 14 phases / 5 ans (REDOX_COSMIC_XWAYLAND_RS_PLAN.md).

Phase 1 — Audit Redox (docs/existing-redox-gui.md, 486 lignes) :
- Orbital, graphics-ipc (API DRM compatible Linux subset KMS), inputd, vesad
- relibc support : AF_UNIX, SCM_RIGHTS, shm_open, mmap, poll
- 3 manques identifiés : memfd_create, keymap XKB, AT-SPI

Phase 2 — Validation primitives sur Redox via redoxer (5 tests + 1 POC) :
- test-unix-socket : SOCK_STREAM Wayland-shaped roundtrip
- test-fd-passing : SCM_RIGHTS mono-process (artefact kernel)
- test-fd-passing-fork : SCM_RIGHTS multi-process (validation Wayland critique)
- test-shm-open : shm_open + mmap + persistance + unlink
- test-poll-multifd : poll() multiplexing + POLLHUP
- poc-pixels : datapath shm + SCM_RIGHTS bout en bout (10000 pixels ARGB)

Phase 3 — wayland-rs sur Redox (compile + runtime) :
- wayland-{scanner,backend,server,client} compilent pour x86_64-unknown-redox
  sans patch upstream (rustix supporte Redox via libc backend)
- test-handshake : server/client wl_registry handshake roundtrip
- test-shm-pipeline : pipeline complet (ListeningSocket Unix réel + fd passing
  via wl_shm.create_pool + wl_shm_pool + wl_buffer + wl_surface + commit +
  serveur lit pixels via fd reçu, validation pixel-perfect)

Verdict phase 3 : wayland-rs upstream est viable sur Redox out-of-the-box,
le port "Wayland sur Redox" est désormais un problème de compositor à écrire,
pas de stack à porter.

Prérequis build : redoxer (pas cargo direct, car CMSG_NXTHDR/CMSG_DATA
ne sont pas linkés autrement vers librelibc.a).

Leyoda 2026 – GPLv3
This commit is contained in:
Votre Nom 2026-05-08 17:41:55 +02:00
commit 53e6626231
21 changed files with 3676 additions and 0 deletions

22
.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# Rust build artifacts
target/
*/target/
**/target/
Cargo.lock
# Generated test outputs
*.ppm
*.png
*.pcap
# Editor / OS
.vscode/
.idea/
.DS_Store
*.swp
*.swo
*~
# Local environment
.env
.envrc

70
README.md Normal file
View file

@ -0,0 +1,70 @@
# redox-wayland-compositor
Portage de Wayland sur Redox OS, en Rust pur, visant à terme à remplacer
Orbital comme serveur graphique de base.
**État** : phases 1+2+3 du plan directeur complétées le 2026-05-08.
Le pipeline shm Wayland complet est validé end-to-end sur Redox via redoxer
(crate `redox-wl-test-shm-pipeline`) : socket Unix réel, `wl_compositor` +
`wl_shm` globals, fd passing via SCM_RIGHTS, `wl_shm_pool` + `wl_buffer`,
`wl_surface.attach`/`damage`/`commit`, lecture pixels côté serveur via le
fd reçu — pixel-perfect.
## Structure
```
.
├── REDOX_COSMIC_XWAYLAND_RS_PLAN.md # plan directeur 14 phases / 5 ans
├── docs/
│ ├── existing-redox-gui.md # phase 1 : audit Orbital + graphics-ipc + inputd + relibc
│ └── redox-wayland-primitives.md # phase 2 : 5 primitives + POC pixels validés sur Redox
├── crates/
│ ├── redox-wl-test-unix-socket/ # primitive 1 : SOCK_STREAM Wayland-shaped
│ ├── redox-wl-test-fd-passing/ # primitive 2 : SCM_RIGHTS mono-process (artefact)
│ ├── redox-wl-test-fd-passing-fork/ # primitive 2b : SCM_RIGHTS via fork (vrai cas Wayland)
│ ├── redox-wl-test-shm-open/ # primitive 3 : shm_open + mmap MAP_SHARED
│ ├── redox-wl-test-poll-multifd/ # primitive 4 : poll() multiplexing + POLLHUP
│ ├── redox-wl-poc-pixels/ # POC : datapath shm + SCM_RIGHTS bout en bout
│ ├── redox-wl-test-handshake/ # phase 3 : wayland-rs server/client registry
│ └── redox-wl-test-shm-pipeline/ # phase 3 : pipeline shm complet validé
└── (pas de Cargo.toml racine : chaque crate est standalone — voir note ci-dessous)
```
> **Note structure** : pas de workspace Cargo racine. Chaque crate dans `crates/`
> est autonome avec son propre `Cargo.toml` et son propre `target/` lors de la build.
> Raison : `redoxer build`/`redoxer run` cherche le binaire dans `<crate>/target/...`
> et n'arrive pas à le retrouver si le `target/` est centralisé au niveau workspace.
> Ce sera repensé quand on aura un compositor unique vs plusieurs binaires de tests.
## Prérequis
- Rust nightly (récent — testé 2026-05-07)
- Target installé : `rustup target add x86_64-unknown-redox --toolchain nightly`
- `cargo install redoxer` puis `redoxer install` (sysroot Redox ~456 Mo)
- Repo upstream `Smithay/wayland-rs` cloné en `../wayland-rs/` (paths relatifs depuis `crates/*/Cargo.toml`)
## Reproduire les tests
Chaque crate s'exécute via redoxer (mini-VM Redox sous QEMU) :
```bash
cd crates/redox-wl-test-shm-pipeline
redoxer run --release
```
Pattern attendu en fin d'exécution : `[test-XX] PASS: ...`.
> **Important** : utiliser `redoxer build` ou `redoxer run`, **pas** `cargo build --target=x86_64-unknown-redox` direct
> qui fait un fail au link (`undefined reference CMSG_NXTHDR/CMSG_DATA`) parce que le linker host
> ne sait pas chaîner librelibc.a du sysroot Redox. `redoxer` configure le linker correctement.
## Roadmap
Voir `REDOX_COSMIC_XWAYLAND_RS_PLAN.md` pour le plan complet 14 phases / 5 ans.
Prochaine étape : **phase 4 — display backend Redox via `graphics-ipc::V2GraphicsHandle`**.
## Licence
Leyoda 2026 — GPLv3+

View file

@ -0,0 +1,892 @@
# Plan global 3-5 ans : compositor Wayland Rust minimal pour remplacer Orbital
## Document A Etudier
Ce fichier est le document global de travail a etudier en priorite :
```text
REDOX_COSMIC_XWAYLAND_RS_PLAN.md
```
Il doit servir de plan directeur pour avancer vers une GUI Redox moderne :
- d'abord un compositor Wayland minimal en Rust qui remplace Orbital ;
- ensuite un backend plus robuste pour Smithay/COSMIC ;
- plus tard une compatibilite X11 via un serveur X11 Rust compatible Xwayland.
## Vision
Construire progressivement un compositor Wayland minimal, natif Redox, ecrit principalement en Rust, qui remplace Orbital comme serveur graphique de base.
La cible initiale n'est pas COSMIC complet. La cible initiale est plus fondamentale :
```text
Applications Wayland simples
|
v
Compositor Wayland minimal Rust
|
v
Backend Redox : inputd + graphics-ipc + schemes
|
v
vesad / framebuffer firmware au debut
GPU userspace Redox plus tard
```
La cible long terme est :
```text
Applications Wayland natives / COSMIC
Applications X11 via serveur X11 Rust compatible Xwayland
|
v
Compositor Wayland Redox
|
v
Redox natif, sans Orbital
```
Pour etre complete, la GUI Redox doit couvrir trois manques structurels :
- acceleration materielle via GPU ;
- support Wayland standard, ou protocole equivalent clairement documente ;
- accessibilite : lecteurs d'ecran, navigation clavier avancee, focus visible, raccourcis, contraste, zoom et APIs d'assistance.
## Constat Sur L'Existant Redox
### VESA / `vesad`
`vesad` n'est pas vraiment un driver GPU. Il ecrit dans un framebuffer fourni par le firmware via UEFI ou BIOS.
Implication pour le plan :
- le premier compositor doit accepter un rendu framebuffer simple ;
- le rendu CPU reste obligatoire ;
- il ne faut pas attendre les drivers GPU pour commencer ;
- le backend display doit rester compatible avec un framebuffer firmware.
### GPUs
Redox n'a pas encore de pile GPU complete comparable a Linux/BSD.
Sur Linux/BSD, la communication GPU passe par DRM dans le kernel, exposee par `libdrm`, puis utilisee par Mesa3D pour OpenGL/Vulkan.
Dans Redox, un "driver DRM" doit plutot etre un daemon userspace qui parle au materiel via schemes et appels systeme.
Implication pour le plan :
- l'acceleration GPU est un objectif de maturite, pas un pre-requis du MVP ;
- le compositor doit separer clairement :
- protocole Wayland ;
- gestion surfaces ;
- rendu ;
- presentation display ;
- allocation buffers ;
- il faudra plus tard un backend Redox dans Mesa3D pour utiliser ces drivers userspace.
### Software Rendering
LLVMpipe fonctionne deja comme emulation OpenGL CPU.
Implication pour le plan :
- le rendu logiciel est le chemin initial naturel ;
- les clients OpenGL peuvent passer par LLVMpipe/OSMesa quand c'est possible ;
- l'objectif court terme n'est pas Vulkan/OpenGL accelere, mais des surfaces `wl_shm` fiables ;
- OSMesa et LLVMpipe doivent etre conserves comme ponts de compatibilite pendant la transition.
### Orbital
Orbital fournit actuellement :
- display server ;
- window manager ;
- compositor ;
- launcher ;
- file manager ;
- text editor ;
- calculator ;
- terminal emulator ;
- raccourcis Super affiches en overlay ;
- protocole client via `orbclient`.
Orbital est plus simple que X11/Wayland. Il est moins avance, mais suffisant pour porter beaucoup de programmes Linux/BSD via des bibliotheques adaptees.
Bibliotheques deja utiles sur Orbital :
- `winit` ;
- `softbuffer` ;
- Slint via `winit`/`softbuffer` ;
- Iced via `winit`/`softbuffer` ;
- egui via `winit` ou SDL2 ;
- SDL1.2 ;
- SDL2 ;
- Mesa3D OSMesa.
Securite deja interessante :
- un programme GUI Orbital ne peut pas lire les events input ou le framebuffer des autres fenetres ;
- ce modele est plus proche de Wayland que de X11.
Implication pour le plan :
- Orbital ne doit pas etre jete brutalement ;
- il sert de reference et de fallback pendant plusieurs annees ;
- les bibliotheques deja portees vers Orbital sont des candidats de migration vers Wayland Redox ;
- la securite type Wayland doit etre preservee ;
- `orbclient` ne doit pas devenir la nouvelle API long terme.
## Strategie Corrigee
### Mauvais Chemin
Ne pas commencer par :
- porter COSMIC complet ;
- reecrire Xwayland ;
- attendre les drivers GPU ;
- concevoir un protocole graphique maison concurrent de Wayland ;
- remplacer tout Orbital d'un coup.
### Bon Chemin
Commencer par :
1. Valider les primitives Redox necessaires a Wayland.
2. Faire fonctionner `wayland-rs` sur Redox.
3. Ecrire un compositor Wayland minimal.
4. Reutiliser les idees d'Orbital pour display, input, damage, session et securite.
5. Rendre ce compositor capable de remplacer Orbital experimentalement.
6. Porter progressivement les bibliotheques et clients existants.
7. Preparer ensuite Smithay/COSMIC.
8. Traiter le serveur X11 Rust compatible Xwayland comme un projet long terme separe.
## Objectif Principal
Remplacer Orbital par un compositor Wayland natif Redox capable de :
- prendre le controle de l'ecran sans Orbital ;
- afficher une ou plusieurs surfaces Wayland ;
- recevoir l'input via Redox ;
- gerer focus, clavier, souris, resize et damage ;
- fournir un protocole Wayland suffisamment standard pour porter des clients simples ;
- garder un rendu CPU fiable via framebuffer/LLVMpipe/OSMesa ;
- preparer une future acceleration GPU sans l'imposer au MVP ;
- preparer une couche d'accessibilite ;
- servir de base future a COSMIC.
## Non-Objectifs Initiaux
Ne pas viser dans le MVP :
- COSMIC complet ;
- Xwayland ou equivalent X11 ;
- acceleration GPU obligatoire ;
- Mesa3D GPU complet ;
- Vulkan/OpenGL accelere ;
- multi-GPU ;
- HDR, color management avance, VRR ;
- portails desktop complets ;
- PipeWire/screencast ;
- decorations complexes ;
- compatibilite totale avec GTK/Qt/Electron ;
- lecteur d'ecran complet ;
- accessibilite desktop complete.
Ces objectifs restent importants, mais ils viennent apres le compositor Wayland minimal.
## Architecture Initiale
```text
redox-wayland-compositor
|
+-- redox_display
| +-- graphics-ipc
| +-- vesad/framebuffer firmware
| +-- framebuffer CPU
| +-- damage flush
| +-- future DRM userspace driver daemon
|
+-- redox_input
| +-- inputd
| +-- keyboard
| +-- pointer
| +-- buttons
| +-- relative motion
|
+-- wayland_core
| +-- wl_display
| +-- wl_registry
| +-- wl_compositor
| +-- wl_surface
| +-- wl_shm
| +-- wl_output
| +-- wl_seat
| +-- xdg_shell plus tard
|
+-- compositor_core
| +-- surfaces
| +-- windows
| +-- focus
| +-- stacking
| +-- damage
| +-- frame callbacks
|
+-- renderer
| +-- CPU blit
| +-- alpha blend
| +-- cursor composition
| +-- LLVMpipe/OSMesa path for clients
| +-- GPU backend later
|
+-- accessibility
+-- keyboard navigation
+-- visible focus
+-- metadata windows/surfaces
+-- high contrast
+-- zoom later
```
## Relation Avec Orbital
Orbital doit etre remplace progressivement, pas ignore.
### A Reutiliser Comme Reference
- [main.rs](main.rs) : demarrage via `VT`, lancement d'une commande de login/session.
- [core/display.rs](core/display.rs) : ouverture display, framebuffer CPU, cursor plane, `dirty_framebuffer`.
- [core/mod.rs](core/mod.rs) : event loop Redox, `inputd`, scheme.
- [compositor.rs](compositor.rs) : damage tracking, curseur, composition logicielle.
- [scheme.rs](scheme.rs) : focus, input routing, fenetres, raccourcis.
- [window.rs](window.rs) : modele de fenetre, buffers, flags, events.
### A Conserver Pendant Transition
- fallback Orbital dans les images de test ;
- applications Orbital essentielles ;
- bibliotheques deja portees : `winit`, `softbuffer`, Slint, Iced, egui, SDL, OSMesa ;
- experience utilisateur de base : terminal, launcher, raccourcis.
### A Remplacer
- protocole `orbital:` ;
- API client `orbclient` comme interface graphique principale ;
- mapping direct des fenetres via `mmap_prep` Orbital ;
- semantics de fenetres propres a Orbital ;
- hypothese qu'une seule implementation serveur suffit pour tous les clients.
## Roadmap Globale
| Phase | Nom | Objectif |
| --- | --- | --- |
| 0 | Cadrage | depot, build, documentation, perimetre |
| 1 | Audit existant Redox | vesad, graphics-ipc, inputd, Orbital, LLVMpipe |
| 2 | Primitives Redox | sockets, fd passing, shm, mmap, poll |
| 3 | Wayland-rs minimal | serveur Wayland + client test |
| 4 | Display backend | affichage Redox sans Orbital |
| 5 | Input backend | clavier/souris via inputd |
| 6 | Surfaces shm | affichage de surfaces Wayland |
| 7 | Compositor utilisable | focus, stacking, damage, cursor |
| 8 | Remplacement Orbital experimental | boot Redox vers compositor Wayland |
| 9 | Migration bibliotheques | winit, softbuffer, SDL, clients simples |
| 10 | Stabilisation desktop | xdg-shell, clipboard, output, tests |
| 11 | Accessibilite initiale | navigation clavier, focus visible, metadata UI |
| 12 | Acceleration GPU | abstraction renderer, backend GPU experimental |
| 13 | Pont vers Smithay/COSMIC | preparation port COSMIC |
| 14 | Compat X11 Rust | projet separe apres socle stable |
## Phase 0 : Cadrage
### Objectif
Creer un cadre de travail clair.
### Taches
- Creer un depot ou sous-depot `redox-wayland-compositor`.
- Definir les crates :
- `redox-wl-compositor`
- `redox-wl-display`
- `redox-wl-input`
- `redox-wl-render`
- `redox-wl-accessibility`
- `redox-wl-protocol-tests`
- Definir les clients de test.
- Definir la strategie de fallback Orbital.
- Definir la matrice de compatibilite.
### Validation
Un binaire minimal compile et s'integre dans l'environnement de build Redox.
## Phase 1 : Audit De L'Existant Redox
### Objectif
Comprendre exactement ce qui existe avant d'ecrire le nouveau compositor.
### Taches
- Lire le code Orbital actuel.
- Lire le fonctionnement de `graphics-ipc`.
- Lire le fonctionnement de `inputd`.
- Identifier le chemin exact vers `vesad`.
- Identifier comment LLVMpipe/OSMesa sont integres.
- Lister les bibliotheques deja fonctionnelles sur Orbital.
- Lister les APIs Redox disponibles pour sockets, fd passing, shm, mmap.
- Documenter les differences avec Linux.
### Livrables
- `docs/existing-redox-gui.md`
- schema de la stack actuelle ;
- liste des composants reutilisables ;
- liste des blocages.
### Validation
Le projet sait expliquer comment Orbital affiche une fenetre aujourd'hui et comment le nouveau compositor remplacera chaque partie.
## Phase 2 : Primitives Systeme Redox Pour Wayland
### Objectif
Valider les mecanismes necessaires au protocole Wayland.
### Primitives critiques
- Unix domain sockets ;
- passage de file descriptors ;
- memoire partagee ;
- `mmap` ;
- `poll` ou event loop equivalente ;
- timers ;
- gestion des processus clients.
### Taches
- Ecrire tests serveur/client socket.
- Ecrire tests fd passing.
- Ecrire tests shm/mmap.
- Ecrire tests event loop multi-fd.
- Corriger ou completer Redox/relibc si necessaire.
### Validation
Un serveur peut recevoir un client, lui envoyer une zone memoire partagee, detecter des changements et traiter plusieurs fd dans une boucle d'evenements.
## Phase 3 : Wayland-rs Minimal Sur Redox
### Objectif
Faire tourner un serveur Wayland minimal et un client Wayland minimal.
### Protocoles prioritaires
- `wl_display`
- `wl_registry`
- `wl_compositor`
- `wl_surface`
- `wl_shm`
- `wl_buffer`
- `wl_output`
- `wl_seat`
- `wl_keyboard`
- `wl_pointer`
### Taches
- Porter ou adapter `wayland-backend`.
- Porter ou adapter `wayland-server`.
- Porter ou adapter `wayland-client`.
- Generer les bindings necessaires.
- Ecrire un client Wayland test.
### Validation
Un client Wayland simple peut creer une surface shm et envoyer un commit au serveur.
## Phase 4 : Backend Display Redox
### Objectif
Ouvrir l'ecran Redox et dessiner sans Orbital.
### Taches
- Creer `RedoxOutput`.
- Ouvrir le display via les primitives Redox existantes.
- Supporter le framebuffer CPU fourni par le chemin firmware/vesad.
- Implementer clear screen.
- Implementer draw rectangle.
- Implementer flush damage.
- Implementer resize si possible.
- Ajouter curseur logiciel initial.
### Validation
Un binaire prend l'ecran sans Orbital et affiche une scene simple.
## Phase 5 : Backend Input Redox
### Objectif
Recevoir clavier, souris et boutons via `inputd`.
### Taches
- Ouvrir le consumer input Redox.
- Lire clavier.
- Lire souris absolue.
- Lire souris relative.
- Lire boutons et scroll.
- Normaliser les coordonnees.
- Router l'input vers une surface.
### Validation
Le compositor recoit l'input et peut deplacer un curseur ou focaliser une surface.
## Phase 6 : Surfaces Wayland SHM
### Objectif
Afficher de vraies surfaces Wayland basees sur `wl_shm`.
### Formats initiaux
- `XRGB8888`
- `ARGB8888`
### Taches
- Recevoir buffers `wl_shm`.
- Gerer `attach`.
- Gerer `damage`.
- Gerer `commit`.
- Composer les surfaces dans le framebuffer Redox.
- Envoyer `frame callbacks`.
### Validation
Une surface Wayland shm s'affiche et se met a jour correctement.
## Phase 7 : Compositor Utilisable Minimal
### Objectif
Passer d'une demo a un mini desktop experimental.
### Fonctionnalites
- stacking ;
- focus clavier ;
- focus pointer ;
- move de fenetres ;
- resize simple ;
- curseur ;
- damage tracking ;
- frame scheduling ;
- fermeture client ;
- raccourcis globaux.
### Validation
Deux clients Wayland simples s'affichent, recoivent input, peuvent etre focalises et deplaces.
## Phase 8 : Remplacement Experimental d'Orbital
### Objectif
Demarrer Redox directement sur le compositor Wayland minimal.
### Taches
- Integrer le compositor dans le boot/session.
- Definir `WAYLAND_DISPLAY`.
- Lancer un client Wayland test au demarrage.
- Garder une option fallback Orbital.
- Documenter la procedure de retour arriere.
### Validation
Orbital n'est plus necessaire pour afficher le premier client graphique de test.
## Phase 9 : Migration Des Bibliotheques Existantes
### Objectif
Utiliser la force de l'existant Redox/Orbital pour alimenter le nouveau monde Wayland.
### Taches
- Auditer `winit` Redox/Orbital.
- Ajouter ou adapter un backend Wayland Redox pour `winit`.
- Auditer `softbuffer`.
- Tester Slint, Iced et egui sur le nouveau chemin.
- Tester SDL2 Wayland si possible.
- Conserver OSMesa/LLVMpipe pour les clients qui ont besoin d'OpenGL CPU.
### Validation
Au moins une application Rust via `winit` ou `softbuffer` s'affiche dans le compositor Wayland Redox.
## Phase 10 : Stabilisation Desktop Minimal
### Objectif
Rendre le remplacement d'Orbital credible pour le developpement experimental.
### Fonctionnalites
- `xdg-shell` basique ;
- clipboard Wayland minimal ;
- multi-fenetre robuste ;
- resize output ;
- key repeat ;
- keymap simple ;
- logs structures ;
- tests pixels ;
- tests input.
### Validation
Des clients Wayland non triviaux peuvent tourner sans adaptation lourde.
## Phase 11 : Accessibilite Initiale
### Objectif
Faire de l'accessibilite une capacite native du desktop Redox.
### Fonctionnalites prioritaires
- navigation clavier complete dans le shell minimal ;
- focus visible ;
- ordre de focus stable ;
- raccourcis configurables ;
- contraste eleve ;
- taille de curseur configurable ;
- zoom logiciel basique ;
- metadata de fenetres/surfaces.
### Validation
La session minimale peut etre utilisee sans souris pour lancer, focaliser, deplacer et fermer une application.
## Phase 12 : Acceleration GPU Experimentale
### Objectif
Preparer la transition vers l'acceleration materielle sans casser le rendu CPU.
### Taches
- Definir un trait `Renderer`.
- Garder le renderer CPU comme reference.
- Ajouter abstraction de buffers :
- shm CPU ;
- futur buffer GPU ;
- import/export futur.
- Etudier :
- drivers DRM userspace Redox ;
- backend Redox Mesa3D ;
- LLVMpipe comme transition ;
- virtio-gpu ;
- wgpu si viable ;
- renderer Rust dedie si necessaire.
### Validation
Le compositor peut choisir entre renderer CPU et backend experimental sans changer sa logique Wayland.
## Phase 13 : Preparation Smithay/COSMIC
### Objectif
Transformer les acquis du compositor minimal en backend reusable pour Smithay/COSMIC.
### Taches
- Extraire un backend Redox propre :
- display ;
- input ;
- session ;
- renderer logiciel.
- Porter une demo type `smallvil` ou `anvil`.
- Auditer `cosmic-comp`.
- Remplacer les dependances Linux :
- libinput ;
- udev ;
- libseat/logind ;
- systemd ;
- EGL/GBM si absent.
### Validation
Smithay ou un `cosmic-comp` reduit peut utiliser les primitives developpees.
## Phase 14 : Horizon Serveur X11 Rust Compatible Xwayland
### Objectif
Apres stabilisation Wayland, lancer un projet separe de serveur X11 Rust.
### MVP X11
- handshake X11 ;
- table de ressources ;
- `CreateWindow` ;
- `MapWindow` ;
- `DestroyWindow` ;
- `ConfigureWindow` ;
- `PutImage` ;
- `CopyArea` ;
- events clavier/souris ;
- `Expose` ;
- fenetre top-level transformee en surface Wayland.
### Extensions ensuite
- BIG-REQUESTS ;
- MIT-SHM ;
- XFIXES ;
- DAMAGE ;
- RENDER ;
- RANDR minimal ;
- XInput2 minimal ;
- Composite plus tard ;
- GLX tres tard.
### Validation
Une application X11 simple peut ouvrir une fenetre, dessiner et recevoir input dans la session Wayland Redox.
## Jalons Annuels Indicatifs
### Annee 1 : Audit Et Fondations
Livrables :
- audit complet de l'existant Redox GUI ;
- tests fd passing/shm/mmap ;
- serveur Wayland minimal ;
- client Wayland test ;
- backend display affichant des pixels sans Orbital.
Validation :
- le socle systeme est compris ;
- une surface shm peut etre recue ;
- l'ecran peut etre controle dans une demo hors Orbital.
### Annee 2 : Premier Compositor
Livrables :
- input Redox ;
- surfaces shm composees ;
- curseur ;
- focus ;
- damage ;
- plusieurs fenetres simples.
Validation :
- deux clients Wayland simples s'affichent et recoivent input.
### Annee 3 : Remplacement Experimental d'Orbital
Livrables :
- integration session ;
- terminal minimal ;
- launcher minimal ;
- clipboard texte initial ;
- `xdg-shell` basique ;
- navigation clavier ;
- focus visible.
Validation :
- Orbital n'est plus requis pour une session graphique experimentale.
### Annee 4 : Stabilisation Et Migration
Livrables :
- multi-fenetre robuste ;
- tests automatiques ;
- keymap/repeat ;
- migration `winit`/`softbuffer` ou equivalent ;
- mode contraste eleve ;
- zoom logiciel basique ;
- trait renderer CPU/GPU.
Validation :
- au moins une app issue de l'ecosysteme existant Redox/Orbital tourne sur le nouveau compositor.
### Annee 5 : Smithay/COSMIC/GPU
Livrables :
- backend Smithay Redox avance ;
- premiere execution de `cosmic-comp` reduit ou demo equivalente ;
- backend GPU experimental ou rapport technique documentant les blocages ;
- service accessibilite experimental ;
- debut du serveur X11 Rust seulement si le socle est stable.
Validation :
- decision claire : continuer compositor maison, migrer vers Smithay, ou brancher COSMIC directement ;
- decision claire sur le chemin GPU : Mesa/DRM userspace, virtio-gpu, wgpu, renderer Rust dedie, ou CPU optimise.
## Definition Du MVP
Le MVP est atteint quand :
- Redox peut demarrer une session graphique sans Orbital ;
- le compositor Wayland minimal prend l'ecran ;
- un client Wayland shm s'affiche ;
- clavier et souris fonctionnent ;
- plusieurs surfaces peuvent coexister ;
- le focus fonctionne ;
- le damage fonctionne ;
- une application minimale type terminal ou launcher peut etre lancee ;
- la navigation clavier minimale fonctionne ;
- le focus visible est implemente.
## Definition Du Remplacement Orbital
Orbital peut etre considere remplace experimentalement quand :
- le boot graphique peut utiliser le compositor Wayland dans une image de test ;
- les applications de base ont une alternative Wayland ;
- le debug reste possible depuis un terminal ;
- le compositor peut etre quitte ou redemarre proprement ;
- un fallback Orbital existe pendant la transition ;
- l'utilisateur peut effectuer les actions principales au clavier.
Orbital peut etre considere remplace fonctionnellement quand :
- les composants desktop essentiels sont disponibles en Wayland ;
- les tests de session passent regulierement ;
- la gestion input/output est stable ;
- la memoire partagee ne fuit pas ;
- les crashs clients ne tuent pas le compositor ;
- les bases d'accessibilite sont presentes ;
- une strategie GPU est implementee ou documentee avec backend experimental.
## Risques Majeurs
### Primitives OS Insuffisantes
Wayland depend fortement de sockets, fd passing, shm et mmap.
Mitigation :
- traiter cette phase avant tout port COSMIC ;
- maintenir des tests bas niveau.
### GPU Trop Immature
Redox n'a pas encore de drivers GPU complets.
Mitigation :
- renderer CPU obligatoire ;
- LLVMpipe/OSMesa comme pont ;
- interface renderer stable ;
- backend GPU experimental seulement apres stabilisation.
### Scope Trop Large
COSMIC et X11 peuvent absorber plusieurs annees.
Mitigation :
- les exclure du MVP ;
- avancer par clients Wayland simples.
### Divergence Avec Wayland Standard
Un compositor maison peut devenir incompatible.
Mitigation :
- utiliser `wayland-rs` ;
- tester avec clients externes ;
- implementer `xdg-shell`.
### Accessibilite Oubliee
Un desktop impossible a utiliser sans souris serait incomplet.
Mitigation :
- navigation clavier dans le MVP ;
- focus visible obligatoire ;
- metadata accessibles definies tot.
## Crates Proposees
```text
crates/
redox-wl-compositor/
redox-wl-display/
redox-wl-input/
redox-wl-render/
redox-wl-accessibility/
redox-wl-protocol-tests/
```
## Documents A Produire Ensuite
Apres ce plan global, les documents utiles seront :
```text
docs/existing-redox-gui.md
docs/redox-wayland-primitives.md
docs/compositor-architecture.md
docs/display-backend-redox.md
docs/input-backend-redox.md
docs/software-rendering-and-gpu-roadmap.md
docs/accessibility-roadmap.md
docs/orbital-migration.md
docs/cosmic-smithay-roadmap.md
docs/x11-compat-rust-roadmap.md
```
## Ordre D'Etude Recommande
1. Lire ce document global en entier.
2. Lire le code Orbital actuel :
- `main.rs`
- `core/display.rs`
- `core/mod.rs`
- `compositor.rs`
- `scheme.rs`
- `window.rs`
3. Etudier `graphics-ipc` et `inputd`.
4. Etudier `vesad` et le chemin framebuffer Redox.
5. Etudier comment LLVMpipe/OSMesa sont integres.
6. Etudier `wayland-rs`, surtout `wayland-backend`, `wayland-server`, `wayland-client`.
7. Etudier ensuite Smithay.
8. Etudier COSMIC seulement apres les bases Wayland Redox.
## Regle De Progression
Chaque phase doit produire :
- un binaire testable ;
- une demo visible ;
- des logs exploitables ;
- au moins un test automatique quand possible ;
- une note de limitations.
Ne pas passer a COSMIC tant que le compositor minimal ne peut pas remplacer Orbital dans une image experimentale.
Ne pas lancer le serveur X11 Rust tant que le compositor Wayland ne sait pas gerer correctement surfaces, focus, damage et input.

View file

@ -0,0 +1,7 @@
[package]
name = "redox-wl-poc-pixels"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"

View file

@ -0,0 +1,310 @@
//! POC — preuve par les pixels.
//!
//! Démontre concrètement que le datapath Wayland fonctionne sur Redox :
//! - un CLIENT crée une zone shm, dessine un pattern ARGB déterministe
//! - il envoie le fd au SERVEUR via Unix socket + SCM_RIGHTS
//! - le SERVEUR mappe la zone, vérifie chaque pixel, écrit un PPM
//!
//! C'est exactement ce que fait wl_shm + wl_buffer entre un client Wayland
//! et son compositor, sans le protocole Wayland lui-même. Si ce POC passe,
//! la phase 3 (port wayland-rs) peut démarrer en confiance.
//!
//! Mono-binaire qui fork() : le child fait le client, le parent le serveur.
use std::ffi::CString;
use std::io::{self, Write};
use std::mem::{self, MaybeUninit};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::process::ExitCode;
use std::ptr;
const W: usize = 200;
const H: usize = 200;
const PIXEL_BYTES: usize = 4;
const SHM_BYTES: usize = W * H * PIXEL_BYTES;
const SHM_NAME: &str = "/redox-wl-poc-pixels";
const PPM_OUTPUT: &str = "poc-shm-pixels-readback.ppm";
/// Pattern ARGB8888 déterministe : channel A = 0xFF, R = x, G = y, B = (x+y) mod 256.
fn expected_pixel(x: usize, y: usize) -> u32 {
let a: u32 = 0xFF;
let r: u32 = (x as u32) & 0xFF;
let g: u32 = (y as u32) & 0xFF;
let b: u32 = ((x + y) as u32) & 0xFF;
(a << 24) | (r << 16) | (g << 8) | b
}
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn make_socketpair() -> Result<(OwnedFd, OwnedFd), String> {
let mut fds: [RawFd; 2] = [-1, -1];
if libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) != 0 {
return Err(format!("socketpair: {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])))
}
unsafe fn send_fd_over(socket: RawFd, fd_to_send: RawFd) -> Result<(), String> {
let mut iov_buf = [b'P'; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let cmsg = libc::CMSG_FIRSTHDR(&msg);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<RawFd>() as u32) as _;
let data_ptr = libc::CMSG_DATA(cmsg) as *mut RawFd;
ptr::write_unaligned(data_ptr, fd_to_send);
let n = libc::sendmsg(socket, &msg, 0);
if n < 0 {
return Err(format!("sendmsg: {}", errno_str()));
}
Ok(())
}
unsafe fn recv_fd_from(socket: RawFd) -> Result<OwnedFd, String> {
let mut iov_buf = [0u8; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![MaybeUninit::<u8>::uninit(); cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let n = libc::recvmsg(socket, &mut msg, 0);
if n < 0 {
return Err(format!("recvmsg: {}", errno_str()));
}
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("no cmsg received".into());
}
if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
return Err("unexpected cmsg type".into());
}
let data_ptr = libc::CMSG_DATA(cmsg) as *const RawFd;
let received_fd = ptr::read_unaligned(data_ptr);
Ok(OwnedFd::from_raw_fd(received_fd))
}
unsafe fn shm_create_and_map(name: &str, size: usize) -> Result<(OwnedFd, *mut u8), String> {
let cname = CString::new(name).unwrap();
let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR | libc::O_CREAT, 0o600);
if fd < 0 {
return Err(format!("shm_open: {}", errno_str()));
}
if libc::ftruncate(fd, size as _) != 0 {
libc::close(fd);
return Err(format!("ftruncate: {}", errno_str()));
}
let p = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if p == libc::MAP_FAILED {
libc::close(fd);
return Err(format!("mmap (create): {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fd), p as *mut u8))
}
unsafe fn mmap_existing(fd: RawFd, size: usize) -> Result<*mut u8, String> {
let p = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ,
libc::MAP_SHARED,
fd,
0,
);
if p == libc::MAP_FAILED {
return Err(format!("mmap (recv): {}", errno_str()));
}
Ok(p as *mut u8)
}
unsafe fn child_main(child_sock: OwnedFd) -> ! {
eprintln!("[poc CLIENT pid={}] start", libc::getpid());
// Crée la zone shm
let (shm_fd, map) = match shm_create_and_map(SHM_NAME, SHM_BYTES) {
Ok(v) => v,
Err(e) => {
eprintln!("[poc CLIENT] FAIL shm_create_and_map: {e}");
libc::_exit(2);
}
};
eprintln!(
"[poc CLIENT] shm '{}' size={} mapped at {:p} fd={}",
SHM_NAME, SHM_BYTES, map, shm_fd.as_raw_fd()
);
// Dessine le pattern ARGB déterministe
let pixels = std::slice::from_raw_parts_mut(map as *mut u32, W * H);
for y in 0..H {
for x in 0..W {
pixels[y * W + x] = expected_pixel(x, y);
}
}
eprintln!(
"[poc CLIENT] painted {}x{} ARGB pattern (a=0xFF, r=x, g=y, b=(x+y)&0xFF)",
W, H
);
// Envoie le fd au serveur
if let Err(e) = send_fd_over(child_sock.as_raw_fd(), shm_fd.as_raw_fd()) {
eprintln!("[poc CLIENT] FAIL sendmsg: {e}");
libc::_exit(3);
}
eprintln!("[poc CLIENT] sent shm fd via SCM_RIGHTS");
// On peut munmap + close côté client : le serveur a son propre ref via SCM_RIGHTS
libc::munmap(map as *mut _, SHM_BYTES);
drop(shm_fd);
eprintln!("[poc CLIENT] done, exit 0");
libc::_exit(0);
}
unsafe fn parent_main(parent_sock: OwnedFd, child_pid: libc::pid_t) -> Result<(), String> {
eprintln!("[poc SERVER pid={}] waiting for fd from child {}", libc::getpid(), child_pid);
let received = recv_fd_from(parent_sock.as_raw_fd())?;
let recv_raw = received.as_raw_fd();
eprintln!("[poc SERVER] received fd={recv_raw}");
let map = mmap_existing(recv_raw, SHM_BYTES)?;
eprintln!("[poc SERVER] mmap'd received fd at {:p}", map);
// Vérifie chaque pixel
let pixels = std::slice::from_raw_parts(map as *const u32, W * H);
let mut mismatches: Vec<(usize, usize, u32, u32)> = Vec::new();
for y in 0..H {
for x in 0..W {
let got = pixels[y * W + x];
let want = expected_pixel(x, y);
if got != want {
if mismatches.len() < 5 {
mismatches.push((x, y, got, want));
}
}
}
}
if !mismatches.is_empty() {
for (x, y, got, want) in &mismatches {
eprintln!(
"[poc SERVER] MISMATCH at ({x},{y}): got {got:#010x} want {want:#010x}"
);
}
return Err(format!("{} pixel mismatches over {}", mismatches.len(), W * H));
}
eprintln!(
"[poc SERVER] all {} pixels match expected pattern",
W * H
);
// Bonus : dump en PPM pour visualisation (P6 binaire RGB)
match write_ppm(PPM_OUTPUT, pixels, W, H) {
Ok(()) => eprintln!("[poc SERVER] PPM dump written to {PPM_OUTPUT}"),
Err(e) => eprintln!("[poc SERVER] WARN: PPM dump failed: {e}"),
}
libc::munmap(map as *mut _, SHM_BYTES);
drop(received);
// Reap child
let mut status: libc::c_int = 0;
let r = libc::waitpid(child_pid, &mut status, 0);
if r < 0 {
return Err(format!("waitpid: {}", errno_str()));
}
if !libc::WIFEXITED(status) || libc::WEXITSTATUS(status) != 0 {
return Err(format!(
"child exited abnormally: status={status}"
));
}
eprintln!("[poc SERVER] child reaped cleanly");
// Cleanup shm
let cname = CString::new(SHM_NAME).unwrap();
libc::shm_unlink(cname.as_ptr());
Ok(())
}
fn write_ppm(path: &str, pixels: &[u32], w: usize, h: usize) -> io::Result<()> {
let mut f = std::fs::File::create(path)?;
write!(f, "P6\n{w} {h}\n255\n")?;
let mut row = vec![0u8; w * 3];
for y in 0..h {
for x in 0..w {
let argb = pixels[y * w + x];
let r = ((argb >> 16) & 0xFF) as u8;
let g = ((argb >> 8) & 0xFF) as u8;
let b = (argb & 0xFF) as u8;
row[x * 3] = r;
row[x * 3 + 1] = g;
row[x * 3 + 2] = b;
}
f.write_all(&row)?;
}
Ok(())
}
fn run() -> Result<(), String> {
println!(
"[poc] preuve par les pixels: client {}x{} ARGB → SCM_RIGHTS → serveur",
W, H
);
unsafe {
let (sock_parent, sock_child) = make_socketpair()?;
let pid = libc::fork();
if pid < 0 {
return Err(format!("fork: {}", errno_str()));
}
if pid == 0 {
drop(sock_parent);
child_main(sock_child);
}
drop(sock_child);
parent_main(sock_parent, pid)?;
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[poc] PASS: full Wayland-like datapath validated on Redox");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[poc] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,7 @@
[package]
name = "redox-wl-test-fd-passing-fork"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"

View file

@ -0,0 +1,215 @@
//! Test 2b — fd passing via SCM_RIGHTS, multi-process via fork().
//!
//! Mono-process test (test-02) revealed the kernel reuses fd numbers and
//! does not duplicate the file table entry. This test validates whether
//! SCM_RIGHTS works correctly *across* a process boundary, which is the
//! actual Wayland use case.
//!
//! Flow:
//! 1. Parent opens a tmp file with a marker
//! 2. socketpair(AF_UNIX, SOCK_STREAM)
//! 3. fork()
//! 4. Parent: closes child end, sendmsg with the tmp_fd
//! 5. Child: closes parent end, recvmsg, reads via received fd, exits with status 0 if marker matches
//! 6. Parent: waitpid, returns child's exit status
use std::ffi::CString;
use std::io;
use std::mem::{self, MaybeUninit};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::process::ExitCode;
use std::ptr;
const MARKER: &[u8] = b"REDOX-FORK-FD-PASSING-OK-2026";
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn make_socketpair() -> Result<(OwnedFd, OwnedFd), String> {
let mut fds: [RawFd; 2] = [-1, -1];
if libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) != 0 {
return Err(format!("socketpair: {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])))
}
unsafe fn open_tmp_with_marker() -> Result<OwnedFd, String> {
let path = CString::new("/tmp/test-02b-fd-passing-fork.bin").unwrap();
let fd = libc::open(
path.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_TRUNC,
0o600,
);
if fd < 0 {
return Err(format!("open tmp: {}", errno_str()));
}
let n = libc::write(fd, MARKER.as_ptr() as *const _, MARKER.len());
if n != MARKER.len() as isize {
return Err(format!("write tmp: {}", errno_str()));
}
libc::lseek(fd, 0, libc::SEEK_SET);
Ok(OwnedFd::from_raw_fd(fd))
}
unsafe fn send_fd_over(socket: RawFd, fd_to_send: RawFd) -> Result<(), String> {
let mut iov_buf = [b'X'; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let cmsg = libc::CMSG_FIRSTHDR(&msg);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<RawFd>() as u32) as _;
let data_ptr = libc::CMSG_DATA(cmsg) as *mut RawFd;
ptr::write_unaligned(data_ptr, fd_to_send);
let n = libc::sendmsg(socket, &msg, 0);
if n < 0 {
return Err(format!("sendmsg: {}", errno_str()));
}
Ok(())
}
unsafe fn recv_fd_from(socket: RawFd) -> Result<OwnedFd, String> {
let mut iov_buf = [0u8; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![MaybeUninit::<u8>::uninit(); cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let n = libc::recvmsg(socket, &mut msg, 0);
if n < 0 {
return Err(format!("recvmsg: {}", errno_str()));
}
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("no cmsg".into());
}
if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
return Err(format!(
"unexpected cmsg level={} type={}",
(*cmsg).cmsg_level,
(*cmsg).cmsg_type
));
}
let data_ptr = libc::CMSG_DATA(cmsg) as *const RawFd;
let received_fd = ptr::read_unaligned(data_ptr);
Ok(OwnedFd::from_raw_fd(received_fd))
}
unsafe fn child_main(child_sock: OwnedFd) -> ! {
let raw_sock = child_sock.as_raw_fd();
eprintln!("[test-02b CHILD pid={}] recvmsg on sock {}", libc::getpid(), raw_sock);
let received = match recv_fd_from(raw_sock) {
Ok(fd) => fd,
Err(e) => {
eprintln!("[test-02b CHILD] recvmsg fail: {e}");
libc::_exit(2);
}
};
let recv_raw = received.as_raw_fd();
eprintln!("[test-02b CHILD] received fd={recv_raw}");
libc::lseek(recv_raw, 0, libc::SEEK_SET);
let mut readback = vec![0u8; MARKER.len()];
let n = libc::read(recv_raw, readback.as_mut_ptr() as *mut _, readback.len());
if n != MARKER.len() as isize {
eprintln!(
"[test-02b CHILD] read returned {n}, errno={}",
io::Error::last_os_error().raw_os_error().unwrap_or(0)
);
libc::_exit(3);
}
if readback != MARKER {
eprintln!(
"[test-02b CHILD] marker mismatch: {:?}",
String::from_utf8_lossy(&readback)
);
libc::_exit(4);
}
eprintln!("[test-02b CHILD] marker matches, exit 0");
libc::_exit(0);
}
fn run() -> Result<(), String> {
println!("[test-02b] fd passing via SCM_RIGHTS across fork()");
unsafe {
let (sock_parent, sock_child) = make_socketpair()?;
let tmp_fd = open_tmp_with_marker()?;
println!("[test-02b PARENT] tmp fd={}, sockets {}/{}",
tmp_fd.as_raw_fd(), sock_parent.as_raw_fd(), sock_child.as_raw_fd());
let pid = libc::fork();
if pid < 0 {
return Err(format!("fork: {}", errno_str()));
}
if pid == 0 {
// Child: drop tmp_fd, drop parent socket, run child_main on its own socket
drop(tmp_fd);
drop(sock_parent);
child_main(sock_child);
}
// Parent path
drop(sock_child);
println!("[test-02b PARENT] forked child pid={pid}, sending fd");
send_fd_over(sock_parent.as_raw_fd(), tmp_fd.as_raw_fd())?;
drop(tmp_fd);
drop(sock_parent);
let mut status: libc::c_int = 0;
let r = libc::waitpid(pid, &mut status, 0);
if r < 0 {
return Err(format!("waitpid: {}", errno_str()));
}
let exited = libc::WIFEXITED(status);
let code = libc::WEXITSTATUS(status);
println!("[test-02b PARENT] child reaped: exited={exited} code={code}");
if !exited {
return Err("child did not exit normally".into());
}
if code != 0 {
return Err(format!("child exited with status {code}"));
}
// Cleanup tmp file
let path = std::ffi::CString::new("/tmp/test-02b-fd-passing-fork.bin").unwrap();
libc::unlink(path.as_ptr());
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-02b] PASS: fd passed across fork() correctly");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-02b] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,7 @@
[package]
name = "redox-wl-test-fd-passing"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"

View file

@ -0,0 +1,195 @@
//! Test 2 — fd passing via sendmsg/recvmsg with SCM_RIGHTS.
//!
//! This is THE critical primitive for Wayland: clients pass shm fds
//! (and dmabuf fds) to the compositor through this mechanism.
//!
//! relibc Redox (platform/redox/socket.rs:272,447,835,924) declares
//! support — this test validates the actual behaviour.
//!
//! Flow:
//! 1. socketpair(AF_UNIX, SOCK_STREAM)
//! 2. open a tmp file, write a known marker into it
//! 3. sendmsg the fd via SCM_RIGHTS through socket A
//! 4. recvmsg on socket B, extract the fd
//! 5. read from the received fd, check marker matches
//!
//! Single-process test (no fork needed): we send the fd to ourselves
//! through the socketpair to validate the mechanism works end-to-end.
use std::ffi::CString;
use std::io;
use std::mem::{self, MaybeUninit};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::process::ExitCode;
use std::ptr;
const MARKER: &[u8] = b"REDOX-FD-PASSING-OK-2026";
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn make_socketpair() -> Result<(OwnedFd, OwnedFd), String> {
let mut fds: [RawFd; 2] = [-1, -1];
let r = libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr());
if r != 0 {
return Err(format!("socketpair: {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])))
}
unsafe fn open_tmp_with_marker() -> Result<OwnedFd, String> {
let path = CString::new("/tmp/test-02-fd-passing.bin").unwrap();
let fd = libc::open(
path.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_TRUNC,
0o600,
);
if fd < 0 {
return Err(format!("open tmp: {}", errno_str()));
}
let n = libc::write(fd, MARKER.as_ptr() as *const _, MARKER.len());
if n != MARKER.len() as isize {
return Err(format!("write tmp: {}", errno_str()));
}
libc::lseek(fd, 0, libc::SEEK_SET);
Ok(OwnedFd::from_raw_fd(fd))
}
unsafe fn send_fd_over(socket: RawFd, fd_to_send: RawFd) -> Result<(), String> {
let mut iov_buf = [b'X'; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("CMSG_FIRSTHDR null".into());
}
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<RawFd>() as u32) as _;
let data_ptr = libc::CMSG_DATA(cmsg) as *mut RawFd;
ptr::write_unaligned(data_ptr, fd_to_send);
let n = libc::sendmsg(socket, &msg, 0);
if n < 0 {
return Err(format!("sendmsg: {}", errno_str()));
}
println!("[test-02] sendmsg sent {n} bytes + 1 fd via SCM_RIGHTS");
Ok(())
}
unsafe fn recv_fd_from(socket: RawFd) -> Result<OwnedFd, String> {
let mut iov_buf = [0u8; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![MaybeUninit::<u8>::uninit(); cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let n = libc::recvmsg(socket, &mut msg, 0);
if n < 0 {
return Err(format!("recvmsg: {}", errno_str()));
}
println!("[test-02] recvmsg got {n} bytes + control of {} bytes", msg.msg_controllen);
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("no cmsg in received message".into());
}
if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
return Err(format!(
"unexpected cmsg level={} type={}",
(*cmsg).cmsg_level,
(*cmsg).cmsg_type
));
}
let data_ptr = libc::CMSG_DATA(cmsg) as *const RawFd;
let received_fd = ptr::read_unaligned(data_ptr);
if received_fd < 0 {
return Err(format!("received negative fd: {received_fd}"));
}
Ok(OwnedFd::from_raw_fd(received_fd))
}
fn run() -> Result<(), String> {
println!("[test-02] fd passing via SCM_RIGHTS");
unsafe {
let (sock_a, sock_b) = make_socketpair()?;
let tmp_fd = open_tmp_with_marker()?;
println!("[test-02] tmp fd = {}", tmp_fd.as_raw_fd());
// Strategy: send a dup'd fd, then close BOTH the original and the dup.
// If SCM_RIGHTS works per POSIX, the received fd is an independent
// reference to the open file and must remain readable.
let dup_fd = libc::dup(tmp_fd.as_raw_fd());
if dup_fd < 0 {
return Err(format!("dup: {}", errno_str()));
}
println!("[test-02] dup'd tmp_fd ({}) -> {dup_fd}", tmp_fd.as_raw_fd());
send_fd_over(sock_a.as_raw_fd(), dup_fd)?;
let received = recv_fd_from(sock_b.as_raw_fd())?;
let recv_raw = received.as_raw_fd();
println!("[test-02] received fd = {recv_raw}");
// Close BOTH the original and the dup. If SCM_RIGHTS gave us a real
// duplicate, the received fd must survive.
let orig_raw = tmp_fd.as_raw_fd();
drop(tmp_fd);
libc::close(dup_fd);
println!("[test-02] closed original fd ({orig_raw}) and dup ({dup_fd}); reading via received only");
libc::lseek(recv_raw, 0, libc::SEEK_SET);
let mut readback = vec![0u8; MARKER.len()];
let n = libc::read(recv_raw, readback.as_mut_ptr() as *mut _, readback.len());
if n != MARKER.len() as isize {
return Err(format!("read from received fd: only {n} bytes (errno {})",
io::Error::last_os_error().raw_os_error().unwrap_or(0)));
}
if readback != MARKER {
return Err(format!("marker mismatch: got {:?}", String::from_utf8_lossy(&readback)));
}
println!("[test-02] read {} bytes via received fd: marker matches", n);
// Cleanup tmp file
let path = std::ffi::CString::new("/tmp/test-02-fd-passing.bin").unwrap();
libc::unlink(path.as_ptr());
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-02] PASS: fd passing works, received fd reads correct marker");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-02] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,9 @@
[package]
name = "redox-wl-test-handshake"
version = "0.1.0"
edition = "2021"
[dependencies]
wayland-server = { path = "../../../wayland-rs/wayland-server", default-features = false }
wayland-client = { path = "../../../wayland-rs/wayland-client", default-features = false }
wayland-backend = { path = "../../../wayland-rs/wayland-backend", default-features = false }

View file

@ -0,0 +1,200 @@
//! Test 5 — Wayland handshake server/client sur Redox.
//!
//! Valide que wayland-rs (server + client + backend) ne se contente pas de
//! compiler pour Redox mais fonctionne réellement à l'exécution :
//!
//! 1. Serveur Wayland local + globals (wl_compositor, wl_shm)
//! 2. socketpair AF_UNIX, on insère le côté serveur via insert_client
//! 3. Client Wayland connecté sur l'autre côté via WAYLAND_SOCKET fd env
//! 4. Client fait wl_display.get_registry, dispatch
//! 5. On vérifie que les globals attendus arrivent côté client
//!
//! Si ça passe, le port wayland-rs sur Redox est validé runtime, pas juste
//! compile-time.
use std::os::unix::net::UnixStream;
use std::process::ExitCode;
use std::sync::Arc;
use wayland_client::{
Connection as ClientConnection, Dispatch as ClientDispatch, EventQueue,
backend::Backend as ClientBackend,
protocol::wl_registry,
};
use wayland_server::{
Display as ServerDisplay, GlobalDispatch, New,
backend::ClientData,
protocol::{wl_compositor, wl_shm},
};
// ---- Server side ----
struct ServerState;
#[derive(Debug)]
struct DumbClientData;
impl ClientData for DumbClientData {}
impl GlobalDispatch<wl_compositor::WlCompositor, ()> for ServerState {
fn bind(
_state: &mut Self,
_handle: &wayland_server::DisplayHandle,
_client: &wayland_server::Client,
_resource: New<wl_compositor::WlCompositor>,
_data: &(),
_data_init: &mut wayland_server::DataInit<'_, Self>,
) {
}
}
impl wayland_server::Dispatch<wl_compositor::WlCompositor, ()> for ServerState {
fn request(
_state: &mut Self,
_client: &wayland_server::Client,
_resource: &wl_compositor::WlCompositor,
_request: wl_compositor::Request,
_data: &(),
_dh: &wayland_server::DisplayHandle,
_data_init: &mut wayland_server::DataInit<'_, Self>,
) {
}
}
impl GlobalDispatch<wl_shm::WlShm, ()> for ServerState {
fn bind(
_state: &mut Self,
_handle: &wayland_server::DisplayHandle,
_client: &wayland_server::Client,
_resource: New<wl_shm::WlShm>,
_data: &(),
_data_init: &mut wayland_server::DataInit<'_, Self>,
) {
}
}
impl wayland_server::Dispatch<wl_shm::WlShm, ()> for ServerState {
fn request(
_state: &mut Self,
_client: &wayland_server::Client,
_resource: &wl_shm::WlShm,
_request: wl_shm::Request,
_data: &(),
_dh: &wayland_server::DisplayHandle,
_data_init: &mut wayland_server::DataInit<'_, Self>,
) {
}
}
// ---- Client side ----
#[derive(Default, Debug)]
struct ClientState {
globals: Vec<(u32, String, u32)>,
done: bool,
}
impl ClientDispatch<wl_registry::WlRegistry, ()> for ClientState {
fn event(
state: &mut Self,
_registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_data: &(),
_conn: &ClientConnection,
_qhandle: &wayland_client::QueueHandle<Self>,
) {
if let wl_registry::Event::Global { name, interface, version } = event {
println!("[test-05 CLIENT] global #{name}: {interface} v{version}");
state.globals.push((name, interface, version));
}
}
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
println!("[test-05] wayland-rs handshake server/client on Redox");
// -- Server setup --
let mut server_display: ServerDisplay<ServerState> = ServerDisplay::new()?;
let mut dh = server_display.handle();
dh.create_global::<ServerState, wl_compositor::WlCompositor, _>(5, ());
dh.create_global::<ServerState, wl_shm::WlShm, _>(1, ());
println!("[test-05 SERVER] created wl_compositor v5 + wl_shm v1 globals");
// -- Socket linking --
let (s_server, s_client) = UnixStream::pair()?;
s_server.set_nonblocking(true)?;
let _client_handle = dh.insert_client(s_server, Arc::new(DumbClientData))?;
println!("[test-05] linked server side via insert_client");
// -- Client setup using the other end of the socketpair --
let client_backend = ClientBackend::connect(s_client)?;
let client_conn = ClientConnection::from_backend(client_backend);
let mut event_queue: EventQueue<ClientState> = client_conn.new_event_queue();
let qhandle = event_queue.handle();
let _registry = client_conn.display().get_registry(&qhandle, ());
println!("[test-05 CLIENT] called get_registry, dispatching...");
let mut server_state = ServerState;
let mut client_state = ClientState::default();
// -- Roundtrip: dispatch alternately until client has events --
for round in 0..10 {
// server side
server_display.dispatch_clients(&mut server_state)?;
server_display.flush_clients()?;
// client side
let _ = event_queue.flush();
let processed = event_queue.dispatch_pending(&mut client_state)?;
if processed > 0 {
println!("[test-05] round {round}: client processed {processed} event(s)");
}
if !client_state.globals.is_empty() {
client_state.done = true;
break;
}
// Block briefly waiting for incoming events on client side
match event_queue.prepare_read() {
Some(read_guard) => {
let _ = read_guard.read();
}
None => {}
}
}
if !client_state.done || client_state.globals.is_empty() {
return Err(format!(
"client did not receive globals (got {})",
client_state.globals.len()
)
.into());
}
let names: Vec<&str> = client_state.globals.iter().map(|(_, n, _)| n.as_str()).collect();
let has_compositor = names.contains(&"wl_compositor");
let has_shm = names.contains(&"wl_shm");
if !has_compositor || !has_shm {
return Err(format!("missing expected globals: got {:?}", names).into());
}
println!(
"[test-05] received {} global(s): {:?}",
client_state.globals.len(),
names
);
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-05] PASS: wayland-rs server/client handshake works on Redox");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-05] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,7 @@
[package]
name = "redox-wl-test-poll-multifd"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"

View file

@ -0,0 +1,120 @@
//! Test 4 — poll() over multiple fds.
//!
//! A Wayland compositor multiplexes many fds: the listening socket,
//! each connected client, the input device(s), idle timers, signals.
//! This test validates that poll() correctly demultiplexes.
//!
//! Setup: 3 pipes (a, b, c). We write into b only, then poll all 3.
//! Only fd_b should report POLLIN. We read it. Then we close a, expecting
//! POLLHUP on a-read side. Finally we test timeout-only behaviour.
use std::io;
use std::os::fd::RawFd;
use std::process::ExitCode;
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn make_pipe() -> Result<(RawFd, RawFd), String> {
let mut fds: [RawFd; 2] = [-1, -1];
if libc::pipe(fds.as_mut_ptr()) != 0 {
return Err(format!("pipe: {}", errno_str()));
}
Ok((fds[0], fds[1]))
}
unsafe fn poll_revents(fds: &mut [libc::pollfd], timeout_ms: i32) -> Result<i32, String> {
let r = libc::poll(fds.as_mut_ptr(), fds.len() as _, timeout_ms);
if r < 0 {
return Err(format!("poll: {}", errno_str()));
}
Ok(r)
}
fn run() -> Result<(), String> {
println!("[test-04] poll() multi-fd multiplexing");
unsafe {
// 3 pipes; we'll only watch the read ends.
let (ra, wa) = make_pipe()?;
let (rb, wb) = make_pipe()?;
let (rc, wc) = make_pipe()?;
println!("[test-04] pipes: ra={ra} rb={rb} rc={rc}");
// ---- Subtest A: timeout-only, no fd ready ----
let mut pfds = [
libc::pollfd { fd: ra, events: libc::POLLIN, revents: 0 },
libc::pollfd { fd: rb, events: libc::POLLIN, revents: 0 },
libc::pollfd { fd: rc, events: libc::POLLIN, revents: 0 },
];
let n = poll_revents(&mut pfds, 50)?;
if n != 0 {
return Err(format!("expected timeout (0), got {n}"));
}
println!("[test-04] A: timeout 50ms with no data → poll returned 0 OK");
// ---- Subtest B: write into pipe B only ----
let payload = b"X";
let n = libc::write(wb, payload.as_ptr() as *const _, 1);
if n != 1 {
return Err(format!("write to wb: {}", errno_str()));
}
for p in pfds.iter_mut() { p.revents = 0; }
let n = poll_revents(&mut pfds, 1000)?;
if n != 1 {
return Err(format!("expected 1 ready fd, got {n}"));
}
let ready: Vec<_> = pfds.iter().enumerate()
.filter(|(_, p)| p.revents & libc::POLLIN != 0)
.map(|(i, _)| i)
.collect();
if ready != [1] {
return Err(format!("expected only index 1 (rb) ready, got {ready:?}"));
}
println!("[test-04] B: poll detected only rb (index 1) ready, as expected");
let mut buf = [0u8; 1];
let r = libc::read(rb, buf.as_mut_ptr() as *mut _, 1);
if r != 1 || buf[0] != b'X' {
return Err(format!("read rb: r={r} buf={:?}", buf));
}
println!("[test-04] B: read 1 byte 'X' from rb OK");
// ---- Subtest C: close write end of A, expect HUP on read end ----
libc::close(wa);
for p in pfds.iter_mut() { p.revents = 0; }
let n = poll_revents(&mut pfds, 100)?;
if n < 1 {
return Err(format!("expected at least 1 ready (POLLHUP on ra), got {n}"));
}
let hup_a = pfds[0].revents & (libc::POLLHUP | libc::POLLIN) != 0;
if !hup_a {
return Err(format!("expected HUP/IN on ra, revents={:#x}", pfds[0].revents));
}
println!("[test-04] C: closing wa caused POLLHUP/POLLIN on ra (revents={:#x})",
pfds[0].revents);
// Cleanup
libc::close(ra);
libc::close(rb); libc::close(wb);
libc::close(rc); libc::close(wc);
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-04] PASS: poll() multiplexes correctly across fds and detects HUP");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-04] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,7 @@
[package]
name = "redox-wl-test-shm-open"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"

View file

@ -0,0 +1,142 @@
//! Test 3 — shm_open + mmap MAP_SHARED.
//!
//! Wayland clients use shm_open + ftruncate + mmap to create wl_shm pools.
//! The compositor then receives the fd via SCM_RIGHTS and mmaps the same region.
//!
//! relibc Redox maps shm_open("/foo") to open("/scheme/shm/foo").
//! This test validates:
//! 1. shm_open with O_RDWR | O_CREAT works
//! 2. ftruncate sets the size
//! 3. mmap MAP_SHARED returns a writable region
//! 4. writes are persistent: close + reopen + remap reads back the data
//! 5. shm_unlink removes the segment
//!
//! Two passes (write then read-back) simulate the producer/consumer pattern
//! used by Wayland: client writes pixels, compositor reads them.
use std::ffi::CString;
use std::io;
use std::os::fd::RawFd;
use std::process::ExitCode;
use std::ptr;
const SHM_NAME: &str = "/redox-wl-test-03";
const SIZE: usize = 4096;
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn shm_open_rw_create(name: &str, mode: libc::mode_t) -> Result<RawFd, String> {
let cname = CString::new(name).unwrap();
let fd = libc::shm_open(
cname.as_ptr(),
libc::O_RDWR | libc::O_CREAT,
mode as _,
);
if fd < 0 {
return Err(format!("shm_open({name}): {}", errno_str()));
}
Ok(fd)
}
unsafe fn shm_open_rw(name: &str) -> Result<RawFd, String> {
let cname = CString::new(name).unwrap();
let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR, 0);
if fd < 0 {
return Err(format!("shm_open(reopen {name}): {}", errno_str()));
}
Ok(fd)
}
unsafe fn map_rw(fd: RawFd, size: usize) -> Result<*mut u8, String> {
let p = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if p == libc::MAP_FAILED {
return Err(format!("mmap MAP_SHARED: {}", errno_str()));
}
Ok(p as *mut u8)
}
fn run() -> Result<(), String> {
println!("[test-03] shm_open + mmap MAP_SHARED");
unsafe {
// Phase 1: create + write
let fd1 = shm_open_rw_create(SHM_NAME, 0o600)?;
println!("[test-03] phase1: shm_open created, fd={fd1}");
if libc::ftruncate(fd1, SIZE as _) != 0 {
return Err(format!("ftruncate: {}", errno_str()));
}
let map1 = map_rw(fd1, SIZE)?;
println!("[test-03] phase1: mmap at {:p}", map1);
// Write a recognisable pattern (ARGB8888 simulation: 256 pixels of 0xDEADBEEF)
let pixels = std::slice::from_raw_parts_mut(map1 as *mut u32, SIZE / 4);
for (i, p) in pixels.iter_mut().enumerate() {
*p = 0xDEAD_BEEF_u32.wrapping_add(i as u32);
}
println!("[test-03] phase1: wrote {} u32 pixels", pixels.len());
if libc::munmap(map1 as *mut _, SIZE) != 0 {
return Err(format!("munmap phase1: {}", errno_str()));
}
libc::close(fd1);
// Phase 2: reopen + check pattern
let fd2 = shm_open_rw(SHM_NAME)?;
println!("[test-03] phase2: shm_open reopened, fd={fd2}");
let map2 = map_rw(fd2, SIZE)?;
println!("[test-03] phase2: mmap at {:p}", map2);
let pixels2 = std::slice::from_raw_parts(map2 as *const u32, SIZE / 4);
for (i, &p) in pixels2.iter().enumerate() {
let expected = 0xDEAD_BEEF_u32.wrapping_add(i as u32);
if p != expected {
libc::munmap(map2 as *mut _, SIZE);
libc::close(fd2);
let cname = CString::new(SHM_NAME).unwrap();
libc::shm_unlink(cname.as_ptr());
return Err(format!("pixel {i} mismatch: got {p:#x}, expected {expected:#x}"));
}
}
println!("[test-03] phase2: pattern verified across close/reopen");
if libc::munmap(map2 as *mut _, SIZE) != 0 {
return Err(format!("munmap phase2: {}", errno_str()));
}
libc::close(fd2);
// Cleanup
let cname = CString::new(SHM_NAME).unwrap();
if libc::shm_unlink(cname.as_ptr()) != 0 {
return Err(format!("shm_unlink: {}", errno_str()));
}
println!("[test-03] cleanup: shm_unlink OK");
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-03] PASS: shm_open/mmap/persistence/unlink all work");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-03] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,10 @@
[package]
name = "redox-wl-test-shm-pipeline"
version = "0.1.0"
edition = "2021"
[dependencies]
wayland-server = { path = "../../../wayland-rs/wayland-server", default-features = false }
wayland-client = { path = "../../../wayland-rs/wayland-client", default-features = false }
wayland-backend = { path = "../../../wayland-rs/wayland-backend", default-features = false }
libc = "0.2"

View file

@ -0,0 +1,589 @@
//! Test 6 — Pipeline complet wayland-rs sur Redox.
//!
//! C'est ce que fait un compositor Wayland minimal :
//! - serveur expose un socket Unix (ListeningSocket)
//! - client se connecte via WAYLAND_SOCKET (vrai pattern Wayland)
//! - client crée wl_shm_pool depuis un fd shm
//! - client crée wl_buffer depuis le pool, peint un pattern ARGB
//! - client crée wl_surface, attach, damage, commit
//! - serveur reçoit le commit, mappe le buffer fd, lit les pixels, vérifie
//!
//! Si ce test passe, le pipeline shm Wayland complet fonctionne sur Redox.
//! C'est la preuve technique que la phase 4-7 du plan directeur (display
//! backend + input + surfaces shm + compositor minimal) est réalisable
//! sans surprise majeure.
use std::ffi::CString;
use std::io;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd};
use std::os::unix::net::{UnixListener, UnixStream};
use std::process::ExitCode;
use std::ptr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use wayland_client::{
Connection as ClientConnection, Dispatch as ClientDispatch, EventQueue, Proxy as _,
QueueHandle as ClientQueueHandle,
backend::Backend as ClientBackend,
protocol::{
wl_buffer::WlBuffer as ClientBuffer, wl_compositor::WlCompositor as ClientCompositor,
wl_registry, wl_shm::WlShm as ClientShm, wl_shm_pool::WlShmPool as ClientShmPool,
wl_surface::WlSurface as ClientSurface,
},
};
use wayland_server::{
Client, DataInit, Display as ServerDisplay, DisplayHandle, GlobalDispatch,
backend::ClientData,
protocol::{
wl_buffer, wl_callback, wl_compositor, wl_shm, wl_shm_pool, wl_surface,
},
};
const W: i32 = 100;
const H: i32 = 100;
const STRIDE: i32 = W * 4;
const POOL_SIZE: i32 = STRIDE * H;
const SOCKET_NAME: &str = "wayland-test-06";
fn expected_pixel(x: i32, y: i32) -> u32 {
let a: u32 = 0xFF;
let r: u32 = (x as u32) & 0xFF;
let g: u32 = (y as u32) & 0xFF;
let b: u32 = ((x + y) as u32) & 0xFF;
(a << 24) | (r << 16) | (g << 8) | b
}
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
// =====================================================================
// Server side
// =====================================================================
#[derive(Default)]
struct ServerObservation {
surface_created: bool,
pool_created_with_fd: Option<i32>,
buffer_created: Option<(i32, i32)>, // (w, h)
surface_attached: bool,
surface_committed: bool,
/// Set by main loop after reading pixels successfully
pixels_verified: bool,
}
struct ServerState {
obs: Arc<Mutex<ServerObservation>>,
}
#[derive(Debug)]
struct DumbClientData;
impl ClientData for DumbClientData {}
// ---- wl_compositor ----
impl GlobalDispatch<wl_compositor::WlCompositor, ()> for ServerState {
fn bind(
_state: &mut Self,
_handle: &DisplayHandle,
_client: &Client,
resource: wayland_server::New<wl_compositor::WlCompositor>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl wayland_server::Dispatch<wl_compositor::WlCompositor, ()> for ServerState {
fn request(
state: &mut Self,
_client: &Client,
_resource: &wl_compositor::WlCompositor,
request: wl_compositor::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
if let wl_compositor::Request::CreateSurface { id } = request {
data_init.init(id, ());
state.obs.lock().unwrap().surface_created = true;
eprintln!("[server] wl_compositor.create_surface");
}
}
}
// ---- wl_shm ----
impl GlobalDispatch<wl_shm::WlShm, ()> for ServerState {
fn bind(
_state: &mut Self,
_handle: &DisplayHandle,
_client: &Client,
resource: wayland_server::New<wl_shm::WlShm>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
let shm = data_init.init(resource, ());
// Advertise the format
shm.format(wl_shm::Format::Argb8888);
}
}
impl wayland_server::Dispatch<wl_shm::WlShm, ()> for ServerState {
fn request(
state: &mut Self,
_client: &Client,
_resource: &wl_shm::WlShm,
request: wl_shm::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
if let wl_shm::Request::CreatePool { id, fd, size } = request {
let raw_fd = fd.as_raw_fd();
// Dup the fd so we keep it after the OwnedFd in the request is dropped
let our_fd = unsafe { libc::dup(raw_fd) };
state.obs.lock().unwrap().pool_created_with_fd = Some(our_fd);
data_init.init(id, PoolData { fd: our_fd, size });
eprintln!("[server] wl_shm.create_pool fd={raw_fd} size={size} (server dup={our_fd})");
}
}
}
// ---- wl_shm_pool ----
struct PoolData {
fd: i32,
size: i32,
}
impl wayland_server::Dispatch<wl_shm_pool::WlShmPool, PoolData> for ServerState {
fn request(
state: &mut Self,
_client: &Client,
_resource: &wl_shm_pool::WlShmPool,
request: wl_shm_pool::Request,
_data: &PoolData,
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
if let wl_shm_pool::Request::CreateBuffer {
id,
offset,
width,
height,
stride,
format,
} = request
{
data_init.init(id, ());
state.obs.lock().unwrap().buffer_created = Some((width, height));
eprintln!(
"[server] wl_shm_pool.create_buffer offset={offset} {width}x{height} stride={stride} format={format:?}"
);
}
}
}
// ---- wl_buffer ----
impl wayland_server::Dispatch<wl_buffer::WlBuffer, ()> for ServerState {
fn request(
_state: &mut Self,
_client: &Client,
_resource: &wl_buffer::WlBuffer,
_request: wl_buffer::Request,
_data: &(),
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
}
}
// ---- wl_surface ----
impl wayland_server::Dispatch<wl_surface::WlSurface, ()> for ServerState {
fn request(
state: &mut Self,
_client: &Client,
_resource: &wl_surface::WlSurface,
request: wl_surface::Request,
_data: &(),
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
wl_surface::Request::Attach { .. } => {
state.obs.lock().unwrap().surface_attached = true;
eprintln!("[server] wl_surface.attach");
}
wl_surface::Request::Damage { .. }
| wl_surface::Request::DamageBuffer { .. } => {
eprintln!("[server] wl_surface.damage");
}
wl_surface::Request::Commit => {
state.obs.lock().unwrap().surface_committed = true;
eprintln!("[server] wl_surface.commit");
}
_ => {}
}
}
}
impl wayland_server::Dispatch<wl_callback::WlCallback, ()> for ServerState {
fn request(
_state: &mut Self,
_client: &Client,
_resource: &wl_callback::WlCallback,
_request: wl_callback::Request,
_data: &(),
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
}
}
// =====================================================================
// Client side
// =====================================================================
#[derive(Default)]
struct ClientState {
compositor: Option<ClientCompositor>,
shm: Option<ClientShm>,
}
impl ClientDispatch<wl_registry::WlRegistry, ()> for ClientState {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_data: &(),
_conn: &ClientConnection,
qh: &ClientQueueHandle<Self>,
) {
if let wl_registry::Event::Global { name, interface, version } = event {
eprintln!("[client] global #{name}: {interface} v{version}");
match interface.as_str() {
"wl_compositor" => {
state.compositor = Some(registry.bind(name, version, qh, ()));
}
"wl_shm" => {
state.shm = Some(registry.bind(name, version, qh, ()));
}
_ => {}
}
}
}
}
macro_rules! noop_dispatch {
($iface:ty) => {
impl ClientDispatch<$iface, ()> for ClientState {
fn event(
_state: &mut Self,
_r: &$iface,
_event: <$iface as Proxy>::Event,
_data: &(),
_conn: &ClientConnection,
_qh: &ClientQueueHandle<Self>,
) {
}
}
};
}
use wayland_client::Proxy;
noop_dispatch!(ClientCompositor);
noop_dispatch!(ClientShm);
noop_dispatch!(ClientShmPool);
noop_dispatch!(ClientBuffer);
noop_dispatch!(ClientSurface);
// =====================================================================
// Process orchestration
// =====================================================================
unsafe fn create_shm_buffer(name: &str, size: usize) -> Result<(OwnedFd, *mut u8), String> {
let cname = CString::new(name).unwrap();
let _ = libc::shm_unlink(cname.as_ptr());
let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR | libc::O_CREAT, 0o600);
if fd < 0 {
return Err(format!("shm_open: {}", errno_str()));
}
if libc::ftruncate(fd, size as _) != 0 {
libc::close(fd);
return Err(format!("ftruncate: {}", errno_str()));
}
let p = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if p == libc::MAP_FAILED {
libc::close(fd);
return Err(format!("mmap: {}", errno_str()));
}
Ok((unsafe { OwnedFd::from_raw_fd(fd) }, p as *mut u8))
}
use std::os::fd::FromRawFd;
unsafe fn child_main(server_path: &str) -> ! {
eprintln!("[client pid={}] connecting to {server_path}", libc::getpid());
// Connect to the server socket
let stream = match UnixStream::connect(server_path) {
Ok(s) => s,
Err(e) => {
eprintln!("[client] connect: {e}");
libc::_exit(2);
}
};
let fd = stream.into_raw_fd();
// wayland-client connect_to_env reads WAYLAND_SOCKET
std::env::set_var("WAYLAND_SOCKET", fd.to_string());
let conn = match ClientConnection::connect_to_env() {
Ok(c) => c,
Err(e) => {
eprintln!("[client] connect_to_env: {e}");
libc::_exit(3);
}
};
let mut event_queue: EventQueue<ClientState> = conn.new_event_queue();
let qh = event_queue.handle();
let _registry = conn.display().get_registry(&qh, ());
let mut state = ClientState::default();
// Roundtrip to receive globals
if let Err(e) = event_queue.roundtrip(&mut state) {
eprintln!("[client] roundtrip 1: {e}");
libc::_exit(4);
}
if state.compositor.is_none() || state.shm.is_none() {
eprintln!("[client] missing compositor/shm globals");
libc::_exit(5);
}
eprintln!("[client] got compositor + shm globals");
// Create the shm buffer
let (shm_fd, map) = match create_shm_buffer("/wl-test-06-buffer", POOL_SIZE as usize) {
Ok(v) => v,
Err(e) => {
eprintln!("[client] shm: {e}");
libc::_exit(6);
}
};
// Paint pattern
let pixels = std::slice::from_raw_parts_mut(map as *mut u32, (W * H) as usize);
for y in 0..H {
for x in 0..W {
pixels[(y * W + x) as usize] = expected_pixel(x, y);
}
}
eprintln!("[client] painted {}x{} ARGB pattern in shm", W, H);
// Create wl_shm_pool from the fd
let shm = state.shm.clone().unwrap();
let pool = shm.create_pool(shm_fd.as_fd(), POOL_SIZE, &qh, ());
// Create wl_buffer
let buffer = pool.create_buffer(
0, W, H, STRIDE,
wayland_client::protocol::wl_shm::Format::Argb8888,
&qh, (),
);
// Create wl_surface
let compositor = state.compositor.clone().unwrap();
let surface = compositor.create_surface(&qh, ());
// attach, damage, commit
surface.attach(Some(&buffer), 0, 0);
surface.damage_buffer(0, 0, W, H);
surface.commit();
eprintln!("[client] attached + damaged + committed");
// Flush all pending writes
if let Err(e) = event_queue.flush() {
eprintln!("[client] flush: {e}");
libc::_exit(7);
}
// Roundtrip to ensure server has processed everything
let _ = event_queue.roundtrip(&mut state);
eprintln!("[client] done, exit 0");
libc::_exit(0);
}
fn run() -> Result<(), String> {
println!("[test-06] Wayland shm pipeline on Redox");
// Pick a socket path. We won't use bind_auto because XDG_RUNTIME_DIR
// is not guaranteed inside the redoxer mini-VM.
let socket_path = format!("/tmp/{SOCKET_NAME}.sock");
let _ = std::fs::remove_file(&socket_path);
let _ = std::fs::remove_file(format!("/tmp/{SOCKET_NAME}.sock.lock"));
let listener = UnixListener::bind(&socket_path)
.map_err(|e| format!("UnixListener::bind({socket_path}): {e}"))?;
listener
.set_nonblocking(true)
.map_err(|e| format!("set_nonblocking: {e}"))?;
println!("[server] listening on {socket_path}");
let obs = Arc::new(Mutex::new(ServerObservation::default()));
let mut display: ServerDisplay<ServerState> = ServerDisplay::new()
.map_err(|e| format!("Display::new: {e:?}"))?;
let mut dh = display.handle();
dh.create_global::<ServerState, wl_compositor::WlCompositor, _>(5, ());
dh.create_global::<ServerState, wl_shm::WlShm, _>(1, ());
// Fork the client
let pid = unsafe { libc::fork() };
if pid < 0 {
return Err(format!("fork: {}", errno_str()));
}
if pid == 0 {
unsafe { child_main(&socket_path) };
}
let mut state = ServerState { obs: obs.clone() };
let start = Instant::now();
let timeout = Duration::from_secs(5);
let mut accepted_pid: Option<libc::pid_t> = None;
while start.elapsed() < timeout {
// Accept new client
if accepted_pid.is_none() {
match listener.accept() {
Ok((stream, _addr)) => {
stream.set_nonblocking(true).ok();
dh.insert_client(stream, Arc::new(DumbClientData))
.map_err(|e| format!("insert_client: {e:?}"))?;
accepted_pid = Some(pid);
println!("[server] accepted client connection");
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(e) => return Err(format!("accept: {e}")),
}
}
display
.dispatch_clients(&mut state)
.map_err(|e| format!("dispatch_clients: {e:?}"))?;
display
.flush_clients()
.map_err(|e| format!("flush_clients: {e:?}"))?;
let snap = obs.lock().unwrap();
if snap.surface_committed && snap.pool_created_with_fd.is_some() {
// We have all we need to verify pixels
break;
}
drop(snap);
std::thread::sleep(Duration::from_millis(10));
}
// Capture observation state
let surface_committed;
let buffer_created;
let surface_attached;
let pool_fd;
{
let s = obs.lock().unwrap();
surface_committed = s.surface_committed;
buffer_created = s.buffer_created;
surface_attached = s.surface_attached;
pool_fd = s.pool_created_with_fd;
}
if !surface_committed {
return Err("server did not observe wl_surface.commit within 5s".into());
}
if buffer_created.is_none() {
return Err("server did not observe wl_shm_pool.create_buffer".into());
}
if !surface_attached {
return Err("server did not observe wl_surface.attach".into());
}
let pool_fd = pool_fd.ok_or("server did not receive shm fd")?;
println!("[server] all expected protocol events observed; mapping fd to verify pixels");
// Map the received fd and verify pixels
let map = unsafe {
libc::mmap(
ptr::null_mut(),
POOL_SIZE as usize,
libc::PROT_READ,
libc::MAP_SHARED,
pool_fd,
0,
)
};
if map == libc::MAP_FAILED {
return Err(format!("server mmap: {}", errno_str()));
}
let pixels = unsafe { std::slice::from_raw_parts(map as *const u32, (W * H) as usize) };
let mut mismatches: Vec<(i32, i32, u32, u32)> = Vec::new();
for y in 0..H {
for x in 0..W {
let got = pixels[(y * W + x) as usize];
let want = expected_pixel(x, y);
if got != want && mismatches.len() < 5 {
mismatches.push((x, y, got, want));
}
}
}
unsafe { libc::munmap(map, POOL_SIZE as usize) };
if !mismatches.is_empty() {
for (x, y, got, want) in &mismatches {
eprintln!("[server] mismatch at ({x},{y}): got {got:#010x} want {want:#010x}");
}
return Err(format!("{} pixel mismatches in {} total", mismatches.len(), W * H));
}
println!(
"[server] all {} pixels match expected pattern (read via fd received through Wayland protocol)",
W * H
);
// Reap child
let mut status: libc::c_int = 0;
unsafe { libc::waitpid(pid, &mut status, 0) };
let exit_code = unsafe { libc::WEXITSTATUS(status) };
if exit_code != 0 {
return Err(format!("child exited with status {exit_code}"));
}
println!("[server] child reaped cleanly");
// Cleanup
let _ = std::fs::remove_file(&socket_path);
let _ = std::fs::remove_file(format!("{socket_path}.lock"));
let cname = CString::new("/wl-test-06-buffer").unwrap();
unsafe { libc::shm_unlink(cname.as_ptr()) };
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[test-06] PASS: full Wayland shm pipeline works on Redox");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[test-06] FAIL: {e}");
ExitCode::FAILURE
}
}
}

View file

@ -0,0 +1,7 @@
[package]
name = "redox-wl-test-unix-socket"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"

View file

@ -0,0 +1,78 @@
//! Test 1 — Unix socket SOCK_STREAM with Wayland-shaped messages.
//!
//! Wayland wire format (libwayland reference):
//! - Header: object_id (u32) + (size << 16 | opcode) (u32) = 8 bytes
//! - Payload: variable, 4-byte aligned
//!
//! This test:
//! 1. Creates a SOCK_STREAM socketpair (AF_UNIX)
//! 2. Side A sends a message with header + payload
//! 3. Side B reads it back, validates byte-for-byte
//! 4. Ensures partial reads don't corrupt the stream
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::process::ExitCode;
const OBJECT_ID: u32 = 0x0000_0001;
const OPCODE: u16 = 0x000A;
const PAYLOAD: &[u8] = b"hello-wayland-on-redox-roundtrip";
fn main() -> ExitCode {
println!("[test-01] Unix socket SOCK_STREAM Wayland-shaped roundtrip");
let (mut a, mut b) = match UnixStream::pair() {
Ok(pair) => pair,
Err(e) => {
eprintln!("[test-01] FAIL: UnixStream::pair: {e}");
return ExitCode::FAILURE;
}
};
let payload_len = PAYLOAD.len();
let aligned_len = (payload_len + 3) & !3;
let total_size = 8 + aligned_len;
let mut msg = Vec::with_capacity(total_size);
msg.extend_from_slice(&OBJECT_ID.to_ne_bytes());
let size_op: u32 = ((total_size as u32) << 16) | (OPCODE as u32);
msg.extend_from_slice(&size_op.to_ne_bytes());
msg.extend_from_slice(PAYLOAD);
msg.resize(total_size, 0);
if let Err(e) = a.write_all(&msg) {
eprintln!("[test-01] FAIL: write_all: {e}");
return ExitCode::FAILURE;
}
println!("[test-01] sent {} bytes (header 8 + payload {} aligned to {})",
total_size, payload_len, aligned_len);
let mut header = [0u8; 8];
if let Err(e) = b.read_exact(&mut header) {
eprintln!("[test-01] FAIL: read header: {e}");
return ExitCode::FAILURE;
}
let recv_oid = u32::from_ne_bytes(header[0..4].try_into().unwrap());
let recv_so = u32::from_ne_bytes(header[4..8].try_into().unwrap());
let recv_size = (recv_so >> 16) as usize;
let recv_op = (recv_so & 0xFFFF) as u16;
if recv_oid != OBJECT_ID || recv_op != OPCODE || recv_size != total_size {
eprintln!("[test-01] FAIL: header mismatch oid={recv_oid:#x} op={recv_op:#x} size={recv_size}");
return ExitCode::FAILURE;
}
let mut buf = vec![0u8; aligned_len];
if let Err(e) = b.read_exact(&mut buf) {
eprintln!("[test-01] FAIL: read payload: {e}");
return ExitCode::FAILURE;
}
if &buf[..payload_len] != PAYLOAD {
eprintln!("[test-01] FAIL: payload mismatch");
return ExitCode::FAILURE;
}
println!("[test-01] PASS: roundtrip OK, {} bytes recv, oid={:#x} op={:#x}",
total_size, recv_oid, recv_op);
ExitCode::SUCCESS
}

486
docs/existing-redox-gui.md Normal file
View file

@ -0,0 +1,486 @@
# Audit Phase 1 — Existant Redox GUI
> Document produit le 2026-05-08 dans le cadre du plan directeur
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md` (phase 1 : audit avant code).
>
> **Périmètre** : comprendre l'architecture graphique actuelle de Redox
> (Orbital + drivers + primitives) pour calibrer le portage Wayland.
## Résumé exécutif
Redox dispose **déjà d'une fondation graphique plus mûre que prévu** :
- **Une API DRM compatible Linux** (subset KMS) exposée via `graphics-ipc` et utilisée par Orbital
- **Plusieurs drivers** : VESA firmware (`vesad`), virtio-gpu (`virtio-gpud`), Intel HD (`ihdgd`)
- **Un input multiplexer** (`inputd`) qui gère VT switching, keymaps, producteurs/consommateurs
- **Orbital** très compact (~3900 lignes Rust) qui valide le pattern serveur-clients via scheme
Côté primitives système, **l'essentiel de ce que Wayland exige est déjà là** :
- AF_UNIX sockets via le scheme `chan:`
- `sendmsg`/`recvmsg` avec **SCM_RIGHTS implémenté** (fd passing) ✅
- `shm_open` via `/scheme/shm/`
- `mmap MAP_SHARED`
- `poll`/`epoll`
- ⚠️ `memfd_create` : numéro syscall défini mais implémentation à vérifier
**Conclusion** : la phase 2 (primitives OS) du plan directeur sera moins lourde que craint.
La majorité des briques existent — il faudra surtout valider le comportement par tests dédiés.
---
## 1. Architecture Orbital
### 1.1 Point d'entrée et boot
`orbital/src/main.rs` (82 lignes) :
```text
main()
└─ orbital()
├─ lit env "VT" (passé par inputd au boot)
├─ Orbital::open_display() ← ouvre display via inputd
├─ exec "inputd -A <vt>" ← s'enregistre comme handler du VT
├─ Config::from_path("/ui/orbital.toml")
├─ OrbitalScheme::new(displays, config)
└─ orbital.run(scheme, login_cmd) ← event loop principal
```
**Note séquence de boot** : c'est `inputd` qui orchestre l'ouverture du display
au moment où le VT s'active. Orbital ne touche pas directement aux drivers
graphiques — il passe par inputd qui lui retourne un fd `/scheme/.../v2/...`.
### 1.2 Event loop (`core/mod.rs`, 662 lignes)
Architecture deux-fd :
```text
EventQueue<Source>
├─ Scheme fd (clients Orbital) → SchemeSync trait
└─ Input fd (depuis inputd consumer) → ConsumerHandleEvent::Events
```
**Patterns clés** :
- Utilise `event::EventQueue` (crate `redox_event`) — équivalent Redox de
`wl_event_loop` Wayland ou `epoll` Linux
- Chaque source a son fd, multiplexé en édge-triggered READ
- Les requests scheme bloquantes sont stockées dans `delayed: VecDeque` puis
réveillées quand un input arrive (équivalent du `wl_display_dispatch_pending`
Wayland)
**Réutilisable pour le compositor Wayland** :
- Le pattern EventQueue avec sources typées (`user_data!` macro) est exactement
ce qu'il faut pour le compositor Wayland
- Le mécanisme `delayed` pour les reads bloquants est réutilisable tel quel
### 1.3 Protocole `orbital:` (le scheme)
Implémenté dans `core/mod.rs::OrbitalHandler::SchemeSync` et `scheme.rs` (1721 lignes).
**API client** (ce qu'un client appelle pour ouvrir une fenêtre) :
```text
open("/scheme/orbital/<flags>/<x>/<y>/<w>/<h>/<title>")
→ returns window_fd
write(window_fd, "T,nouveau titre") → titre
write(window_fd, "S,800,600") → resize
write(window_fd, "P,100,200") → position
write(window_fd, "Y,0,0,800,600,...") → damage rectangles
write(window_fd, "F,t,1") → flag transparent on
read(window_fd, &mut events) → pull events (clavier, souris, ...)
mmap_prep(window_fd, ...) → obtient un buffer mmap zero-copy
fsync(window_fd) → commit (équivalent wl_surface.commit)
```
**Concepts à transposer en Wayland** :
| Orbital | Équivalent Wayland |
|---|---|
| open() avec path verbeux | `xdg_surface` + `xdg_toplevel` configure |
| write("S,...") resize | `xdg_toplevel.configure` + ack |
| write("Y,...") damage | `wl_surface.damage_buffer` |
| mmap_prep | `wl_shm_pool.create_buffer` + `wl_surface.attach` |
| fsync | `wl_surface.commit` |
| read events | event queue Wayland |
**Limites du protocole Orbital** :
- API basée sur strings dans les writes — pas type-safe, pas de versioning
- Une seule sémantique d'extension (préfixes "A","D","F","M","P","S","T","Y")
- Pas de négociation de capabilities côté serveur
- Buffer mmap'é direct — moins flexible que `wl_shm_pool` mais plus rapide
### 1.4 Composition (`compositor.rs`, 281 lignes)
Compositor logiciel CPU pur :
- `Image::roi_mut()` pour écrire dans le framebuffer
- `Color` ARGB8888
- Damage rectangles propagés aux clients via `Y,...`
- Curseur SW si pas de hardware cursor disponible (cf `core/display.rs:140`
`set_client_capability(CursorPlaneHotspot, true)`)
**Réutilisable directement** : la logique damage tracking + composition CPU
est exactement ce qu'il faut pour la phase 6-7 du plan (`wl_shm` + composition).
---
## 2. Stack graphique Redox
### 2.1 API `graphics-ipc` (la lib commune)
`base/drivers/graphics/graphics-ipc/src/lib.rs` (127 lignes).
**Découverte critique** (commentaire ligne 13-15) :
> *The v2 graphics API allows creating framebuffers on the fly, using them for
> page flipping and handles all displays using a single fd. This is basically
> a subset of the Linux DRM interface with a couple of custom ioctls in the
> place of the KMS ioctls that are missing.*
**Conséquence** : Redox a une **API DRM compatible Linux** (subset KMS).
La crate Rust `drm` upstream fonctionne directement sur Redox.
API exposée :
```rust
V2GraphicsHandle::from_file(file)
├─ resource_handles() ← liste connectors/encoders/CRTCs
├─ get_connector(handle, force_probe)
├─ get_encoder(handle)
├─ create_dumb_buffer(size, fourcc, bpp)
├─ map_dumb_buffer(buffer) ← mmap CPU-accessible
├─ add_framebuffer(buffer, depth, bpp)
├─ set_crtc(crtc, fb, position, connectors, mode)
├─ dirty_framebuffer(fb, &[ClipRect]) ← damage flush
├─ move_cursor / set_cursor2
└─ destroy_*
```
`CpuBackedBuffer` (lignes 46-127) :
- Wrapper sur `DumbBuffer` DRM
- Optionnel : shadow buffer host (write-combining mitigation)
- `sync_rect()` pour copier shadow → on-screen par damage
**Implications pour le portage Wayland** :
1. Pas besoin de réinventer le display backend — l'API DRM existe
2. Peut être utilisée telle quelle par un compositor Wayland Rust
3. `CpuBackedBuffer::shadow_buf()` retourne `&mut [u8]` — exactement
le format SHM que Wayland attend pour la composition
### 2.2 Drivers graphiques disponibles
Localisation : `base/drivers/graphics/`.
| Driver | Cible | Statut | Notes |
|---|---|---|---|
| **`vesad`** (133 + 275 lignes) | VESA framebuffer firmware (BIOS/UEFI) | Stable | Lit FRAMEBUFFER_* env, support multi-FB |
| **`virtio-gpud`** (615 + 528 lignes) | virtio-gpu (QEMU/KVM) | Actif | C'est ce qui tourne sur ton image desktop |
| **`ihdgd`** | Intel HD Graphics | Actif | Driver natif HW |
| `fbcond` | Console framebuffer | — | Texte console |
| `fbbootlogd` | Boot log framebuffer | — | Affichage early boot |
| `console-draw` | Helpers dessin console | — | Lib commune |
| `driver-graphics` | Couche d'abstraction commune (`GraphicsScheme`) | — | C'est elle qui exporte `display.<driver>` |
Tous publient leur scheme sous forme `display.<nom>` (ex: `display.vesa`).
### 2.3 Chemin d'ouverture du display
```text
Orbital
└─> ConsumerHandle::new_vt() // /scheme/input/consumer
└─> open_display_v2()
└─> fpath() pour récupérer le path complet
└─> open("/scheme/.../v2/<display>") // fd graphics-ipc v2
└─> V2GraphicsHandle::from_file
```
C'est `inputd` qui détient la connaissance du driver graphique actif et
expose un chemin standard. Orbital ne hard-code pas `vesa` ou `virtio-gpu`
il demande à inputd.
**Implication pour le compositor Wayland** : suivre le même pattern.
Demander à `inputd` quel display est associé au VT, ouvrir le fd retourné,
le wrapper en `V2GraphicsHandle`. Pas de hardcode de driver.
### 2.4 `vesad` — le chemin firmware framebuffer
`base/drivers/graphics/vesad/src/main.rs` (133 lignes).
**Bootstrap** (lignes 19-44) :
```rust
FRAMEBUFFER_WIDTH // hex string, ex "780" → 1920
FRAMEBUFFER_HEIGHT // ex "438" → 1080
FRAMEBUFFER_ADDR // adresse physique du FB fournie par firmware
FRAMEBUFFER_STRIDE // bytes par ligne
```
Le bootloader (UEFI/BIOS) place ces variables avant de chainer vers le kernel.
`vesad` mappe l'adresse physique en mémoire userspace et expose le FB via
`GraphicsScheme`.
**Fallback bootloader env** (lignes 60-84) : `vesad` lit `/scheme/sys/env`
pour `FRAMEBUFFER1`, `FRAMEBUFFER2`, ... — support multi-écran natif au boot
si le firmware en remonte plusieurs.
**Implication** : pour la phase 4 du plan (backend display), partir de `vesad`
comme chemin de validation initial = sûr et simple. virtio-gpud est plus
puissant mais plus complexe (gpu commandes, virgl optionnel).
---
## 3. Stack input
### 3.1 `inputd` — le multiplexer
`base/drivers/inputd/src/{lib.rs,main.rs}` (211 + 663 lignes).
**Schemes exposés** :
| Path | Pour | Rôle |
|---|---|---|
| `/scheme/input/consumer` | Orbital, futur compositor | reçoit les events triés (orbclient::Event) |
| `/scheme/input/consumer_bootlog` | bootlog | events early-boot |
| `/scheme/input/producer` | drivers ps2d, usbhidd | écrit des events bruts |
| `/scheme/input/control` | gestion VT/keymap | active VT, change keymap |
| `/scheme/input/handle/<scheme>` | délégation à un autre scheme | déléguer un VT à un autre serveur |
| `/scheme/input/handle_early/<scheme>` | idem mais avant init VT | bootlog, etc. |
**API ConsumerHandle** :
```rust
let h = ConsumerHandle::new_vt()?;
let display_fd = h.open_display_v2()?; // ← ouvre le display de ce VT
let evt_fd = h.event_handle(); // ← fd à mettre dans EventQueue
match h.read_events(&mut events)? {
ConsumerHandleEvent::Events(slice) => /* traite events */,
ConsumerHandleEvent::Handoff => /* VT change, on perd le contrôle */,
}
```
Le **handoff via `ESTALE`** est crucial : quand le VT switch (Ctrl-Alt-F2),
inputd retourne ESTALE — Orbital sait qu'il doit se mettre en veille
proprement. Le compositor Wayland devra gérer la même sémantique.
### 3.2 Drivers input
`base/drivers/input/` :
- `ps2d` — clavier/souris PS/2 (legacy + KVM)
- `usbhidd` — HID USB (clavier, souris, tablette)
Tous écrivent vers `/scheme/input/producer`. inputd dédoublonne, séquence,
attribue au VT actif, transmet au consumer.
### 3.3 Format d'event
`orbclient::Event` (struct C-compatible) — défini dans la crate `orbclient`.
Types : `KeyEvent`, `MouseEvent`, `MouseRelativeEvent`, `ButtonEvent`,
`ScrollEvent`, `FocusEvent`, `QuitEvent`, etc.
**Pour Wayland** :
- Mapping `orbclient::KeyEvent``wl_keyboard.key` : direct
- Codes de touches : `orbclient` utilise des scancodes — il faudra
traduire vers les **keycodes XKB** que Wayland attend
- ⚠️ **xkb keymap** : à concevoir, voir section 5
---
## 4. Primitives OS pour Wayland (relibc)
Vérifications effectuées dans `~/Projets/Redox/relibc/src/`.
### 4.1 Sockets Unix (AF_UNIX)
`platform/redox/socket.rs` :
- AF_UNIX **bind/connect** : implémenté via syscall direct (commentaire ligne 69 :
*"bind/connect with AF_UNIX were replaced with SYS_CALL"*)
- Path encoding : `chan:<path>` (ligne 177 : `buf.starts_with(b"chan:")`)
- SOCK_STREAM ✅ et SOCK_DGRAM ✅ supportés
- Compatible avec l'usage Wayland (`$XDG_RUNTIME_DIR/wayland-N`)
### 4.2 fd passing — `sendmsg` / `recvmsg` avec SCM_RIGHTS
**🟢 Critique** : `platform/redox/socket.rs` :
- Lignes 272 et 447 : `(SOL_SOCKET, SCM_RIGHTS)` traité dans cmsghdr
- Lignes 835 et 924 : `recvmsg` / `sendmsg` implémentés en interne
C'est **le pré-requis n°1** du plan (risque majeur 1) — il est rempli.
Il faut quand même écrire des tests dédiés en phase 2 pour valider :
- Passage simple de fd entre deux processus
- Multiple fds dans un même cmsg
- Passage d'un fd shm + lecture côté receveur
### 4.3 Mémoire partagée — `shm_open` + mmap
**`shm_open`** : `header/sys_mman/mod.rs:185` :
```rust
pub unsafe extern "C" fn shm_open(name, oflag, mode) -> c_int {
// traduit "/foo" → "/scheme/shm/foo" puis open() classique
}
```
Mécanisme : un scheme dédié `/scheme/shm/` sert de namespace
(équivalent `/dev/shm` Linux mais via le scheme system).
**`mmap MAP_SHARED`** : présent (`header/sys_mman/mod.rs`). Tous les buffers
DRM dumb sont déjà mappés ainsi par `graphics-ipc`.
### 4.4 ⚠️ `memfd_create`
- Numéro syscall défini : `__NR_memfd_create = 319` (x86_64)
- **Implémentation** : pas vue dans la sortie grep — probablement absente
ou stub
- **Impact pour Wayland** : libwayland-client moderne préfère `memfd_create`
pour les shm pools (pas de path needed). Fallback : `shm_open` avec nom
unique → fonctionne, juste un peu plus verbeux
À traiter en phase 2 :
- Soit implémenter `memfd_create` au-dessus de `shm_open + shm_unlink`
- Soit ajouter une couche `cfg(target_os = "redox")` côté `wayland-client`
qui force le path shm_open
### 4.5 Event loop / poll
- `header/poll/mod.rs` : `poll()` POSIX disponible
- `header/sys_select/mod.rs` : `select` POSIX
- `header/sys_epoll/mod.rs` : `epoll_*` (probablement émulé sur Redox via
l'EventQueue native — à vérifier)
- **Native Redox** : `redox_event::EventQueue` — utilisé par Orbital, plus
idiomatique. Pour le compositor Rust pur, autant l'utiliser directement
plutôt que de passer par poll() POSIX.
---
## 5. Manques identifiés (à traiter dans le plan)
### 5.1 Pas de keymap XKB sur Redox
Wayland exige que le serveur envoie un keymap XKB au client (`wl_keyboard.keymap`
event, format `XKB_V1`). Or :
- Redox utilise des scancodes orbclient
- Pas de `xkbcommon` packagé sur Redox (à confirmer en cherchant la recette)
- Pas de fichiers de keymap XKB système
**À traiter en phase 5 (backend input)** :
- Option A : porter `libxkbcommon` (C, ~10k loc)
- Option B : utiliser la crate Rust `xkb` (wrapper xkbcommon, dépend du C)
- Option C : implémenter un sous-ensemble XKB en Rust (plus long mais
cohérent avec l'esprit du projet)
Recommandation : **Option C** mais avec scope minimal (US layout + variantes
basiques) au début. La crate `xkeysym` peut servir de base.
### 5.2 Pas d'AT-SPI / dbus pour l'accessibilité
- Phase 11 du plan vise navigation clavier + métadata d'accessibilité
- Sur Linux, AT-SPI transporte ces métadata via dbus
- Dbus existe sur Redox mais portage limité ; AT-SPI n'est pas porté
- À concevoir : protocole Redox simple d'accessibilité, ou wayland-protocols
custom (text-input-v3 comme point de départ)
### 5.3 GPU userspace driver manquant
- vesad et virtio-gpud existent mais pas de DRM driver "GPU complet" hors virtio
- Mesa3D Redox = LLVMpipe seulement (CPU)
- Phase 12 du plan = GPU expérimental — restera CPU pour le MVP
- À documenter : aucune accélération hardware n'est garantie pour les phases 0-11
### 5.4 Pas de session manager / VT API stable
- Sur Linux : `logind` / `seatd` gèrent les VT et l'accès aux devices
- Sur Redox : `inputd` joue un rôle similaire mais pas équivalent
- Pour Smithay (phase 13), il faudra un backend session Redox basé sur inputd
---
## 6. Ce qui est réutilisable directement
| Composant Redox actuel | Usage dans le compositor Wayland |
|---|---|
| `redox_event::EventQueue` | Boucle principale du compositor |
| `redox-scheme::SchemeSync` | Si on choisit aussi de publier un scheme `wayland:` |
| `graphics-ipc::V2GraphicsHandle` | Backend display (phase 4) — direct |
| `graphics-ipc::CpuBackedBuffer` | wl_shm composition (phase 6) — direct |
| `inputd::ConsumerHandle` | Backend input (phase 5) — direct |
| `orbital/src/compositor.rs` damage tracking | Référence pour notre damage logic (phase 6-7) |
| `orbital/src/scheme.rs` focus + raccourcis | Référence pour focus management (phase 7) |
| `redox-log` | Logging |
| Crate `drm` upstream | Si on veut adresser DRM directement |
---
## 7. Décisions recommandées pour la suite
### Phase 2 (primitives OS) — peut être réduite
Plutôt que d'auditer toutes les primitives, **se limiter à des tests dédiés** :
1. Test fd passing : 2 processus, l'un crée un memfd-like, l'autre le mappe
2. Test Unix socket SOCK_STREAM avec messages Wayland-shaped (header binaire)
3. Test event loop multi-fd avec signaux POSIX
4. Test concurrent shm_open/mmap depuis plusieurs processus
Si les 4 tests passent, on a la garantie que `wayland-rs` peut tourner.
Estimation : **1 mois à 2h/jour** au lieu des 6 mois initialement budgétés.
### Phase 3 (port wayland-rs) — à découper
`wayland-rs` upstream a peu de deps OS-specifiques dans son crate `wayland-backend`.
Ordre suggéré :
1. `wayland-scanner` (génération code XML → Rust) : pure logic, devrait marcher tel quel
2. `wayland-backend` : couche socket — vérifier le `cfg(unix)` vs ajout d'un
`cfg(target_os = "redox")`
3. `wayland-server` : haut niveau — devrait fonctionner si backend OK
4. `wayland-client` : symétrique
### Phase 4 (display backend) — voie rapide possible
`V2GraphicsHandle` est si proche de l'API DRM Linux qu'on peut s'inspirer
directement de `core/display.rs` d'Orbital pour le RedoxOutput.
**Estimation : 2 mois à 2h/jour** (au lieu des 6 mois si on partait de zéro).
---
## 8. Repos clonés localement pour la suite
```text
~/Projets/Redox/
├── orbital/ ← référence compositor (gitlab.redox-os.org/redox-os/orbital)
├── base/ ← drivers + graphics-ipc + inputd (gitlab.redox-os.org/redox-os/base)
├── relibc/ ← libc Redox, primitives POSIX (--depth 1)
├── redox-src/ ← repo umbrella, ~/Projets/Redox/redox-src/build/x86_64/desktop/harddrive.img
└── docs/existing-redox-gui.md ← ce document
```
Sources additionnelles à cloner si on creuse plus :
- `redox-os/wayland-rs-redox-fork` *(à créer le moment venu)*
- `redox-os/drivers` *(déjà inclus dans base/drivers/)*
- `Smithay/smithay` *(plus tard, phase 13)*
---
## 9. Documents à produire ensuite
Selon le plan directeur (section "Documents A Produire Ensuite") :
| Document | Quand | Contenu |
|---|---|---|
| `redox-wayland-primitives.md` | Phase 2, après tests | Résultats des 4 tests dédiés + conclusions |
| `compositor-architecture.md` | Phase 3-4 | Crates, traits, flux de données |
| `display-backend-redox.md` | Phase 4 | Détails RedoxOutput + dirty_framebuffer flow |
| `input-backend-redox.md` | Phase 5 | Mapping orbclient → Wayland + XKB strategy |
| `software-rendering-and-gpu-roadmap.md` | Phase 4 + 12 | CPU paths + GPU horizon |
| `accessibility-roadmap.md` | Phase 11 | Décisions sans AT-SPI |
| `orbital-migration.md` | Phase 8-9 | Comment porter winit/softbuffer/clients |
| `cosmic-smithay-roadmap.md` | Phase 13 | Quand y aller, dépendances bloquantes |
| `x11-compat-rust-roadmap.md` | Phase 14 | Projet séparé |
---
*Fin de l'audit phase 1.*

View file

@ -0,0 +1,296 @@
# Phase 2 — Primitives Redox pour Wayland : résultats des tests
> Document produit le 2026-05-08 dans le cadre du plan directeur
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`.
>
> **Périmètre** : valider par tests dédiés que les 4 primitives système
> indispensables à Wayland (Unix sockets, fd passing SCM_RIGHTS, shm_open+mmap,
> poll multi-fd) fonctionnent sur Redox. Décision conditionnée par l'audit
> phase 1 (`existing-redox-gui.md`) qui a montré que ces primitives
> existent en théorie dans relibc.
## Verdict global
**✅ Wayland est techniquement viable sur Redox.**
Les 4 primitives critiques fonctionnent dans le scénario réel Wayland
(client + compositor en deux processus séparés). **Aucun blocage** identifié
pour démarrer la phase 3 (port `wayland-rs`).
| Test | Scope | Résultat |
|---|---|---|
| Test 1 — Unix socket SOCK_STREAM | mono-process via socketpair | ✅ PASS |
| Test 2 — SCM_RIGHTS | mono-process | ⚠️ Artefact (numéro fd réutilisé) |
| Test 2b — SCM_RIGHTS | multi-process (fork) | ✅ PASS |
| Test 3 — shm_open + mmap MAP_SHARED | mono-process | ✅ PASS |
| Test 4 — poll() multi-fd | mono-process | ✅ PASS |
## Méthodologie
Workspace : `~/Projets/Redox/redox-wayland-tests/`
Toolchain : `nightly-2026-05-07` + target `x86_64-unknown-redox`
Exécution : `redoxer run --release` (mini-VM Redox sous QEMU/KVM)
Chaque test est un binaire Rust autonome utilisant uniquement la crate
`libc 0.2` (pour rester proche du métal et éviter d'introduire des biais
d'abstraction).
---
## Test 1 — Unix socket SOCK_STREAM avec format Wayland
### Objectif
Valider qu'un message Wayland-shaped (header `object_id` + `(size << 16 | opcode)`,
payload aligné sur 4 bytes) traverse une socketpair AF_UNIX/SOCK_STREAM sans
corruption.
### Setup
- `socketpair(AF_UNIX, SOCK_STREAM, 0, fds)`
- Construire un message : 8 bytes header + 32 bytes payload aligné = 40 bytes
- write_all côté A, read_exact header puis payload côté B
### Résultat
```text
[test-01] sent 40 bytes (header 8 + payload 32 aligned to 32)
[test-01] PASS: roundtrip OK, 40 bytes recv, oid=0x1 op=0xa
```
**Conclusion** : `wayland-rs/wayland-backend` peut utiliser des Unix sockets
Redox sans modification au niveau wire format.
---
## Test 2 — SCM_RIGHTS mono-process
### Objectif
Valider l'API SCM_RIGHTS via `sendmsg`/`recvmsg`. Vérifier que le fd
reçu donne accès au même fichier que le fd envoyé.
### Observation initiale (FAIL)
En mono-process :
- `sendmsg(SCM_RIGHTS, [fd=5])` → succès
- `recvmsg(...)` → control de 24 bytes, fd reçu = **5** (identique)
- `dup(tmp_fd) → 8`, send 8, recv → **8**
Conclusion : le kernel Redox **réutilise le numéro fd** plutôt que de créer
un nouveau slot dans la fd table. Quand on `close(tmp_fd)` puis `close(dup)`,
le fd reçu devient EBADF.
### Interprétation
Ce comportement est **un artefact mono-process** :
- Mono-process, le sender et le receiver partagent la même fd table
- Le kernel optimise en évitant de créer un slot redondant
- Ce n'est **pas** un comportement représentatif du cas Wayland réel
(client et compositor sont toujours en deux processus séparés)
### Code relibc concerné
`relibc/src/platform/redox/socket.rs:289` (send) et `:467` (recv) :
```rust
redox_rt::sys::sys_call_wo(socket, &fds_slice, CallFlags::FD, &[])?
redox_rt::sys::sys_call_ro(socket, fds_bytes, call_flags, &[])?
```
Le syscall Redox `sys_call_wo/ro` avec `CallFlags::FD` est l'implémentation
réelle. Sa sémantique multi-process est validée par le test 2b ci-dessous.
---
## Test 2b — SCM_RIGHTS multi-process (le vrai test Wayland)
### Objectif
Valider que SCM_RIGHTS transfère effectivement un fd d'un processus parent
vers un processus enfant créé par `fork()`. C'est le scénario Wayland exact.
### Setup
1. Parent : `open("/tmp/...")` avec un marker connu
2. Parent : `socketpair(AF_UNIX, SOCK_STREAM)`
3. Parent : `fork()`
4. Parent : `sendmsg(SCM_RIGHTS, [tmp_fd])`, drop tmp_fd
5. Child : `recvmsg`, lit via le fd reçu, vérifie le marker, `_exit(0/N)`
6. Parent : `waitpid`, recupère exit code
### Résultat
```text
[test-02b PARENT] tmp fd=5, sockets 6/7
[test-02b PARENT] forked child pid=36, sending fd
[test-02b CHILD pid=36] recvmsg on sock 7
[test-02b CHILD] received fd=5
[test-02b CHILD] marker matches, exit 0
[test-02b PARENT] child reaped: exited=true code=0
[test-02b] PASS: fd passed across fork() correctly
```
**Conclusion critique** : Le child a reçu fd=5 (numéro réutilisé localement,
ce qui est normal puisque sa fd table est indépendante du parent), et la
**lecture via ce fd a retourné le marker écrit par le parent**.
C'est la sémantique POSIX correcte pour SCM_RIGHTS et c'est **exactement
ce que Wayland exige** : un client envoie un fd shm au compositor, le
compositor reçoit dans sa propre fd table un descripteur fonctionnel.
### Implication pour le projet
Le risque majeur n°1 du plan directeur (cf section "Risques Majeurs") est levé :
> *Wayland depend fortement de sockets, fd passing, shm et mmap.
> Mitigation : traiter cette phase avant tout port COSMIC ;
> maintenir des tests bas niveau.*
Pré-requis fonctionnel : ✅ confirmé.
Tests bas niveau à maintenir : ✅ ce document + le code source
`redox-wayland-tests/`.
---
## Test 3 — shm_open + mmap MAP_SHARED
### Objectif
Valider que `shm_open` (qui sur Redox est traduit en
`open("/scheme/shm/<name>")` par relibc) supporte le pattern Wayland :
créer une zone partagée nommée, écrire, refermer, rouvrir, relire.
### Setup
1. `shm_open("/redox-wl-test-03", O_RDWR|O_CREAT, 0600)`
2. `ftruncate(fd, 4096)`
3. `mmap(NULL, 4096, RW, MAP_SHARED, fd, 0)`
4. Écrire 1024 u32 = pattern `0xDEADBEEF + index`
5. `munmap` + `close`
6. `shm_open("/redox-wl-test-03", O_RDWR)` — réouverture
7. `mmap` à nouveau
8. Vérifier le pattern bit à bit
9. `shm_unlink`
### Résultat
```text
[test-03] phase1: shm_open created, fd=5
[test-03] phase1: mmap at 0x15000
[test-03] phase1: wrote 1024 u32 pixels
[test-03] phase2: shm_open reopened, fd=5
[test-03] phase2: mmap at 0x15000
[test-03] phase2: pattern verified across close/reopen
[test-03] cleanup: shm_unlink OK
[test-03] PASS: shm_open/mmap/persistence/unlink all work
```
**Notes** :
- Le pattern est intégralement préservé entre close/reopen → la zone
partagée vit indépendamment des fds qui la mappent (sémantique POSIX
correcte)
- `shm_unlink` retire effectivement la zone
- `mmap` retourne la même adresse (0x15000) sur les deux mappings — pas
une garantie POSIX mais cohérent
**Conclusion** : Wayland peut utiliser `wl_shm_pool` via `shm_open`+`mmap`
sans adaptation. Pour `memfd_create` (préféré par libwayland-client moderne),
voir section "Manque restant" plus bas.
---
## Test 4 — poll() multi-fd
### Objectif
Valider le multiplexage `poll()` qui sera la base de l'event loop du
compositor (équivalent `wl_event_loop`).
### Setup
Trois pipes (`ra/wa`, `rb/wb`, `rc/wc`). On `poll()` les trois read ends
avec POLLIN. Trois sous-tests :
- A : timeout 50ms sans data → doit retourner 0
- B : `write(wb, "X")` → poll doit ne signaler que `rb`
- C : `close(wa)` → poll doit signaler ra (POLLHUP/POLLIN)
### Résultat
```text
[test-04] A: timeout 50ms with no data → poll returned 0 OK
[test-04] B: poll detected only rb (index 1) ready, as expected
[test-04] B: read 1 byte 'X' from rb OK
[test-04] C: closing wa caused POLLHUP/POLLIN on ra (revents=0x1)
[test-04] PASS: poll() multiplexes correctly across fds and detects HUP
```
**Conclusion** : multiplexage fiable, comportement POSIX standard.
On peut soit utiliser `poll()` directement, soit s'appuyer sur
`redox_event::EventQueue` (utilisé par Orbital) qui wrappe le scheme
event Redox de manière plus idiomatique.
---
## Manques restants à traiter
### `memfd_create` non implémenté
L'audit phase 1 avait noté :
> Numéro syscall défini (`__NR_memfd_create = 319`) mais pas vu d'impl
> dans relibc.
Confirmé dans ce travail. **Impact** : libwayland-client moderne préfère
`memfd_create` aux `shm_open` (pas de path nécessaire, anti-leak en cas
de crash client).
**Solutions possibles, par ordre de simplicité** :
1. **Wrapper côté wayland-rs Redox** : ajouter un `cfg(target_os = "redox")`
qui force le path `shm_open` avec un nom unique (ex: `pid + counter`)
2. **Implémenter `memfd_create` dans relibc Redox** au-dessus de
`shm_open` + `shm_unlink` immédiat (pour rendre la zone anonyme)
3. **Ajouter un syscall Redox `memfd_create` natif** (le plus propre,
le plus long)
Recommandation pour la phase 3 : option 1 dans un premier temps. Si on
upstream chez Redox plus tard, option 2.
### Numéros de fd réutilisés en mono-process
Constat du test 2 : pas vraiment un bug, mais à documenter dans
`compositor-architecture.md` pour qu'on n'écrive pas de tests qui
reposent sur des numéros distincts.
### `pipe()` ne semble pas exposer pipes nommés FIFO
Non testé ici. Pas critique pour Wayland (on utilise socket pairs).
À noter si on veut un jour porter du code Linux qui utilise des FIFO.
---
## Conclusions pour la suite du plan
### Phase 2 → ✅ TERMINÉE
La phase 2 du plan visait :
> *Valider les mécanismes nécessaires au protocole Wayland.*
Mission accomplie en **une session** au lieu des 6 mois budgétés. C'est
possible parce que l'audit phase 1 avait déjà localisé les implémentations
relibc — les tests n'avaient qu'à confirmer le comportement.
### Implications pour le calendrier annuel
Le plan directeur prévoyait année 1 = "audit + primitives". On a fait
les deux en deux sessions de 2h. **Année 1 peut donc démarrer la phase 3
dès maintenant**, avec une marge confortable.
### Premier ticket de phase 3
> Cloner `wayland-rs` upstream, ajouter target `x86_64-unknown-redox`,
> tenter `cargo build` sur les crates `wayland-scanner`, `wayland-backend`,
> `wayland-server`, `wayland-client`. Documenter les `cfg(unix)` qui ont
> besoin d'un `cfg(target_os = "redox")` distinct.
---
## Code source des tests
```text
~/Projets/Redox/redox-wayland-tests/
├── test-01-unix-socket/ (40 bytes Wayland-shaped roundtrip)
├── test-02-fd-passing/ (mono-process, expose l'artefact)
├── test-02b-fd-passing-fork/ (multi-process via fork — la vraie validation)
├── test-03-shm-open/ (shm_open + mmap + persistance)
└── test-04-poll-multifd/ (poll multiplexing + POLLHUP)
```
À conserver en CI quand le repo `redox-wayland-compositor` sera créé
(phase 0 du plan, à venir).
---
*Fin du document de phase 2.*