# 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](phase7-8-1-before-resize.png) — fenêtre initiale 320×200 cyan + bordure noire à (60, 60) - ![grown](phase7-8-2-resize-grown.png) — après phase 1 de resize (delta cursor (+200, +200) appliqué à BottomRight) : fenêtre agrandie à ~520×400 - ![shrunk](phase7-8-3-resize-shrunk.png) — 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` ```rust 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_surface` → `wl_surface` → `SurfaceData` → `SurfaceId` 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.*