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) <noreply@anthropic.com>
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
"""Appels propres à macOS. Rien ici ne doit être importé directement par le noyau."""
|
|
import fcntl
|
|
import os
|
|
import subprocess
|
|
|
|
_SONS = {
|
|
"start": "/System/Library/Sounds/Pop.aiff",
|
|
"done": "/System/Library/Sounds/Glass.aiff",
|
|
"error": "/System/Library/Sounds/Basso.aiff",
|
|
}
|
|
|
|
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:
|
|
"""Implémentation du contrat Systeme pour macOS."""
|
|
|
|
def jouer_son(self, nom):
|
|
chemin = _SONS.get(nom)
|
|
if not chemin:
|
|
return
|
|
try:
|
|
subprocess.Popen(["afplay", chemin])
|
|
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:
|
|
return bool(self._sonde_modificateurs() & _MASQUE_MODIFICATEURS)
|
|
except Exception:
|
|
return False # dans le doute, ne jamais bloquer le collage
|
|
|
|
def raccourci_defaut(self):
|
|
return "<cmd>+<ctrl>+d"
|
|
|
|
def chemin_log(self):
|
|
return os.path.expanduser("~/Library/Logs/Zonza.log")
|
|
|
|
def dossier_donnees(self):
|
|
return os.path.expanduser("~/Library/Application Support/Zonza")
|
|
|
|
def acquerir_verrou_instance(self, chemin):
|
|
parent = os.path.dirname(chemin)
|
|
if parent:
|
|
os.makedirs(parent, exist_ok=True)
|
|
fichier = open(chemin, "a")
|
|
try:
|
|
fcntl.flock(fichier, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except OSError:
|
|
fichier.close()
|
|
return None
|
|
return fichier
|