Ajoute la couche Windows et la commande de diagnostic
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""Appels propres à Windows. Rien ici ne doit être importé directement par le noyau.
|
||||
|
||||
Miroir de `systeme/macos.py`, mais avec une contrainte que macOS n'a pas :
|
||||
`winsound`, `msvcrt` et `ctypes.windll` n'EXISTENT PAS sur macOS. Un import en
|
||||
tête de fichier rendrait ce module impossible à charger — donc impossible à
|
||||
tester — depuis la machine de développement (aucune machine Windows n'est
|
||||
disponible pour ce portage). Les trois dépendances sont donc INJECTABLES :
|
||||
`jouer`, `clavier`, `verrouiller`. Par défaut (None), elles se résolvent
|
||||
paresseusement vers l'API Windows réelle au moment de l'appel, jamais à
|
||||
l'import. Les tests injectent des doublures et n'exécutent donc jamais une
|
||||
ligne de code Windows — ils éprouvent la LOGIQUE de cette classe (mapping des
|
||||
sons, masque des touches, construction des chemins, séquence d'acquisition du
|
||||
verrou), pas le comportement réel de winsound/msvcrt/user32, qu'aucune
|
||||
machine ici ne peut vérifier.
|
||||
"""
|
||||
import os
|
||||
|
||||
# Alias système Windows joués par winsound.PlaySound(alias, SND_ALIAS | SND_ASYNC).
|
||||
_SONS = {
|
||||
"start": "SystemAsterisk",
|
||||
"done": "SystemExclamation",
|
||||
"error": "SystemHand",
|
||||
}
|
||||
|
||||
# Virtual-key codes Windows des cinq touches modificatrices à surveiller.
|
||||
# ctrl, shift, alt (menu), windows gauche, windows droite.
|
||||
_TOUCHES_MODIFICATRICES = (0x11, 0x10, 0x12, 0x5B, 0x5C)
|
||||
|
||||
# Dans le retour de GetAsyncKeyState, le bit de poids fort indique que la
|
||||
# touche est enfoncée EN CE MOMENT (les bits bas concernent un état passé
|
||||
# qui ne nous intéresse pas ici).
|
||||
_BIT_ENFONCEE = 0x8000
|
||||
|
||||
|
||||
class SystemeWindows:
|
||||
"""Implémentation du contrat Systeme pour Windows.
|
||||
|
||||
jouer(alias) : joue un son. clavier(vk) : rend l'état d'une touche.
|
||||
verrouiller(fichier) : pose un verrou exclusif non bloquant sur `fichier`,
|
||||
lève OSError s'il est déjà pris. Les trois par défaut à None : résolus
|
||||
paresseusement vers l'API Windows réelle au premier usage.
|
||||
"""
|
||||
|
||||
def __init__(self, jouer=None, clavier=None, verrouiller=None):
|
||||
self._jouer = jouer
|
||||
self._clavier = clavier
|
||||
self._verrouiller = verrouiller
|
||||
|
||||
# --- son ------------------------------------------------------------
|
||||
def jouer_son(self, nom):
|
||||
alias = _SONS.get(nom)
|
||||
if not alias:
|
||||
return
|
||||
try:
|
||||
(self._jouer or self._jouer_reel)(alias)
|
||||
except Exception:
|
||||
pass # le son ne doit jamais casser la dictée (même raison qu'en macOS)
|
||||
|
||||
@staticmethod
|
||||
def _jouer_reel(alias):
|
||||
import winsound
|
||||
winsound.PlaySound(alias, winsound.SND_ALIAS | winsound.SND_ASYNC)
|
||||
|
||||
# --- clavier ----------------------------------------------------------
|
||||
def modificateurs_enfonces(self):
|
||||
sonder = self._clavier or self._clavier_reel
|
||||
try:
|
||||
return any(sonder(vk) & _BIT_ENFONCEE for vk in _TOUCHES_MODIFICATRICES)
|
||||
except Exception:
|
||||
return False # dans le doute, ne jamais bloquer le collage (idem macOS)
|
||||
|
||||
@staticmethod
|
||||
def _clavier_reel(vk):
|
||||
import ctypes
|
||||
return ctypes.windll.user32.GetAsyncKeyState(vk)
|
||||
|
||||
# --- raccourci --------------------------------------------------------
|
||||
def raccourci_defaut(self):
|
||||
# <cmd> n'existe pas sur Windows : pas d'équivalent à la touche Commande.
|
||||
return "<ctrl>+<alt>+d"
|
||||
|
||||
# --- chemins ------------------------------------------------------------
|
||||
def chemin_log(self):
|
||||
return os.path.join(self._dossier_local_appdata(), "Zonza", "Zonza.log")
|
||||
|
||||
def dossier_donnees(self):
|
||||
return os.path.join(self._dossier_local_appdata(), "Zonza")
|
||||
|
||||
@staticmethod
|
||||
def _dossier_local_appdata():
|
||||
# os.environ est standard : lisible (et falsifiable par les tests via
|
||||
# monkeypatch.setenv) depuis n'importe quelle plateforme, contrairement
|
||||
# à winsound/ctypes.windll/msvcrt.
|
||||
return os.environ.get("LOCALAPPDATA", os.path.expanduser("~"))
|
||||
|
||||
# --- verrou d'instance --------------------------------------------------
|
||||
def acquerir_verrou_instance(self, chemin):
|
||||
parent = os.path.dirname(chemin)
|
||||
if parent:
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
fichier = open(chemin, "a+")
|
||||
fichier.seek(0)
|
||||
# msvcrt.locking verrouille un OCTET à partir de la position courante ;
|
||||
# il exige que cet octet existe déjà dans le fichier, d'où cette garde.
|
||||
if os.fstat(fichier.fileno()).st_size == 0:
|
||||
fichier.write("0")
|
||||
fichier.flush()
|
||||
fichier.seek(0)
|
||||
try:
|
||||
(self._verrouiller or self._verrouiller_reel)(fichier)
|
||||
except OSError:
|
||||
fichier.close()
|
||||
return None
|
||||
return fichier
|
||||
|
||||
@staticmethod
|
||||
def _verrouiller_reel(fichier):
|
||||
import msvcrt
|
||||
fichier.seek(0)
|
||||
msvcrt.locking(fichier.fileno(), msvcrt.LK_NBLCK, 1)
|
||||
Reference in New Issue
Block a user