- systeme/macos.py : SystemeMacOS (sons afplay, modificateurs Quartz, chemins, verrou d'instance fcntl) implementant le contrat Systeme. - systeme/__init__.py : choisir_systeme() fabrique selon sys.platform, refuse clairement une plateforme inconnue. - core.py : verrou d'instance (fcntl, LOCK_PATH, acquire_single_instance_lock) retire ; import os retire aussi, verifie inutilise ailleurs dans le fichier. - app.py : passe par choisir_systeme().acquerir_verrou_instance(dossier_donnees() + instance.lock) au lieu de core.acquire_single_instance_lock. Seul changement dans ce fichier, nom de fichier inchange. - tests/test_single_instance.py : reoriente vers choisir_systeme(), memes assertions et meme nombre de tests. - tests/test_systeme.py (nouveau) : verrou du test_le_verrou_refuse_une_seconde_prise garde desormais la reference au premier verrou — sans variable, CPython le garbage-collectait entre les deux assert (refcount 0 -> fermeture -> verrou relache), le test passait pour la mauvaise raison. 89 tests verts (84 + 5).
55 lines
1.6 KiB
Python
55 lines
1.6 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",
|
|
}
|
|
|
|
# Masque des modificateurs (ctrl, shift, alt, cmd) dans les drapeaux Quartz.
|
|
_MASQUE_MODIFICATEURS = 0x000F0000
|
|
|
|
|
|
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 modificateurs_enfonces(self):
|
|
try:
|
|
from Quartz import CGEventSourceFlagsState
|
|
return bool(CGEventSourceFlagsState(1) & _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
|