Document de synthèse pour la phase 13.2 (wl_output v3 +
wl_subcompositor/wl_subsurface). Couvre :
- Verdict global et les 5 sprints livrés (a, b.1, b.2, b.3 + fix, b.4)
- Détails de chaque sprint avec commits référencés
- Stratégie de tests hybride natif+runtime (innovation 13.2)
- 6 limitations connues documentées pour traitement futur
- Liste complète des fichiers livrés
- Critère de fin et étapes suivantes possibles (13.3 backend extraction,
13.2.c retry sctk, durcissement)
Format aligné sur phase13-1-b-observations.md et phase13-1-c-cursor.md
pour cohérence audit trail.
Leyoda 2026 – GPLv3
B.2 (ion page fault) : pas fixable dans ce repo. Source d'ion clonée
depuis upstream et inspectée : pas de commit récent visant ce pattern
de crash, binaire stripped sur l'image (impossible de mapper RIP=0x2335ae
à une fonction sans non-stripped build). Rapport upstream-ready rédigé
dans docs/phase13-1-c-ion-bug-b2-upstream.md, à coller sur
gitlab.redox-os.org/redox-os/ion quand on aura un compte.
Bonus diagnostic noté : warning relibc « Cancellation for unknown id »
apparaît juste avant le crash ion, peut être lié à un side-effect dans
procmgr (à investiguer côté relibc aussi).
Workaround documenté pour utilisateurs : quitter les clients background
AVANT le serveur Wayland (ESC sur le client, puis Ctrl+Q sur le compo).
B.4 (image étroite) : run-qemu.sh warn maintenant si harddrive.img <
4 GiB, avec les commandes de resize prêtes à copier-coller. Continue
sans bloquer (image actuelle de l'utilisateur fait 10 GiB, no-op pour
notre setup, mais utile pour quelqu'un qui clone le repo et utilise
l'image desktop par défaut de Redox.)
Leyoda 2026 – GPLv3
Bug B.1 13.1.b résolu : ps2d sous Redox+QEMU délivre les coords absolues
du pointer dans 0..65535 (16-bit virtuel HID), pas en pixels écran. Notre
PointerMotion faisait une assignation directe → clamp permanent à
(width-1, height-1) → cursor coincé bas-droite, hit_test toujours None.
Fix : conversion fixed-point x*screen_w/65536 (algo orbital upstream,
cf orbital/src/scheme.rs:1583). Le clamp_cursor de 13.1.b reste comme
garde-fou pour valeurs hors-spec.
Validation runtime : curseur suit fidèlement la souris dans toute l'aire
écran 1280×800, clics en (370,239), (522,303), (746,175), (1260,142),
etc. Plus jamais de (1279,799) systématique.
Bonus DX : filtre tracing par défaut « info,cursor=debug » pour éviter
d'avoir à taper RUST_LOG complexe dans la fenêtre QEMU graphique (pas
de copier-coller).
Instrumentation `target="cursor"` laissée en place pour debug futur des
événements pointer.
Bug B.2 (page fault ion) non traité, reste à reporter upstream.
Leyoda 2026 – GPLv3
4 bugs identifiés et corrigés grâce au client tiers :
- ESC mangée par le compositor au lieu d'être forwardée (raccourci compo
déplacé sur Ctrl+Q + tracking ctrl_held)
- Cursor accumulait les deltas PS/2 hors-écran (ajout screen_w/h +
clamp_cursor + set_screen_size au boot)
- Keycode envoyé scancode+8 au lieu d'evdev brut (bug racine — le +8
est entre evdev et X11, pas entre PS/2 et evdev)
- Event loop client ne tolérait pas Interrupted/BrokenPipe transitoires
→ FAIL au lieu de PASS sur sortie normale (5e adaptation Redox côté
client, upstream-compatible)
Plus : tick log compo passé en debug! pour silencer la console série,
run-qemu.sh durci (check /dev/fuse, IMAGE/REDOXFS overridables).
6/6 checkpoints 13.1 verts. Bugs secondaires (curseur stuck en bas-droite,
ion page fault sur broken pipe d'un job background) listés dans le doc
pour follow-up en 13.1.c.
Leyoda 2026 – GPLv3
Crate redox-wl-real-client-simple-window : port Redox de l'exemple
upstream wayland-rs/wayland-client/examples/simple_window.rs.
Premier client que nous n'avons PAS écrit pour valider notre compositor,
donc révélateur des paths protocolaires non anticipés.
Code upstream préservé verbatim sauf 4 adaptations Redox justifiées
dans le doc :
- Connection::connect_to_env() → UnixStream::connect(SOCKET_PATH)
car WAYLAND_DISPLAY pas garanti dans l'init Redox.
- tempfile::tempfile() → libc::shm_open + ftruncate + mmap car
O_TMPFILE/mkostemp non garantis par relibc.
- Attente initiale 50×100ms sur l'existence du socket (lancement
parallèle compositor + client dans init script).
- Logs tee stdout + /scheme/debug pour observer côté serial host.
L'algorithme draw() gradient ARGB, les Dispatch impls, le delegate_noop!,
init_xdg_surface, Close/ESC sont conservés ligne pour ligne. Tout bug
observé sera donc attribuable au compositor, pas au port.
Cross-compile via `redoxer build --release` : 1.1 Mo, ELF statique,
0 warning, 0 erreur (testé 2026-05-15).
Doc phase13-1-real-client-simple-window.md : justifications des 4
adaptations, procédure runtime make qemu, 6 critères d'observation
pour la 13.1.b runtime à venir.
Runtime non validé (exige make qemu interactif) — sera consigné en
phase 13.1.b après test utilisateur.
Leyoda 2026 – GPLv3
3 livrables qui polissent le compositor pour des toolkits Wayland
réels.
Frontend additions :
1. set_min_size / set_max_size
- XdgToplevelData : min_size/max_size: Mutex<(i32, i32)>
- Handlers SetMinSize/SetMaxSize stockent
- apply_interactive_drag(Resize) clamp new_w/new_h aux contraintes
après compute_resize_geom
2. Activated state au focus change
- WaylandFrontend.toplevels_by_id: HashMap<SurfaceId, XdgToplevel>
peuplé à xdg_surface.GetToplevel, nettoyé à wl_surface.Destroy
et garbage_collect_dead_clients
- Méthode send_focus_configure(toplevel, sid, states) : envoie
configure(w, h, states) avec taille du buffer courant +
xdg_surface.configure(serial) + update last_serial
- set_focus envoie configure([Activated]) à la nouvelle focused
surface et configure([]) à l'ancienne
3. Throttling configure pendant resize
- InteractiveDrag : last_configure_size + last_configure_at
- apply_interactive_drag(Resize) skip l'envoi si taille inchangée
OU elapsed < 16ms (max ~60fps), mais applique quand même le
delta de position pour que la fenêtre suive le curseur
Test client : set_min_size(150, 80) + set_max_size(600, 400)
Validation runtime :
- [frontend] xdg_toplevel.set_min_size(150, 80) + set_max_size logs
- focus change → configure(320x200) reçu côté client = Activated OK
- Configure pendant resize : un seul w=150 h=80 envoyé pour les 4
phases de cursor (clamp + throttling = même résultat → skip)
Capture phase7-9-2-clamp-min.png montre la fenêtre clampée à 150x80
malgré des cursor moves qui auraient produit des tailles négatives.
Bilan phase 7 (1-9) close. Compositor 7.x suffisamment poli pour
toolkits Wayland réels : xdg-shell complet, focus avec Activated,
input filtré, cursor, move/resize avec contraintes, robustesse,
multi-clients avec cleanup.
Prochain jalon : port COSMIC (phase 13 plan-directeur, réordonnée
avant GPU).
Doc complète : docs/phase7-9-polish.md
Leyoda 2026 – GPLv3
Resize complet implémenté (compute selon edges + configure cycle)
+ durcissements sécu Move/Resize.
Frontend additions :
- Helper compute_resize_geom(edges, start_x/y/w/h, dx, dy) qui
interprète le bitmask xdg_toplevel::ResizeEdge :
Top(1)/Bottom(2)/Left(4)/Right(8). Coins = combinaisons.
Clamp w/h à MIN_RESIZE_DIM=32.
- Handler xdg_toplevel.Resize complet : valide serial!=0,
validation soft du serial vs last_button_serial (log warning,
accept), refuse si drag déjà actif, check client_id (caller ==
owner de la surface — sécu défense en profondeur). Capture
start_geom (x,y,w,h) + start_cursor, stocke InteractiveDrag::
Resize(edges).
- apply_interactive_drag(Resize) : compute new geom, modifie
registry x/y, envoie xdg_toplevel.configure(w, h, [Resizing]) +
xdg_surface.configure(serial). Met à jour
XdgSurfaceData.last_serial = serial pour que l'ack du client
soit accepté (sinon refusé par garde 7.5).
- Move handler : check client_id ajouté en mode défense en
profondeur (un client ne peut pas drag une surface d'un autre).
Nouveau crate : redox-wl-test-client-resize (~285 lignes)
- Bind wl_seat
- 1 fenêtre 320x200 cyan + bordure noire
- À T+8s : toplevel.resize(seat, 1, BottomRight)
- Écoute xdg_toplevel.Configure : à chaque new size, ack +
nouveau shm pool + nouveau buffer + attach + commit
- Vec<WlShmPool>/Vec<WlBuffer> pour ne pas drop les anciens
pendant que le serveur peut encore les utiliser
Pièges trouvés :
- last_serial doit être mis à jour à chaque configure envoyé
pendant le resize, sinon l'ack du client est refusé par le
garde 7.5 (serial > last_sent → ignoring).
- Le client peut ignorer la taille initiale suggérée par le
configure du compositor (legal selon spec). Le compositor
compose à la taille du buffer attaché.
- À chaque resize, garder les anciens pool+buffer en mémoire
pour ne pas crasher le serveur qui mmap dessus.
Validation runtime : 3 captures à 3 tailles distinctes (320x200
initial, ~520x300 agrandi, ~70x25 rétréci) confirment le pipeline
end-to-end. Logs symétriques côté client et compositor.
Bilan phase 7 (1-8) :
- 7.1 xdg-shell, 7.2 wl_seat, 7.3 cursor, 7.4 focus/raise,
7.5 robustesse, 7.6 multi-clients, 7.7 move, 7.8 resize
- Compositor 7.x désormais utilisable pour un toolkit Wayland
basique. Prochain jalon : port COSMIC (phase 13 plan-directeur,
réordonnée avant GPU).
Doc complète : docs/phase7-8-resize.md
Leyoda 2026 – GPLv3
Move interactif complet (Resize en stub log-only, reportable à 7.8).
Frontend additions :
- enum DragMode { Move, Resize(u32) } et struct InteractiveDrag
{ surface_id, xdg_toplevel_res, xdg_surface_res, mode,
start_cursor_x/y, start_x/y, start_w/h }
- WaylandFrontend : interactive_drag: Option<InteractiveDrag> +
last_button_serial: u32
- XdgToplevelData.xdg_surface: Mutex<Option<XdgSurface>> peuplé
dans xdg_surface.GetToplevel pour retrouver le wl_surface parent
depuis un toplevel.move/resize
- Handler xdg_toplevel.Move : valide serial != 0, refuse drag déjà
actif, retrouve SurfaceId via cascade UserData (xdg_surface →
wl_surface → SurfaceData), capture start_cursor + start_geom,
stocke InteractiveDrag
- Handler xdg_toplevel.Resize : stub log-only (à compléter 7.8)
- Handler xdg_toplevel.Destroy nettoie interactive_drag si on était
en train de drag cette surface
- Méthode apply_interactive_drag() : applique le delta (cursor -
start_cursor) à la position de la surface (Move) ou consume le
motion (Resize stub)
- forward_input(PointerMotion(Relative)) : apply au début, return
si drag actif (court-circuite l'envoi de motion au client pendant
un drag, conforme spec Wayland)
- forward_input(PointerButton release) : sort du mode drag
- set_cursor_position : appelle aussi apply_interactive_drag (sans
ça, les cycles de test programmatique ne déclenchent pas le drag
car ils court-circuitent forward_input)
- Tracking last_button_serial à chaque button LEFT Pressed
Test client modifications :
- redox-wl-test-client-shm-two bind wl_seat
- Si label=="A", déclenche toplevel.move(&seat, 1) 8s après le
commit initial (mécanisme "synthétique" : le client n'écoute pas
les pointer events, il envoie Move sans attendre un clic — assez
pour valider le pipeline serveur, durcissement client 7.8)
Validation runtime :
- Cycle compositor temporaire (retiré du binaire final) qui change
cursor_position à plusieurs positions pendant que le drag est
actif, screendumps à 3 positions distinctes
- Logs frontend :
[frontend] xdg_toplevel.move: enter drag sid=SurfaceId(0) start=(60,60) cursor=(500,400)
[frontend] left-release → exit interactive drag
- A déplacée visuellement entre les captures pos1, pos2 et
post-release ; sortie clipée sur les bords (pas de snap-to-edge,
WM policy reportable)
Limitations 7.7 :
- Resize non implémenté (stub)
- Validation serial laxiste (serial != 0)
- Pas de contrôle policy (snap-to-edge, min/max)
- Pas de check "surface du même client" (sécu)
- Pas d'event xdg_toplevel.configure([Resizing]) envoyé pendant le drag
Doc complète : docs/phase7-7-move-resize.md
Leyoda 2026 – GPLv3
3 livrables :
1. Cleanup post-disconnect (corrige sub-bug 7.5)
- DumbClientData::disconnected push dans Arc<Mutex<Vec<ClientId>>>
partagé (peuplé à accept_pending_clients)
- SurfaceData.client_id: Mutex<Option<ClientId>> capturé au
wl_compositor.create_surface pendant que _client: &Client est
encore vivant (à la déconnexion surf.client() retourne None,
on ne pourrait plus déduire le mapping)
- WaylandFrontend.garbage_collect_dead_clients drain la queue
et nettoie surfaces_by_id + registry + focused_surface +
cursor_surface_id + pointers/keyboards orphelins
- Appelée à chaque tick depuis le compositor binaire après
dispatch_clients
2. wl_buffer.release après commit-copy
- SurfaceData.pending_buffer passé de Option<BufferData> à
Option<wl_buffer::WlBuffer> pour avoir le Resource sous la main
- Au commit, après la lecture des params via
buf.data::<BufferData>().cloned() et la copie des pixels,
appel buf.release() qui signale au client qu'il peut réutiliser
son buffer
3. Filtrage events par client focused
- forward_input calcule focused_client_id depuis
focused_surface.client().map(|c| c.id())
- wl_pointer.{motion,button,axis,frame} et wl_keyboard.key
n'arrivent qu'aux Resources dont client_id matche le focused
- PointerButton recalcule focused_cid APRÈS le hit_test+set_focus
pour que le clic atterrisse bien sur le nouveau client
Pièges trouvés :
- Resource n'a pas de client_id() direct → utiliser
client().map(|c| c.id())
- À l'instant du disconnected(), surf.client() retourne déjà None
→ capturer le ClientId au CreateSurface, pas après
Validation runtime :
- Test fuzz : surface fantôme du fuzz1 (brutal exit) nettoyée,
surfaces=0 stable post-fuzz, capture phase7-6-cleanup-no-ghost.png
confirme visuellement (vs rectangle noir 7.5)
- Test 2 clients : redox-wl-test-client-shm-two avec parent + fork
affiche A vert + B magenta en parallèle, surfaces=2 stable,
capture phase7-6-two-clients.png
- Log frontend : [frontend] garbage_collect: client X → destroyed
1 surfaces (fuzz1), 0 surfaces (fuzz2-4 qui ont cleanup propre)
Doc complète : docs/phase7-6-multi-clients.md
Leyoda 2026 – GPLv3
Compositor anti-panic face à 4 cas malformés exercés en succession :
brutal exit sans destroy, ack_configure avec mauvais serial,
create_buffer avec dimensions nulles, create_buffer avec stride
incohérent. Aucun crash, aucun blocage, ticks compositor continus
30s+ après la fin du fuzz.
Frontend hardening :
- BufferData.valid: bool, mis à false dans wl_shm_pool.create_buffer
si dimensions/stride/offset incohérents avec la taille du pool.
Le wl_buffer est quand même créé (contrat wayland-server) mais
ignoré au commit.
- ShmPool::read_argb signature passée de Vec<u32> à Option<Vec<u32>>.
Refuse de lire si w/h/stride invalides ou si l'accès final
dépasse self.size. Calculs en checked_add/checked_mul pour éviter
tout overflow sur des params adversariaux. Évite tout accès UB.
- xdg_surface.ack_configure refuse les serials > last_sent (log +
ignore, pas de post_error pour 7.5 — tolérance volontaire).
- wl_surface.commit court-circuite la lecture pour les buffers
invalides ou si read_argb retourne None (log warning, surface
garde son ancien contenu).
Nouveau crate : redox-wl-test-fuzz-protocol (~370 lignes)
- fork() pour chaque cas afin qu'un crash potentiel d'un cas ne
contamine pas les suivants
- 4 cas : brutal exit, bad ack serial, null dimensions, bad stride
- Le parent attend chaque enfant via waitpid avant le suivant
Validation runtime QEMU :
- [fuzz1..4] tous PASS, [fuzz] PASS final
- [frontend] xdg_surface.ack_configure: serial 99999 > last_sent 2, ignoring
- [frontend] wl_shm_pool.create_buffer rejected: offset=0 width=0 height=0 stride=0
- [frontend] wl_shm_pool.create_buffer rejected: offset=0 width=100 height=10 stride=10
- Compositor continue à ticker 30+ s post-fuzz, curseur actif,
surfaces des fuzz suivants créées et focusées normalement.
Sub-bug documenté (à corriger 7.6) : la surface du fuzz1 (exit
brutal sans destroy) persiste après la déconnexion du client.
wayland-server détecte le close socket mais ne réveille pas
automatiquement le wl_surface.Destroy handler. À hooker dans
DumbClientData::disconnected pour le cleanup explicite.
Doc complète : docs/phase7-5-robustness.md.
Leyoda 2026 – GPLv3
Sur clic gauche, le compositor fait hit_test à la position curseur,
raise la surface ciblée au top du Z-order et transfère le keyboard
focus à cette surface (broadcast wl_keyboard.leave/enter via le
set_focus déjà implémenté en 7.2).
Frontend additions :
- HashMap<SurfaceId, wl_surface::WlSurface> dans WaylandFrontend,
peuplée au wl_compositor.create_surface (capture du retour de
data_init.init), nettoyée au wl_surface.destroy
- Au wl_surface.destroy : clear focused_surface et cursor_surface_id
si la surface détruite était l'une de ces références (évite les
wl_surface fantômes dans les events suivants)
- forward_input(PointerButton.left=true) déclenche
registry.hit_test(cursor_x, cursor_y), puis si la cible n'est pas
une surface curseur : registry.raise + set_focus(target)
- println! tracing pour [frontend] left-click et focus change
Nouveau crate : redox-wl-test-client-shm-two
- Binaire qui fork() : parent = fenêtre A (verte, pyramide), enfant
= fenêtre B (magenta, double cercle) après sleep 800ms
- 2 connexions Wayland indépendantes au même socket compositor
- timeout 160s aligné sur le compositor 180s
Validation runtime : 4 captures synchronisées via cycle de
positionnement curseur temporaire (retiré après) prouvent les
2 transitions de Z-order :
- initial : B au top (commit le plus récent)
- click@(80,80) → hit A → A passe au top
- click@(400,280) → hit B → B repasse au top
Traces /tmp/comp.log (extraites via redoxfs) confirment :
[frontend] left-click @ (80, 80) → hit_test = Some(SurfaceId(0))
[frontend] focus change: Some(SurfaceId(1)) → Some(SurfaceId(0))
[frontend] left-click @ (400, 280) → hit_test = Some(SurfaceId(1))
[frontend] focus change: Some(SurfaceId(0)) → Some(SurfaceId(1))
Pipeline validé end-to-end :
mouse_button QEMU → ps2d → inputd → InputBackend::poll →
RedoxInputEvent::PointerButton → forward_input → hit_test →
raise + set_focus → wl_keyboard.leave/enter broadcast.
Doc complète : docs/phase7-4-focus-raise.md.
Leyoda 2026 – GPLv3
Sprite curseur 16x16 ARGB dessiné par-dessus la composition après
`SurfaceRegistry::compose_into()`, avec alpha blending non
prémultiplié (`out = src + dst * (1 - src.a)`) et hot-spot
configurable.
Frontend additions :
- `cursor_surface_id` / `cursor_hot_x` / `cursor_hot_y` /
`cursor_visible` dans `WaylandFrontend`
- `is_cursor: AtomicBool` dans `SurfaceData`
- `default_cursor_sprite()` : flèche hardcoded 16x16
- `blend_argb_over(src, dst)` avec fast paths a=0/a=255
- `draw_cursor<F: Framebuffer>(target)` : clip aux bords du fb,
blit pixel par pixel
- `set_cursor_initial_position` / `set_cursor_position` /
`cursor_position` publiques
- `wl_pointer.set_cursor` handler : store la surface client,
marque `is_cursor = true`, l'exclut du Z-order (visible=false)
- `wl_surface.commit` lit `is_cursor` → si curseur, pas de
raise/focus et reste invisible dans la composition normale
- `cursor_visible = true` au premier PointerMotion(Relative)
Binaire compositor :
- `set_cursor_initial_position(fb_w/2, fb_h/2)` au boot
- `frontend.draw_cursor(&mut output)` après `compose_into`
- timeout porté de 60s à 180s pour validation visuelle confortable
Test client SHM :
- timeout porté de 25s à 170s pour rester aligné avec le compositor
Validation runtime : 5 screendumps à 5 positions distinctes
prouvent que `draw_cursor` est appelé correctement quel que soit
`(cursor_x, cursor_y)`, dont 2 captures par-dessus la fenêtre
client SHM (overlay alpha-blended sur les bandes arc-en-ciel).
Note runtime : Redox n'a pas de driver USB tablet opérationnel
sous QEMU. `mouse_move` PS/2 du monitor QEMU ne produit pas non
plus de PointerMotion côté inputd. Validation faite en mode
programmatique via un cycle temporaire `set_cursor_position`,
retiré du binaire après screendumps. À investiguer ps2d/vesad
en phase 7.5 ou plus tard.
Doc complète : `docs/phase7-3-cursor.md`.
Leyoda 2026 – GPLv3
Capture preuve : docs/phase7-2-input-routing.png — fenêtre client
xdg_toplevel 480x320 (damier turquoise) à (60,60), compositor stable
pendant que les keyboard events transitent en parallèle.
Validation runtime exhaustive : tous les events injectés via QEMU
sendkey/mouse_button arrivent au client via wl_keyboard.key /
wl_pointer.button :
[client-input] wl_keyboard.key key=54 Pressed ← 'c'
[client-input] wl_keyboard.key key=50 Pressed ← shift
[client-input] wl_keyboard.key key=38 Pressed ← 'a' avec shift
[client-input] wl_keyboard.key key=37 Pressed ← ctrl
...
Modifications redox-wl-wayland-frontend :
- + dep redox-wl-input (pour InputEvent type)
- wl_seat global v7 avec capabilities = Pointer | Keyboard
- wl_seat.name = "redox-wl-seat0" (v2+)
- Dispatch wl_seat : GetPointer, GetKeyboard, GetTouch (no-op),
Release ; au get_keyboard envoie keymap NoKeymap + repeat_info
- Dispatch wl_pointer / wl_keyboard / wl_touch : Release retire la
resource de state.{pointers,keyboards}
- forward_input(InputEvent) public method qui broadcast
wl_keyboard.key, wl_pointer.motion/button/axis/frame aux clients
- set_focus(surface) public method qui envoie keyboard/pointer
enter/leave events sur changement de focus
- Tracking : focused_surface, cursor_x/y, next_input_serial,
input_time_ms, pointers/keyboards Vec<Resource>
Modif wl_surface.commit : appelle set_focus(Some(_resource)) pour que
la dernière surface commitée reçoive l'enter automatiquement
(politique simple 7.2, à raffiner en 7.4).
Modif compositor binaire (redox-wl-compositor) :
- Forward chaque InputEvent au frontend.forward_input(&ev)
- Esc reste géré côté compositor pour exit propre
Bin redox-wl-test-client-input ajouté (~280 lignes) :
- Bind wl_compositor + wl_shm + xdg_wm_base + wl_seat
- get_keyboard + get_pointer après reception caps
- Crée xdg_toplevel + buffer ARGB damier turquoise
- Log chaque wl_keyboard.{enter,leave,key,modifiers,repeat_info}
et wl_pointer.{enter,leave,motion,button,axis}
- Boucle event_queue : flush + prepare_read.read + dispatch_pending
(CORRECT pattern pour wayland-rs ; le bug initial était d'utiliser
juste dispatch_pending qui ne lit pas le socket)
Critère de fin 7.2 validé : un client qui bind wl_seat reçoit
keyboard events via wl_keyboard.key sans panic serveur.
Limitations connues (sous-tickets ultérieurs) :
- Keymap NoKeymap (pas de XKB layout) — 7.2 utilise scancodes raw
- Broadcast à tous les keyboards/pointers (pas de filtrage par
client focus) — multi-client viendra en 7.6
- Pas de pointer.motion testé (besoin -device usb-tablet QEMU)
- Pas de validation modifier state (juste enter envoie 0,0,0,0)
Image Redox restaurée à boot Orbital normal.
Phrase reprise 7.3 :
> Reprendre au commit XXX : Phase 7.3 curseur software. Dessiner un
> sprite curseur 16x16 par-dessus la composition, position basée sur
> InputBackend cursor_x/y. Hot-spot configurable via wl_pointer.set_cursor
> (déjà no-op à 7.2). Tester avec usb-tablet QEMU pour avoir motion absolu.
Leyoda 2026 – GPLv3
Captures preuves dans docs/phase6-3-*.png : 4 frames qui prouvent
visuellement que raise change l'ordre Z et que compose_into propage le
résultat à l'écran QEMU :
- default-z.png : 3 surfaces overlap, blue top (créé en dernier)
- red-top.png : sendkey 1 → raise(red) → red couvre vert et bleu
- green-top.png : sendkey 2 → raise(green) → green couvre tout dans sa zone
- blue-top.png : sendkey 3 → raise(blue) → retour visuel à initial
Modifications :
compositor-core (commit dbf3bff → maintenant) :
- + iter_z_order_front_to_back() : utile pour hit testing
- + hit_test(x, y) -> Option<SurfaceId> : trouve la surface visible la
plus haute qui contient le point
- + 4 tests unitaires : 27 total / 27 pass natif (0.00s)
redox-wl-display :
- + dep redox-wl-compositor-core
- + impl Framebuffer for RedoxOutput (délègue à pixels_mut + width/height)
bin redox-wl-test-compose-static (190 lignes) :
- ouvre RedoxOutput + take_crtc
- crée InputBackend partagé
- 3 surfaces ARGB unies (rouge/vert/bleu) avec overlap centré
- boucle event : '1'/'2'/'3' raise resp. red/green/blue
- clic souris → hit_test puis raise (motion non testé sans usb-tablet)
- ré-render seulement si raise → économie CPU
- present_with_takeover() à chaque iter pour tenir le CRTC
Validation QEMU automatisée : sendkey 1/2/3 + screendump entre chaque.
Les 4 PNG montrent l'ordre Z évoluer correctement.
Image Redox restaurée à boot Orbital normal.
docs/phase6-compositor-core.md : compte-rendu 6.1-6.3 complet,
architecture, dépendances, API, limitations, plan 6.4.
Phase 6.3 close. Reste 6.4 (frontend Wayland : wl_compositor + wl_shm
+ xdg-shell mappés vers compositor-core, damage tracking, frame
callbacks). Estimé 2-3 sessions.
Leyoda 2026 – GPLv3
Capture : docs/phase4-victory-1280x800.png — dégradé ARGB animé 1280x800
écrit par redox-wl-fullscreen-paint, occupant tout l'écran QEMU sans
trace de bootlog, fbcond ou Orbital.
Cause racine du verrou (3 bugs en cascade) :
1. ConsumerHandle local à RedoxOutput::open() → droppé en fin de fn →
inputd::on_close retirait le VT de self.vts → tous les `inputd -A <vt>`
ultérieurs retournaient warning "switch to non-existent VT"
2. L'env var VT=N posée par init n'a aucun lien avec le VT alloué par
inputd. inputd auto-incrémente next_vt_id à partir de 2 (VT 1 réservé
bootlog). Avec fbbootlogd VT 1 + fbcond VT 2, notre paint = VT 3.
3. Sans le bon VT activé, set_crtc est silencieusement no-op côté
driver-graphics (lib.rs:575 : `if *vt == self.active_vt { ... }`).
Fixes :
- RedoxOutput stocke `_consumer: ConsumerHandle` pour préserver le VT
- RedoxOutput.vt() lu via fpath sur consumer fd (inputd retourne
`<scheme>/<vt>`)
- Binary lit output.vt() puis fait inputd -A <vt> avec le bon numéro
- 300ms de sleep pour propagation active_vt avant take_crtc
Validation automatisée : qemu -display none + monitor unix socket +
ncat -U pour sendkey ret + screendump à T+14s + ImageMagick.
Image Redox restaurée à boot Orbital normal après la session.
Phase 4 close. La piste 1 (consume events VT) reste utile pour le
hot-switch propre Ctrl+Alt+Fn mais n'est plus bloquante.
Leyoda 2026 – GPLv3
Crates ajoutés :
- redox-wl-display (lib) : RedoxOutput { open, take_crtc, pixels_mut,
present, present_with_takeover, Drop }
- redox-wl-fullscreen-paint (bin) : take CRTC + 30 frames ARGB animées
Validation logique du pipeline display sur Redox bootée :
- ConsumerHandle::new_vt() OK
- open_display_v2() retourne le fd graphics-ipc
- V2GraphicsHandle énumère 1 connecteur 1280x800
- CpuBackedBuffer ARGB8888 plein écran allocable
- add_framebuffer + set_crtc + dirty_framebuffer répondent tous OK
- 30 itérations sync_rect sans erreur ni leak
Validation visuelle automatisée via QEMU monitor screendump :
- QEMU en -display none + -monitor unix:socket
- ncat -U envoie sendkey ret au bootloader puis screendump à T+15s
- ImageMagick convertit PPM → PNG, visualisable
Verrou identifié : fbbootlogd (lancé par init.initfs.d/20_fbbootlogd.service,
embarqué dans le blob initfs) écrit directement dans le framebuffer mémoire
mappé par vesad, hors du pipeline DRM. Il ne release pas le display quand
notre paint fait set_crtc.
Pour vrai visuel, il faut soit :
1. Consommer les events VT côté RedoxOutput (le pattern Orbital, propre)
2. Désactiver fbbootlogd dans l'image (rapide, debug)
3. Implémenter le handoff complet (long, prod)
Le pipeline étant validé, on peut passer phase 5 (input backend) et revenir
sur le visuel quand on aura un compositor qui consomme les events VT.
docs/phase4-display-backend.md enrichi avec l'analyse complète.
Leyoda 2026 – GPLv3
Test exécuté avec succès sur Redox bootée via make qemu :
[disp] 1 connector(s) reported by KMS subset
[disp] #0 connector ::Handle(19): state=Connected, 1 mode(s)
[disp] first mode: 1280x800
[disp] CpuBackedBuffer allocated 64x64 ARGB8888 (shadow=false)
[disp] painted test pattern + sync_rect
[disp] PASS: display backend pipeline reachable
Implications majeures :
- la crate drm 0.15 upstream fonctionne sur Redox runtime, pas seulement
compile-time
- graphics-ipc::V2GraphicsHandle est un subset DRM/KMS suffisant pour
un compositor minimal
- inputd accepte plusieurs consumers sur des VTs différents → on peut
développer un compositor sur VT 2 tout en gardant Orbital sur VT 3
(validation de la stratégie de coexistence du plan directeur)
Le test n'inclut pas encore modeset/scanout (set_crtc) — c'est l'étape
suivante : crate redox-wl-display + binaire qui prend le CRTC pour
afficher de vrais pixels.
docs/phase4-display-backend.md : compte-rendu complet + roadmap.
Leyoda 2026 – GPLv3