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:
commit
53e6626231
21 changed files with 3676 additions and 0 deletions
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal 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
70
README.md
Normal 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+
|
||||
892
REDOX_COSMIC_XWAYLAND_RS_PLAN.md
Normal file
892
REDOX_COSMIC_XWAYLAND_RS_PLAN.md
Normal 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.
|
||||
|
||||
7
crates/redox-wl-poc-pixels/Cargo.toml
Normal file
7
crates/redox-wl-poc-pixels/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "redox-wl-poc-pixels"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
310
crates/redox-wl-poc-pixels/src/main.rs
Normal file
310
crates/redox-wl-poc-pixels/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
7
crates/redox-wl-test-fd-passing-fork/Cargo.toml
Normal file
7
crates/redox-wl-test-fd-passing-fork/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "redox-wl-test-fd-passing-fork"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
215
crates/redox-wl-test-fd-passing-fork/src/main.rs
Normal file
215
crates/redox-wl-test-fd-passing-fork/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
7
crates/redox-wl-test-fd-passing/Cargo.toml
Normal file
7
crates/redox-wl-test-fd-passing/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "redox-wl-test-fd-passing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
195
crates/redox-wl-test-fd-passing/src/main.rs
Normal file
195
crates/redox-wl-test-fd-passing/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
9
crates/redox-wl-test-handshake/Cargo.toml
Normal file
9
crates/redox-wl-test-handshake/Cargo.toml
Normal 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 }
|
||||
200
crates/redox-wl-test-handshake/src/main.rs
Normal file
200
crates/redox-wl-test-handshake/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
crates/redox-wl-test-poll-multifd/Cargo.toml
Normal file
7
crates/redox-wl-test-poll-multifd/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "redox-wl-test-poll-multifd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
120
crates/redox-wl-test-poll-multifd/src/main.rs
Normal file
120
crates/redox-wl-test-poll-multifd/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
7
crates/redox-wl-test-shm-open/Cargo.toml
Normal file
7
crates/redox-wl-test-shm-open/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "redox-wl-test-shm-open"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
142
crates/redox-wl-test-shm-open/src/main.rs
Normal file
142
crates/redox-wl-test-shm-open/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
10
crates/redox-wl-test-shm-pipeline/Cargo.toml
Normal file
10
crates/redox-wl-test-shm-pipeline/Cargo.toml
Normal 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"
|
||||
589
crates/redox-wl-test-shm-pipeline/src/main.rs
Normal file
589
crates/redox-wl-test-shm-pipeline/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
7
crates/redox-wl-test-unix-socket/Cargo.toml
Normal file
7
crates/redox-wl-test-unix-socket/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "redox-wl-test-unix-socket"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
78
crates/redox-wl-test-unix-socket/src/main.rs
Normal file
78
crates/redox-wl-test-unix-socket/src/main.rs
Normal 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
486
docs/existing-redox-gui.md
Normal 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.*
|
||||
296
docs/redox-wayland-primitives.md
Normal file
296
docs/redox-wayland-primitives.md
Normal 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.*
|
||||
Loading…
Add table
Add a link
Reference in a new issue