Skip to content

Commit 7a36ab5

Browse files
authored
Merge c64d24c into a232641
2 parents a232641 + c64d24c commit 7a36ab5

8 files changed

Lines changed: 467 additions & 116 deletions

File tree

source/bdDetect.py

Lines changed: 122 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"""
1414

1515
import itertools
16-
from collections import namedtuple, defaultdict, OrderedDict
16+
from collections import defaultdict, OrderedDict
1717
import threading
1818
from concurrent.futures import ThreadPoolExecutor, Future
1919
import typing
@@ -26,28 +26,43 @@
2626
from baseObject import AutoPropertyObject
2727
import re
2828
from winAPI import messageWindow
29+
import extensionPoints
2930

3031

3132
HID_USAGE_PAGE_BRAILLE = 0x41
3233

33-
3434
DBT_DEVNODES_CHANGED=7
3535

3636
_driverDevices = OrderedDict()
3737
USB_ID_REGEX = re.compile(r"^VID_[0-9A-F]{4}&PID_[0-9A-F]{4}$", re.U)
3838

39-
class DeviceMatch(
40-
namedtuple("DeviceMatch", ("type","id", "port", "deviceInfo"))
41-
):
39+
40+
class DeviceMatch(typing.NamedTuple):
4241
"""Represents a detected device.
43-
@ivar id: The identifier of the device.
44-
@type id: str
45-
@ivar port: The port that can be used by a driver to communicate with a device.
46-
@type port: str
47-
@ivar deviceInfo: all known information about a device.
48-
@type deviceInfo: dict
4942
"""
50-
__slots__ = ()
43+
type: str
44+
"""The type of the device."""
45+
id: str
46+
"""The identifier of the device."""
47+
port: str
48+
"""The port that can be used by a driver to communicate with a device."""
49+
deviceInfo: typing.Dict[str, str]
50+
"""All known information about a device."""
51+
52+
53+
scanForDevices = extensionPoints.Chain[typing.Tuple[str, DeviceMatch]]()
54+
"""
55+
A Chain that can be iterated to scan for devices.
56+
Registered handlers should yield a tuple containing a driver name as str and DeviceMatch
57+
Handlers are called with these keyword arguments:
58+
@param Usb: Whether the handler is expected to yield USB devices.
59+
@type usb: bool
60+
@param Bluetooth: Whether the handler is expected to yield USB devices.
61+
@type Bluetooth: bool
62+
@param limitToDevices: Drivers to which detection should be limited.
63+
C{None} if no driver filtering should occur.
64+
"""
65+
5166

5267
# Device type constants
5368
#: Key constant for HID devices
@@ -210,6 +225,24 @@ class _DeviceInfoFetcher(AutoPropertyObject):
210225
"""Utility class that caches fetched info for available devices for the duration of one core pump cycle."""
211226
cachePropertiesByDefault = True
212227

228+
def __init__(self):
229+
self._btDevsLock = threading.Lock
230+
self._btDevsCache: typing.Optional[typing.Tuple[str, DeviceMatch]] = None
231+
232+
#: Type info for auto property: _get_btDevsCache
233+
btDevsCache: typing.Optional[typing.List[typing.Tuple[str, DeviceMatch]]]
234+
235+
def _get_btDevsCache(self):
236+
with self._btDevsLock():
237+
return self._btDevsCache
238+
239+
def _set_btDevsCache(
240+
self,
241+
cache: typing.Optional[typing.List[typing.Tuple[str, DeviceMatch]]]
242+
):
243+
with self._btDevsLock():
244+
self._btDevsCache = cache
245+
213246
#: Type info for auto property: _get_comPorts
214247
comPorts: typing.List[typing.Dict]
215248

@@ -228,42 +261,27 @@ def _get_usbDevices(self) -> typing.List[typing.Dict]:
228261
def _get_hidDevices(self) -> typing.List[typing.Dict]:
229262
return list(hwPortUtils.listHidDevices(onlyAvailable=True))
230263

231-
#: The single instance of the device info fetcher.
232-
#: @type: L{_DeviceInfoFetcher}
233-
deviceInfoFetcher = _DeviceInfoFetcher()
264+
265+
deviceInfoFetcher: _DeviceInfoFetcher
266+
234267

235268
class Detector(object):
236269
"""Detector class used to automatically detect braille displays.
237270
This should only be used by the L{braille} module.
238271
"""
239272

240-
def __init__(
241-
self,
242-
usb: bool = True,
243-
bluetooth: bool = True,
244-
limitToDevices: typing.Optional[typing.List[str]] = None
245-
):
273+
def __init__(self):
246274
"""Constructor.
247-
The keyword arguments initialize the detector in a particular state.
248-
On an initialized instance, these initial arguments can be overridden by calling
249-
L{_queueBgScan} or L{rescan}.
250-
@param usb: Whether this instance should detect USB devices initially.
251-
@param bluetooth: Whether this instance should detect Bluetooth devices initially.
252-
@param limitToDevices: Drivers to which detection should be limited initially.
253-
C{None} if no driver filtering should occur.
275+
After construction, a scan should be queued with L{queueBgScan}.
254276
"""
255277
self._executor = ThreadPoolExecutor(1)
256-
self._btDevsLock = threading.Lock()
257-
self._btDevs: typing.Optional[typing.Tuple[str, DeviceMatch]] = None
258278
self._queuedFuture: typing.Optional[Future] = None
259279
messageWindow.pre_handleWindowMessage.register(self.handleWindowMessage)
260280
appModuleHandler.post_appSwitch.register(self.pollBluetoothDevices)
261281
self._stopEvent = threading.Event()
262-
self._detectUsb = usb
263-
self._detectBluetooth = bluetooth
264-
self._limitToDevices = limitToDevices
265-
# Perform initial scan.
266-
self._queueBgScan(usb=usb, bluetooth=bluetooth, limitToDevices=limitToDevices)
282+
self._detectUsb = True
283+
self._detectBluetooth = True
284+
self._limitToDevices = None
267285

268286
def _queueBgScan(
269287
self,
@@ -296,55 +314,51 @@ def _stopBgScan(self):
296314
# If this future belongs to a scan that is currently running or finished, this does nothing.
297315
self._queuedFuture.cancel()
298316

299-
def _bgScanUsb(self, limitToDevices: typing.Optional[typing.List[str]]):
300-
"""Helper method to perform background scanning for USB devices.
301-
@param limitToDevices: Drivers to which detection should be limited for this scan.
302-
C{None} if no driver filtering should occur.
317+
@staticmethod
318+
def _bgScanUsb(
319+
usb: bool = True,
320+
limitToDevices: typing.Optional[typing.List[str]] = None,
321+
):
322+
"""Handler for L{scanForDevices} that yields USB devices.
323+
See the L{scanForDevices} documentation for information about the parameters.
303324
"""
304-
if self._stopEvent.isSet():
325+
if not usb:
305326
return
306327
for driver, match in getDriversForConnectedUsbDevices():
307-
if self._stopEvent.isSet():
308-
return
309328
if limitToDevices and driver not in limitToDevices:
310329
continue
311-
if braille.handler.setDisplayByName(driver, detected=match):
312-
return
330+
yield (driver, match)
313331

