Phase 13.2.b.2 — wl_subsurface rendering : position absolue = parent + offset

Wire le SubsurfaceData (introduit en 13.2.b.1) au commit du child wl_surface :
- Ajout d'un champ subsurface_link: Mutex<Option<Arc<SubsurfaceData>>>
  à SurfaceData, peuplé par wl_subcompositor.GetSubsurface
- Au commit du child : on détecte le subsurface_link, on remonte la
  position absolue du parent depuis le registry, on additionne l'offset
  pending (modifiable via SetPosition), et on l'écrit sur pending state
  du child avant que registry.commit applique pending→current
- Skip raise() et set_focus() pour les subsurfaces : elles ne grabbent
  pas le focus, et leur z-order reste à parent.z+1 par construction
  (création après le parent dans le registry)

compose_into n'est pas modifié : il itère le z_order et dessine chaque
surface à son x/y absolu. Le child apparaît donc naturellement à la
bonne position après notre prep.

Limitations connues (à traiter en phase ultérieure si besoin) :
- Si le parent toplevel bouge (drag interactif phase 7.7), ses
  subsurfaces ne suivent pas tant que le client ne re-commit pas
  l'enfant. Pour les use-cases statiques (notre test 13.2.b.3 à venir),
  c'est OK ; pour Wayland conforme strict il faudra walker les enfants
  au move du parent.
- wl_subsurface.Destroy ne unmap pas le child (spec violation mineure).
  Si le client veut vraiment retirer la subsurface, il doit aussi
  destroy son wl_surface enfant — chemin déjà géré.
- Pas de cascade sync : un commit du parent en mode sync devrait
  flusher les pending states des subsurfaces sync. Ignoré pour l'instant
  (notre client de test commitera child directement).

Les tests natifs 13.2.a et 13.2.b.1 continuent de passer (vérifié).

Leyoda 2026 – GPLv3
This commit is contained in:
Votre Nom 2026-05-16 14:13:31 +02:00
parent f9c3de13da
commit bba2d7beb6

View file

