Skip to content

Commit 5cee909

Browse files
author
My Name
committed
0.4 rc1
1 parent 6e711a5 commit 5cee909

File tree

3 files changed

+183
-61
lines changed

3 files changed

+183
-61
lines changed

Diff for: blaze/main.py

+145-59
Original file line numberDiff line numberDiff line change
@@ -176,54 +176,117 @@ def isSystemTrayAvailable():
176176
return QSystemTrayIcon.isSystemTrayAvailable()
177177

178178
def toggle_recording(self):
179-
# Check if transcriber is properly initialized
180-
if not self.recording and (not hasattr(self, 'transcription_manager') or not self.transcription_manager or
181-
not hasattr(self.transcription_manager.transcriber, 'model') or not self.transcription_manager.transcriber.model):
182-
# Transcriber is not properly initialized, show a message
183-
self.ui_manager.show_notification(
184-
self,
185-
"No Models Downloaded",
186-
"No Whisper models are downloaded. Please go to Settings to download a model.",
187-
self.normal_icon
188-
)
189-
# Open settings window to allow user to download a model
190-
self.toggle_settings()
179+
"""Toggle recording state with improved resilience to rapid clicks"""
180+
# Use a lock to prevent concurrent execution of this method
181+
if hasattr(self, '_recording_lock') and self._recording_lock:
182+
logger.info("Recording toggle already in progress, ignoring request")
191183
return
192184

193-
if self.recording:
194-
# Stop recording
195-
self.recording = False
196-
self.record_action.setText("Start Recording")
197-
self.setIcon(self.normal_icon)
198-
199-
# Update progress window before stopping recording
200-
if self.progress_window:
201-
self.progress_window.set_processing_mode()
202-
self.progress_window.set_status("Processing audio...")
203-
204-
# Stop the actual recording
205-
if self.audio_manager:
206-
try:
207-
self.audio_manager.stop_recording()
208-
except Exception as e:
209-
logger.error(f"Error stopping recording: {e}")
210-
if self.progress_window:
211-
self.progress_window.close()
212-
self.progress_window = None
213-
return
214-
else:
215-
# Start recording
216-
self.recording = True
217-
# Show progress window
218-
if not self.progress_window:
185+
# Set lock
186+
self._recording_lock = True
187+
188+
try:
189+
# Check if transcriber is properly initialized
190+
if not self.recording and (not hasattr(self, 'transcription_manager') or not self.transcription_manager or
191+
not hasattr(self.transcription_manager.transcriber, 'model') or not self.transcription_manager.transcriber.model):
192+
# Transcriber is not properly initialized, show a message
193+
self.ui_manager.show_notification(
194+
self,
195+
"No Models Downloaded",
196+
"No Whisper models are downloaded. Please go to Settings to download a model.",
197+
self.normal_icon
198+
)
199+
# Open settings window to allow user to download a model
200+
self.toggle_settings()
201+
return
202+
203+
# Get current state before changing it (for logging)
204+
current_state = "recording" if self.recording else "not recording"
205+
new_state = "stop recording" if self.recording else "start recording"
206+
logger.info(f"Toggle recording: {current_state} -> {new_state}")
207+
208+
if self.recording:
209+
# Stop recording
210+
# Update UI first to give immediate feedback
211+
self.record_action.setText("Start Recording")
212+
self.setIcon(self.normal_icon)
213+
214+
# Update progress window before stopping recording
215+
if self.progress_window:
216+
self.progress_window.set_processing_mode()
217+
self.progress_window.set_status("Processing audio...")
218+
219+
# Stop the actual recording
220+
if self.audio_manager:
221+
try:
222+
# Only change recording state after successful stop
223+
result = self.audio_manager.stop_recording()
224+
if result:
225+
self.recording = False
226+
logger.info("Recording stopped successfully")
227+
else:
228+
# Revert UI if stop failed
229+
logger.error("Failed to stop recording")
230+
self.record_action.setText("Stop Recording")
231+
self.setIcon(self.recording_icon)
232+
except Exception as e:
233+
logger.error(f"Error stopping recording: {e}")
234+
# Revert UI if exception occurred
235+
self.record_action.setText("Stop Recording")
236+
self.setIcon(self.recording_icon)
237+
if self.progress_window:
238+
self.progress_window.close()
239+
self.progress_window = None
240+
else:
241+
# No audio manager, just update state
242+
self.recording = False
243+
else:
244+
# Start recording
245+
# Always create a fresh progress window
246+
# Close any existing window first
247+
if self.progress_window:
248+
self.ui_manager.safely_close_window(self.progress_window, "before new recording")
249+
self.progress_window = None
250+
251+
# Create a new progress window
219252
self.progress_window = ProgressWindow("Voice Recording")
220253
self.progress_window.stop_clicked.connect(self._stop_recording)
221-
self.progress_window.show()
222-
223-
# Start recording
224-
self.record_action.setText("Stop Recording")
225-
self.setIcon(self.recording_icon)
226-
self.audio_manager.start_recording()
254+
self.progress_window.show()
255+
256+
# Update UI to give immediate feedback
257+
self.record_action.setText("Stop Recording")
258+
self.setIcon(self.recording_icon)
259+
260+
# Start the actual recording
261+
if self.audio_manager:
262+
try:
263+
# Only change recording state after successful start
264+
result = self.audio_manager.start_recording()
265+
if result:
266+
self.recording = True
267+
logger.info("Recording started successfully")
268+
else:
269+
# Revert UI if start failed
270+
logger.error("Failed to start recording")
271+
self.record_action.setText("Start Recording")
272+
self.setIcon(self.normal_icon)
273+
if self.progress_window:
274+
self.progress_window.close()
275+
self.progress_window = None
276+
except Exception as e:
277+
logger.error(f"Error starting recording: {e}")
278+
# Revert UI if exception occurred
279+
self.record_action.setText("Start Recording")
280+
self.setIcon(self.normal_icon)
281+
if self.progress_window:
282+
self.progress_window.close()
283+
self.progress_window = None
284+
else:
285+
# No audio manager, just update state
286+
self.recording = True
287+
finally:
288+
# Always release the lock
289+
self._recording_lock = False
227290

