1515import itertools
1616from collections import namedtuple , defaultdict , OrderedDict
1717import threading
18-
18+ from concurrent . futures import ThreadPoolExecutor , Future
1919import typing
2020import hwPortUtils
2121import braille
22- import winKernel
2322import winUser
2423from logHandler import log
2524import config
@@ -238,121 +237,119 @@ class Detector(object):
238237 This should only be used by the L{braille} module.
239238 """
240239
241- def __init__ (self , usb = True , bluetooth = True , limitToDevices = None ):
240+ def __init__ (
241+ self ,
242+ usb : bool = True ,
243+ bluetooth : bool = True ,
244+ limitToDevices : typing .Optional [typing .List [str ]] = None
245+ ):
242246 """Constructor.
243247 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}.
248+ On an initialized instance, these initial arguments can be overridden by calling
249+ L{_queueBgScan} or L{rescan}.
245250 @param usb: Whether this instance should detect USB devices initially.
246- @type usb: bool
247251 @param bluetooth: Whether this instance should detect Bluetooth devices initially.
248- @type bluetooth: bool
249252 @param limitToDevices: Drivers to which detection should be limited initially.
250253 C{None} if no driver filtering should occur.
251254 """
252- self ._BgScanApc = winKernel . PAPCFUNC ( self . _bgScan )
255+ self ._executor = ThreadPoolExecutor ( 1 )
253256 self ._btDevsLock = threading .Lock ()
254- self ._btDevs = None
257+ self ._btDevs : typing .Optional [typing .Tuple [str , DeviceMatch ]] = None
258+ self ._queuedFuture : typing .Optional [Future ] = 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-
267- @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.
266+ self ._queueBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
267+
268+ def _queueBgScan (
269+ self ,
270+ usb : bool = False ,
271+ bluetooth : bool = False ,
272+ limitToDevices : typing .Optional [typing .List [str ]] = None
273+ ):
274+ """Queues a scan for devices.
281275 If a scan is already in progress, a new scan will be queued after the current scan.
282276 To explicitely cancel a scan in progress, use L{rescan}.
283277 @param usb: Whether USB devices should be detected for this and subsequent scans.
284- @type usb: bool
285278 @param bluetooth: Whether Bluetooth devices should be detected for this and subsequent scans.
286- @type bluetooth: bool
287279 @param limitToDevices: Drivers to which detection should be limited for this and subsequent scans.
288280 C{None} if no driver filtering should occur.
289281 """
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 )
282+ self ._detectUsb = usb
283+ self ._detectBluetooth = bluetooth
284+ self ._limitToDevices = limitToDevices
285+ if self ._queuedFuture :
286+ # This will cancel a queued scan (i.e. not the currently running scan, if any)
287+ # If this future belongs to a scan that is currently running or finished, this does nothing.
288+ self ._queuedFuture .cancel ()
289+ self ._queuedFuture = self ._executor .submit (self ._bgScan , usb , bluetooth , limitToDevices )
302290
303291 def _stopBgScan (self ):
304292 """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
308293 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
294+ if self ._queuedFuture :
295+ # This will cancel a queued scan (i.e. not the currently running scan, if any)
296+ # If this future belongs to a scan that is currently running or finished, this does nothing.
297+ self ._queuedFuture .cancel ()
298+
299+ # C901 '_bgScan' is too complex
300+ # Note: when working on _bgScan, look for opportunities to simplify
301+ # and move logic out into smaller helper functions.
302+ def _bgScan ( # noqa: C901
303+ self ,
304+ detectUsb : bool ,
305+ detectBluetooth : bool ,
306+ limitToDevices : typing .Optional [typing .List [str ]]
307+ ):
308+ """Performs the actual background scan.
309+ this function should be run on a background thread.
310+ @param usb: Whether USB devices should be detected for this particular scan.
311+ @param bluetooth: Whether Bluetooth devices should be detected for this particular scan.
312+ @param limitToDevices: Drivers to which detection should be limited for this scan.
313+ C{None} if no driver filtering should occur.
314+ """
315+ # Clear the stop event before a scan is started.
316+ # Since a scan can take some time to complete, another thread can set the stop event to cancel it.
317+ self ._stopEvent .clear ()
318+ if detectUsb :
319+ if self ._stopEvent .isSet ():
320+ return
321+ for driver , match in getDriversForConnectedUsbDevices ():
322+ if self ._stopEvent .isSet ():
323+ return
324+ if (limitToDevices and driver not in limitToDevices ):
325+ continue
326+ if braille .handler .setDisplayByName (driver , detected = match ):
327+ return
328+ if detectBluetooth :
329+ if self ._stopEvent .isSet ():
330+ return
331+ with self ._btDevsLock :
332+ if self ._btDevs is None :
333+ btDevs = list (getDriversForPossibleBluetoothDevices ())
334+ # Cache Bluetooth devices for next time.
335+ btDevsCache = []
336+ else :
337+ btDevs = self ._btDevs
338+ btDevsCache = btDevs
339+ for driver , match in btDevs :
340+ if self ._stopEvent .isSet ():
341+ return
342+ if (self ._limitToDevices and driver not in limitToDevices ):
343+ continue
344+ if btDevsCache is not btDevs :
345+ btDevsCache .append ((driver , match ))
346+ if braille .handler .setDisplayByName (driver , detected = match ):
347+ return
348+ if self ._stopEvent .isSet ():
349+ return
350+ if btDevsCache is not btDevs :
351+ with self ._btDevsLock :
352+ self ._btDevs = btDevsCache
356353
357354 def rescan (self , usb = True , bluetooth = True , limitToDevices = None ):
358355 """Stop a current scan when in progress, and start scanning from scratch.
@@ -367,7 +364,7 @@ def rescan(self, usb=True, bluetooth=True, limitToDevices=None):
367364 with self ._btDevsLock :
368365 # A Bluetooth com port or HID device might have been added.
369366 self ._btDevs = None
370- self ._startBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
367+ self ._queueBgScan (usb = usb , bluetooth = bluetooth , limitToDevices = limitToDevices )
371368
372369 def handleWindowMessage (self , msg = None , wParam = None ):
373370 if msg == winUser .WM_DEVICECHANGE and wParam == DBT_DEVNODES_CHANGED :
@@ -382,12 +379,13 @@ def pollBluetoothDevices(self):
382379 with self ._btDevsLock :
383380 if not self ._btDevs :
384381 return
385- self ._startBgScan (bluetooth = self ._detectBluetooth , limitToDevices = self ._limitToDevices )
382+ self ._queueBgScan (bluetooth = self ._detectBluetooth , limitToDevices = self ._limitToDevices )
386383
387384 def terminate (self ):
388385 appModuleHandler .post_appSwitch .unregister (self .pollBluetoothDevices )
389386 messageWindow .pre_handleWindowMessage .unregister (self .handleWindowMessage )
390387 self ._stopBgScan ()
388+ self ._executor .shutdown (wait = False )
391389
392390
393391def getConnectedUsbDevicesForDriver (driver ) -> typing .Iterator [DeviceMatch ]:
0 commit comments