# Phase 7.4 — Focus + raise on click > Document produit le 2026-05-13 dans le cadre du plan directeur > `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`. > > **Scope strict** : > - sur clic gauche, faire `registry.hit_test(cursor_x, cursor_y)` > - si la surface ciblée est non-cursor : `registry.raise(sid)` + > `set_focus(matching wl_surface)` (transfert du keyboard focus) > - ajouter un mapping inverse `SurfaceId → wl_surface::WlSurface` pour > pouvoir retrouver la WlSurface depuis le hit_test > - tester avec 2 fenêtres xdg_toplevel SHM en parallèle (fork) > > **Hors scope 7.4** : focus-follows-mouse, click-through, move/resize > interactifs (7.7), filtrage par client des events pointer (7.6), > validation des serials ack_configure (7.5). ## Verdict **✅ Click-to-raise + focus transfer validés runtime, 2 clients SHM parallèles, 4 captures de preuve.** | Capture | État | |---|---| | `phase7-4-1-initial-B-top.png` | B violet créée en dernier → au top du Z-order, A vert visible sous B. Curseur sur A (80, 80). | | `phase7-4-2-after-click-A-raised.png` | Après clic à (80, 80) sur A : **A passe au top**, B passe en-dessous. Curseur a migré sur B (cycle programmé). | | `phase7-4-3-after-click-B-raised.png` | Après clic à (400, 280) sur B : **B repasse au top**, A passe en-dessous. | Trace `/tmp/comp.log` côté Redox (extraite via redoxfs après le run) : ``` [comp] phase 0: cursor → A (80, 80) [frontend] focus change: None → Some(SurfaceId(0)) # A créée [frontend] focus change: Some(SurfaceId(0)) → Some(SurfaceId(1)) # B créée (commit auto-focus) [comp] 1 input events from inputd # mouse_button 1 = press [frontend] left-click @ (80, 80) → hit_test = Some(SurfaceId(0)) # A trouvée [frontend] focus change: Some(SurfaceId(1)) → Some(SurfaceId(0)) # focus → A [comp] 1 input events from inputd # mouse_button 0 = release [comp] phase 1: cursor → B (400, 280) [comp] 1 input events from inputd # 2e clic press [frontend] left-click @ (400, 280) → hit_test = Some(SurfaceId(1)) # B trouvée [frontend] focus change: Some(SurfaceId(0)) → Some(SurfaceId(1)) # focus → B [comp] 1 input events from inputd # release ``` Le pipeline complet est validé end-to-end : `mouse_button QEMU → ps2d → inputd → InputBackend::poll → RedoxInputEvent::PointerButton → WaylandFrontend::forward_input → SurfaceRegistry::hit_test → registry.raise + set_focus → wl_keyboard.leave/enter broadcast`. ## Modifications apportées ### `redox-wl-wayland-frontend` **`WaylandFrontend`** : nouveau champ ```rust surfaces_by_id: HashMap, ``` peuplé dans le handler `wl_compositor::Request::CreateSurface` (en récupérant le retour de `data_init.init(id, ...)`), nettoyé dans `wl_surface::Request::Destroy` avec en plus le clear de `focused_surface` et `cursor_surface_id` si la surface détruite était ces références (évite les wl_surface fantômes dans les events suivants). **`forward_input(PointerButton { left, .. })`** : avant l'envoi des events button aux pointers, si `left == true` : 1. `self.registry.hit_test(cursor_x, cursor_y) -> Option` 2. Si `Some(sid)` et la surface n'est pas un curseur (`is_cursor.load() == false`), récupérer la `wl_surface` dans `surfaces_by_id`, faire `registry.raise(sid)` puis `set_focus(Some(surf))`. Le `set_focus` envoie déjà les `wl_keyboard.leave` / `wl_keyboard.enter` et `wl_pointer.leave` / `wl_pointer.enter` sur les bons wl_surface, et ses modifiers reset (cf phase 7.2). Pas de duplication de code. **Instrumentation** : `println!` du frontend à chaque clic (`[frontend] left-click @ (...) → hit_test = ...`) et à chaque changement de focus (`[frontend] focus change: old_sid → new_sid`). Sort sur stdout du processus compositor ; le service init redirige vers `/tmp/comp.log` côté Redox pour analyse post-run. ### `redox-wl-test-client-shm-two` (nouveau crate) Binaire qui `fork()` une fois. Parent et enfant exécutent chacun la même séquence de connexion Wayland + xdg-shell, mais avec : - des couleurs ARGB différentes (vert pastel pour A, magenta pour B) - un grand pictogramme central distinctif (pyramide tronquée "A" / deux disques empilés "B") pour identifier la fenêtre dans les screendumps - un sleep 800 ms côté enfant pour garantir l'ordre A créée → B créée - timeout 160 s aligné sur le compositor Le compositor place automatiquement A à `(60, 60)` et B à `(120, 120)` grâce à son cascade offset 7.1, ce qui produit l'overlap voulu pour tester le raise. Au début (avant tout clic) : B est au top car commit le plus récent → politique 6.4 "dernière surface commitée = au-dessus" (à raffiner en phase 7.6/7.7). ## Méthode de validation runtime Limitation runtime héritée 7.3 : Redox n'a pas de driver mouse fonctionnel sous QEMU + virtio-vga (PS/2 mouse_button OK, PS/2 et USB tablet mouse_move muets). Pour positionner le curseur dans une zone précise avant le clic, le compositor a embarqué temporairement un cycle de 4 phases qui appelaient `frontend.set_cursor_position(x, y)` toutes les 15 s entre `(80, 80)` (uniquement sur A) et `(400, 280)` (uniquement sur B). Pendant chaque phase, le script de test envoie via QMP : ``` mouse_button 1 # press mouse_button 0 # release screendump /tmp/screen-N.ppm ``` Le clic, lui, passe par le pipeline réel et déclenche le code production `forward_input → hit_test → raise + set_focus`. Le cycle de positionnement est retiré du binaire après validation (comme en 7.3) ; seul le câblage production est commité. Le service init Redox utilisé (purement temporaire) : ``` # /usr/lib/init.d/40_phase74_focus requires_weak 30_console nowait /usr/bin/launch_phase74.sh # /usr/bin/launch_phase74.sh #!/bin/sh sleep 2 /usr/bin/redox-wl-compositor > /tmp/comp.log 2>&1 & sleep 4 /usr/bin/redox-wl-test-client-shm-two > /tmp/client.log 2>&1 ``` À noter : l'init Redox ne sait pas parser correctement `nowait sh -c "..."` avec guillemets dans la définition de service — il faut passer par un script wrapper sur disque pour fork + redirection. ## Limitations connues (à traiter en sous-tickets ultérieurs) - **Events pointer/keyboard broadcast** : le clic raise + focus est correct, mais les events button/key sont encore broadcast à tous les `wl_pointer` / `wl_keyboard` (pas filtrage par client focused). Reportable à 7.6 (multi-clients). - **Focus follows mouse non implémenté** : on doit cliquer pour changer le focus (modèle click-to-focus). Optionnel selon politique WM, pas dans le scope. - **Pas de transition press/release tracked** : à chaque event `PointerButton { left: true, .. }`, on rejoue le hit_test. En pratique inputd n'envoie qu'1 event par transition donc OK, mais à durcir en 7.5 si on observe des re-raises spurieux. - **Surface détruite tout en étant focused** : géré (focused_surface cleared, cursor_surface_id cleared), mais pas de re-focus auto sur la prochaine surface du Z-order. Le client suivant qui commit reprendra le focus via la politique 6.4 — acceptable pour 7.4. - **Pas encore de fenêtre "popup"** : focus-stealing par xdg_popup reportable. ## Critère de fin 7.4 > Un clic gauche dans la fenêtre cliquée la fait passer au top du > Z-order et lui transfère le keyboard focus, sans déstabiliser le > compositor, multi-clients OK. **✅ Validé.** 2 clients SHM en parallèle, 2 transitions Z-order distinctes, traces frontend explicites confirmant le hit_test et les changements de focus. ## Code ``` crates/redox-wl-wayland-frontend/ # +~30 lignes (HashMap mapping, hit_test branch, logs) crates/redox-wl-test-client-shm-two/ # nouveau crate (~330 lignes) ``` ## Suite phase 7.5 Robustesse paquet A : tests négatifs sur les protocoles. Clients qui : - ferment sans destroy propre - envoient ack_configure avec un mauvais serial - détruisent un buffer encore attaché - attachent un buffer avec width/height/stride invalides - détruisent un xdg_surface avant son toplevel - envoient des messages au mauvais moment du cycle de vie But : le compositor doit dans tous les cas soit ignorer proprement, soit fermer la connexion du client fautif avec un `post_error`, mais JAMAIS paniquer. Estimé : 1-2 sessions. --- *Fin du document de phase 7.4.*