228291
def _stop_recording(self):
229292
"""Internal method to stop recording and start processing"""
@@ -279,21 +342,42 @@ def update_tooltip(self, recognized_text=None):
279342
# Removed update_shortcuts method as part of keyboard shortcut functionality removal
280343

281344
def on_activate(self, reason):
282-
if reason == QSystemTrayIcon.ActivationReason.Trigger: # Left click
283-
# Check if transcriber is properly initialized
284-
if hasattr(self, 'transcription_manager') and self.transcription_manager and hasattr(self.transcription_manager.transcriber, 'model') and self.transcription_manager.transcriber.model:
285-
# Transcriber is properly initialized, proceed with recording
286-
self.toggle_recording()
287-
else:
288-
# Transcriber is not properly initialized, show a message
289-
self.ui_manager.show_notification(
290-
self,
291-
"No Models Downloaded",
292-
"No Whisper models are downloaded. Please go to Settings to download a model.",
293-
self.normal_icon
294-
)
295-
# Open settings window to allow user to download a model
296-
self.toggle_settings()
345+
"""Handle tray icon activation with improved resilience"""
346+
# Ignore activations if we're already processing a click
347+
if hasattr(self, '_activation_lock') and self._activation_lock:
348+
logger.info("Activation already in progress, ignoring request")
349+
return
350+
351+
# Set lock
352+
self._activation_lock = True
353+
354+
try:
355+
if reason == QSystemTrayIcon.ActivationReason.Trigger: # Left click
356+
logger.info("Tray icon left-clicked")
357+
358+
# Check if we're in the middle of processing a recording
359+
if hasattr(self, 'progress_window') and self.progress_window and self.progress_window.isVisible():
360+
if not self.recording and getattr(self.progress_window, 'processing', False):
361+
logger.info("Processing in progress, ignoring activation")
362+
return
363+
364+
# Check if transcriber is properly initialized
365+
if hasattr(self, 'transcription_manager') and self.transcription_manager and hasattr(self.transcription_manager.transcriber, 'model') and self.transcription_manager.transcriber.model:
366+
# Transcriber is properly initialized, proceed with recording
367+
self.toggle_recording()
368+
else:
369+
# Transcriber is not properly initialized, show a message
370+
self.ui_manager.show_notification(
371+
self,
372+
"No Models Downloaded",
373+
"No Whisper models are downloaded. Please go to Settings to download a model.",
374+
self.normal_icon
375+
)
376+
# Open settings window to allow user to download a model
377+
self.toggle_settings()
378+
finally:
379+
# Always release the lock
380+
self._activation_lock = False
297381

