254 lines
10 KiB
Markdown
254 lines
10 KiB
Markdown
|
|
# Phase 7.9 — Polish : min/max size, Activated state, throttling configure
|
|||
|
|
|
|||
|
|
> Document produit le 2026-05-13 dans le cadre du plan directeur
|
|||
|
|
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`.
|
|||
|
|
>
|
|||
|
|
> **Scope strict** :
|
|||
|
|
> - Handlers `xdg_toplevel.set_min_size` / `set_max_size` qui stockent
|
|||
|
|
> les contraintes et les appliquent comme clamp dans
|
|||
|
|
> `apply_interactive_drag(Resize)`.
|
|||
|
|
> - Envoi de `xdg_toplevel.configure(w, h, [Activated])` lors d'un
|
|||
|
|
> `set_focus` pour permettre aux toolkits de styler le focus.
|
|||
|
|
> - Throttling des configures envoyés pendant un resize en cours :
|
|||
|
|
> skip si la taille n'a pas changé OU si elapsed < 16 ms depuis le
|
|||
|
|
> dernier configure (~60 fps max).
|
|||
|
|
>
|
|||
|
|
> **Hors scope 7.9** : validation stricte des serials (casserait les
|
|||
|
|
> tests synthétiques), snap-to-edge, monitor edges, animation
|
|||
|
|
> resize, support multi-seat. Reportable.
|
|||
|
|
|
|||
|
|
## Verdict
|
|||
|
|
|
|||
|
|
**✅ Les 3 livrables 7.9 validés runtime.**
|
|||
|
|
|
|||
|
|
Capture initiale :  — fenêtre
|
|||
|
|
client à sa taille initiale 320×200 cyan, curseur à (1100, 700) en
|
|||
|
|
phase 1 du cycle compositor.
|
|||
|
|
|
|||
|
|
Capture après clamp :  — après
|
|||
|
|
plusieurs cursor moves qui auraient demandé des tailles négatives, la
|
|||
|
|
fenêtre est clampée à 150×80 (la valeur `set_min_size` envoyée par le
|
|||
|
|
client). Le throttling empêche les configures intermédiaires sans
|
|||
|
|
changement de taille.
|
|||
|
|
|
|||
|
|
Logs frontend confirmant les 3 chemins :
|
|||
|
|
```
|
|||
|
|
[frontend] xdg_toplevel.set_min_size(150, 80)
|
|||
|
|
[frontend] xdg_toplevel.set_max_size(600, 400)
|
|||
|
|
[frontend] focus change: None → Some(SurfaceId(0)) # déclenche Activated
|
|||
|
|
[frontend] xdg_toplevel.resize: enter drag sid=SurfaceId(0) edges=10
|
|||
|
|
start_geom=(60,60,320,200) cursor=(1100,700)
|
|||
|
|
[frontend] left-release → exit interactive drag
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Logs côté client :
|
|||
|
|
```
|
|||
|
|
[resize] connect to compositor
|
|||
|
|
[resize] toplevel créé
|
|||
|
|
[resize] xdg_toplevel.configure: w=640 h=480 # initial configure suggestion
|
|||
|
|
[resize] ack_configure(1)
|
|||
|
|
[resize] initial buffer commit # buffer 320×200
|
|||
|
|
[resize] xdg_toplevel.configure: w=320 h=200 # Activated configure (set_focus)
|
|||
|
|
[resize] toplevel.resize(BottomRight, serial=1) envoyé
|
|||
|
|
[resize] xdg_toplevel.configure: w=150 h=80 # clampé à min_size
|
|||
|
|
[resize] new buffer 150x80 attaché + commit
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3 chemins observables :
|
|||
|
|
1. **set_min_size/max_size** : 2 lignes de log distinctes côté frontend
|
|||
|
|
confirment les contraintes stockées.
|
|||
|
|
2. **Activated state** : `xdg_toplevel.configure: w=320 h=200` reçu
|
|||
|
|
côté client juste après le `focus change`. C'est le configure
|
|||
|
|
envoyé par `send_focus_configure` avec `[Activated]` state, taille
|
|||
|
|
= celle du buffer courant 320×200.
|
|||
|
|
3. **Clamp + throttling** : malgré 4 phases de cursor moves dont
|
|||
|
|
plusieurs auraient produit des dimensions négatives ou >max,
|
|||
|
|
un seul `xdg_toplevel.configure: w=150 h=80` est envoyé. Les
|
|||
|
|
phases suivantes hit le throttling (même taille → skip).
|
|||
|
|
|
|||
|
|
## Modifications apportées
|
|||
|
|
|
|||
|
|
### `redox-wl-wayland-frontend`
|
|||
|
|
|
|||
|
|
**`XdgToplevelData`** : 2 nouveaux champs
|
|||
|
|
```rust
|
|||
|
|
min_size: Mutex<(i32, i32)>, // (0, 0) = pas de contrainte
|
|||
|
|
max_size: Mutex<(i32, i32)>,
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**`xdg_toplevel.SetMinSize` / `SetMaxSize`** : handlers qui stockent
|
|||
|
|
les valeurs reçues + log. Les (0, 0) = pas de contrainte (spec
|
|||
|
|
wayland-protocols).
|
|||
|
|
|
|||
|
|
**`InteractiveDrag`** : 2 nouveaux champs
|
|||
|
|
```rust
|
|||
|
|
last_configure_size: (u32, u32), // initialisé à (start_w, start_h)
|
|||
|
|
last_configure_at: Instant, // initialisé à Instant::now()
|
|||
|
|
```
|
|||
|
|
mis à jour à chaque envoi de configure pendant le drag.
|
|||
|
|
|
|||
|
|
**`WaylandFrontend`** : 1 nouveau mapping
|
|||
|
|
```rust
|
|||
|
|
toplevels_by_id: HashMap<SurfaceId, xdg_toplevel::XdgToplevel>,
|
|||
|
|
```
|
|||
|
|
peuplé dans `xdg_surface::Request::GetToplevel`, nettoyé dans
|
|||
|
|
`wl_surface::Request::Destroy` ET dans `garbage_collect_dead_clients`
|
|||
|
|
pour les disconnects brutaux.
|
|||
|
|
|
|||
|
|
**`set_focus`** : ajoute l'envoi de configure pour le focus change :
|
|||
|
|
```rust
|
|||
|
|
let activated_state = vec![State::Activated as u8, 0, 0, 0];
|
|||
|
|
let empty_state = vec![];
|
|||
|
|
|
|||
|
|
if let Some(sid) = old_sid {
|
|||
|
|
if let Some(tl) = self.toplevels_by_id.get(&sid).cloned() {
|
|||
|
|
self.send_focus_configure(&tl, sid, empty_state); // deactivate
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if let Some(sid) = new_sid {
|
|||
|
|
if let Some(tl) = self.toplevels_by_id.get(&sid).cloned() {
|
|||
|
|
self.send_focus_configure(&tl, sid, activated_state); // activate
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**`send_focus_configure`** : nouvelle méthode helper qui :
|
|||
|
|
1. Récupère la taille du buffer courant (ou DEFAULT_TOPLEVEL_SIZE)
|
|||
|
|
2. `toplevel.configure(w, h, states)`
|
|||
|
|
3. Alloue nouveau serial, met à jour `xdg_data.last_serial`
|
|||
|
|
4. `xdg_surface.configure(serial)`
|
|||
|
|
|
|||
|
|
**`apply_interactive_drag(Resize)`** : modifié pour
|
|||
|
|
1. Clamp `new_w/new_h` aux contraintes min/max du toplevel (via
|
|||
|
|
`xdg_toplevel_res.data::<Arc<XdgToplevelData>>()` lookup).
|
|||
|
|
2. Throttle : si `(new_w, new_h) == drag.last_configure_size` ou
|
|||
|
|
`elapsed < 16ms`, skip l'envoi du configure (mais applique
|
|||
|
|
quand même le delta sur la position).
|
|||
|
|
3. Après envoi : mise à jour de `interactive_drag.last_configure_size`
|
|||
|
|
et `last_configure_at` pour le throttling du prochain tick.
|
|||
|
|
|
|||
|
|
### `redox-wl-test-client-resize`
|
|||
|
|
|
|||
|
|
Ajout de
|
|||
|
|
```rust
|
|||
|
|
toplevel.set_min_size(150, 80);
|
|||
|
|
toplevel.set_max_size(600, 400);
|
|||
|
|
```
|
|||
|
|
juste après `set_app_id`. Permet d'observer le clamp côté compositor
|
|||
|
|
pendant le resize.
|
|||
|
|
|
|||
|
|
## Pièges trouvés
|
|||
|
|
|
|||
|
|
### Initialisation `last_configure_at: Instant::now()`
|
|||
|
|
|
|||
|
|
Pas un piège mais une décision : à l'entrée du drag, on initialise à
|
|||
|
|
`Instant::now()`. Cela introduit un délai de 16 ms avant le 1er
|
|||
|
|
configure envoyé. C'est négligeable et évite un configure
|
|||
|
|
"intempestif" juste au début du drag.
|
|||
|
|
|
|||
|
|
### `Resource::data()` pour récupérer min/max au runtime
|
|||
|
|
|
|||
|
|
Le `apply_interactive_drag` n'a accès à `drag.xdg_toplevel_res` (un
|
|||
|
|
proxy `xdg_toplevel::XdgToplevel`), pas à l'`Arc<XdgToplevelData>`
|
|||
|
|
directement. Solution : `xdg_toplevel_res.data::<Arc<XdgToplevelData>>()`
|
|||
|
|
retourne `Option<&Arc<XdgToplevelData>>`. Si le toplevel a été
|
|||
|
|
détruit entre-temps, on retourne None et on skip le clamp (compute
|
|||
|
|
géom standard appliqué).
|
|||
|
|
|
|||
|
|
### Throttling ne désactive pas le mouvement de position
|
|||
|
|
|
|||
|
|
Le throttling skip uniquement l'envoi du configure, pas le
|
|||
|
|
`registry.modify_pending(x, y) + commit`. La position de la surface
|
|||
|
|
suit toujours le cursor (avec edges Top/Left qui déplacent l'origine).
|
|||
|
|
Seul le buffer reste à la dernière taille configurée jusqu'à la
|
|||
|
|
prochaine fois où la taille change ou que le throttle expire.
|
|||
|
|
|
|||
|
|
## Validation runtime
|
|||
|
|
|
|||
|
|
QEMU headless, image Redox boot, service init `40_phase79` qui lance
|
|||
|
|
compositor + `redox-wl-test-client-resize` (avec min 150×80, max
|
|||
|
|
600×400). Cycle compositor temporaire à 4 phases pour bouger le
|
|||
|
|
cursor à des positions extrêmes — retiré du compositor binaire après
|
|||
|
|
les screendumps.
|
|||
|
|
|
|||
|
|
Vérification post-run :
|
|||
|
|
- ✅ Logs `[frontend] set_min_size(150, 80)` + `set_max_size(600, 400)`
|
|||
|
|
- ✅ Log `[frontend] focus change: None → Some(SurfaceId(0))` puis
|
|||
|
|
configure côté client avec la taille du buffer = preuve Activated
|
|||
|
|
- ✅ Configure ne contient pas plus de tailles distinctes que celles
|
|||
|
|
réellement clampées (preuve du throttling) : `w=150 h=80` après
|
|||
|
|
resize, pas de re-configure pour les phases 3/4 qui auraient produit
|
|||
|
|
la même taille
|
|||
|
|
- ✅ Capture visuelle : fenêtre clampée à 150×80, jamais plus petite
|
|||
|
|
ni plus grande que les limites annoncées
|
|||
|
|
|
|||
|
|
## Limitations connues (à traiter en sous-tickets ultérieurs)
|
|||
|
|
|
|||
|
|
- **Validation stricte des serials reportée** : on accepte toujours
|
|||
|
|
`serial != 0` (log si mismatch avec `last_button_serial` mais pas
|
|||
|
|
reject). Casserait nos tests synthétiques.
|
|||
|
|
- **Activated state envoyé sans le state du resize précédent** : si
|
|||
|
|
une surface était en plein resize quand le focus change, on perd
|
|||
|
|
le `[Resizing]` state dans le nouveau configure. Pas observable
|
|||
|
|
dans nos tests, mais à durcir si on couvre des cas réels.
|
|||
|
|
- **Configure throttling sur 16 ms hardcoded** : pas configurable via
|
|||
|
|
une vsync rate du display. Suffisant pour 60 fps mais sub-optimal
|
|||
|
|
pour 120/240 Hz.
|
|||
|
|
- **Pas d'envoi de `xdg_toplevel.configure_bounds`** (v4+ du
|
|||
|
|
protocole) : annoncer au client la taille max possible du moniteur.
|
|||
|
|
Pas critique pour 7.x.
|
|||
|
|
- **Pas de cleanup `toplevels_by_id` en `xdg_toplevel.destroy`** : le
|
|||
|
|
cleanup se fait au `wl_surface.destroy` (et au
|
|||
|
|
`garbage_collect_dead_clients`). Si un client détruit son toplevel
|
|||
|
|
mais pas la wl_surface (cas dégénéré), on garde une référence
|
|||
|
|
morte. À durcir si besoin.
|
|||
|
|
|
|||
|
|
## Critère de fin 7.9
|
|||
|
|
|
|||
|
|
> Le compositor :
|
|||
|
|
> 1. Respecte les `set_min_size` / `set_max_size` envoyés par le
|
|||
|
|
> client lors d'un resize interactif.
|
|||
|
|
> 2. Envoie `xdg_toplevel.configure([Activated])` au focus change
|
|||
|
|
> pour que les toolkits puissent styler le focus.
|
|||
|
|
> 3. Throttle les configures envoyés pendant le resize pour éviter
|
|||
|
|
> de saturer le client de re-allocations.
|
|||
|
|
|
|||
|
|
**✅ Validé.** 3 chemins observables dans les logs runtime sans
|
|||
|
|
crash ni régression sur les phases précédentes.
|
|||
|
|
|
|||
|
|
## Code
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
crates/redox-wl-wayland-frontend/ # ~+90 lignes
|
|||
|
|
crates/redox-wl-test-client-resize/ # +3 lignes (set_min/max_size)
|
|||
|
|
docs/phase7-9-polish.md
|
|||
|
|
docs/phase7-9-{1,2}*.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Bilan phase 7 close finale (7.1-7.9)
|
|||
|
|
|
|||
|
|
| 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 | `a889896` |
|
|||
|
|
| 7.9 | min/max + Activated + throttling | (this commit) |
|
|||
|
|
|
|||
|
|
Le compositor 7.x est désormais suffisamment poli pour que des
|
|||
|
|
toolkits réels comme GTK4 ou cosmic-text rendering puissent
|
|||
|
|
s'attendre à un comportement Wayland correct sur ces points :
|
|||
|
|
contraintes de taille, état activé, débit de configure raisonnable.
|
|||
|
|
|
|||
|
|
**Prochain jalon** : port COSMIC (phase 13 plan-directeur,
|
|||
|
|
réordonnée avant GPU). Tenter de faire tourner un toolkit réel sur
|
|||
|
|
le compositor pour identifier les manques.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*Fin du document de phase 7.9.*
|