🎉 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
This commit is contained in:
Votre Nom 2026-05-13 10:21:05 +02:00
parent 5f7587e79e
commit c40ca9fcc8
7 changed files with 653 additions and 1 deletions

View file

@ -215,6 +215,10 @@ pub struct WaylandFrontend {
/// dernier `wl_pointer.set_cursor`. `None` = pas de curseur custom,
/// on dessine le sprite par défaut (flèche 16x16).
cursor_surface_id: Option<SurfaceId>,
/// Phase 7.4 : mapping inverse SurfaceId → wl_surface, pour pouvoir
/// faire set_focus(target) après un hit_test au clic. Peuplé au
/// `wl_compositor.create_surface`, nettoyé au `wl_surface.destroy`.
surfaces_by_id: HashMap<SurfaceId, wl_surface::WlSurface>,
/// Hot-spot du curseur (offset à soustraire à cursor_x/y pour le placement).
cursor_hot_x: i32,
cursor_hot_y: i32,
@ -259,6 +263,7 @@ impl WaylandFrontend {
cursor_hot_x: 0,
cursor_hot_y: 0,
cursor_visible: false,
surfaces_by_id: HashMap::new(),
})
}
@ -339,6 +344,18 @@ impl WaylandFrontend {
if same {
return;
}
let old_sid = self
.focused_surface
.as_ref()
.and_then(|s| s.data::<Arc<SurfaceData>>())
.and_then(|d| *d.id.lock().unwrap());
let new_sid = new_focus
.as_ref()
.and_then(|s| s.data::<Arc<SurfaceData>>())
.and_then(|d| *d.id.lock().unwrap());
println!(
"[frontend] focus change: {old_sid:?} → {new_sid:?}"
);
let serial_leave = self.alloc_input_serial();
let serial_enter = self.alloc_input_serial();
@ -448,6 +465,32 @@ impl WaylandFrontend {
middle,
right,
} => {
// Phase 7.4 : sur clic gauche (left pressed), hit_test à la
// position curseur et raise + set_focus à la surface ciblée.
// À faire AVANT l'envoi des events button pour que la nouvelle
// surface reçoive enter+modifiers en premier, puis le button.
if *left {
let hit = self.registry.hit_test(self.cursor_x, self.cursor_y);
println!(
"[frontend] left-click @ ({}, {}) → hit_test = {:?}",
self.cursor_x, self.cursor_y, hit
);
if let Some(sid) = hit {
// Ne pas réagir aux surfaces curseur (elles ne devraient pas
// apparaître dans hit_test car visible=false, mais double-belt).
if let Some(surf) = self.surfaces_by_id.get(&sid).cloned() {
if !surf
.data::<Arc<SurfaceData>>()
.map(|d| d.is_cursor.load(Ordering::Relaxed))
.unwrap_or(false)
{
self.registry.raise(sid);
self.set_focus(Some(surf));
}
}
}
}
let time = self.alloc_input_time();
// Code BTN_LEFT/MIDDLE/RIGHT linux/input-event-codes.h
const BTN_LEFT: u32 = 0x110;
@ -723,7 +766,10 @@ impl wayland_server::Dispatch<wl_compositor::WlCompositor, ()> for WaylandFronte
xdg_pending_initial_configure: Mutex::new(false),
is_cursor: AtomicBool::new(false),
};
data_init.init(id, Arc::new(data));
let surf = data_init.init(id, Arc::new(data));
// Phase 7.4 : enregistrer le mapping SurfaceId → WlSurface
// pour permettre raise_at_cursor / set_focus depuis un hit_test.
state.surfaces_by_id.insert(surface_id, surf);
}
wl_compositor::Request::CreateRegion { id } => {
// Région no-op : on alloue la resource pour que le client
@ -946,6 +992,18 @@ impl wayland_server::Dispatch<wl_surface::WlSurface, Arc<SurfaceData>> for Wayla
let mut id_lock = data.id.lock().unwrap();
if let Some(id) = id_lock.take() {
state.registry.destroy(id);
// Phase 7.4 : retirer du mapping inverse + clear focus
// si c'était la surface focalisée (sinon des events seraient
// envoyés à un wl_surface fantôme).
if let Some(removed) = state.surfaces_by_id.remove(&id) {
if state.focused_surface.as_ref() == Some(&removed) {
state.focused_surface = None;
}
}
// Si c'était le curseur custom, retomber sur le sprite par défaut.
if state.cursor_surface_id == Some(id) {
state.cursor_surface_id = None;
}
}
}
_ => {}