298382
def quit_application(self):
299383
try:
@@ -426,6 +510,8 @@ def _close_progress_window(self, context=""):
426510
"""Helper method to safely close progress window"""
427511
if self.progress_window:
428512
self.ui_manager.safely_close_window(self.progress_window, f"progress {context}")
513+
# Explicitly set to None to force recreation on next recording
514+
self.progress_window = None
429515
else:
430516
logger.warning(f"Progress window not found when trying to close {context}".strip())
431517

Diff for: blaze/managers/audio_manager.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import logging
9+
import time
910
from PyQt6.QtCore import QObject, pyqtSignal
1011

1112
logger = logging.getLogger(__name__)
@@ -57,7 +58,7 @@ def initialize(self):
5758
return False
5859

5960
def start_recording(self):
60-
"""Start audio recording
61+
"""Start audio recording with improved error handling
6162
6263
Returns:
6364
--------
@@ -74,7 +75,20 @@ def start_recording(self):
7475
return True
7576

7677
try:
78+
# Check if recorder is ready
79+
if not hasattr(self.recorder, 'start_recording'):
80+
logger.error("Recorder object does not have start_recording method")
81+
self.recording_failed.emit("Invalid recorder object")
82+
return False
83+
84+
# Start recording with timeout protection
85+
start_time = time.time()
7786
self.recorder.start_recording()
87+
88+
# Verify recording started within reasonable time
89+
if time.time() - start_time > 2.0: # More than 2 seconds is suspicious
90+
logger.warning("Recording start took unusually long time")
91+
7892
self.is_recording = True
7993
logger.info("Recording started")
8094
return True
@@ -84,7 +98,7 @@ def start_recording(self):
8498
return False
8599

86100
def stop_recording(self):
87-
"""Stop audio recording
101+
"""Stop audio recording with improved error handling
88102
89103
Returns:
90104
--------
@@ -100,7 +114,20 @@ def stop_recording(self):
100114
return True
101115

102116
try:
117+
# Check if recorder is ready
118+
if not hasattr(self.recorder, '_stop_recording'):
119+
logger.error("Recorder object does not have _stop_recording method")
120+
self.recording_failed.emit("Invalid recorder object")
121+
return False
122+
123+
# Stop recording with timeout protection
124+
start_time = time.time()
103125
self.recorder._stop_recording()
126+
127+
# Verify recording stopped within reasonable time
128+
if time.time() - start_time > 2.0: # More than 2 seconds is suspicious
129+
logger.warning("Recording stop took unusually long time")
130+
104131
self.is_recording = False
105132
logger.info("Recording stopped")
106133
return True

Diff for: blaze/managers/ui_manager.py

+9
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,17 @@ def safely_close_window(self, window, window_name=""):
6363

6464
try:
6565
logger.info(f"Closing {window_name} window")
66+
67+
# Reset any processing state if it exists
68+
if hasattr(window, 'processing'):
69+
window.processing = False
70+
71+
# Hide first to give immediate visual feedback
6672
window.hide()
73+
74+
# Then close and schedule for deletion
6775
window.close()
76+
window.deleteLater()
6877

6978
# Force an immediate process of events
7079
QApplication.processEvents()

0 commit comments

Comments
 (0)