redox-wayland-compositor/docs/phase7-4-focus-raise.md
Votre Nom c40ca9fcc8 🎉 Phase 7.4 — focus + raise on click validĂ©s runtime
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
2026-05-13 10:21:05 +02:00

8.2 KiB

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

surfaces_by_id: HashMap<SurfaceId, wl_surface::WlSurface>,

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<SurfaceId>
  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.