Attend le relachement des touches avant de coller
Deux dictees ont ete transcrites correctement puis n'ont jamais atteint la zone de
saisie, sans la moindre erreur : inject_text avait bien envoye ses touches. Cause :
la hotkey Cmd+Ctrl+D etait encore enfoncee quand Zonza a simule Cmd+V. Le systeme a
recu Cmd+Ctrl+V, que l'application cible ignore. Le code croyait avoir reussi.
inject_text patiente donc jusqu'a ce qu'aucun modificateur physique ne soit enfonce
(drapeaux Quartz), au plus 2 s. Au-dela, il colle quand meme : mieux vaut un collage
douteux qu'une transcription perdue. Le delai du presse-papier passe de 0,05 a 0,12 s,
0,05 etant juste.
Et surtout, le collage se TRACE desormais ("[collage] ok" ou l'avertissement) : sans
cela, un echec futur resterait invisible dans le log, comme celui-ci l'a ete.
L'attente est une unite pure et bornee (core.attendre, horloge et sommeil injectables).
Detecteur verifie en conditions reelles : False au repos, retour immediat.
55 tests passent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f8c30e0402
commit
58ed0d9e6c
21
core.py
21
core.py
@ -13,6 +13,8 @@ CONFIG = {
|
||||
"hotkey": "<cmd>+<ctrl>+d", # Cmd+Ctrl+D (libre : ni macOS ni VS Code ne l'utilisent)
|
||||
"sounds": True,
|
||||
"min_duration_s": 0.3,
|
||||
"attente_modificateurs_s": 2.0, # on patiente avant de coller (cf inject_text)
|
||||
"delai_presse_papier_s": 0.12, # 0.05 s etait juste : le presse-papier n'etait pas pret
|
||||
"max_duration_s": 120.0, # sécurité : arrêt auto après 2 min (fenêtre jamais figée)
|
||||
"sample_rate": 16000,
|
||||
"fps": 30,
|
||||
@ -105,6 +107,25 @@ _PROPORTIONS = {
|
||||
}
|
||||
|
||||
|
||||
def attendre(condition, delai_max_s, pas_s, dormir=None, horloge=None):
|
||||
"""Attend que `condition()` soit vraie, au plus `delai_max_s`. Rend True/False.
|
||||
|
||||
Bornee volontairement : mieux vaut coller avec un modificateur encore enfonce
|
||||
que perdre la transcription si une touche reste bloquee. `dormir` et `horloge`
|
||||
sont injectables pour tester sans attendre reellement.
|
||||
"""
|
||||
import time as _t
|
||||
dormir = dormir or _t.sleep
|
||||
horloge = horloge or _t.monotonic
|
||||
debut = horloge()
|
||||
while True:
|
||||
if condition():
|
||||
return True
|
||||
if horloge() - debut >= delai_max_s:
|
||||
return False
|
||||
dormir(pas_s)
|
||||
|
||||
|
||||
def bar_metrics(bar_height):
|
||||
"""Mesures de l'overlay pour une hauteur de barre donnee, en points."""
|
||||
if bar_height <= 0:
|
||||
|
||||
33
engine.py
33
engine.py
@ -11,7 +11,7 @@ import pyperclip
|
||||
import sounddevice as sd
|
||||
from pynput.keyboard import Controller, Key
|
||||
|
||||
from core import CONFIG, clean_transcript
|
||||
from core import CONFIG, attendre, clean_transcript
|
||||
|
||||
SOUNDS = {
|
||||
"start": "/System/Library/Sounds/Pop.aiff",
|
||||
@ -35,15 +35,42 @@ def play_sound(name):
|
||||
pass # le son ne doit jamais casser le daemon
|
||||
|
||||
|
||||
# Masque des modificateurs (ctrl, shift, alt, cmd) dans les drapeaux Quartz.
|
||||
_MASQUE_MODIFICATEURS = 0x000F0000
|
||||
|
||||
|
||||
def modificateurs_enfonces():
|
||||
"""True si au moins un modificateur physique est actuellement enfoncé."""
|
||||
try:
|
||||
from Quartz import CGEventSourceFlagsState
|
||||
return bool(CGEventSourceFlagsState(1) & _MASQUE_MODIFICATEURS)
|
||||
except Exception:
|
||||
return False # dans le doute, ne jamais bloquer le collage
|
||||
|
||||
|
||||
def inject_text(text):
|
||||
"""Copie le texte dans le presse-papier puis colle avec Cmd+V dans l'app active."""
|
||||
"""Copie le texte dans le presse-papier puis colle avec Cmd+V dans l'app active.
|
||||
|
||||
On attend d'abord que l'utilisateur ait relâché ses touches : s'il tient encore
|
||||
Cmd+Ctrl+D quand on envoie Cmd+V, le système reçoit Cmd+Ctrl+V et l'application
|
||||
cible ne colle rien — sans la moindre erreur de notre côté (constaté le
|
||||
2026-08-21 : deux dictées transcrites, jamais collées).
|
||||
"""
|
||||
if not text:
|
||||
return
|
||||
pyperclip.copy(text)
|
||||
time.sleep(0.05) # laisser le presse-papier se mettre à jour
|
||||
libre = attendre(
|
||||
lambda: not modificateurs_enfonces(),
|
||||
CONFIG["attente_modificateurs_s"],
|
||||
0.03,
|
||||
)
|
||||
if not libre:
|
||||
print("[collage] modificateurs encore enfoncés — collage tenté quand même")
|
||||
time.sleep(CONFIG["delai_presse_papier_s"]) # laisser le presse-papier se mettre à jour
|
||||
with _kbd.pressed(Key.cmd):
|
||||
_kbd.press("v")
|
||||
_kbd.release("v")
|
||||
print("[collage] ok")
|
||||
|
||||
|
||||
class Recorder:
|
||||
|
||||
55
tests/test_attente.py
Normal file
55
tests/test_attente.py
Normal file
@ -0,0 +1,55 @@
|
||||
"""Attente d'une condition avant le collage.
|
||||
|
||||
Bug du 2026-08-21 : deux dictees transcrites correctement n'ont jamais atteint la
|
||||
zone de saisie. Aucune exception — les touches AVAIENT ete envoyees. Cause : la
|
||||
hotkey Cmd+Ctrl+D etait encore enfoncee quand Zonza a simule Cmd+V ; le systeme a
|
||||
recu Cmd+Ctrl+V, que l'application cible ignore.
|
||||
|
||||
D'ou une attente du relachement des modificateurs avant de coller. Bornee : si une
|
||||
touche reste enfoncee (clavier bloque), on colle quand meme plutot que de perdre le
|
||||
texte.
|
||||
"""
|
||||
from core import attendre
|
||||
|
||||
|
||||
def test_condition_deja_vraie_ne_dort_pas():
|
||||
sommeils = []
|
||||
assert attendre(lambda: True, 1.0, 0.05, dormir=sommeils.append, horloge=lambda: 0.0) is True
|
||||
assert sommeils == []
|
||||
|
||||
|
||||
def test_condition_devient_vraie_apres_quelques_tours():
|
||||
tours = {"n": 0}
|
||||
def cond():
|
||||
tours["n"] += 1
|
||||
return tours["n"] >= 3
|
||||
t = {"v": 0.0}
|
||||
def horloge(): return t["v"]
|
||||
def dormir(s): t["v"] += s
|
||||
assert attendre(cond, 1.0, 0.05, dormir=dormir, horloge=horloge) is True
|
||||
assert tours["n"] == 3
|
||||
|
||||
|
||||
def test_rend_False_au_bout_du_temps_imparti():
|
||||
t = {"v": 0.0}
|
||||
def horloge(): return t["v"]
|
||||
def dormir(s): t["v"] += s
|
||||
assert attendre(lambda: False, 0.2, 0.05, dormir=dormir, horloge=horloge) is False
|
||||
|
||||
|
||||
def test_le_temps_imparti_est_respecte():
|
||||
t = {"v": 0.0}
|
||||
dormis = []
|
||||
def horloge(): return t["v"]
|
||||
def dormir(s): t["v"] += s; dormis.append(s)
|
||||
attendre(lambda: False, 0.2, 0.05, dormir=dormir, horloge=horloge)
|
||||
assert sum(dormis) <= 0.25, "l'attente ne doit pas deborder"
|
||||
|
||||
|
||||
def test_un_delai_nul_tente_quand_meme_une_fois():
|
||||
essais = {"n": 0}
|
||||
def cond():
|
||||
essais["n"] += 1
|
||||
return False
|
||||
attendre(cond, 0.0, 0.05, dormir=lambda s: None, horloge=lambda: 0.0)
|
||||
assert essais["n"] >= 1
|
||||
Loading…
Reference in New Issue
Block a user