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)