Skip to content

Commit 6ada406

Browse files
committed
fix: clear preview before processing
1 parent af959fb commit 6ada406

6 files changed

Lines changed: 107 additions & 1 deletion

File tree

lib/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,7 @@ def _stop_recording(self):
14451445
print("Recording stopped", flush=True)
14461446

14471447
try:
1448+
self._clear_mic_osd_preview_text()
14481449

14491450
# Set visualizer to processing state (keep it visible during transcription)
14501451
self._set_visualizer_state('processing')

lib/mic_osd/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,17 @@ def _poll_state_file(self):
313313
state = f.read().strip()
314314
if state and state != self._last_visualizer_state:
315315
self._last_visualizer_state = state
316+
if self.window and hasattr(self.window, 'set_visualizer_state'):
317+
self.window.set_visualizer_state(state)
316318
# Update visualization state if it has the set_state method
317319
if hasattr(self.visualization, 'set_state'):
318320
self.visualization.set_state(state)
319321
else:
320322
# No state file means default to recording state
321323
if self._last_visualizer_state != 'recording':
322324
self._last_visualizer_state = 'recording'
325+
if self.window and hasattr(self.window, 'set_visualizer_state'):
326+
self.window.set_visualizer_state('recording')
323327
if hasattr(self.visualization, 'set_state'):
324328
self.visualization.set_state('recording')
325329

lib/mic_osd/window.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self, visualization, width=300, height=60):
5151
self._width = width
5252
self._height = height
5353
self._preview_text = ""
54+
self._visualizer_state = "recording"
5455

5556
# Layer shell MUST be initialized immediately after window creation
5657
# and BEFORE any other window configuration
@@ -137,14 +138,19 @@ def set_preview_text(self, text: str):
137138
"""Set compact transcript preview text."""
138139
self._preview_text = (text or "").rstrip('\r\n')
139140
self.drawing_area.queue_draw()
141+
142+
def set_visualizer_state(self, state: str):
143+
"""Track visualizer state so partial previews only render while recording."""
144+
self._visualizer_state = (state or "recording").lower()
145+
self.drawing_area.queue_draw()
140146

141147
def set_visualization(self, visualization):
142148
"""Change the visualization type."""
143149
self.visualization = visualization
144150
self.drawing_area.queue_draw()
145151

146152
def _draw_preview_text(self, cr: cairo.Context, width: int, height: int):
147-
if not self._preview_text:
153+
if not self._preview_text or self._visualizer_state != "recording":
148154
return
149155

150156
padding = 14

tests/test_main_startup_safety.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,37 @@ def test_show_mic_osd_clears_preview_before_showing(self):
5858
self.assertIsNotNone(show_line)
5959
self.assertLess(clear_line, show_line)
6060

61+
def test_stop_recording_clears_preview_before_processing_state(self):
62+
tree = ast.parse((ROOT / "lib" / "main.py").read_text(encoding="utf-8"))
63+
64+
stop_func = None
65+
for node in ast.walk(tree):
66+
if isinstance(node, ast.FunctionDef) and node.name == "_stop_recording":
67+
stop_func = node
68+
break
69+
70+
self.assertIsNotNone(stop_func)
71+
72+
clear_line = None
73+
processing_line = None
74+
for node in ast.walk(stop_func):
75+
if not isinstance(node, ast.Call):
76+
continue
77+
if isinstance(node.func, ast.Attribute) and node.func.attr == "_clear_mic_osd_preview_text":
78+
clear_line = node.lineno
79+
elif (
80+
isinstance(node.func, ast.Attribute)
81+
and node.func.attr == "_set_visualizer_state"
82+
and node.args
83+
and isinstance(node.args[0], ast.Constant)
84+
and node.args[0].value == "processing"
85+
):
86+
processing_line = node.lineno
87+
88+
self.assertIsNotNone(clear_line)
89+
self.assertIsNotNone(processing_line)
90+
self.assertLess(clear_line, processing_line)
91+
6192

6293
if __name__ == "__main__":
6394
unittest.main()

tests/test_mic_osd_main_fallback.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,25 @@ def test_cleanup_uses_preview_file_helper(self):
8282
)
8383
self.assertTrue(calls_helper)
8484

85+
def test_state_poll_updates_window_visualizer_state(self):
86+
tree = ast.parse((ROOT / "lib" / "mic_osd" / "main.py").read_text(encoding="utf-8"))
87+
88+
poll_func = None
89+
for node in ast.walk(tree):
90+
if isinstance(node, ast.FunctionDef) and node.name == "_poll_state_file":
91+
poll_func = node
92+
break
93+
94+
self.assertIsNotNone(poll_func)
95+
96+
calls_window_state = any(
97+
isinstance(node, ast.Call)
98+
and isinstance(node.func, ast.Attribute)
99+
and node.func.attr == "set_visualizer_state"
100+
for node in ast.walk(poll_func)
101+
)
102+
self.assertTrue(calls_window_state)
103+
85104

86105
if __name__ == "__main__":
87106
unittest.main()

tests/test_mic_osd_runner.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,35 @@ def fire(self):
3333
self.callback(*self.args)
3434

3535

36+
class FakeCairoContext:
37+
def __init__(self):
38+
self.shown_text = []
39+
40+
def select_font_face(self, *args):
41+
pass
42+
43+
def set_font_size(self, *args):
44+
pass
45+
46+
def text_extents(self, text):
47+
return (0, 0, len(text) * 5, 10, 0, 0)
48+
49+
def set_source_rgba(self, *args):
50+
pass
51+
52+
def rectangle(self, *args):
53+
pass
54+
55+
def fill(self):
56+
pass
57+
58+
def move_to(self, *args):
59+
pass
60+
61+
def show_text(self, text):
62+
self.shown_text.append(text)
63+
64+
3665
class MicOSDRunnerTests(unittest.TestCase):
3766
def _import_window_with_stubs(self):
3867
for module_name in ("mic_osd.window",):
@@ -219,6 +248,22 @@ def text_extents(self, text):
219248
self.assertEqual(window._text_width(ObjectContext(), "abcd"), 20)
220249
self.assertEqual(window._text_height(ObjectContext(), "abcd"), 10)
221250

251+
def test_preview_text_draws_only_while_recording(self):
252+
window_module, _ = self._import_window_with_stubs()
253+
window = object.__new__(window_module.OSDWindow)
254+
window._preview_text = "live partial"
255+
window._visualizer_state = "processing"
256+
257+
processing_cr = FakeCairoContext()
258+
window._draw_preview_text(processing_cr, 400, 68)
259+
260+
window._visualizer_state = "recording"
261+
recording_cr = FakeCairoContext()
262+
window._draw_preview_text(recording_cr, 400, 68)
263+
264+
self.assertEqual(processing_cr.shown_text, [])
265+
self.assertEqual(recording_cr.shown_text, ["live partial"])
266+
222267
def test_preview_text_preserves_spaces_but_trims_newlines(self):
223268
with tempfile.TemporaryDirectory() as tmp:
224269
preview_file = Path(tmp) / "hyprwhspr" / "transcript_preview"

0 commit comments

Comments
 (0)