redox-wayland-compositor/docs/phase7-7-move-resize.md
Votre Nom 50f7a064e4 🎉 Phase 7.7 — move interactif xdg_toplevel validé runtime
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
2026-05-13 19:22:04 +02:00

10 KiB

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 — état pendant le drag : A clipée partiellement à gauche (delta négatif appliqué depuis sa position initiale (60, 60))
  • pos2 — état pendant le drag : A déplacée en haut, partiellement clipée en y négatif (delta différent)
  • post-release — é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

interactive_drag: Option<InteractiveDrag>,
last_button_serial: u32,

XdgToplevelData : nouveau champ

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_surfacewl_surfaceSurfaceDataSurfaceId 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.