52
52
# Random environment variable to speed up webcam opening on the MSMF backend.
53
53
# https://github.com/opencv/opencv/issues/17687
54
54
os .environ ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS" ] = "0"
55
-
56
- WINDOW_NAME = "Babble App"
57
- CAM_NAME = "-CAMWIDGET-"
58
- SETTINGS_NAME = "-SETTINGSWIDGET-"
59
- ALGO_SETTINGS_NAME = "-ALGOSETTINGSWIDGET-"
60
- CALIB_SETTINGS_NAME = "-CALIBSETTINGSWIDGET-"
61
- CAM_RADIO_NAME = "-CAMRADIO-"
62
- SETTINGS_RADIO_NAME = "-SETTINGSRADIO-"
63
- ALGO_SETTINGS_RADIO_NAME = "-ALGOSETTINGSRADIO-"
64
- CALIB_SETTINGS_RADIO_NAME = "-CALIBSETTINGSRADIO-"
65
-
66
55
page_url = "https://github.com/Project-Babble/ProjectBabble/releases/latest"
67
56
appversion = "Babble v2.0.7"
68
57
@@ -102,30 +91,56 @@ async def check_for_updates(config, notification_manager):
102
91
f'\033 [93m[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.needUpdateOne" )} [{ appversion } ] { lang ._instance .get_string ("babble.needUpdateTwo" )} [{ latestversion } ] { lang ._instance .get_string ("babble.needUpdateThree" )} .\033 [0m'
103
92
)
104
93
await notification_manager .show_notification (appversion , latestversion , page_url )
94
+
105
95
except requests .exceptions .Timeout :
106
96
print (f'[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.updateTimeout" )} ' )
107
97
except requests .exceptions .HTTPError as e :
108
98
print (f'[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.updateHttpError" )} : { e } ' )
109
99
except requests .exceptions .ConnectionError :
110
100
print (f'[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.noInternet" )} ' )
111
101
except Exception as e :
112
- print (f'[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.updateCheckFailed" )} : { e } ' )
102
+ print (f'[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.updateCheckFailed" )} : { e } ' )
103
+
113
104
class ThreadManager :
114
- def __init__ (self ):
115
- self .threads = []
116
-
117
- def add_thread (self , thread ):
118
- self .threads .append (thread )
105
+ def __init__ (self , cancellation_event ):
106
+ """Initialize ThreadManager with a cancellation event for signaling threads."""
107
+ self .threads = [] # List of (thread, shutdown_obj) tuples
108
+ self .cancellation_event = cancellation_event
109
+ self .logger = logging .getLogger ("ThreadManager" )
110
+
111
+ def add_thread (self , thread , shutdown_obj = None ):
112
+ """Add a thread and its optional shutdown object to the manager."""
113
+ self .threads .append ((thread , shutdown_obj ))
119
114
thread .start ()
120
-
121
- def shutdown_all (self ):
122
- for thread in self .threads :
123
- if hasattr (thread , 'shutdown' ) and callable (thread .shutdown ):
124
- thread .shutdown ()
125
-
126
- for thread in self .threads :
115
+ self .logger .debug (f"Started thread: { thread .name } " )
116
+
117
+ def shutdown_all (self , timeout = 5.0 ):
118
+ """Shutdown all managed threads with a configurable timeout."""
119
+ self .logger .info ("Initiiating shutdown of all threads" )
120
+ self .cancellation_event .set () # Signal all threads to stop
121
+
122
+ # Call shutdown methods on associated objects if available
123
+ for thread , shutdown_obj in self .threads :
124
+ if shutdown_obj and hasattr (shutdown_obj , 'shutdown' ) and callable (shutdown_obj .shutdown ):
125
+ try :
126
+ self .logger .debug (f"Calling shutdown on { shutdown_obj } " )
127
+ shutdown_obj .shutdown ()
128
+ except Exception as e :
129
+ self .logger .error (f"Error shutting down { shutdown_obj } : { e } " )
130
+
131
+ # Join threads with the specified timeout
132
+ for thread , _ in self .threads :
127
133
if thread .is_alive ():
128
- thread .join ()
134
+ self .logger .debug (f"Joining thread: { thread .name } with timeout { timeout } s" )
135
+ thread .join (timeout = timeout )
136
+
137
+ # Remove terminated threads from the list
138
+ self .threads = [(t , s ) for t , s in self .threads if t .is_alive ()]
139
+
140
+ if self .threads :
141
+ self .logger .warning (f"{ len (self .threads )} threads still alive: { [t .name for t , _ in self .threads ]} " )
142
+ else :
143
+ self .logger .info ("All threads terminated successfully" )
129
144
130
145
async def async_main ():
131
146
ensurePath ()
@@ -153,13 +168,12 @@ async def async_main():
153
168
154
169
timerResolution (True )
155
170
156
- thread_manager = ThreadManager ()
171
+ thread_manager = ThreadManager (cancellation_event )
157
172
158
173
osc_queue : queue .Queue [tuple [bool , int , int ]] = queue .Queue (maxsize = 10 )
159
174
osc = VRChatOSC (cancellation_event , osc_queue , config )
160
- osc_thread = threading .Thread (target = osc .run )
161
- thread_manager .add_thread (osc_thread )
162
-
175
+ osc_thread = threading .Thread (target = osc .run , name = "OSCThread" )
176
+ thread_manager .add_thread (osc_thread , shutdown_obj = osc )
163
177
cams = [
164
178
CameraWidget (Tab .CAM , config , osc_queue ),
165
179
]
@@ -177,56 +191,56 @@ async def async_main():
177
191
"TABSELECTRADIO" ,
178
192
background_color = bg_color_clear ,
179
193
default = (config .cam_display_id == Tab .CAM ),
180
- key = CAM_RADIO_NAME ,
194
+ key = UIConstants . CAM_RADIO_NAME ,
181
195
),
182
196
sg .Radio (
183
197
lang ._instance .get_string ("babble.settingsPage" ),
184
198
"TABSELECTRADIO" ,
185
199
background_color = bg_color_clear ,
186
200
default = (config .cam_display_id == Tab .SETTINGS ),
187
- key = SETTINGS_RADIO_NAME ,
201
+ key = UIConstants . SETTINGS_RADIO_NAME ,
188
202
),
189
203
sg .Radio (
190
204
lang ._instance .get_string ("babble.algoSettingsPage" ),
191
205
"TABSELECTRADIO" ,
192
206
background_color = bg_color_clear ,
193
207
default = (config .cam_display_id == Tab .ALGOSETTINGS ),
194
- key = ALGO_SETTINGS_RADIO_NAME ,
208
+ key = UIConstants . ALGO_SETTINGS_RADIO_NAME ,
195
209
),
196
210
sg .Radio (
197
211
lang ._instance .get_string ("babble.calibrationPage" ),
198
212
"TABSELECTRADIO" ,
199
213
background_color = bg_color_clear ,
200
214
default = (config .cam_display_id == Tab .CALIBRATION ),
201
- key = CALIB_SETTINGS_RADIO_NAME ,
215
+ key = UIConstants . CALIB_SETTINGS_RADIO_NAME ,
202
216
),
203
217
],
204
218
[
205
219
sg .Column (
206
220
cams [0 ].widget_layout ,
207
221
vertical_alignment = "top" ,
208
- key = CAM_NAME ,
222
+ key = UIConstants . CAM_NAME ,
209
223
visible = (config .cam_display_id in [Tab .CAM ]),
210
224
background_color = bg_color_highlight ,
211
225
),
212
226
sg .Column (
213
227
settings [0 ].widget_layout ,
214
228
vertical_alignment = "top" ,
215
- key = SETTINGS_NAME ,
229
+ key = UIConstants . SETTINGS_NAME ,
216
230
visible = (config .cam_display_id in [Tab .SETTINGS ]),
217
231
background_color = bg_color_highlight ,
218
232
),
219
233
sg .Column (
220
234
settings [1 ].widget_layout ,
221
235
vertical_alignment = "top" ,
222
- key = ALGO_SETTINGS_NAME ,
236
+ key = UIConstants . ALGO_SETTINGS_NAME ,
223
237
visible = (config .cam_display_id in [Tab .ALGOSETTINGS ]),
224
238
background_color = bg_color_highlight ,
225
239
),
226
240
sg .Column (
227
241
settings [2 ].widget_layout ,
228
242
vertical_alignment = "top" ,
229
- key = CALIB_SETTINGS_NAME ,
243
+ key = UIConstants . CALIB_SETTINGS_NAME ,
230
244
visible = (config .cam_display_id in [Tab .CALIBRATION ]),
231
245
background_color = bg_color_highlight ,
232
246
),
@@ -247,22 +261,22 @@ async def async_main():
247
261
# the cam needs to be running before it is passed to the OSC
248
262
if config .settings .gui_ROSC :
249
263
osc_receiver = VRChatOSCReceiver (cancellation_event , config , cams )
250
- osc_receiver_thread = threading .Thread (target = osc_receiver .run )
251
- thread_manager .add_thread (osc_receiver_thread )
264
+ osc_receiver_thread = threading .Thread (target = osc_receiver .run , name = "OSCReceiverThread" )
265
+ thread_manager .add_thread (osc_receiver_thread , shutdown_obj = osc_receiver )
252
266
ROSC = True
253
267
254
268
# Create the window
255
269
window = sg .Window (
256
270
f"{ AppConstants .VERSION } " , layout , icon = os .path .join ('Images' , 'logo.ico' ), background_color = bg_color_clear )
257
271
258
272
# Run the main loop
259
- await main_loop (window , config , cams , settings , cancellation_event )
273
+ await main_loop (window , config , cams , settings , thread_manager )
260
274
261
275
# Cleanup after main loop exits
262
276
timerResolution (False )
263
277
print (f'\033 [94m[{ lang ._instance .get_string ("log.info" )} ] { lang ._instance .get_string ("babble.exit" )} \033 [0m' )
264
278
265
- async def main_loop (window , config , cams , settings , cancellation_event ):
279
+ async def main_loop (window , config , cams , settings , thread_manager ):
266
280
tint = AppConstants .DEFAULT_WINDOW_FOCUS_REFRESH
267
281
fs = False
268
282
@@ -273,7 +287,8 @@ async def main_loop(window, config, cams, settings, cancellation_event):
273
287
# Exit code here
274
288
for cam in cams :
275
289
cam .stop ()
276
- cancellation_event .set ()
290
+ thread_manager .shutdown_all ()
291
+ window .close ()
277
292
return
278
293
279
294
try :
@@ -295,57 +310,57 @@ async def main_loop(window, config, cams, settings, cancellation_event):
295
310
except KeyError :
296
311
pass
297
312
298
- if values [CAM_RADIO_NAME ] and config .cam_display_id != Tab .CAM :
313
+ if values [UIConstants . CAM_RADIO_NAME ] and config .cam_display_id != Tab .CAM :
299
314
cams [0 ].start ()
300
315
settings [0 ].stop ()
301
316
settings [1 ].stop ()
302
317
settings [2 ].stop ()
303
- window [CAM_NAME ].update (visible = True )
304
- window [SETTINGS_NAME ].update (visible = False )
305
- window [ALGO_SETTINGS_NAME ].update (visible = False )
306
- window [CALIB_SETTINGS_NAME ].update (visible = False )
318
+ window [UIConstants . CAM_NAME ].update (visible = True )
319
+ window [UIConstants . SETTINGS_NAME ].update (visible = False )
320
+ window [UIConstants . ALGO_SETTINGS_NAME ].update (visible = False )
321
+ window [UIConstants . CALIB_SETTINGS_NAME ].update (visible = False )
307
322
config .cam_display_id = Tab .CAM
308
323
config .save ()
309
324
310
- elif values [SETTINGS_RADIO_NAME ] and config .cam_display_id != Tab .SETTINGS :
325
+ elif values [UIConstants . SETTINGS_RADIO_NAME ] and config .cam_display_id != Tab .SETTINGS :
311
326
cams [0 ].stop ()
312
327
settings [1 ].stop ()
313
328
settings [2 ].stop ()
314
329
settings [0 ].start ()
315
- window [CAM_NAME ].update (visible = False )
316
- window [SETTINGS_NAME ].update (visible = True )
317
- window [ALGO_SETTINGS_NAME ].update (visible = False )
318
- window [CALIB_SETTINGS_NAME ].update (visible = False )
330
+ window [UIConstants . CAM_NAME ].update (visible = False )
331
+ window [UIConstants . SETTINGS_NAME ].update (visible = True )
332
+ window [UIConstants . ALGO_SETTINGS_NAME ].update (visible = False )
333
+ window [UIConstants . CALIB_SETTINGS_NAME ].update (visible = False )
319
334
config .cam_display_id = Tab .SETTINGS
320
335
config .save ()
321
336
322
337
elif (
323
- values [ALGO_SETTINGS_RADIO_NAME ]
338
+ values [UIConstants . ALGO_SETTINGS_RADIO_NAME ]
324
339
and config .cam_display_id != Tab .ALGOSETTINGS
325
340
):
326
341
cams [0 ].stop ()
327
342
settings [0 ].stop ()
328
343
settings [2 ].stop ()
329
344
settings [1 ].start ()
330
- window [CAM_NAME ].update (visible = False )
331
- window [SETTINGS_NAME ].update (visible = False )
332
- window [ALGO_SETTINGS_NAME ].update (visible = True )
333
- window [CALIB_SETTINGS_NAME ].update (visible = False )
345
+ window [UIConstants . CAM_NAME ].update (visible = False )
346
+ window [UIConstants . SETTINGS_NAME ].update (visible = False )
347
+ window [UIConstants . ALGO_SETTINGS_NAME ].update (visible = True )
348
+ window [UIConstants . CALIB_SETTINGS_NAME ].update (visible = False )
334
349
config .cam_display_id = Tab .ALGOSETTINGS
335
350
config .save ()
336
351
337
352
elif (
338
- values [CALIB_SETTINGS_RADIO_NAME ]
353
+ values [UIConstants . CALIB_SETTINGS_RADIO_NAME ]
339
354
and config .cam_display_id != Tab .CALIBRATION
340
355
):
341
356
cams [0 ].start () # Allow tracking to continue in calibration tab
342
357
settings [0 ].stop ()
343
358
settings [1 ].stop ()
344
359
settings [2 ].start ()
345
- window [CAM_NAME ].update (visible = False )
346
- window [SETTINGS_NAME ].update (visible = False )
347
- window [ALGO_SETTINGS_NAME ].update (visible = False )
348
- window [CALIB_SETTINGS_NAME ].update (visible = True )
360
+ window [UIConstants . CAM_NAME ].update (visible = False )
361
+ window [UIConstants . SETTINGS_NAME ].update (visible = False )
362
+ window [UIConstants . ALGO_SETTINGS_NAME ].update (visible = False )
363
+ window [UIConstants . CALIB_SETTINGS_NAME ].update (visible = True )
349
364
config .cam_display_id = Tab .CALIBRATION
350
365
config .save ()
351
366
0 commit comments