From bfafc6a48c5af0c54a310c5c2ef732f868297458 Mon Sep 17 00:00:00 2001 From: Ralph Mayola Date: Sun, 23 Aug 2026 20:04:52 +0200 Subject: [PATCH] Le masque des modificateurs couvre enfin Command, et jamais Caps Lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux defauts trouves en rendant la sonde injectable. 1. Le masque etait ecrit 0x000F0000 A LA MAIN. Il couvrait Shift, Control et Alt mais PAS Command (0x00100000) — pourtant la touche du raccourci de Zonza. Maintenir Cmd seul passait donc inapercu. Le masque est desormais COMPOSE depuis les constantes nommees de Quartz : un masque calcule de tete se trompe en silence, compose il ne peut plus. 2. Le test lisait le CLAVIER PHYSIQUE (`modificateurs_enfonces() is False`). Son resultat dependait des mains de l'utilisateur pendant la suite — un echec inreproductible a ete observe ce jour, puis 17 executions vertes. La sonde est rendue injectable, comme SystemeWindows l'avait deja, et les quatre modificateurs sont eprouves un par un. Un test verifie que la sonde reelle rend un booleen sans lever, SANS affirmer sa valeur. Le repli hors Quartz excluait aussi le verrouillage majuscule : actif, il aurait fait attendre 2 s avant CHAQUE collage, en permanence. 195 tests. Co-Authored-By: Claude Opus 5 (1M context) --- systeme/macos.py | 35 +++++++++++++++++++++++++++++++---- tests/test_systeme.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/systeme/macos.py b/systeme/macos.py index f9ac6e1..48b8c59 100644 --- a/systeme/macos.py +++ b/systeme/macos.py @@ -9,8 +9,24 @@ _SONS = { "error": "/System/Library/Sounds/Basso.aiff", } -# Masque des modificateurs (ctrl, shift, alt, cmd) dans les drapeaux Quartz. -_MASQUE_MODIFICATEURS = 0x000F0000 +def _masque_modificateurs(): + """Masque des modificateurs, COMPOSE depuis les constantes nommees de Quartz. + + Il etait ecrit 0x000F0000 a la main : ce masque couvrait Shift, Control et Alt + mais PAS Command (0x00100000), pourtant la touche du raccourci de Zonza. Un + masque calcule de tete se trompe en silence ; compose depuis les constantes, + il ne peut plus. Repli sur la valeur complete si Quartz est indisponible. + """ + try: + from Quartz import (kCGEventFlagMaskAlternate, kCGEventFlagMaskCommand, + kCGEventFlagMaskControl, kCGEventFlagMaskShift) + return (kCGEventFlagMaskShift | kCGEventFlagMaskControl + | kCGEventFlagMaskAlternate | kCGEventFlagMaskCommand) + except Exception: + return 0x001E0000 # sans le verrouillage majuscule : il resterait actif en permanence + + +_MASQUE_MODIFICATEURS = _masque_modificateurs() class SystemeMacOS: @@ -25,10 +41,21 @@ class SystemeMacOS: except Exception: pass # le son ne doit jamais casser la dictée + def __init__(self, sonde_modificateurs=None): + """`sonde_modificateurs` est injectable : sans cela, tout test de cette + methode lirait le CLAVIER PHYSIQUE, et son resultat dependrait des mains + de l'utilisateur au moment ou la suite tourne. Meme forme que + SystemeWindows, qui recoit deja son clavier.""" + self._sonde_modificateurs = sonde_modificateurs or self._drapeaux_reels + + @staticmethod + def _drapeaux_reels(): + from Quartz import CGEventSourceFlagsState + return CGEventSourceFlagsState(1) + def modificateurs_enfonces(self): try: - from Quartz import CGEventSourceFlagsState - return bool(CGEventSourceFlagsState(1) & _MASQUE_MODIFICATEURS) + return bool(self._sonde_modificateurs() & _MASQUE_MODIFICATEURS) except Exception: return False # dans le doute, ne jamais bloquer le collage diff --git a/tests/test_systeme.py b/tests/test_systeme.py index 3595234..254f1bc 100644 --- a/tests/test_systeme.py +++ b/tests/test_systeme.py @@ -35,5 +35,37 @@ def test_le_verrou_refuse_une_seconde_prise(): assert s.acquerir_verrou_instance(chemin) is None -def test_aucun_modificateur_enfonce_pendant_les_tests(): - assert choisir_systeme().modificateurs_enfonces() is False +def test_les_modificateurs_sont_lus_par_le_masque(): + """La sonde est injectee : sans cela ce test lirait le CLAVIER PHYSIQUE et + echouerait au hasard, selon qu'une touche est enfoncee ou non pendant la + suite. C'est arrive le 2026-08-23.""" + from systeme.macos import SystemeMacOS + assert SystemeMacOS(sonde_modificateurs=lambda: 0x00000000).modificateurs_enfonces() is False + # chaque modificateur, un par un, depuis les constantes nommees + from Quartz import (kCGEventFlagMaskAlternate, kCGEventFlagMaskCommand, + kCGEventFlagMaskControl, kCGEventFlagMaskShift) + for drapeau in (kCGEventFlagMaskShift, kCGEventFlagMaskControl, + kCGEventFlagMaskAlternate, kCGEventFlagMaskCommand): + assert SystemeMacOS(sonde_modificateurs=lambda d=drapeau: d).modificateurs_enfonces() is True + + +def test_une_sonde_qui_leve_ne_bloque_jamais_le_collage(): + from systeme.macos import SystemeMacOS + + def sonde_cassee(): + raise RuntimeError("Quartz indisponible") + + assert SystemeMacOS(sonde_modificateurs=sonde_cassee).modificateurs_enfonces() is False + + +def test_le_masque_couvre_command(): + """Command etait ABSENT du masque ecrit a la main : Zonza ne voyait jamais + cette touche, pourtant celle de son propre raccourci.""" + from Quartz import kCGEventFlagMaskCommand + from systeme.macos import _MASQUE_MODIFICATEURS + assert kCGEventFlagMaskCommand & _MASQUE_MODIFICATEURS + + +def test_la_sonde_reelle_rend_un_booleen_sans_lever(): + """On n'affirme PAS sa valeur : elle depend du clavier de l'utilisateur.""" + assert isinstance(choisir_systeme().modificateurs_enfonces(), bool)