9.6 KiB
Supports amovibles : géométrie dynamique, V1
Investigation du 21 octobre 2026, Arch Linux, SwayFX, écran eDP-1 à échelle fractionnaire 1,4, Quickshell 0.3.2-1, Qt 6.12.0.
Cause démontrée
Le modèle JSON est correctement chargé et les delegates sont correctement créés. Dans la reproduction initiale, QML mesure un popup de 400 × 161 pour Ventoy, puis 400 × 279 pour Ventoy + PHOTO_BAK ; chaque carte mesure 376 × 108. Pourtant, la capture après ajout montre toujours UNE carte Ventoy étirée verticalement. Les tailles de police QML restent 14 et 11 pixels ; les boutons mesurent toujours 30 pixels de haut.
La trace WAYLAND_DEBUG=1 identifie la collision :
wp_viewport.set_destination(400, 279)
xdg_popup.reposition(positioner, 1) # Qt
xdg_popup.reposition(positioner, 0) # Quickshell
xdg_popup.repositioned(0) # seule réponse observée
xdg_popup.configure(..., 400, 279)
Après cette séquence, aucun nouveau buffer de la surface du popup n'est attaché. Le compositeur affiche donc l'ancien buffer dans le nouveau viewport. Cela produit une déformation du rendu, sans changement de la police logique.
Dans Qt 6.12, QWaylandXdgSurface::Popup::reposition() incrémente
m_waitingForRepositionSerial, positionne m_waitingForReposition = true, et
isExposed() renvoie false tant que cette attente subsiste. Seul le retour du
jeton attendu lève cette attente. En parallèle, Quickshell 0.3.2 envoie une
requête directe avec le jeton constant zéro. SwayFX peut remplacer la requête
précédente par cette dernière : Qt ne reçoit pas le jeton qu'il attend.
Fermer le popup détruit sa fenêtre native (deleteOnInvisible() de
ProxyPopupWindow) et son rôle xdg_popup. Rouvrir crée un nouvel état Qt,
sans attente orpheline, et un buffer à la bonne taille. C'est la raison précise
de la réparation apparente par fermeture/réouverture.
Les forceLayout(), le délai de 60 ms et le remplacement de Flow par Row
ne réparent pas cette attente du protocole. Un positionneur peut calculer une
bonne taille alors que Qt bloque les nouveaux rendus. Le défaut peut aussi
empêcher les phases de polish suivantes de terminer, laissant alors certaines
mesures de positionneurs périmées.
Sources primaires :
- Quickshell 0.3.2, positionnement Wayland
- Quickshell 0.3.2, cycle de vie du popup
- Qt 6.12, repositionnement et exposition xdg-shell
Correction
tools/quickshell/qt612-popup-owner.patch conserve les propriétés d'ancrage de
Quickshell et confie le repositionnement à
QWaylandShellSurface::setWindowGeometry() pour Qt >= 6.12. Qt possède ainsi
les requêtes ET leur acquittement ; aucun jeton brut zéro ne vient remplacer
ses jetons. La surface reste ouverte et peut réellement être redimensionnée.
Aucune taille de fenêtre fixe supplémentaire, aucun timer de rendu et aucune
fermeture/réouverture automatique.
Ce correctif est dans le moteur, car une modification du composant QML seul ne peut pas réparer proprement cet état privé de Qt. Il est compilé avec les headers privés de la version Qt installée ; aucune bibliothèque système n'est remplacée et aucune structure ABI n'est manipulée.
tools/build-quickshell-popup-fix utilise les archives Quickshell 0.3.2 et
CLI11 2.5.0 avec SHA-256 vérifiés, deux compilations simultanées au maximum et
une installation privée dans ~/.local/opt/quickshell-qt612-popup. Le script
est volontairement limité à Qt 6.12.x, version étudiée. Relancer ce script
après chaque mise à jour Qt. Le lanceur labfy-quickshell refuse un binaire
compilé pour une autre version Qt.
Dans RemovableMediaIndicator.qml, retrait des tentatives de relayout,
restauration de Flow, maintien de l'ancre tant que le popup est ouvert et
message « Aucun support amovible » pour la liste vide. Le popup reste ouvert
pour permettre la réapparition d'un support. Le protocole JSON, le backend et
les commandes de stockage restent inchangés.
Test reproductible
~/.dotfiles/tools/build-quickshell-popup-fix
python ~/.dotfiles/quickshell/.config/quickshell/labfy-sway/tests/removable_geometry/wayland.py \
--qs ~/.local/opt/quickshell-qt612-popup/bin/quickshell \
--artifacts /tmp/removable-geometry-validation
Le test nécessite une session SwayFX active, grim et Pillow. Il copie le
renderer dans le répertoire d'artefacts, redirige uniquement cette copie vers
un snapshot temporaire, neutralise runAction() et utilise les modes
TEST_ONLY des popups Impression/Sauvegarde. Aucun snapshot de production,
montage, démontage, éjection ou sauvegarde réelle n'est modifié.
Trois cycles successifs : Ventoy → Ventoy + PHOTO_BAK → Ventoy → non monté → aucun → Ventoy. Le popup n'est jamais fermé pendant ces 18 étapes. Le test vérifie les dimensions effectives, les polices, l'absence de chevauchement ou de débordement des boutons et la hauteur exacte du contenu. Il compare ensuite chaque capture Wayland à une référence de même état après réouverture. C'est cette comparaison qui détecte un ancien buffer étiré malgré de bonnes propriétés QML. Tolérance : moins de 0,1 % des pixels diffèrent de plus de 24 niveaux par canal et erreur moyenne maximale par canal < 1/255. La variation des contours de rectangles après un aller-retour de taille à l'échelle 1,4 atteint 16/255 dans les captures des supports, sans différence dans les glyphes. Pour les popups voisins, la comparaison utilise la même limite d'erreur moyenne ; le nombre de pixels différents est enregistré sans seuil, car les positions fractionnaires de leurs glyphes changent l'anticrénelage.
Les popups Impression et Sauvegarde sont également testés dynamiquement puis
comparés à des références après réouverture. Le fichier wayland.log permet
de vérifier les jetons et les nouveaux buffers réellement publiés.
Résultats
Validation concluante avec le binaire installé, lancé via labfy-quickshell.
| État (trois cycles) | Fenêtre logique | Cartes |
|---|---|---|
| Ventoy | 400 × 161 | 1 × (376 × 108) |
| Ventoy + PHOTO_BAK | 400 × 279 | 2 × (376 × 108) |
| Retour à Ventoy | 400 × 161 | 1 × (376 × 108) |
| Ventoy non monté | 400 × 161 | 1 × (376 × 108) |
| Aucun support | 400 × 67 | aucune |
| Réapparition de Ventoy | 400 × 161 | 1 × (376 × 108) |
Les 18 étapes restent sur la même surface, visible en permanence. Polices 14/11 pixels, boutons 30 pixels de haut, espacements et bornes vérifiés. Les 18 comparaisons de captures des supports passent : aucun pixel ne diffère de plus de 24/255 ; erreur moyenne maximale par canal 0,444/255.
Dix transitions des popups voisins passent aussi, avec captures complètes :
Impression (empty, jobs, unavailable, empty) et Sauvegarde (idle,
copying, error, success, unmounted, idle). Erreur moyenne maximale
0,684/255. Contrôle visuel des états multiples, non monté, vide, impression
et sauvegarde ; pas de texte ou bouton étiré, tronqué ou superposé.
Le binaire Arch d'origine, avec le même QML corrigé et le même test, échoue : le premier remplacement de snapshot peut laisser le Flow à 180 pixels de haut et les boutons sur trois lignes. Le problème est donc indépendant des modifications du layout QML. La reproduction initiale avec les anciens correctifs montrait aussi l'ancienne carte étirée malgré des mesures correctes.
qmllint passe sans diagnostic pour les trois indicateurs. git diff --check,
la syntaxe Bash des deux lanceurs et l'application à blanc du patch passent.
Compilation complète et installation privée réussies. Le service Quickshell
utilise désormais ce binaire ; son journal confirme Configuration Loaded.
Les avertissements de découverte I3 utilisent le repli habituel sur SWAYSOCK.
Preuves persistantes :
~/.local/state/labfy-removable-geometry-v1/ :
after.png: défaut initial, une seule carte étirée après ajout ;reopened-settled.png: même état réparé par réouverture ;baseline.log: collision des jetons et absence de nouveaux buffers ;stock-final/etstock-final.log: échec avec le moteur Arch d'origine ;validated/measurements.json: dimensions des 18 étapes ;validated/pixel-comparison.jsonetvalidated/other-popups.json: comparaisons ;validated/*.png: captures dynamiques et références ;validated/wayland.log: jetons Qt acquittés et buffers effectivement renouvelés ;build.logetrebuild.log: compilation locale.
Limites et maintenance
Aucune action sur les supports réels n'a été effectuée. Le branchement physique et les commandes de montage/démontage/éjection restent à vérifier manuellement au besoin ; leur implémentation n'a pas changé. La validation porte sur SwayFX, Qt 6.12.0, un écran à échelle 1,4, et les deux popups voisins cités. Les autres compositeurs, échelles, déplacements entre écrans et l'ensemble des autres popups ne constituent pas une matrice validée exhaustivement.
Après une mise à jour Qt, recompiler avec
~/.dotfiles/tools/build-quickshell-popup-fix, puis relancer
quickshell-labfy-sway.service. Le lanceur refuse une ancienne compilation
pour éviter un décalage avec l'ABI privée Qt. Le binaire /usr/bin/qs reste
inchangé et conserve le défaut ; lancer directement ce binaire pour une
nouvelle instance contourne le correctif local.
Statut : QUICKSHELL_REMOVABLE_MEDIA_GEOMETRY_V1=READY_FOR_REAL_TEST.