From 48cf2b2539fc8aba209c221752547bd9b6051d96 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:19:34 +0200 Subject: [PATCH 1/3] fix(app): fix tray icon unresponsiveness when toggling recording The tray icon became unresponsive after a recording cycle because thread references were not cleaned up and race conditions existed between the monitoring and recording threads. - Add `_recording_started` flag to synchronize icon updates - Clean up thread references in `callback_stop_recording` - Pass `icon` to `start_recording` to track startup state - Fix minor linting issue and set default action for Stop menu item Generated with opencode Co-Authored-By: opencode --- scribe/app.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scribe/app.py b/scribe/app.py index a8694ed..9890685 100644 --- a/scribe/app.py +++ b/scribe/app.py @@ -221,7 +221,7 @@ def get_parser(): # Commencer l'enregistrement -def start_recording(micro, transcriber, clipboard=True, keyboard=False, latency=0, ascii=False, output_file=None, callback=None, **greetings): +def start_recording(micro, transcriber, icon=None, clipboard=True, keyboard=False, latency=0, ascii=False, output_file=None, callback=None, **greetings): if keyboard: from scribe.keyboard import type_text @@ -234,6 +234,8 @@ def start_recording(micro, transcriber, clipboard=True, keyboard=False, latency= fulltext = "" for result in transcriber.start_recording(micro, **greetings): + if icon is not None: + icon._recording_started = True if result.get('text'): clear_line() @@ -290,7 +292,7 @@ def update_icon(icon, force=False): icon.update_menu() else: - if force or getattr(icon, "_icon_label", None) != None: + if force or getattr(icon, "_icon_label", None) is not None: icon.icon = image icon._icon_label = None icon.update_menu() @@ -298,7 +300,7 @@ def update_icon(icon, force=False): def start_monitoring(icon): transcriber = icon._transcriber try: - while transcriber.busy: + while transcriber.busy and getattr(icon, "_recording_started", False): update_icon(icon) time.sleep(0.1) @@ -319,6 +321,10 @@ def callback_stop_recording(icon, item): icon._recording_thread.join() if hasattr(icon, "_monitoring_thread"): icon._monitoring_thread.join() + # Clean up thread references + icon._recording_thread = None + icon._monitoring_thread = None + icon._recording_started = False def callback_record(icon, item): transcriber = icon._transcriber @@ -333,7 +339,8 @@ def callback_record(icon, item): icon._monitoring_thread.join() transcriber.busy = True # this is a hack to prevent race conditions between the below threads - icon._recording_thread = threading.Thread(target=start_recording, args=(micro, transcriber), kwargs=kwargs) + icon._recording_started = False + icon._recording_thread = threading.Thread(target=start_recording, args=(micro, transcriber, icon), kwargs=kwargs) icon._recording_thread.start() icon._monitoring_thread = threading.Thread(target=start_monitoring, args=(icon,)) icon._monitoring_thread.start() @@ -408,8 +415,8 @@ def is_option_visible(item): options = [name for name in kwargs if isinstance(kwargs[name], bool)] + [name for name in transcriber_options if isinstance(getattr(transcriber, name), bool)] menus = [] - menus.append(Item(f"Record", callback_record, visible=is_not_recording, default=True)) - menus.append(Item("Stop", callback_stop_recording, visible=is_recording)) + menus.append(Item("Record", callback_record, visible=is_not_recording, default=True)) + menus.append(Item("Stop", callback_stop_recording, visible=is_recording, default=True)) menus.append(Item("Choose Model", pystrayMenu( *(Item(f"{name}", callback_set_model, checked=is_checked_model) for name in other_transcribers_dict))) ) From c89123f592db8a899b8109de8b107ad2a6dc0b84 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:24:04 +0200 Subject: [PATCH 2/3] fix(app): prevent AttributeError when thread is None Add 'is not None' checks before calling 'is_alive()' on thread attributes. This prevents an AttributeError when a recording has been stopped and the thread reference has been cleared. Generated with opencode Co-Authored-By: opencode --- scribe/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scribe/app.py b/scribe/app.py index 9890685..2b038ed 100644 --- a/scribe/app.py +++ b/scribe/app.py @@ -332,10 +332,10 @@ def callback_record(icon, item): # transcriber.log("Still busy recording or transcribing.") return callback_stop_recording(icon, item) # play / stop behavior - if hasattr(icon, "_recording_thread") and icon._recording_thread.is_alive(): + if hasattr(icon, "_recording_thread") and icon._recording_thread is not None and icon._recording_thread.is_alive(): icon._recording_thread.join() - if hasattr(icon, "_monitoring_thread") and icon._monitoring_thread.is_alive(): + if hasattr(icon, "_monitoring_thread") and icon._monitoring_thread is not None and icon._monitoring_thread.is_alive(): icon._monitoring_thread.join() transcriber.busy = True # this is a hack to prevent race conditions between the below threads From f44a54dd0c1c9969ce73c2486c02f92700a67b8c Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sat, 3 Jan 2026 08:12:43 +0200 Subject: [PATCH 3/3] fix(app): reset tray icon immediately when stopping recording When stopping a recording session, the tray icon stayed in the recording or busy state until all threads were joined. By explicitly setting the recording and busy flags and calling update_icon before joining, the icon returns to its initial state immediately upon user action. This also allows the monitoring thread loop to exit cleanly as soon as the stop command is issued. Generated with opencode Co-Authored-By: opencode --- scribe/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scribe/app.py b/scribe/app.py index 2b038ed..4ec13c6 100644 --- a/scribe/app.py +++ b/scribe/app.py @@ -317,6 +317,9 @@ def callback_stop_recording(icon, item): transcriber = icon._transcriber # Here we need to stop the recording thread transcriber.interrupt = True + transcriber.recording = False + transcriber.busy = False + update_icon(icon) if hasattr(icon, "_recording_thread"): icon._recording_thread.join() if hasattr(icon, "_monitoring_thread"):