# 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` pour éviter de prendre un mutex sur le hot path de la composition. **`WaylandFrontend`** : - nouveaux champs : - `cursor_surface_id: Option` — 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(target)` — dessine le sprite par-dessus le framebuffer avec alpha blending **Sprite par défaut** : `default_cursor_sprite()` retourne `(Vec, 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(&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.*