"""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 "++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