🎉 Phase 7.8 — resize interactif xdg_toplevel + durcissements
Resize complet implémenté (compute selon edges + configure cycle) + durcissements sécu Move/Resize. Frontend additions : - Helper compute_resize_geom(edges, start_x/y/w/h, dx, dy) qui interprète le bitmask xdg_toplevel::ResizeEdge : Top(1)/Bottom(2)/Left(4)/Right(8). Coins = combinaisons. Clamp w/h à MIN_RESIZE_DIM=32. - Handler xdg_toplevel.Resize complet : valide serial!=0, validation soft du serial vs last_button_serial (log warning, accept), refuse si drag déjà actif, check client_id (caller == owner de la surface — sécu défense en profondeur). Capture start_geom (x,y,w,h) + start_cursor, stocke InteractiveDrag:: Resize(edges). - apply_interactive_drag(Resize) : compute new geom, modifie registry x/y, envoie xdg_toplevel.configure(w, h, [Resizing]) + xdg_surface.configure(serial). Met à jour XdgSurfaceData.last_serial = serial pour que l'ack du client soit accepté (sinon refusé par garde 7.5). - Move handler : check client_id ajouté en mode défense en profondeur (un client ne peut pas drag une surface d'un autre). Nouveau crate : redox-wl-test-client-resize (~285 lignes) - Bind wl_seat - 1 fenêtre 320x200 cyan + bordure noire - À T+8s : toplevel.resize(seat, 1, BottomRight) - Écoute xdg_toplevel.Configure : à chaque new size, ack + nouveau shm pool + nouveau buffer + attach + commit - Vec<WlShmPool>/Vec<WlBuffer> pour ne pas drop les anciens pendant que le serveur peut encore les utiliser Pièges trouvés : - last_serial doit être mis à jour à chaque configure envoyé pendant le resize, sinon l'ack du client est refusé par le garde 7.5 (serial > last_sent → ignoring). - Le client peut ignorer la taille initiale suggérée par le configure du compositor (legal selon spec). Le compositor compose à la taille du buffer attaché. - À chaque resize, garder les anciens pool+buffer en mémoire pour ne pas crasher le serveur qui mmap dessus. Validation runtime : 3 captures à 3 tailles distinctes (320x200 initial, ~520x300 agrandi, ~70x25 rétréci) confirment le pipeline end-to-end. Logs symétriques côté client et compositor. Bilan phase 7 (1-8) : - 7.1 xdg-shell, 7.2 wl_seat, 7.3 cursor, 7.4 focus/raise, 7.5 robustesse, 7.6 multi-clients, 7.7 move, 7.8 resize - Compositor 7.x désormais utilisable pour un toolkit Wayland basique. Prochain jalon : port COSMIC (phase 13 plan-directeur, réordonnée avant GPU). Doc complète : docs/phase7-8-resize.md Leyoda 2026 – GPLv3
This commit is contained in:
parent
50f7a064e4
commit
a8898960f1
7 changed files with 838 additions and 21 deletions
|
|
@ -203,12 +203,56 @@ struct XdgToplevelData {
|
|||
enum DragMode {
|
||||
Move,
|
||||
/// Resize avec un bitmask de bords tirés. Le contenu est un
|
||||
/// `xdg_toplevel::ResizeEdge` interprété par le compositor pour
|
||||
/// calculer (new_w, new_h, new_x, new_y).
|
||||
#[allow(dead_code)]
|
||||
/// `xdg_toplevel::ResizeEdge` interprété par `compute_resize_geom`
|
||||
/// pour calculer (new_x, new_y, new_w, new_h).
|
||||
Resize(u32),
|
||||
}
|
||||
|
||||
/// Phase 7.8 : taille minimale d'une surface pendant le resize.
|
||||
/// Sous cette valeur, on clamp pour éviter w/h <= 0 qui ferait planter
|
||||
/// les clients tout en validant leur buffer.
|
||||
const MIN_RESIZE_DIM: u32 = 32;
|
||||
|
||||
/// Phase 7.8 : calcule la nouvelle géométrie d'une surface en cours de
|
||||
/// resize, selon le bitmask `edges` xdg-shell et le delta cursor.
|
||||
///
|
||||
/// edges bitmask : Top=1, Bottom=2, Left=4, Right=8 (coins = combinaisons).
|
||||
fn compute_resize_geom(
|
||||
edges: u32,
|
||||
start_x: i32,
|
||||
start_y: i32,
|
||||
start_w: u32,
|
||||
start_h: u32,
|
||||
dx: i32,
|
||||
dy: i32,
|
||||
) -> (i32, i32, u32, u32) {
|
||||
let mut new_x = start_x;
|
||||
let mut new_y = start_y;
|
||||
let mut new_w = start_w as i32;
|
||||
let mut new_h = start_h as i32;
|
||||
// Right (8) : étend / contracte la droite
|
||||
if edges & 8 != 0 {
|
||||
new_w = (start_w as i32).saturating_add(dx);
|
||||
}
|
||||
// Left (4) : déplace l'origine et inverse le delta sur la largeur
|
||||
if edges & 4 != 0 {
|
||||
new_w = (start_w as i32).saturating_sub(dx);
|
||||
new_x = start_x.saturating_add(dx);
|
||||
}
|
||||
// Bottom (2) : étend / contracte le bas
|
||||
if edges & 2 != 0 {
|
||||
new_h = (start_h as i32).saturating_add(dy);
|
||||
}
|
||||
// Top (1) : déplace l'origine et inverse le delta sur la hauteur
|
||||
if edges & 1 != 0 {
|
||||
new_h = (start_h as i32).saturating_sub(dy);
|
||||
new_y = start_y.saturating_add(dy);
|
||||
}
|
||||
let new_w = new_w.max(MIN_RESIZE_DIM as i32) as u32;
|
||||
let new_h = new_h.max(MIN_RESIZE_DIM as i32) as u32;
|
||||
(new_x, new_y, new_w, new_h)
|
||||
}
|
||||
|
||||
/// Phase 7.7 : état d'un drag interactif en cours. Posé par le handler
|
||||
/// `xdg_toplevel.Move`/`Resize`, lu par `forward_input` pour appliquer
|
||||
/// les deltas, retiré au release du bouton gauche.
|
||||
|
|
@ -562,19 +606,21 @@ impl WaylandFrontend {
|
|||
(self.cursor_x, self.cursor_y)
|
||||
}
|
||||
|
||||
/// Phase 7.7 : si un drag interactif est actif, applique le delta
|
||||
/// `(cursor - start_cursor)` à la position de la surface ciblée et
|
||||
/// retourne `true` pour court-circuiter l'envoi de motion au client.
|
||||
/// Retourne `false` si pas de drag, le caller doit alors faire son
|
||||
/// envoi normal.
|
||||
/// Phase 7.7/7.8 : si un drag interactif est actif, applique le delta
|
||||
/// `(cursor - start_cursor)`.
|
||||
/// - Move : déplace la surface.
|
||||
/// - Resize : compute new geom, modifie x/y dans le registry,
|
||||
/// envoie `toplevel.configure(w, h, [Resizing])` +
|
||||
/// `xdg_surface.configure(serial)` pour que le client re-peigne.
|
||||
/// Retourne `true` pour court-circuiter l'envoi de motion au client.
|
||||
fn apply_interactive_drag(&mut self) -> bool {
|
||||
let Some(drag) = self.interactive_drag.clone() else {
|
||||
return false;
|
||||
};
|
||||
let dx = self.cursor_x - drag.start_cursor_x;
|
||||
let dy = self.cursor_y - drag.start_cursor_y;
|
||||
match drag.mode {
|
||||
DragMode::Move => {
|
||||
let dx = self.cursor_x - drag.start_cursor_x;
|
||||
let dy = self.cursor_y - drag.start_cursor_y;
|
||||
let new_x = drag.start_x.saturating_add(dx);
|
||||
let new_y = drag.start_y.saturating_add(dy);
|
||||
self.registry.modify_pending(drag.surface_id, |s| {
|
||||
|
|
@ -584,10 +630,44 @@ impl WaylandFrontend {
|
|||
self.registry.commit(drag.surface_id);
|
||||
true
|
||||
}
|
||||
DragMode::Resize(_) => {
|
||||
// Stub 7.7 : pas de resize effectif (cf XdgToplevel.Resize handler).
|
||||
// Au minimum on consomme le motion pour éviter de l'envoyer
|
||||
// au client pendant qu'il pense être en mode resize.
|
||||
DragMode::Resize(edges) => {
|
||||
let (new_x, new_y, new_w, new_h) = compute_resize_geom(
|
||||
edges,
|
||||
drag.start_x,
|
||||
drag.start_y,
|
||||
drag.start_w,
|
||||
drag.start_h,
|
||||
dx,
|
||||
dy,
|
||||
);
|
||||
// Mettre à jour la position côté compositor immédiatement
|
||||
// (la taille effective dépendra du prochain commit du client).
|
||||
self.registry.modify_pending(drag.surface_id, |s| {
|
||||
s.x = new_x;
|
||||
s.y = new_y;
|
||||
});
|
||||
self.registry.commit(drag.surface_id);
|
||||
// Annoncer la nouvelle taille au client. Le state
|
||||
// `Resizing` (3) indique au toolkit qu'il est en train
|
||||
// d'être redimensionné.
|
||||
let resizing_state: Vec<u8> = vec![
|
||||
(xdg_toplevel::State::Resizing as u32) as u8,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
];
|
||||
drag.xdg_toplevel_res
|
||||
.configure(new_w as i32, new_h as i32, resizing_state);
|
||||
let serial = self.next_xdg_serial;
|
||||
self.next_xdg_serial = self.next_xdg_serial.wrapping_add(1).max(1);
|
||||
// Phase 7.8 : mettre à jour last_serial côté XdgSurfaceData
|
||||
// pour que l'ack du client soit accepté (sinon ack_configure
|
||||
// refuse car serial > last_sent qui n'avait été mis à jour
|
||||
// qu'à l'initial configure).
|
||||
if let Some(xdg_data) = drag.xdg_surface_res.data::<Arc<XdgSurfaceData>>() {
|
||||
*xdg_data.last_serial.lock().unwrap() = serial;
|
||||
}
|
||||
drag.xdg_surface_res.configure(serial);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -1731,13 +1811,21 @@ impl wayland_server::Dispatch<xdg_toplevel::XdgToplevel, Arc<XdgToplevelData>>
|
|||
seat: _,
|
||||
serial,
|
||||
} => {
|
||||
// Phase 7.7 : entrer en mode drag-move.
|
||||
// Validation laxiste : on accepte tout `serial != 0` pour
|
||||
// 7.7 ; à durcir en 7.8 (comparer à `last_button_serial`).
|
||||
// Phase 7.7/7.8 : entrer en mode drag-move.
|
||||
if serial == 0 {
|
||||
println!("[frontend] xdg_toplevel.move: serial=0 refused");
|
||||
return;
|
||||
}
|
||||
// Phase 7.8 : validation soft du serial — log warning si
|
||||
// mismatch mais accept quand même (à durcir en 7.9 quand
|
||||
// tous les clients utiliseront un vrai serial capté
|
||||
// depuis pointer.button).
|
||||
if state.last_button_serial != 0 && serial != state.last_button_serial {
|
||||
println!(
|
||||
"[frontend] xdg_toplevel.move: serial {serial} ≠ last_button {} (accepting)",
|
||||
state.last_button_serial
|
||||
);
|
||||
}
|
||||
if state.interactive_drag.is_some() {
|
||||
println!("[frontend] xdg_toplevel.move: drag already in progress");
|
||||
return;
|
||||
|
|
@ -1753,6 +1841,15 @@ impl wayland_server::Dispatch<xdg_toplevel::XdgToplevel, Arc<XdgToplevelData>>
|
|||
let Some(surf_data) = wl_surf.data::<Arc<SurfaceData>>() else {
|
||||
return;
|
||||
};
|
||||
// Phase 7.8 : check "surface du même client".
|
||||
let caller_cid = resource.client().map(|c| c.id());
|
||||
let owner_cid = surf_data.client_id.lock().unwrap().clone();
|
||||
if caller_cid != owner_cid {
|
||||
println!(
|
||||
"[frontend] xdg_toplevel.move: caller={caller_cid:?} ≠ owner={owner_cid:?} → REJECTED"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(sid) = *surf_data.id.lock().unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -1788,16 +1885,77 @@ impl wayland_server::Dispatch<xdg_toplevel::XdgToplevel, Arc<XdgToplevelData>>
|
|||
serial,
|
||||
edges,
|
||||
} => {
|
||||
// Phase 7.7 : stub log-only. Implémentation complète
|
||||
// reportée à 7.8 (compute new_w/new_h selon edges et
|
||||
// envoyer configure).
|
||||
// Phase 7.8 : Resize complet.
|
||||
let edges_raw = match edges.into_result() {
|
||||
Ok(e) => e as u32,
|
||||
Err(_) => 0,
|
||||
};
|
||||
if serial == 0 {
|
||||
println!("[frontend] xdg_toplevel.resize: serial=0 refused");
|
||||
return;
|
||||
}
|
||||
if edges_raw == 0 {
|
||||
println!("[frontend] xdg_toplevel.resize: edges=None, ignoring");
|
||||
return;
|
||||
}
|
||||
if state.last_button_serial != 0 && serial != state.last_button_serial {
|
||||
println!(
|
||||
"[frontend] xdg_toplevel.resize: serial {serial} ≠ last_button {} (accepting, durcir 7.9)",
|
||||
state.last_button_serial
|
||||
);
|
||||
}
|
||||
if state.interactive_drag.is_some() {
|
||||
println!("[frontend] xdg_toplevel.resize: drag already in progress");
|
||||
return;
|
||||
}
|
||||
let Some(xdg_surf) = data.xdg_surface.lock().unwrap().clone() else {
|
||||
return;
|
||||
};
|
||||
let Some(xdg_data) = xdg_surf.data::<Arc<XdgSurfaceData>>() else {
|
||||
return;
|
||||
};
|
||||
let wl_surf = &xdg_data.wl_surface;
|
||||
let Some(surf_data) = wl_surf.data::<Arc<SurfaceData>>() else {
|
||||
return;
|
||||
};
|
||||
// Phase 7.8 : check "surface du même client"
|
||||
let caller_cid = resource.client().map(|c| c.id());
|
||||
let owner_cid = surf_data.client_id.lock().unwrap().clone();
|
||||
if caller_cid != owner_cid {
|
||||
println!(
|
||||
"[frontend] xdg_toplevel.resize: caller={caller_cid:?} ≠ owner={owner_cid:?} → REJECTED"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(sid) = *surf_data.id.lock().unwrap() else {
|
||||
return;
|
||||
};
|
||||
let Some(s) = state.registry.get(sid) else {
|
||||
return;
|
||||
};
|
||||
let st = s.current();
|
||||
let (start_w, start_h) = st
|
||||
.buffer
|
||||
.as_ref()
|
||||
.map(|b| (b.width, b.height))
|
||||
.unwrap_or((DEFAULT_TOPLEVEL_SIZE.0 as u32, DEFAULT_TOPLEVEL_SIZE.1 as u32));
|
||||
let drag = InteractiveDrag {
|
||||
surface_id: sid,
|
||||
xdg_toplevel_res: resource.clone(),
|
||||
xdg_surface_res: xdg_surf,
|
||||
mode: DragMode::Resize(edges_raw),
|
||||
start_cursor_x: state.cursor_x,
|
||||
start_cursor_y: state.cursor_y,
|
||||
start_x: st.x,
|
||||
start_y: st.y,
|
||||
start_w,
|
||||
start_h,
|
||||
};
|
||||
println!(
|
||||
"[frontend] xdg_toplevel.resize: serial={serial} edges={edges_raw} (stub, ignored)"
|
||||
"[frontend] xdg_toplevel.resize: enter drag sid={sid:?} edges={edges_raw} start_geom=({},{},{},{}) cursor=({},{})",
|
||||
st.x, st.y, start_w, start_h, state.cursor_x, state.cursor_y
|
||||
);
|
||||
state.interactive_drag = Some(drag);
|
||||
}
|
||||
xdg_toplevel::Request::Destroy => {
|
||||
// Si on était en train de drag cette surface, abandonner le mode
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue