1515import itertools
1616from collections import namedtuple , defaultdict , OrderedDict
1717import threading
18-
18+ from concurrent .futures import ThreadPoolExecutor
19+ import queue
1920import typing
2021import hwPortUtils
2122import braille
22- import winKernel
2323import winUser
2424from logHandler import log
2525import config
@@ -238,121 +238,122 @@ class Detector(object):
238238 This should only be used by the L{braille} module.
239239 """
240240
241- def __init__ (self , usb = True , bluetooth = True , limitToDevices = None ):
241+ def __init__ (
242+ self ,
243+ usb : bool = True ,
244+ bluetooth : bool = True ,
245+ limitToDevices : typing .Optional [typing .List [str ]] = None
246+ ):
242247 """Constructor.
243248 The keyword arguments initialize the detector in a particular state.
244- On an initialized instance, these initial arguments can be overridden by calling L{_startBgScan} or L{rescan}.
249+ On an initialized instance, these initial arguments can be overridden by calling
250+ L{_queueBgScan} or L{rescan}.
245251 @param usb: Whether this instance should detect USB devices initially.
246- @type usb: bool
247252 @param bluetooth: Whether this instance should detect Bluetooth devices initially.
248- @type bluetooth: bool
249253 @param limitToDevices: Drivers to which detection should be limited initially.
250254 C{None} if no driver filtering should occur.
251255 """
252- self ._BgScanApc = winKernel . PAPCFUNC ( self . _bgScan )
256+ self ._executor = ThreadPoolExecutor ( 1 )
253257 self ._btDevsLock = threading .Lock ()
254258 self ._btDevs = None
255259 messageWindow .pre_handleWindowMessage .register (self .handleWindowMessage )
256260 appModuleHandler .post_appSwitch .register (self .pollBluetoothDevices )
257261 self ._stopEvent = threading .Event ()
258- self ._queuedScanLock = threading .Lock ()
259- self ._scanQueued = False
260262 self ._detectUsb = usb
261263 self ._detectBluetooth = bluetooth
262264 self ._limitToDevices = limitToDevices
263- self ._runningApcLock = threading .Lock ()
264265 # Perform initial scan.
265- self ._startBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
266+ self ._queueBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
266267
267268 @property
268- def _scanQueuedSafe (self ):
269- """Returns L{_scanQueued} in a thread safe way by using L{_queuedScanLock}."""
270- with self ._queuedScanLock :
271- return self ._scanQueued
272-
273- @_scanQueuedSafe .setter
274- def _scanQueuedSafe (self , state ):
275- """Sets L{_scanQueued} in a thread safe way by using L{_queuedScanLock}."""
276- with self ._queuedScanLock :
277- self ._scanQueued = state
278-
279- def _startBgScan (self , usb = False , bluetooth = False , limitToDevices = None ):
280- """Starts a scan for devices.
269+ def _scanQueued (self ) -> bool :
270+ return not self ._executor ._work_queue .empty ()
271+
272+ def _queueBgScan (
273+ self ,
274+ usb : bool = False ,
275+ bluetooth : bool = False ,
276+ limitToDevices : typing .Optional [typing .List [str ]] = None
277+ ):
278+ """Queues a scan for devices.
281279 If a scan is already in progress, a new scan will be queued after the current scan.
282280 To explicitely cancel a scan in progress, use L{rescan}.
283281 @param usb: Whether USB devices should be detected for this and subsequent scans.
284- @type usb: bool
285282 @param bluetooth: Whether Bluetooth devices should be detected for this and subsequent scans.
286- @type bluetooth: bool
287283 @param limitToDevices: Drivers to which detection should be limited for this and subsequent scans.
288284 C{None} if no driver filtering should occur.
289285 """
290- with self ._queuedScanLock :
291- self ._detectUsb = usb
292- self ._detectBluetooth = bluetooth
293- self ._limitToDevices = limitToDevices
294- if not self ._scanQueued :
295- self ._scanQueued = True
296- if self ._runningApcLock .locked ():
297- # There's currently a scan in progress.
298- # Since the scan is embeded in a loop, it will automatically do another scan,
299- # unless a display has been found.
300- return
301- braille ._BgThread .queueApc (self ._BgScanApc )
286+ self ._detectUsb = usb
287+ self ._detectBluetooth = bluetooth
288+ self ._limitToDevices = limitToDevices
289+ if not self ._scanQueued :
290+ self ._executor .submit (self ._bgScan , usb , bluetooth , limitToDevices )
302291
303292 def _stopBgScan (self ):
304293 """Stops the current scan as soon as possible and prevents a queued scan to start."""
305- if not self ._runningApcLock .locked ():
306- # No scan to stop
307- return
308294 self ._stopEvent .set ()
309- self ._scanQueuedSafe = False
310-
311- def _bgScan (self , param ):
312- if self ._runningApcLock .locked ():
313- log .debugWarning ("Braille display detection background scan APC executed while one is already running" )
314- return
315- with self ._runningApcLock :
316- while self ._scanQueuedSafe :
317- # Clear the stop event before a scan is started.
318- # Since a scan can take some time to complete, another thread can set the stop event to cancel it.
319- self ._stopEvent .clear ()
320- with self ._queuedScanLock :
321- self ._scanQueued = False
322- detectUsb = self ._detectUsb
323- detectBluetooth = self ._detectBluetooth
324- limitToDevices = self ._limitToDevices
325- if detectUsb :
326- if self ._stopEvent .isSet ():
327- continue
328- for driver , match in getDriversForConnectedUsbDevices ():
329- if self ._stopEvent .isSet () or (self ._limitToDevices and driver not in self ._limitToDevices ):
330- continue
331- if braille .handler .setDisplayByName (driver , detected = match ):
332- return
333- if detectBluetooth :
334- if self ._stopEvent .isSet ():
335- continue
336- with self ._btDevsLock :
337- if self ._btDevs is None :
338- btDevs = list (getDriversForPossibleBluetoothDevices ())
339- # Cache Bluetooth devices for next time.
340- btDevsCache = []
341- else :
342- btDevs = self ._btDevs
343- btDevsCache = btDevs
344- for driver , match in btDevs :
345- if self ._stopEvent .isSet () or (self ._limitToDevices and driver not in self ._limitToDevices ):
346- continue
347- if btDevsCache is not btDevs :
348- btDevsCache .append ((driver , match ))
349- if braille .handler .setDisplayByName (driver , detected = match ):
350- return
351- if self ._stopEvent .isSet ():
352- continue
353- if btDevsCache is not btDevs :
354- with self ._btDevsLock :
355- self ._btDevs = btDevsCache
295+ # Cancel queued scans
296+ try :
297+ while self ._scanQueued :
298+ workItem = self ._executor ._work_queue .get_nowait ()
299+ workItem .future .cancel ()
300+ except queue .Empty :
301+ pass
302+
303+ # C901 '_bgScan' is too complex
304+ # Note: when working on _bgScan, look for opportunities to simplify
305+ # and move logic out into smaller helper functions.
306+ def _bgScan ( # noqa: C901
307+ self ,
308+ detectUsb : bool ,
309+ detectBluetooth : bool ,
310+ limitToDevices : typing .Optional [typing .List [str ]]
311+ ):
312+ """Performs the actual background scan.
313+ this function should be run on a background thread.
314+ @param usb: Whether USB devices should be detected for this particular scan.
315+ @param bluetooth: Whether Bluetooth devices should be detected for this particular scan.
316+ @param limitToDevices: Drivers to which detection should be limited for this scan.
317+ C{None} if no driver filtering should occur.
318+ """
319+ # Clear the stop event before a scan is started.
320+ # Since a scan can take some time to complete, another thread can set the stop event to cancel it.
321+ self ._stopEvent .clear ()
322+ if detectUsb :
323+ if self ._stopEvent .isSet ():
324+ return
325+ for driver , match in getDriversForConnectedUsbDevices ():
326+ if self ._stopEvent .isSet ():
327+ return
328+ if (limitToDevices and driver not in limitToDevices ):
329+ continue
330+ if braille .handler .setDisplayByName (driver , detected = match ):
331+ return
332+ if detectBluetooth :
333+ if self ._stopEvent .isSet ():
334+ return
335+ with self ._btDevsLock :
336+ if self ._btDevs is None :
337+ btDevs = list (getDriversForPossibleBluetoothDevices ())
338+ # Cache Bluetooth devices for next time.
339+ btDevsCache = []
340+ else :
341+ btDevs = self ._btDevs
342+ btDevsCache = btDevs
343+ for driver , match in btDevs :
344+ if self ._stopEvent .isSet ():
345+ return
346+ if (self ._limitToDevices and driver not in limitToDevices ):
347+ continue
348+ if btDevsCache is not btDevs :
349+ btDevsCache .append ((driver , match ))
350+ if braille .handler .setDisplayByName (driver , detected = match ):
351+ return
352+ if self ._stopEvent .isSet ():
353+ return
354+ if btDevsCache is not btDevs :
355+ with self ._btDevsLock :
356+ self ._btDevs = btDevsCache
356357
357358 def rescan (self , usb = True , bluetooth = True , limitToDevices = None ):
358359 """Stop a current scan when in progress, and start scanning from scratch.
@@ -367,7 +368,7 @@ def rescan(self, usb=True, bluetooth=True, limitToDevices=None):
367368 with self ._btDevsLock :
368369 # A Bluetooth com port or HID device might have been added.
369370 self ._btDevs = None
370- self ._startBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
371+ self ._queueBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
371372
372373 def handleWindowMessage (self , msg = None , wParam = None ):
373374 if msg == winUser .WM_DEVICECHANGE and wParam == DBT_DEVNODES_CHANGED :
@@ -382,12 +383,13 @@ def pollBluetoothDevices(self):
382383 with self ._btDevsLock :
383384 if not self ._btDevs :
384385 return
385- self ._startBgScan (bluetooth = self ._detectBluetooth , limitToDevices = self ._limitToDevices )
386+ self ._queueBgScan (bluetooth = self ._detectBluetooth , limitToDevices = self ._limitToDevices )
386387
387388 def terminate (self ):
388389 appModuleHandler .post_appSwitch .unregister (self .pollBluetoothDevices )
389390 messageWindow .pre_handleWindowMessage .unregister (self .handleWindowMessage )
390391 self ._stopBgScan ()
392+ self ._executor .shutdown (wait = False )
391393
392394
393395def getConnectedUsbDevicesForDriver (driver ) -> typing .Iterator [DeviceMatch ]:
0 commit comments