redox-wayland-compositor/docs/phase7-8-resize.md
Votre Nom a8898960f1 🎉 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
2026-05-13 19:47:53 +02:00

11 KiB
Raw Blame History

Phase 7.8 — Resize interactif complet + durcissements

Document produit le 2026-05-13 dans le cadre du plan directeur REDOX_COSMIC_XWAYLAND_RS_PLAN.md.

Scope strict :

  • xdg_toplevel.Resize { seat, serial, edges } complet : compute (new_x, new_y, new_w, new_h) selon le bitmask edges, mise à jour immédiate de la position côté serveur, envoi de xdg_toplevel.configure(w, h, [Resizing]) + xdg_surface.configure(serial) pour que le client recrée son buffer.
  • Durcissement validation serial sur Move/Resize : log warning si serial != last_button_serial (validation soft, accept quand même pour ne pas casser les tests synthétiques).
  • Check "surface du même client" sur Move/Resize : refuse si le client_id de l'appelant ≠ propriétaire de la surface.

Hors scope 7.8 : contraintes min/max size, snap-to-edge, mode validation stricte du serial (acceptable laxisme en 7.x), animation resize, throttling des configures.

Verdict

Resize interactif validé runtime sur 3 tailles distinctes.

Captures :

  • before — fenêtre initiale 320×200 cyan + bordure noire à (60, 60)
  • grown — après phase 1 de resize (delta cursor (+200, +200) appliqué à BottomRight) : fenêtre agrandie à ~520×400
  • shrunk — après phase 3 (delta cursor (-250, -180)) : fenêtre rétrécie à ~70×25, près du minimum

Logs côté client confirment le cycle complet :

[resize] connect to compositor
[resize] toplevel créé
[resize] xdg_toplevel.configure: w=640 h=480        # initial configure du compositor
[resize] ack_configure(1)
[resize] initial buffer commit                       # 320x200 cyan
[resize] toplevel.resize(BottomRight, serial=1) envoyé
[resize] xdg_toplevel.configure: w=520 h=300        # configure après cursor move
[resize] new buffer 520x300 attaché + commit
[resize] xdg_toplevel.configure: w=170 h=120        # autre cursor move
[resize] new buffer 170x120 attaché + commit

Logs côté compositor :

[frontend] xdg_toplevel.resize: enter drag sid=SurfaceId(0) edges=10 \
    start_geom=(60,60,320,200) cursor=(600,400)
[frontend] left-release → exit interactive drag

edges=10 = Right(8) | Bottom(2) = BottomRight, comme demandé par le client.

Modifications apportées

Helper compute_resize_geom

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)

Interprète le bitmask xdg_toplevel::ResizeEdge :

  • Right(8) : étend la largeur depuis la droite (new_w = start_w + dx)
  • Left(4) : déplace l'origine X (new_x = start_x + dx) et inverse le delta sur la largeur (new_w = start_w - dx)
  • Bottom(2) : étend la hauteur depuis le bas
  • Top(1) : déplace l'origine Y et inverse le delta sur la hauteur

Les coins sont les combinaisons (TopRight = 9, BottomRight = 10, etc.). Le résultat est clampé à MIN_RESIZE_DIM = 32 minimum pour éviter w/h <= 0 qui ferait planter les clients tout en validant leur buffer.

