redox-wayland-compositor/docs/phase7-3-cursor.md
Votre Nom 5f7587e79e 🎉 Phase 7.3 — curseur software validé runtime
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
2026-05-13 09:06:56 +02:00

267 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 7.3 — Curseur software
> Document produit le 2026-05-09 dans le cadre du plan directeur
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`.
>
> **Scope strict** :
> - dessiner un sprite curseur 16×16 par-dessus la composition après
> `compose_into()`, avec alpha blending
> - position basée sur `cursor_x/y` du frontend (déjà tracké via
> `PointerMotion` / `PointerMotionRelative` en 7.2)
> - hot-spot configurable
> - implémenter `wl_pointer.set_cursor` : stocker la surface client comme
> curseur ; dessiner son buffer si fourni, sinon le sprite par défaut
> - exclure la surface curseur du Z-order normal (pas de raise/focus)
>
> **Hors scope 7.3** : focus + raise on click (7.4), robustesse paquet A
> (7.5), multi-clients (7.6), move/resize interactifs (7.7).
## Verdict
**✅ Curseur software validé runtime sur Redox.**
5 captures à 5 positions distinctes, dont 2 par-dessus la fenêtre client
SHM (overlay alpha-blended) :
| Capture | Position cursor | Affichage |
|---|---|---|
| `phase7-3-cursor-pos-center.png` | (640, 400) | curseur au centre, fenêtre client en haut-gauche |
| `phase7-3-cursor-over-window-200x150.png` | (200, 150) | curseur **par-dessus** la bande verte de la fenêtre client |
| `phase7-3-cursor-over-window-250x200.png` | (250, 200) | curseur **par-dessus** la bande violette de la fenêtre client |
| `phase7-3-cursor-pos-900x600.png` | (900, 600) | curseur en bas-droite sur fond |
| `phase7-3-cursor-pos-50x750.png` | (50, 750) | curseur en bas-gauche sur fond |
Les 5 captures montrent le sprite par défaut (flèche blanche + contour
noir, 16×16, hot-spot (0,0)) dessiné après `compose_into()` et avant
`present_with_takeover()`.
## Modifications apportées
### `redox-wl-wayland-frontend`
**`SurfaceData`** :
- ajout `is_cursor: AtomicBool` — marque les surfaces désignées comme
curseur par `wl_pointer.set_cursor`. Atomic plutôt que `Mutex<bool>`
pour éviter de prendre un mutex sur le hot path de la composition.
**`WaylandFrontend`** :
- nouveaux champs :
- `cursor_surface_id: Option<SurfaceId>` — surface curseur du dernier
`set_cursor`, ou `None` pour le sprite par défaut
- `cursor_hot_x: i32`, `cursor_hot_y: i32` — hot-spot du sprite
- `cursor_visible: bool` — false avant le premier `PointerMotion`,
ou si `set_cursor(None)` (hide cursor explicite)
- nouvelles méthodes publiques :
- `set_cursor_initial_position(x, y)` — pour le compositor : place le
curseur au centre du framebuffer au boot
- `set_cursor_position(x, y)` — setter générique (utile pour tests
programmatiques en attendant un driver mouse fonctionnel)
- `cursor_position() -> (i32, i32)` — getter
- `draw_cursor<F: Framebuffer + ?Sized>(target)` — dessine le sprite
par-dessus le framebuffer avec alpha blending
**Sprite par défaut** : `default_cursor_sprite()` retourne `(Vec<u32>,
16, 16, 0, 0)`. Layout en code Rust :
```text
K . . . . . . . . . . . . . . . K=noir 0xFF000000
K K . . . . . . . . . . . . . . W=blanc 0xFFFFFFFF
K W K . . . . . . . . . . . . . .=transparent 0x00000000
K W W K . . . . . . . . . . . .
K W W W K . . . . . . . . . . .
K W W W W K . . . . . . . . . .
K W W W W W K . . . . . . . . .
K W W W W W W K . . . . . . . .
K W W W W W W W K . . . . . . .
K W W W W W W W W K . . . . . .
K W W W W W K K K K K . . . . .
K W W K W W K . . . . . . . . .
K W K . K W W K . . . . . . . .
K K . . K W W K . . . . . . . .
. . . . . K W W K . . . . . . .
. . . . . . K K . . . . . . . .
```
**Alpha blending** : `blend_argb_over(src, dst) -> u32` implémente la
formule standard `out = src + dst * (1 - src.a)`. Fast paths pour
`src.a == 0` (passthrough) et `src.a == 255` (overwrite). Pas de
prémultiplication ; cohérent avec le sprite par défaut (pixels
transparents ont RGB=0) et fonctionnel sur les buffers ARGB non-
prémultipliés des clients.
### `wl_pointer.set_cursor` handler
```rust
wl_pointer::Request::SetCursor { surface, hotspot_x, hotspot_y, .. } => {
match surface {
Some(surf) => {
// Marque la surface comme curseur
sd.is_cursor.store(true, Ordering::Relaxed);
state.cursor_surface_id = Some(sid);
// La masque dans la composition normale
state.registry.modify_pending(sid, |s| s.visible = false);
state.registry.commit(sid);
state.cursor_hot_x = hotspot_x;
state.cursor_hot_y = hotspot_y;
}
None => {
// Hide cursor explicite (spec Wayland)
state.cursor_surface_id = None;
state.cursor_visible = false;
}
}
}
```
### `wl_surface.commit` modifié
La branche commit lit maintenant `data.is_cursor` :
- si **non-curseur** : comportement 7.2 (modify_pending visible=true,
commit, raise au top, set_focus à cette surface)
- si **curseur** : modify_pending visible=**false** (exclu de la
composition normale), commit, **pas** de raise, **pas** de set_focus
Cela garantit qu'une surface curseur ne pollue ni le Z-order normal ni
le routage clavier/souris du toolkit, tout en préservant le buffer
dans le registry pour que `draw_cursor()` puisse le lire.
### `draw_cursor` pipeline
```rust
pub fn draw_cursor<F: Framebuffer + ?Sized>(&self, target: &mut F) {
if !self.cursor_visible { return; }
let (pixels, sw, sh, hot_x, hot_y) = self.current_cursor_sprite();
// surf_x0 = cursor_x - hot_x, idem y
// clip aux bords du framebuffer
// pour chaque pixel : dst = blend_argb_over(src, dst)
}
```
`current_cursor_sprite()` retourne soit le buffer du client (via
`registry.get(cursor_surface_id).current().buffer`), soit le sprite par
défaut. Le sprite par défaut est reconstruit à chaque appel (16×16 =
256 alloc + push, négligeable à 30fps).
### `redox-wl-compositor` (binaire)
- `frontend.set_cursor_initial_position(fb_w/2, fb_h/2)` au démarrage,
pour que le curseur soit visible au centre avant tout event souris
- `frontend.draw_cursor(&mut output)` après `compose_into`, avant
`present_with_takeover`
- timeout du runloop porté à 180s (vs 60s en 7.2) pour rendre les tests
de validation visuelle multi-positions plus confortables
## Méthode de validation visuelle
QEMU monitor `mouse_move dx dy` envoie un mouvement *relatif* via le
PS/2 par défaut. **Redox ne semble pas avoir de driver USB tablet
opérationnel** : malgré `-device usb-tablet`, aucun `PointerMotion`
absolu n'arrive jusqu'à `inputd` (vérifié via grep sur le serial log).
Seuls les `mouse_button` PS/2 génèrent des events visibles côté
compositor (1 `input event from inputd` par sendkey).
Comme `mouse_move dx dy` avec de gros deltas ne fait pas bouger le
curseur côté Redox non plus (les deltas sont probablement saturés ou
ignorés par le driver PS/2 ps2d), la validation a été faite en
**mode programmatique** : un cycle temporaire dans le binaire compositor
faisait osciller `frontend.set_cursor_position(x, y)` entre 5 positions
toutes les 4 secondes. 5 screendumps QEMU pris à intervalles synchrones
confirment :
1. le sprite est rendu après la composition (overlay)
2. `draw_cursor` réagit correctement à n'importe quel `(cursor_x,
cursor_y)` du frontend
3. l'alpha blending fonctionne (la pointe blanc+contour noir se découpe
nettement sur fond uni comme sur la fenêtre client multicolore)
4. la surface client SHM reste correctement composée — curseur et
surface coexistent
Le cycle de validation a été **retiré du binaire après validation**.
Le compositor commité ne fait que `set_cursor_initial_position` au
boot + `draw_cursor` par frame. Quand un vrai driver mouse Redox
(ou un client `wl_pointer.set_cursor`) prendra le relais, ce sera via
`forward_input(PointerMotion...)` qui met déjà à jour `cursor_x/y`.
## Logs serial typiques
```
[comp] Phase 6.4 — compositor Wayland démarrage
[comp] display 1280x800, VT=3
[comp] CRTC pris
[comp] Wayland socket : /tmp/redox-wl-comp.sock
[client] connect to compositor
[client] xdg_toplevel créé, attente initial configure
[client] initial configure reçu, serial=1
[client] ack_configure(1)
[client] buffer attach + damage + commit POST-ack
[comp] tick=30 surfaces=1 elapsed=1.2s
... (compositeur stable, 30fps, 1 surface client)
[comp] tick=4170 surfaces=1 elapsed=169.4s
[client] done, destroy propre
[client] PASS
[comp] tick=4410 surfaces=0 elapsed=179.1s
[comp] timeout atteint, exit
[comp] PASS
```
Aucun panic. Aucun warning runtime côté frontend.
## Limitations connues (à traiter en sous-tickets ultérieurs)
- **wl_pointer.set_cursor non testé runtime** : aucun client de test
n'utilise encore set_cursor. Le store côté serveur est implémenté et
exclu du Z-order, le sprite par défaut est la fallback. À durcir en
phase 7.5/7.6 avec un client qui fournit son propre sprite.
- **Driver mouse Redox non opérationnel sous QEMU avec virtio-vga** :
`mouse_move dx dy` du monitor QEMU ne produit pas de PointerMotion
côté `inputd`. À investiguer côté `ps2d` / vesad / configuration
QEMU. Pas bloquant car la phase 7.3 dessine bien le curseur dès qu'on
fixe sa position via API ; le câblage `forward_input → cursor_x/y`
est unchanged depuis 7.2 et testable dès qu'on aura mouse events.
- **Pas de cursor theme / thèmes XCursor** : un seul sprite hardcoded.
Les clients qui voudront un curseur "I-beam" ou "wait" devront
envoyer leur propre buffer via set_cursor.
- **Pas de damage tracking sur la zone curseur** : on recompose tout
et on blend par-dessus à chaque frame. À 30fps + 16×16 = 256
pixels/frame, c'est négligeable. À optimiser quand on aura damage
tracking complet (phase 7+ tardive).
- **Alpha blending non prémultiplié** : fonctionne pour le sprite par
défaut et pour les buffers ARGB simples des clients, mais la spec
wl_shm.Argb8888 dit "premultiplied". À durcir en 7.5 si on rencontre
un client qui se plaint.
## Critère de fin 7.3
> Un sprite curseur 16×16 est dessiné par-dessus la composition à la
> position `(cursor_x, cursor_y)` du frontend, avec alpha blending, hot-
> spot configurable, sans déstabiliser la composition ni le routage
> input existants.
**✅ Validé.** 5 captures à 5 positions distinctes prouvent que :
- le sprite est rendu (visible sur fond uni comme sur surface client)
- la position est paramétrable via `set_cursor_position`
- l'alpha blending est correct (silhouette nette sur tous fonds)
- la surface client reste correctement composée en coexistence
- aucun panic, aucun warning runtime côté frontend ou compositor
## Code
```
crates/redox-wl-wayland-frontend/ # +~180 lignes (sprite, blend, draw_cursor, set_cursor handler)
crates/redox-wl-compositor/ # +3 lignes (set_cursor_initial_position + draw_cursor + timeout 180s)
```
## Suite phase 7.4
Focus + raise on click via `hit_test()`. Quand un client clique dans
une surface, le compositor doit :
1. appeler `registry.hit_test(cursor_x, cursor_y)` pour trouver la
surface ciblée
2. la `raise()` au top du Z-order
3. envoyer `set_focus(Some(target))` pour transférer le keyboard focus
Estimé : 1 session.
---
*Fin du document de phase 7.3.*