Skip to content

Commit

Permalink
Use a separate thread for background scanning.
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonard de Ruijter committed Jun 5, 2018
1 parent 9e154b2 commit 5b97f39
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 47 deletions.
95 changes: 55 additions & 40 deletions source/bdDetect.py
Expand Up @@ -16,6 +16,7 @@
import itertools
from collections import namedtuple, defaultdict, OrderedDict
import threading
import thread
import wx
import hwPortUtils
import braille
Expand Down Expand Up @@ -156,9 +157,9 @@ class Detector(object):
"""

def __init__(self):
self._BgScanApc = winKernel.PAPCFUNC(self._bgScan)
self._btDevsLock = threading.Lock()
self._btDevs = None
self._thread = None
core.post_windowMessageReceipt.register(self.handleWindowMessage)
appModuleHandler.post_appSwitch.register(self.pollBluetoothDevices)
self._stopEvent = threading.Event()
Expand All @@ -168,54 +169,65 @@ def __init__(self):
self._startBgScan(usb=True, bluetooth=True)

def _startBgScan(self, usb=False, bluetooth=False):
detectionParam = usb | bluetooth << 1
with self._queuedScanLock:
self._detectUsb = usb
self._detectBluetooth = bluetooth
if not self._scanQueued:
braille._BgThread.queueApc(self._BgScanApc, param=detectionParam)
self._scanQueued = True
if self._thread and self._thread.isAlive():
# There's currently a scan in progress.
# Since the scan is embeded in a loop, it will automatically do another scan,
# unless a display has been found.
return
self._thread = threading.Thread(target=self._bgScan)
self._thread.start()

def _stopBgScan(self):
if not self._thread or not self._thread.isAlive():
# No scan to stop
return
self._stopEvent.set()

def _bgScan(self, param):
# Clear the stop event before a scan is started.
# Since a scan can take some time to complete, another thread can set the stop event to cancel it.
self._stopEvent.clear()
detectUsb = bool(param & DETECT_USB)
detectBluetooth = bool(param & DETECT_BLUETOOTH)
with self._queuedScanLock:
self._scanQueued = False
if detectUsb:
if self._stopEvent.isSet():
return
for driver, match in getDriversForConnectedUsbDevices():
def _bgScan(self):
while self._scanQueued:
# Clear the stop event before a scan is started.
# Since a scan can take some time to complete, another thread can set the stop event to cancel it.
self._stopEvent.clear()
with self._queuedScanLock:
self._scanQueued = False
detectUsb = self._detectUsb
detectBluetooth = self._detectBluetooth
if detectUsb:
if self._stopEvent.isSet():
return
if braille.handler.setDisplayByName(driver, detected=match):
return
if detectBluetooth:
if self._stopEvent.isSet():
return
with self._btDevsLock:
if self._btDevs is None:
btDevs = list(getDriversForPossibleBluetoothDevices())
# Cache Bluetooth devices for next time.
btDevsCache = []
else:
btDevs = self._btDevs
btDevsCache = btDevs
for driver, match in btDevs:
continue
for driver, match in getDriversForConnectedUsbDevices():
if self._stopEvent.isSet():
continue
if braille.handler.setDisplayByName(driver, detected=match):
return
if detectBluetooth:
if self._stopEvent.isSet():
return
if btDevsCache is not btDevs:
btDevsCache.append((driver, match))
if braille.handler.setDisplayByName(driver, detected=match):
return
if self._stopEvent.isSet():
return
if btDevsCache is not btDevs:
continue
with self._btDevsLock:
self._btDevs = btDevsCache
if self._btDevs is None:
btDevs = list(getDriversForPossibleBluetoothDevices())
# Cache Bluetooth devices for next time.
btDevsCache = []
else:
btDevs = self._btDevs
btDevsCache = btDevs
for driver, match in btDevs:
if self._stopEvent.isSet():
continue
if btDevsCache is not btDevs:
btDevsCache.append((driver, match))
if braille.handler.setDisplayByName(driver, detected=match):
return
if self._stopEvent.isSet():
continue
if btDevsCache is not btDevs:
with self._btDevsLock:
self._btDevs = btDevsCache

def rescan(self):
"""Stop a current scan when in progress, and start scanning from scratch."""
Expand All @@ -231,7 +243,7 @@ def handleWindowMessage(self, msg=None, wParam=None):

def pollBluetoothDevices(self):
"""Poll bluetooth devices that might be in range.
This does not cancel the current scan and only queues a new scan when no scan is in progress."""
This does not cancel the current scan."""
with self._btDevsLock:
if not self._btDevs:
return
Expand All @@ -241,6 +253,9 @@ def terminate(self):
appModuleHandler.post_appSwitch.unregister(self.pollBluetoothDevices)
core.post_windowMessageReceipt.unregister(self.handleWindowMessage)
self._stopBgScan()
if self._thread and self._thread.ident != thread.get_ident():
self._thread.join(2.5)
self._thread = None

def getConnectedUsbDevicesForDriver(driver):
"""Get any connected USB devices associated with a particular driver.
Expand Down
11 changes: 4 additions & 7 deletions source/braille.py
Expand Up @@ -1554,7 +1554,6 @@ def __init__(self):
self._detector = None

def terminate(self):
bgThreadStopTimeout = 2.5 if self._detectionEnabled else None
self._disableDetection()
if self._messageCallLater:
self._messageCallLater.Stop()
Expand All @@ -1566,7 +1565,7 @@ def terminate(self):
if self.display:
self.display.terminate()
self.display = None
_BgThread.stop(timeout=bgThreadStopTimeout)
_BgThread.stop()

def getTether(self):
return self._tether
Expand Down Expand Up @@ -1632,9 +1631,8 @@ def setDisplayByName(self, name, isFallback=False, detected=None):
# Re-initialize with supported kwargs.
extensionPoints.callWithSupportedKwargs(newDisplay.__init__, **kwargs)
else:
if newDisplay.isThreadSafe and not detected:
if newDisplay.isThreadSafe:
# Start the thread if it wasn't already.
# Auto detection implies the thread is already started.
_BgThread.start()
try:
newDisplay = newDisplay(**kwargs)
Expand Down Expand Up @@ -1971,7 +1969,6 @@ def _enableDetection(self):
if self._detectionEnabled and self._detector:
self._detector.rescan()
return
_BgThread.start()
config.conf["braille"]["display"] = AUTO_DISPLAY_NAME
self.setDisplayByName("noBraille", isFallback=True)
self._detector = bdDetect.Detector()
Expand Down Expand Up @@ -2018,7 +2015,7 @@ def queueApc(cls, func, param=0):
ctypes.windll.kernel32.QueueUserAPC(func, cls.handle, param)

@classmethod
def stop(cls, timeout=None):
def stop(cls):
if not cls.thread:
return
cls.exit = True
Expand All @@ -2028,7 +2025,7 @@ def stop(cls, timeout=None):
cls.ackTimerHandle = None
# Wake up the thread. It will exit when it sees exit is True.
cls.queueApc(cls.executor)
cls.thread.join(timeout)
cls.thread.join()
cls.exit = False
winKernel.closeHandle(cls.handle)
cls.handle = None
Expand Down

0 comments on commit 5b97f39

Please sign in to comment.