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
272 lines
11 KiB
Markdown
272 lines
11 KiB
Markdown
# 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 :
|
||
-  — fenêtre initiale 320×200
|
||
cyan + bordure noire à (60, 60)
|
||
-  — après phase 1 de resize
|
||
(delta cursor (+200, +200) appliqué à BottomRight) : fenêtre
|
||
agrandie à ~520×400
|
||
-  — 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<WlShmPool>/Vec<WlBuffer> 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.*
|