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
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 :
— é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 bitmaskxdg_toplevel::ResizeEdgebrut.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 :
- Validation laxiste du serial :
serial != 0accepté (durcir 7.8). - Refus si un drag est déjà en cours (un seul drag actif à la fois).
- Retrouve
xdg_surface→wl_surface→SurfaceData→SurfaceIdvia les data UserData. - Récupère la position actuelle de la surface dans le registry pour
en faire
start_x,start_y. Capture aussistart_cursor_x/y. - Stocke
InteractiveDrag::Movedansself.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)selonedges(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_configurepuis 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, silabel == "A", calculemove_at = now + 8s; dans la boucle vivante, dès quenow >= move_at, appeltoplevel._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)selonedges+ l'envoi dexdg_toplevel.configure(w, h, [Resizing])+xdg_surface.configure (serial)+ attente deack_configurepuis 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_serialenforcé) - 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.