# 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 : - ![pos1](phase7-7-drag-pos1.png) — état pendant le drag : A clipée partiellement à gauche (delta négatif appliqué depuis sa position initiale (60, 60)) - ![pos2](phase7-7-drag-pos2.png) — état pendant le drag : A déplacée en haut, partiellement clipée en y négatif (delta différent) - ![post-release](phase7-7-post-release.png) — é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, last_button_serial: u32, ``` **`XdgToplevelData`** : nouveau champ ```rust xdg_surface: Mutex>, ``` 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` - 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.*