@@ -176,54 +176,117 @@ def isSystemTrayAvailable():
176
176
return QSystemTrayIcon .isSystemTrayAvailable ()
177
177
178
178
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" )
191
183
return
192
184
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
219
252
self .progress_window = ProgressWindow ("Voice Recording" )
220
253
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
227
290
228
291
def _stop_recording (self ):
229
292
"""Internal method to stop recording and start processing"""
@@ -279,21 +342,42 @@ def update_tooltip(self, recognized_text=None):
279
342
# Removed update_shortcuts method as part of keyboard shortcut functionality removal
280
343
281
344
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
297
381
298
382
def quit_application (self ):
299
383
try :
@@ -426,6 +510,8 @@ def _close_progress_window(self, context=""):
426
510
"""Helper method to safely close progress window"""
427
511
if self .progress_window :
428
512
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
429
515
else :
430
516
logger .warning (f"Progress window not found when trying to close { context } " .strip ())
431
517
0 commit comments