@ -186,6 +186,11 @@ struct SurfaceData {
/// surfaces d'un client déconnecté (à ce moment-là `surf.client()`
/// retourne déjà None, donc on ne pourrait pas re-déduire le ClientId).
client_id: Mutex<Option<ClientId>>,
/// Phase 13.2.b.2 : si cette surface est une subsurface, lien vers les
/// données du wl_subsurface qui la pilote (parent ref, offset, sync).
/// Setté par `wl_subcompositor.GetSubsurface`. Lu au commit de cette
/// surface pour calculer sa position absolue = parent.pos + offset.
subsurface_link: Mutex<Option<Arc<SubsurfaceData>>>,
}
/// Données par-xdg_surface : référence à la wl_surface sous-jacente +
@ -1313,6 +1318,7 @@ impl wayland_server::Dispatch<wl_compositor::WlCompositor, ()> for WaylandFronte
// est encore vivant. À la déconnexion, `surf.client()`
// retournera None et on ne pourrait plus déduire ce mapping.
client_id: Mutex::new(Some(_client.id())),
subsurface_link: Mutex::new(None),
};
let surf = data_init.init(id, Arc::new(data));
// Phase 7.4 : enregistrer le mapping SurfaceId → WlSurface
@ -1521,6 +1527,35 @@ impl wayland_server::Dispatch<wl_surface::WlSurface, Arc<SurfaceData>> for Wayla
}
let is_cursor = data.is_cursor.load(Ordering::Relaxed);
// Phase 13.2.b.2 : si cette surface est une subsurface,
// recalculer sa position absolue à partir de parent.pos +
// offset, avant que le commit applique pending→current.
// Détection via subsurface_link, peuplé par GetSubsurface.
let subsurface_link = data.subsurface_link.lock().unwrap().clone();
let is_subsurface = subsurface_link.is_some();
if let Some(sub_data) = &subsurface_link {
let parent_id_opt = sub_data
.parent
.data::<Arc<SurfaceData>>()
.and_then(|sd| *sd.id.lock().unwrap());
if let Some(parent_id) = parent_id_opt {
let parent_pos = state.registry.get(parent_id).map(|s| {
let cs = s.current();
(cs.x, cs.y)
});
if let Some((px, py)) = parent_pos {
let (ox, oy) = *sub_data.position.lock().unwrap();
state.registry.modify_pending(id, |s| {
s.x = px + ox;
s.y = py + oy;
});
tracing::debug!(
"subsurface commit: pos absolue = parent({},{}) + offset({},{}) = ({},{})",
px, py, ox, oy, px + ox, py + oy
);
}
}
}
// Lire le buffer attaché (s'il y en a un). On `take()` pour
// que le pending_buffer soit vidé après chaque commit
@ -1560,10 +1595,13 @@ impl wayland_server::Dispatch<wl_surface::WlSurface, Arc<SurfaceData>> for Wayla
buf.release();
}
state.registry.commit(id);
if !is_cursor {
if !is_cursor && !is_subsurface {
// Promouvoir au top du Z-order au commit (politique simple :
// dernière surface qui commit = au-dessus). À raffiner en
// phase 7 (focus, raise on click, etc.).
// Phase 13.2.b.2 : les subsurfaces ne raisent pas
// indépendamment de leur parent ; elles vivent à
// parent.z + 1 par convention de leur création.
state.registry.raise(id);
}
@ -1572,11 +1610,13 @@ impl wayland_server::Dispatch<wl_surface::WlSurface, Arc<SurfaceData>> for Wayla
state.frame_callbacks.append(&mut *cbs);
drop(cbs);
if !is_cursor {
if !is_cursor && !is_subsurface {
// Phase 7.2 : la surface qui vient de commiter et raise
// devient automatiquement la surface focalisée. Envoie les
// events keyboard/pointer enter/leave en conséquence.
// Une surface curseur n'a évidemment pas le focus — on skip.
// Phase 13.2.b.2 : pareil pour les subsurfaces — le
// focus reste sur le parent toplevel.
state.set_focus(Some(_resource.clone()));
}
}
@ -2329,15 +2369,11 @@ impl wayland_server::Dispatch<wl_subcompositor::WlSubcompositor, ()> for Wayland
} => {
// Vérif soft : rôle déjà attribué ? (xdg_toplevel notamment).
// Pour 13.2.b.1 on se contente de logger ; le protocole exige
// un bad_surface error mais on attendra 13.2.b.2 pour
// l'enforcer car ça touche au design role-tracking.
// un bad_surface error mais on attendra une phase ultérieure
// pour l'enforcer (role-tracking).
if let Some(child_data) = surface.data::<Arc<SurfaceData>>() {
let id_lock = child_data.id.lock().unwrap();
if id_lock.is_some() {
// La surface a déjà été registered comme toplevel ?
// Hmm, en fait avoir un SurfaceId ne signifie pas un
// rôle xdg — c'est juste qu'elle vit dans le registry.
// On ne fait rien de plus que log debug.
tracing::debug!(
"GetSubsurface: surface child déjà dans registry (id={:?})",
*id_lock
@ -2345,11 +2381,17 @@ impl wayland_server::Dispatch<wl_subcompositor::WlSubcompositor, ()> for Wayland
}
}
let data = Arc::new(SubsurfaceData {
parent,
child: surface,
parent: parent.clone(),
child: surface.clone(),
position: Mutex::new((0, 0)),
sync: AtomicBool::new(true), // default = sync per spec
});
// Phase 13.2.b.2 : lier la SurfaceData de la child au
// SubsurfaceData pour que le commit puisse calculer la
// position absolue parent.pos + offset.
if let Some(child_sd) = surface.data::<Arc<SurfaceData>>() {
*child_sd.subsurface_link.lock().unwrap() = Some(data.clone());
}
data_init.init(id, data);
tracing::debug!("wl_subcompositor.get_subsurface: subsurface créée");
}