Move interactif complet (Resize en stub log-only, reportable à 7.8).
Frontend additions :
- enum DragMode { Move, Resize(u32) } et struct InteractiveDrag
{ surface_id, xdg_toplevel_res, xdg_surface_res, mode,
start_cursor_x/y, start_x/y, start_w/h }
- WaylandFrontend : interactive_drag: Option<InteractiveDrag> +
last_button_serial: u32
- XdgToplevelData.xdg_surface: Mutex<Option<XdgSurface>> peuplé
dans xdg_surface.GetToplevel pour retrouver le wl_surface parent
depuis un toplevel.move/resize
- Handler xdg_toplevel.Move : valide serial != 0, refuse drag déjà
actif, retrouve SurfaceId via cascade UserData (xdg_surface →
wl_surface → SurfaceData), capture start_cursor + start_geom,
stocke InteractiveDrag
- Handler xdg_toplevel.Resize : stub log-only (à compléter 7.8)
- Handler xdg_toplevel.Destroy nettoie interactive_drag si on était
en train de drag cette surface
- Méthode apply_interactive_drag() : applique le delta (cursor -
start_cursor) à la position de la surface (Move) ou consume le
motion (Resize stub)
- forward_input(PointerMotion(Relative)) : apply au début, return
si drag actif (court-circuite l'envoi de motion au client pendant
un drag, conforme spec Wayland)
- forward_input(PointerButton release) : sort du mode drag
- set_cursor_position : appelle aussi apply_interactive_drag (sans
ça, les cycles de test programmatique ne déclenchent pas le drag
car ils court-circuitent forward_input)
- Tracking last_button_serial à chaque button LEFT Pressed
Test client modifications :
- redox-wl-test-client-shm-two bind wl_seat
- Si label=="A", déclenche toplevel.move(&seat, 1) 8s après le
commit initial (mécanisme "synthétique" : le client n'écoute pas
les pointer events, il envoie Move sans attendre un clic — assez
pour valider le pipeline serveur, durcissement client 7.8)
Validation runtime :
- Cycle compositor temporaire (retiré du binaire final) qui change
cursor_position à plusieurs positions pendant que le drag est
actif, screendumps à 3 positions distinctes
- Logs frontend :
[frontend] xdg_toplevel.move: enter drag sid=SurfaceId(0) start=(60,60) cursor=(500,400)
[frontend] left-release → exit interactive drag
- A déplacée visuellement entre les captures pos1, pos2 et
post-release ; sortie clipée sur les bords (pas de snap-to-edge,
WM policy reportable)
Limitations 7.7 :
- Resize non implémenté (stub)
- Validation serial laxiste (serial != 0)
- Pas de contrôle policy (snap-to-edge, min/max)
- Pas de check "surface du même client" (sécu)
- Pas d'event xdg_toplevel.configure([Resizing]) envoyé pendant le drag
Doc complète : docs/phase7-7-move-resize.md
Leyoda 2026 – GPLv3
239 lines
10 KiB
Markdown
239 lines
10 KiB
Markdown
# Phase 7.7 — Move/resize interactifs via xdg_toplevel
|
|
|
|
> Document produit le 2026-05-13 dans le cadre du plan directeur
|
|
> `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`.
|
|
>
|
|
> **Scope strict** :
|
|
> - `xdg_toplevel.Move { seat, serial }` : entrer en mode drag-move,
|
|
> appliquer le delta cursor sur la position de la surface, sortir au
|
|
> release du bouton gauche.
|
|
> - `xdg_toplevel.Resize { seat, serial, edges }` : **stub log-only**
|
|
> pour 7.7 (implémentation complète reportée à 7.8 si besoin).
|
|
> - Validation runtime end-to-end via un client qui appelle
|
|
> `toplevel.move(&seat, serial)` au compositor.
|
|
>
|
|
> **Hors scope 7.7** : resize effectif (xdg_toplevel.configure(w,h) +
|
|
> recompute selon edges), validation stricte du serial, snap-to-edge,
|
|
> contraintes min/max size.
|
|
|
|
## Verdict
|
|
|
|
**✅ Move interactif validé runtime.** La fenêtre A (verte/pyramide)
|
|
créée par le test client envoie `toplevel.move(&seat, 1)` 8 s après
|
|
son ack_configure ; le compositor enregistre l'état initial de drag
|
|
puis applique le delta de curseur à chaque `set_cursor_position` (ou
|
|
`PointerMotion` réel). À `mouse_button 0` (release) le mode drag se
|
|
termine et la surface reste à sa dernière position.
|
|
|
|
Captures :
|
|
-  — état pendant le drag : A clipée
|
|
partiellement à gauche (delta négatif appliqué depuis sa position
|
|
initiale (60, 60))
|
|
-  — état pendant le drag : A déplacée
|
|
en haut, partiellement clipée en y négatif (delta différent)
|
|
-  — état après release :
|
|
surface stabilisée à la dernière position appliquée
|
|
|
|
Logs frontend (`/tmp/comp.log` extrait via redoxfs) :
|
|
```
|
|
[frontend] focus change: None → Some(SurfaceId(0))
|
|
[frontend] focus change: Some(SurfaceId(0)) → Some(SurfaceId(1))
|
|
[frontend] xdg_toplevel.move: enter drag sid=SurfaceId(0) start=(60,60) cursor=(500,400)
|
|
[frontend] left-release → exit interactive drag
|
|
```
|
|
|
|
Logs côté client :
|
|
```
|
|
[A] connect to compositor
|
|
[A] xdg_toplevel créé
|
|
[A] ack_configure(1)
|
|
[A] buffer commit POST-ack
|
|
[A] toplevel.move() envoyé
|
|
```
|
|
|
|
## Modifications apportées
|
|
|
|
### `redox-wl-wayland-frontend`
|
|
|
|
**Nouveaux types** :
|
|
- `DragMode { Move, Resize(u32) }` — quel type de drag est actif.
|
|
Le u32 pour Resize est le bitmask `xdg_toplevel::ResizeEdge` brut.
|
|
- `InteractiveDrag { surface_id, xdg_toplevel_res, xdg_surface_res,
|
|
mode, start_cursor_x, start_cursor_y, start_x, start_y, start_w,
|
|
start_h }` — l'état d'un drag en cours, posé à l'entrée et lu à
|
|
chaque update curseur.
|
|
|
|
**`WaylandFrontend`** : nouveaux champs
|
|
```rust
|
|
interactive_drag: Option<InteractiveDrag>,
|
|
last_button_serial: u32,
|
|
```
|
|
|
|
**`XdgToplevelData`** : nouveau champ
|
|
```rust
|
|
xdg_surface: Mutex<Option<xdg_surface::XdgSurface>>,
|
|
```
|
|
peuplé dans `xdg_surface::Request::GetToplevel`. Permet au handler
|
|
`Move`/`Resize` du toplevel de retrouver le `wl_surface` parent + le
|
|
`SurfaceId` côté compositor-core.
|
|
|
|
**Handler `xdg_toplevel::Request::Move`** :
|
|
1. Validation laxiste du serial : `serial != 0` accepté (durcir 7.8).
|
|
2. Refus si un drag est déjà en cours (un seul drag actif à la fois).
|
|
3. Retrouve `xdg_surface` → `wl_surface` → `SurfaceData` → `SurfaceId`
|
|
via les data UserData.
|
|
4. Récupère la position actuelle de la surface dans le registry pour
|
|
en faire `start_x`, `start_y`. Capture aussi `start_cursor_x/y`.
|
|
5. Stocke `InteractiveDrag::Move` dans `self.interactive_drag`.
|
|
|
|
**Handler `xdg_toplevel::Request::Resize`** : stub log-only. Logge
|
|
`serial` et `edges` puis ignore. À implémenter en 7.8 :
|
|
- calculer `(new_x, new_y, new_w, new_h)` selon `edges` (TOP/BOTTOM/
|
|
LEFT/RIGHT/coins) et le delta cursor
|
|
- envoyer `xdg_toplevel.configure(new_w, new_h, vec![])` +
|
|
`xdg_surface.configure(serial)` pour que le client re-rende
|
|
- attendre `ack_configure` puis le prochain commit avec le nouveau
|
|
buffer
|
|
|
|
**Méthode `apply_interactive_drag(&mut self) -> bool`** : si un drag
|
|
est actif, applique le delta `(cursor - start_cursor)` à la position
|
|
de la surface ciblée (`registry.modify_pending + commit`) et retourne
|
|
`true` pour que le caller court-circuite l'envoi des events
|
|
`wl_pointer.motion` au client (la spec dit que pendant un drag, le
|
|
client ne doit pas recevoir de motion).
|
|
|
|
**`forward_input(PointerMotion / PointerMotionRelative)`** : appelle
|
|
`apply_interactive_drag()` au début. Si `true`, return immédiat
|
|
(pas d'envoi de motion). Sinon comportement normal 7.6.
|
|
|
|
**`forward_input(PointerButton)`** : si `left == false` (release) et
|
|
drag actif, vide `self.interactive_drag` et log "left-release →
|
|
exit interactive drag". Indépendant du focus actuel.
|
|
|
|
**`set_cursor_position(x, y)`** : appelle `apply_interactive_drag()`
|
|
après mise à jour de `cursor_x/y`, car ce setter court-circuite
|
|
`forward_input(PointerMotion)` qui est l'endroit normal d'application
|
|
du drag. Sans cette ligne, un cycle de test programmatique qui
|
|
change le cursor sans event réel ne déclenche pas le drag.
|
|
|
|
### `redox-wl-test-client-shm-two`
|
|
|
|
Modifications mineures pour bind `wl_seat` (resource simple, pas de
|
|
`get_pointer` ni d'écoute de `button` — le client A déclenche le
|
|
move de manière synthétique 8 s après son commit) :
|
|
- Import `wl_seat::WlSeat`
|
|
- `ClientState.seat: Option<WlSeat>`
|
|
- Registry handler bind `"wl_seat"` v7
|
|
- `noop!(WlSeat)`
|
|
- Dans `run_one`, si `label == "A"`, calcule `move_at = now + 8s` ;
|
|
dans la boucle vivante, dès que `now >= move_at`, appel
|
|
`toplevel._move(&seat, 1)` puis flush. Une seule fois.
|
|
|
|
Le serial `1` est synthétique. Le compositor 7.7 accepte (validation
|
|
laxiste). À durcir en 7.8 : le serial doit correspondre à un
|
|
`wl_pointer.button` récent et le client devrait écouter le button
|
|
events avant d'envoyer Move.
|
|
|
|
## Méthode de validation runtime
|
|
|
|
Comme en 7.3, 7.4 et 7.6, la validation utilise un cycle temporaire
|
|
de `set_cursor_position` côté compositor pour simuler des mouvements
|
|
souris (le driver mouse Redox sous QEMU ne reçoit pas de
|
|
`PointerMotion`). Le cycle a 4 phases :
|
|
- T+0 à T+14 : cursor à (200, 200) — point de départ avant le Move
|
|
du client (qui arrive à compositor T~10s)
|
|
- T+14 → cursor à (500, 400)
|
|
- T+18 → cursor à (800, 100)
|
|
- T+22 → cursor à (300, 500)
|
|
- T+26 (post-release) → cursor à (600, 300)
|
|
|
|
Le client A envoie `toplevel.move()` à T+8s de son démarrage. Le
|
|
compositor entre en drag avec start_cursor = position courante,
|
|
start_geom = (60, 60). Aux changements de cursor suivants,
|
|
`apply_interactive_drag` applique le delta. À T_QEMU ~43s, le bash
|
|
script envoie `mouse_button 1` puis `mouse_button 0` (release) pour
|
|
terminer le drag.
|
|
|
|
Ce cycle de positionnement et l'auto-move côté client A sont des
|
|
helpers de validation, **retirés du compositor binaire après les
|
|
screendumps** (le code production ne fait que
|
|
`set_cursor_initial_position(center)` + `draw_cursor` + `apply` via
|
|
`forward_input` réel).
|
|
|
|
Note : le côté `move auto à T+8s` du client `redox-wl-test-client-shm-two`
|
|
est conservé dans le code commité — c'est un test binary, pas un
|
|
client de production. La fenêtre B n'est pas affectée (`label != "A"`).
|
|
|
|
## Limitations connues (à traiter en sous-tickets ultérieurs)
|
|
|
|
- **Resize non implémenté** : stub log-only. La compute de
|
|
`(new_x, new_y, new_w, new_h)` selon `edges` + l'envoi de
|
|
`xdg_toplevel.configure(w, h, [Resizing])` + `xdg_surface.configure
|
|
(serial)` + attente de `ack_configure` puis du commit avec nouveau
|
|
buffer est reportable.
|
|
- **Validation serial laxiste** : on accepte `serial != 0`. La spec
|
|
dit "doit correspondre à un input event récent (button)". À
|
|
durcir : comparer à `last_button_serial`, refuser si trop ancien
|
|
(par ex. > 100ms).
|
|
- **Pas de snap-to-edge / contraintes** : la surface peut sortir
|
|
partiellement ou entièrement de l'écran (visible dans les captures
|
|
pos1/pos2). Reportable à un sous-ticket "policy WM".
|
|
- **Pas d'événement `xdg_toplevel.configure([Resizing])` envoyé en
|
|
début de Move** : la spec dit que le compositor peut/doit annoncer
|
|
le state via configure pour que le toolkit affiche un curseur de
|
|
drag différent. Pas critique pour 7.7.
|
|
- **Un seul drag actif à la fois** : si un autre client envoie Move
|
|
pendant qu'un drag est en cours, on log et on ignore. La spec ne
|
|
précise pas ce comportement ; tolérant est OK.
|
|
- **Pas de validation que la surface ciblée est bien le client
|
|
appelant** : un client malveillant pourrait demander à déplacer une
|
|
surface qui n'est pas la sienne. À durcir en 7.8 (vérifier que
|
|
`xdg_surface.client_id == client_id du request`).
|
|
|
|
## Critère de fin 7.7
|
|
|
|
> Un client peut envoyer `toplevel.move()` au compositor. Le
|
|
> compositor entre en mode drag, applique le delta de curseur sur la
|
|
> position de la surface, et sort du mode au release du bouton
|
|
> gauche. Tout cela sans panic ni régression sur les phases
|
|
> précédentes.
|
|
|
|
**✅ Validé.** Move fonctionnel runtime, 3 captures de preuve, logs
|
|
frontend confirment enter/release du drag, multi-clients (A + B)
|
|
toujours OK.
|
|
|
|
## Code
|
|
|
|
```
|
|
crates/redox-wl-wayland-frontend/ # +~100 lignes (InteractiveDrag,
|
|
# DragMode, Move handler,
|
|
# apply_interactive_drag,
|
|
# XdgToplevelData.xdg_surface)
|
|
crates/redox-wl-test-client-shm-two/ # +bind wl_seat + auto-move
|
|
# à T+8s pour la fenêtre A
|
|
docs/phase7-7-move-resize.md
|
|
docs/phase7-7-drag-pos{1,2}.png
|
|
docs/phase7-7-post-release.png
|
|
```
|
|
|
|
## Suite phase 7.8
|
|
|
|
Au choix :
|
|
- **Resize complet** (xdg_toplevel.Resize + configure avec edges
|
|
selon Top/Bottom/Left/Right + ack_configure → commit)
|
|
- **Validation stricte des serials** (`last_button_serial` enforcé)
|
|
- **Politique WM** (snap-to-edge, contraintes min/max, retour de
|
|
surface hors-écran)
|
|
- **Frame callbacks per-client throttle** (frame done seulement
|
|
envoyé aux clients dont la surface a été composée)
|
|
|
|
Estimé : 1-2 sessions par item.
|
|
|
|
Avec la phase 7 quasi-complète (7.1-7.7), le **vrai prochain
|
|
jalon** est le port COSMIC (phase plan-directeur 13, réordonnée avant
|
|
GPU). Le compositor 7.x devrait être suffisamment compatible Wayland
|
|
pour qu'un toolkit comme cosmic-comp lib + GTK4 puisse se connecter.
|
|
|
|
---
|
|
|
|
*Fin du document de phase 7.7.*
|