04 / 04 À l'arrêt · dernière version le 27 juin 2026
Dofus Switcher
Outil de bureau pour jouer plusieurs comptes Dofus Retro sous KDE Plasma 6 / Wayland : il fait passer d'une fenêtre de jeu à l'autre au clavier ou avec les boutons latéraux de la souris. Construit pour mon propre usage en juin 2026, plus utilisé depuis. Le projet est à l'arrêt, en version 0.7.2.
Le problème
En combat, chaque personnage joue à son tour : avec plusieurs comptes ouverts, il faut passer d'une fenêtre de jeu à la suivante à chaque tour, sans se tromper d'ordre. Je le faisais avec un script bash et deux raccourcis KDE. Le besoin de départ tenait en une ligne : faire défiler les fenêtres avec les deux boutons latéraux de la souris, que la main a déjà sous les doigts pendant qu'elle joue, en remplaçant le script par une vraie application.
Ce qui rendait la solution évidente inutilisable
L'outil le plus évident pour du multi-comptes envoie des clics et des touches aux fenêtres du jeu : une action répétée sur tous les comptes, une macro qui change de fenêtre puis agit. C'est précisément ce que les conditions d'utilisation d'Ankama interdisent, et le risque est la suspension de tous les comptes. La règle a été posée avant la première ligne de code, et rappelée dans la spécification comme non négociable : l'outil ne gère que des fenêtres du système. Il les liste, les ordonne, en active une. Il n'envoie jamais une entrée synthétique à une fenêtre Dofus, ne diffuse rien, n'enchaîne aucune action en jeu.
Deuxième obstacle : sous Wayland, une application ne peut pas activer la fenêtre d'une
autre, et les outils X11 habituels (xdotool, wmctrl) n'y
fonctionnent pas. L'activation passe donc par kdotool, qui passe par KWin, le
compositeur de Plasma. C'est ce qui lie l'outil à KDE Plasma 6.
Pour les boutons de souris, l'outil descend sous le compositeur, au niveau evdev, l'interface du noyau Linux pour les périphériques d'entrée. La capture exclusive y donne deux choses à la fois : l'outil voit les clics des boutons latéraux quelle que soit la fenêtre au premier plan, et il peut les retirer du flux avant que le compositeur ne les reçoive, donc avant qu'ils n'atteignent le jeu.
Ce qui a été choisi, et ce que ça a coûté
La souris est capturée en exclusivité (EVIOCGRAB) : le compositeur ne voit
plus le périphérique physique. Une boucle relaie tous ses événements vers un
périphérique virtuel créé avec uinput, dont les capacités sont copiées depuis la souris
d'origine ; KWin y voit une souris ordinaire, quelle que soit la marque. Seuls les
boutons associés à une action sont retirés du flux, à l'appui comme au relâchement,
avec l'événement MSC_SCAN qui les précède. Les supprimer jusqu'au
prochain SYN aurait été plus simple, mais aurait aussi avalé les
mouvements regroupés dans la même trame.
Ce que KWin reçoit est donc le flux de la souris, inchangé, moins les clics bindés. Rien n'est fabriqué, et rien n'atteint le jeu : c'est ce qui rend la capture compatible avec la contrainte du point 1.
Relayer chaque événement de la souris ajoute forcément du délai. Avant d'écrire ce lot, une mesure a fixé la règle : p99 sous 3 ms, on continue ; au-delà de 5 ms, on abandonne les binds souris. Le p99 consigné dans le code est d'environ 0,007 ms. Les résultats bruts de la mesure n'ont pas été conservés.
La capture ne doit jamais survivre au programme : une souris capturée que personne ne
relâche, c'est un bureau sans pointeur. La session suit un ordre fixe
(résoudre, ouvrir, capturer, relayer) et libère tout dans un bloc finally.
Il n'y a volontairement pas de filet atexit pour la souris : le noyau
relâche la capture quand le descripteur de fichier meurt, y compris sur un crash.
SIGINT et SIGTERM passent par set_wakeup_fd pour
fermer proprement la fenêtre Qt. Un thread superviseur recapture la souris après un
débranchement, en la retrouvant par son identifiant matériel plutôt que par
/dev/input/eventN, qui change d'un démarrage à l'autre.
Le prix : l'utilisateur doit appartenir au groupe input et pouvoir écrire
dans /dev/uinput. L'autodiagnostic vérifie ces droits deux fois.
os.access donne le verdict réel, ACL comprises. getgrouplist
distingue deux causes : l'utilisateur n'est pas dans le groupe, ou il y est mais sa
session n'a pas été rouverte depuis. Un problème de droits ne fait pas échouer
l'autodiagnostic : il sert aussi de test après une mise à jour automatique, et une
permission manquante ne doit pas déclencher un retour à la version précédente sans
rapport.
La race condition corrigée en 0.7.2 se manifestait ainsi : en clics rapides, le cycle
revenait parfois au premier personnage au lieu de passer au suivant. Deux hypothèses
ont été écartées. La garde anti-rafale était la même pour le clavier et la souris. Et
le cycle ne stocke aucun index, puisqu'il relit la fenêtre active à chaque fois.
La cause : avec huit clients ouverts et KWin chargé, kdotool
getactivewindow renvoyait par moments une chaîne vide. Sans fenêtre active
connue, le cycle repartait du début. Le cas était aggravé par une seconde lecture de
la fenêtre active, redondante, alors que la première avait déjà été confirmée.
Le correctif relit au plus deux fois, à 15 ms d'intervalle, puis abandonne, comme le
faisaient déjà depuis la 0.6.5 les deux autres appels à kdotool touchés
par le même défaut. La fenêtre active déjà lue est transmise au cycle, ce qui supprime
la seconde lecture. Quatre tests couvrent le cas.
Un arbitrage a été imposé par une panne. Jusqu'à la 0.5, chaque raccourci lançait un
processus Python, et chaque appui créait un service systemd transitoire. Sous la
répétition automatique du clavier, environ 30 par seconde, cela a saturé
systemd --user, DBus et KWin jusqu'au gel de la machine, le 22 juin 2026.
Depuis la 0.6, les raccourcis sont enregistrés via DBus (KGlobalAccel) dans le processus
de l'interface. Ils ne fonctionnent donc que tant que la fenêtre est ouverte : ni démon,
ni démarrage automatique, ni icône dans la zone de notification. C'est moins pratique,
et c'était voulu.
Les 278 tests tournent sans matériel ni compositeur : evdev, uinput et
kdotool sont remplacés par des doublures. La contrepartie se lit dans la
couverture mesurée le 2 octobre 2026 : le module qui parle vraiment à evdev n'est couvert
qu'à 61 %, et ce chemin réel n'a été validé que par la mesure de latence et l'usage.
Vérifiable
Le dépôt est privé et l'outil n'a jamais été distribué : rien n'est consultable de l'extérieur.
Chiffres déclarés, dépôt privé :
- Tests
- 278 tests pytest tous passants, exécutés le 2 octobre 2026
- Couverture
- 90 % sur core/ mesurée le 2 octobre 2026 ; l'interface Qt n'est pas mesurée