314-
def _bgScanBluetooth(self, limitToDevices: typing.Optional[typing.List[str]]):
315-
"""Helper method to perform background scanning for Bluetooth devices.
316-
@param limitToDevices: Drivers to which detection should be limited for this scan.
317-
C{None} if no driver filtering should occur.
332+
@staticmethod
333+
def _bgScanBluetooth(
334+
bluetooth: bool = True,
335+
limitToDevices: typing.Optional[typing.List[str]] = None,
336+
):
337+
"""Handler for L{scanForDevices} that yields Bluetooth devices and keeps an internal cache of devices.
338+
See the L{scanForDevices} documentation for information about the parameters.
318339
"""
319-
if self._stopEvent.isSet():
340+
if not bluetooth:
320341
return
321-
with self._btDevsLock:
322-
if self._btDevs is None:
323-
btDevs = list(getDriversForPossibleBluetoothDevices())
324-
# Cache Bluetooth devices for next time.
325-
btDevsCache = []
326-
else:
327-
btDevs = self._btDevs
328-
btDevsCache = btDevs
342+
btDevs: typing.Optional[typing.Iterable[typing.Tuple[str, DeviceMatch]]] = _DeviceInfoFetcher.btDevsCache
343+
if btDevs is None:
344+
btDevs = getDriversForPossibleBluetoothDevices()
345+
# Cache Bluetooth devices for next time.
346+
btDevsCache = []
347+
else:
348+
btDevsCache = btDevs
329349
for driver, match in btDevs:
330-
if self._stopEvent.isSet():
331-
return
332350
if limitToDevices and driver not in limitToDevices:
333351
continue
334352
if btDevsCache is not btDevs:
335353
btDevsCache.append((driver, match))
336-
if braille.handler.setDisplayByName(driver, detected=match):
337-
return
338-
if self._stopEvent.isSet():
339-
return
354+
yield (driver, match)
340355
if btDevsCache is not btDevs:
341-
with self._btDevsLock:
342-
self._btDevs = btDevsCache
356+
_DeviceInfoFetcher.btDevsCache = btDevsCache
343357

344358
def _bgScan(
345359
self,
346-
detectUsb: bool,
347-
detectBluetooth: bool,
360+
usb: bool,
361+
bluetooth: bool,
348362
limitToDevices: typing.Optional[typing.List[str]]
349363
):
350364
"""Performs the actual background scan.
@@ -357,12 +371,25 @@ def _bgScan(
357371
# Clear the stop event before a scan is started.
358372
# Since a scan can take some time to complete, another thread can set the stop event to cancel it.
359373
self._stopEvent.clear()
360-
if detectUsb:
361-
self._bgScanUsb(limitToDevices)
362-
if detectBluetooth:
363-
self._bgScanBluetooth(limitToDevices)
374+
iterator = scanForDevices.iter(
375+
usb=usb,
376+
bluetooth=bluetooth,
377+
limitToDevices=limitToDevices,
378+
)
379+
for driver, match in iterator:
380+
if self._stopEvent.is_set():
381+
return
382+
if braille.handler.setDisplayByName(driver, detected=match):
383+
return
384+
if self._stopEvent.is_set():
385+
return
364386

365-
def rescan(self, usb=True, bluetooth=True, limitToDevices=None):
387+
def rescan(
388+
self,
389+
usb: bool = True,
390+
bluetooth: bool = True,
391+
limitToDevices: typing.Optional[typing.List[str]] = None,
392+
):
366393
"""Stop a current scan when in progress, and start scanning from scratch.
367394
@param usb: Whether USB devices should be detected for this and subsequent scans.
368395
@type usb: bool
@@ -372,9 +399,8 @@ def rescan(self, usb=True, bluetooth=True, limitToDevices=None):
372399
C{None} if no driver filtering should occur.
373400
"""
374401
self._stopBgScan()
375-
with self._btDevsLock:
376-
# A Bluetooth com port or HID device might have been added.
377-
self._btDevs = None
402+
# Clear the cache of bluetooth devices so new devices can be picked up.
403+
_DeviceInfoFetcher.btDevsCache = None
378404
self._queueBgScan(usb=usb, bluetooth=bluetooth, limitToDevices=limitToDevices)
379405

380406
def handleWindowMessage(self, msg=None, wParam=None):
@@ -387,15 +413,16 @@ def pollBluetoothDevices(self):
387413
if not self._detectBluetooth:
388414
# Do not poll bluetooth devices at all when bluetooth is disabled.
389415
return
390-
with self._btDevsLock:
391-
if not self._btDevs:
392-
return
416+
if not _DeviceInfoFetcher.btDevsCache:
417+
return
393418
self._queueBgScan(bluetooth=self._detectBluetooth, limitToDevices=self._limitToDevices)
394419

395420
def terminate(self):
396421
appModuleHandler.post_appSwitch.unregister(self.pollBluetoothDevices)
397422
messageWindow.pre_handleWindowMessage.unregister(self.handleWindowMessage)
398423
self._stopBgScan()
424+
# Clear the cache of bluetooth devices so new devices can be picked up with a new instance.
425+
_DeviceInfoFetcher.btDevsCache = None
399426
self._executor.shutdown(wait=False)
400427

401428

@@ -479,12 +506,19 @@ def driverSupportsAutoDetection(driver):
479506
return driver in _driverDevices
480507

481508

482-
def initializeDetectionData():
483-
""" Initialize detection data.
509+
def initialize():
510+
""" Initializes bdDetect, such as detection data.
484511
Calls to addUsbDevices, and addBluetoothDevices.
485512
Specify the requirements for a detected device to be considered a
486513
match for a specific driver.
487514
"""
515+
global deviceInfoFetcher
516+
deviceInfoFetcher = _DeviceInfoFetcher()
517+
518+
scanForDevices.register(Detector._bgScanUsb)
519+
scanForDevices.register(Detector._bgScanBluetooth)
520+
521+
# Add devices
488522
# alva
489523
addUsbDevices("alva", KEY_HID, {
490524
"VID_0798&PID_0640", # BC640
@@ -733,3 +767,11 @@ def initializeDetectionData():
733767
"seikantk",
734768
isSeikaBluetoothDeviceMatch
735769
)
770+
771+
772+
def terminate():
773+
global deviceInfoFetcher
774+
_driverDevices.clear()
775+
scanForDevices.unregister(Detector._bgScanBluetooth)
776+
scanForDevices.unregister(Detector._bgScanUsb)
777+
del deviceInfoFetcher

source/braille.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,15 +1892,14 @@ class BrailleHandler(baseObject.AutoPropertyObject):
18921892
@type currentCellCount: bool
18931893
"""
18941894

1895-
filter_displaySize: extensionPoints.Filter
1895+
filter_displaySize: extensionPoints.Filter[int]
18961896
"""
18971897
Filter that allows components or add-ons to change the display size used for braille output.
18981898
For example, when a system is controlled by a remote system while having a 80 cells display connected,
18991899
the display size should be lowered to 40 whenever the remote system has a 40 cells display connected.
19001900
@param value: the number of cells of the current display.
19011901
@type value: int
19021902
"""
1903-
19041903
displaySizeChanged: extensionPoints.Action
19051904
"""
19061905
Action that allows components or add-ons to be notified of display size changes.
@@ -2089,7 +2088,6 @@ def setDisplayByName( # noqa: C901
20892088
# or situations where the user hasn't set any port.
20902089
if port:
20912090
kwargs["port"] = port
2092-
20932091
try:
20942092
newDisplay = _getDisplayDriver(name)
20952093
oldDisplay = self.display
@@ -2517,8 +2515,9 @@ def _enableDetection(self, usb=True, bluetooth=True, keepCurrentDisplay=False, l
25172515
config.conf["braille"]["display"] = AUTO_DISPLAY_NAME
25182516
if not keepCurrentDisplay:
25192517
self.setDisplayByName("noBraille", isFallback=True)
2520-
self._detector = bdDetect.Detector(usb=usb, bluetooth=bluetooth, limitToDevices=limitToDevices)
2518+
self._detector = bdDetect.Detector()
25212519
self._detectionEnabled = True
2520+
self._detector._queueBgScan(usb=usb, bluetooth=bluetooth, limitToDevices=limitToDevices)
25222521

25232522
def _disableDetection(self):
25242523
"""Disables automatic detection of braille displays."""
@@ -2596,7 +2595,6 @@ def initialize():
25962595
newTableName = brailleTables.RENAMED_TABLES.get(oldTableName)
25972596
if newTableName:
25982597
config.conf["braille"]["translationTable"] = newTableName
2599-
bdDetect.initializeDetectionData()
26002598
handler = BrailleHandler()
26012599
# #7459: the syncBraille has been dropped in favor of the native hims driver.
26022600
# Migrate to renamed drivers as smoothly as possible.

0 commit comments

Comments
 (0)