Handler xdg_toplevel::Request::Resize

  1. Refuse serial == 0 et edges_raw == 0 (None edge).
  2. Validation soft du serial : log si serial != last_button_serial mais accepte quand même.
  3. Refuse si un drag est déjà en cours.
  4. Retrouve xdg_surfacewl_surfaceSurfaceDataSurfaceId via UserData cascade.
  5. Check client_id : resource.client().map(|c| c.id()) doit == surf_data.client_id. Sinon REJECT (un client ne peut pas resize une surface qui n'est pas la sienne).
  6. Capture start_geom = (x, y, w, h) de la surface courante (w/h depuis le buffer attaché, ou DEFAULT_TOPLEVEL_SIZE si pas de buffer).
  7. Stocke InteractiveDrag avec mode: DragMode::Resize(edges).

apply_interactive_drag étendu

Pour DragMode::Resize(edges) :

  1. compute_resize_geom avec le delta cursor.
  2. registry.modify_pending(sid, |s| { s.x = new_x; s.y = new_y; }) + commit. Position appliquée immédiatement (utile si edges incluent Top/Left qui déplacent l'origine).
  3. Envoi xdg_toplevel.configure(new_w, new_h, [Resizing]) pour annoncer au client la nouvelle taille et son état "en cours de resize".
  4. Allocation d'un nouveau xdg_surface.configure(serial) avec un serial frais (next_xdg_serial++).
  5. Mise à jour de XdgSurfaceData.last_serial = serial — sans cette ligne, l'ack_configure du client est refusé par xdg_surface.ack_configure (qui compare serial <= last_sent). Bug détecté à la 1re validation runtime, corrigé.

Move handler : check client_id ajouté

Le même check caller_cid == owner_cid appliqué au xdg_toplevel.Move en 7.7 (sécurité défense en profondeur).

redox-wl-test-client-resize (nouveau crate, ~280 lignes)

Binaire qui :

  1. Bind wl_compositor, wl_shm, xdg_wm_base, wl_seat.
  2. Crée surface + xdg_toplevel + initial commit + ack.
  3. Premier buffer 320×200 cyan avec bordure noire 2px.
  4. À T+8 s : toplevel.resize(&seat, 1, BottomRight).
  5. Sur chaque xdg_toplevel.configure { w, h } reçu : ack, alloue un nouveau shm fd + pool + buffer aux dimensions w×h, attach + damage_buffer + commit.
  6. Garde les anciens WlShmPool et WlBuffer dans des Vec pour éviter qu'ils ne soient drop (le serveur tient encore une référence via le buffer attaché tant qu'on n'a pas commit le suivant).

Pièges trouvés

last_serial doit être mis à jour à chaque configure

Le check ack_configure 7.5 (serial > last_sent → reject) est strict. Si le compositor envoie un nouveau xdg_surface.configure (serial_N) pendant un drag sans mettre à jour xdg_surface_data.last_serial = serial_N, le client va ack ce serial et le compositor va le refuser. En pratique le compositor n'utilise pas l'ack pendant un resize en cours, donc le client ne se rend compte de rien — mais c'est un état corrompu silencieux.

Fix : updater xdg_data.last_serial à chaque envoi de configure dans apply_interactive_drag(Resize).

Le buffer initial 320×200 du client peut différer de la suggestion 640×480 du compositor

Le compositor envoie DEFAULT_TOPLEVEL_SIZE = 640×480 à l'initial configure. Notre test client choisit son propre 320×200 (ignore la suggestion, ce qui est légal selon la spec xdg-shell — la taille suggérée est juste un hint). Le compositor compose à la taille du buffer attaché, pas à la taille configurée. Tout fonctionne, mais attention si on veut un comportement strict.

Vec/Vec pour ne pas drop les anciens

À chaque resize, on crée un nouveau pool+buffer et on les attach. Si on laisse l'ancien WlBuffer drop (destroy() côté wayland-client), ça peut crasher le compositor qui tient encore un mmap dessus. Solution : pousser dans des Vec qui survivent jusqu'à la fin de la fonction run().

wl_buffer.release 7.6 ne crash pas si on re-commit avec un autre buffer

J'avais peur d'un double-release. En pratique wayland-server gère ça proprement via le refcount du Resource (chaque buffer reçoit son propre release à son propre commit, indépendants).

Validation runtime

QEMU headless, image Redox boot complet, service init 40_phase78 qui lance compositor + redox-wl-test-client-resize. Cycle compositor temporaire de 3 phases (cursor à T+5s, T+20s, T+35s) — retiré du binaire final après screendumps.

Vérification post-run :

  • [fuzz] PASS n/a (pas de fuzz dans 7.8) — équivalent : compositor reste vivant tout au long
  • Logs [resize] côté client confirment les 3 configures reçus
    • ack + new buffer à chaque taille
  • Logs [frontend] côté compositor confirment enter drag + release
  • 3 captures visuelles à 3 tailles distinctes

Limitations connues (à traiter en sous-tickets ultérieurs)

  • Validation serial laxiste : on accepte serial != 0 même s'il ne matche pas last_button_serial. Tolérance pour les tests synthétiques. À durcir si un toolkit réel s'avère bogué dans ce domaine.
  • Pas de contraintes min/max size : MIN_RESIZE_DIM = 32 clamp, mais pas de max. Pas d'enforcement de xdg_toplevel.set_min_size / set_max_size envoyés par le client.
  • Pas de snap-to-edge / monitor edges : la fenêtre peut sortir partiellement de l'écran pendant le resize. Politique WM reportable.
  • Configures envoyés à chaque tick avec drag actif : pas de throttling. Si le cursor bouge rapidement, le client reçoit beaucoup de configures et doit réallouer un buffer à chaque fois. À optimiser via debounce ou frame-tick coalescing.
  • Pas d'événement xdg_toplevel.configure([Activated]) envoyé au set_focus : la spec dit que le compositor doit annoncer Activated quand la surface devient focus. Pas critique pour le test, peut perturber les toolkits riches.
  • xdg_toplevel.set_min_size / set_max_size ignorés : à parser et stocker pour les appliquer dans compute_resize_geom.

Critère de fin 7.8

Un client peut envoyer toplevel.resize(seat, serial, edges). Le compositor entre en mode resize, calcule la nouvelle géométrie selon les edges, envoie configure(w, h, [Resizing]) au client. Le client recrée son buffer aux nouvelles dimensions et commit ; le compositor compose à la nouvelle taille. Sortie au release du bouton gauche.

Validé. 3 tailles distinctes capturées (avant, agrandi, rétréci), logs symétriques côté client et compositor, aucun panic, release propre.

Code

crates/redox-wl-wayland-frontend/    # +~120 lignes (compute_resize_geom,
                                       # handler Resize, apply Resize,
                                       # check client_id sur Move/Resize,
                                       # last_serial sync au resize configure)
crates/redox-wl-test-client-resize/   # nouveau crate (~285 lignes)
docs/phase7-8-resize.md
docs/phase7-8-{1,2,3}*.png

Bilan phase 7 close (1-8)

Sous-phase Livrable Commit
7.1 xdg-shell minimal 4bff319
7.2 wl_seat + routing input baa9470
7.3 curseur software + alpha blending 5f7587e
7.4 focus + raise on click c40ca9f
7.5 robustesse paquet A (anti-panic) 7e81dec
7.6 multi-clients (cleanup + release + filtrage) a87de02
7.7 move interactif xdg_toplevel 50f7a06
7.8 resize interactif xdg_toplevel (this commit)

Le compositor 7.x couvre désormais l'essentiel d'un compositor Wayland utilisable :

  • Protocole base : wl_compositor, wl_shm, wl_surface, wl_callback, wl_buffer, wl_region (no-op)
  • xdg-shell : xdg_wm_base, xdg_surface, xdg_toplevel (configure + ack + commit + move + resize), xdg_positioner / xdg_popup (no-op)
  • Input : wl_seat, wl_keyboard, wl_pointer (avec filtrage par client focused)
  • Compositor logic : Z-order avec raise on click, focus avec enter/leave, hit_test, cursor software avec alpha blending, garbage collect des clients déconnectés
  • Robustesse : validations sur AckConfigure, CreateBuffer, read_argb bounds-safe, post-disconnect cleanup
  • Interactivité : move + resize via xdg_toplevel

Prochain jalon : port COSMIC (phase 13 plan-directeur, réordonnée avant GPU). Tenter de faire tourner un toolkit réel (cosmic-comp lib ou GTK4) sur ce compositor pour identifier les manques.

Suite

Voir phrase de reprise pour COSMIC dans le memory file.


Fin du document de phase 7.8.