Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Deprecations
  • Loading branch information
LeonarddeR committed Dec 27, 2024
commit dcd8ffda7fc40c747676cf3b1a4e17f3578fef4d
141 changes: 78 additions & 63 deletions source/bdDetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import itertools
import threading
from concurrent.futures import ThreadPoolExecutor, Future
from enum import StrEnum
from enum import Enum
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -53,15 +53,46 @@
USB_ID_REGEX = re.compile(r"^VID_[0-9A-F]{4}&PID_[0-9A-F]{4}$", re.U)


class DeviceType(StrEnum):
class ProtocolType(str, Enum):
Comment thread
LeonarddeR marked this conversation as resolved.
Outdated
HID = "hid"
"""HID devices"""
SERIAL = "serial"
"""Serial devices (COM ports)"""
CUSTOM = "custom"
"""Devices with a manufacturer specific driver"""
"""Devices with a manufacturer specific protocol"""


class CommunicationType(str, Enum):
Comment thread
LeonarddeR marked this conversation as resolved.
Outdated
BLUETOOTH = "bluetooth"
"""Bluetooth devices"""
USB = "usb"
"""USB devices"""


class _DeviceTypeMeta(type):
# Mapping old attributes to the new enums
_mapping = {
"HID": ProtocolType.HID,
"SERIAL": ProtocolType.SERIAL,
"CUSTOM": ProtocolType.CUSTOM,
"BLUETOOTH": CommunicationType.BLUETOOTH,
}

def __getattr__(cls, name: str) -> ProtocolType | CommunicationType:
repl = cls._mapping.get(name)
if repl is not None and NVDAState._allowDeprecatedAPI():
log.warning(
f"{cls.__name__}.{name} is deprecated. Use {repl} instead.",
)
return repl
raise AttributeError(f"'{cls.__name__}' object has no attribute '{name}'")


class DeviceType(metaclass=_DeviceTypeMeta):
"""This class is kept for backwards compatibility.
Former members were split into the L{ProtocolType} and L{CommunicationType} enums."""

...


def __getattr__(attrName: str) -> Any:
Expand All @@ -73,25 +104,25 @@ def __getattr__(attrName: str) -> Any:
log.warning(f"{attrName} is deprecated.")
return 2
_deprecatedConstantsMap = {
"KEY_HID": DeviceType.HID,
"KEY_SERIAL": DeviceType.SERIAL,
"KEY_BLUETOOTH": DeviceType.BLUETOOTH,
"KEY_CUSTOM": DeviceType.CUSTOM,
"KEY_HID": ProtocolType.HID,
"KEY_SERIAL": ProtocolType.SERIAL,
"KEY_BLUETOOTH": CommunicationType.BLUETOOTH,
"KEY_CUSTOM": ProtocolType.CUSTOM,
}
if attrName in _deprecatedConstantsMap and NVDAState._allowDeprecatedAPI():
replacementSymbol = _deprecatedConstantsMap[attrName]
log.warning(
f"{attrName} is deprecated. " f"Use bdDetect.DeviceType.{replacementSymbol.name} instead. ",
f"{attrName} is deprecated. " f"Use bdDetect.{replacementSymbol} instead. ",
)
return replacementSymbol
return replacementSymbol.value
raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}")


class DeviceMatch(NamedTuple):
"""Represents a detected device."""

type: DeviceType
"""The type of the device."""
type: ProtocolType
"""The protocol type of the device."""
id: str
"""The identifier of the device."""
port: str
Expand All @@ -109,6 +140,8 @@ class _UsbDeviceRegistryEntry:

id: str
"""The identifier of the device."""
type: ProtocolType
"""The protocol type of the device."""
useAsFallback: bool = field(default=False, compare=False)
"""
determine how a device is associated with a driver.
Expand All @@ -123,9 +156,16 @@ class _UsbDeviceRegistryEntry:
It takes a L{DeviceMatch} as its only argument
and returns a C{bool} indicating whether it matched."""

def matches(self, deviceMatch: DeviceMatch) -> bool:
"""Returns whether this registry entry matches a specific device."""
if deviceMatch.type != self.type or deviceMatch.id != self.id:
return False
if self.matchFunc is None:
return True
return self.matchFunc(deviceMatch)

DriverDictT = defaultdict[DeviceType, set[_UsbDeviceRegistryEntry] | MatchFuncT]

DriverDictT = defaultdict[CommunicationType, set[_UsbDeviceRegistryEntry] | MatchFuncT]
_driverDevices = OrderedDict[str, DriverDictT]()

scanForDevices = extensionPoints.Chain[Tuple[str, DeviceMatch]]()
Expand Down Expand Up @@ -158,11 +198,11 @@ def getDriversForConnectedUsbDevices(
@return: Generator of pairs of drivers and device information.
"""
usbCustomDeviceMatches = (
DeviceMatch(DeviceType.CUSTOM, port["usbID"], port["devicePath"], port)
DeviceMatch(ProtocolType.CUSTOM, port["usbID"], port["devicePath"], port)
for port in deviceInfoFetcher.usbDevices
)
usbComDeviceMatches = (
DeviceMatch(DeviceType.SERIAL, port["usbID"], port["port"], port)
DeviceMatch(ProtocolType.SERIAL, port["usbID"], port["port"], port)
for port in deviceInfoFetcher.usbComPorts
)
# Tee is used to ensure that the DeviceMatches aren't created multiple times.
Expand All @@ -172,7 +212,7 @@ def getDriversForConnectedUsbDevices(
# device matches), if one is found early the iteration can stop.
usbHidDeviceMatches, usbHidDeviceMatchesForCustom = itertools.tee(
(
DeviceMatch(DeviceType.HID, port["usbID"], port["devicePath"], port)
DeviceMatch(ProtocolType.HID, port["usbID"], port["devicePath"], port)
for port in deviceInfoFetcher.hidDevices
if port["provider"] == "usb"
)
Expand All @@ -183,21 +223,9 @@ def getDriversForConnectedUsbDevices(
for driver, devs in _driverDevices.items():
if limitToDevices and driver not in limitToDevices:
continue
for type, typeDefs in devs.items():
if (
match.type == type
and (
typeDef := next(
(
t
for t in typeDefs
if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.id
),
None,
)
)
and (not callable(typeDef.matchFunc) or typeDef.matchFunc(match))
):
typeDefs = devs[CommunicationType.USB]
for typeDef in typeDefs:
if typeDef.matches(match):
if typeDef.useAsFallback:
fallbackDriversAndMatches.append({driver, match})
else:
Expand Down Expand Up @@ -225,7 +253,7 @@ def _getStandardHidDriverName() -> str:


def _isHIDUsagePageMatch(match: DeviceMatch, usagePage: int) -> bool:
return match.type == DeviceType.HID and match.deviceInfo.get("HIDUsagePage") == usagePage
return match.type == ProtocolType.HID and match.deviceInfo.get("HIDUsagePage") == usagePage


def _isHIDBrailleMatch(match: DeviceMatch) -> bool:
Expand All @@ -247,7 +275,7 @@ def getDriversForPossibleBluetoothDevices(
@return: Generator of pairs of drivers and port information.
"""
btSerialMatchesForCustom = (
DeviceMatch(DeviceType.SERIAL, port["bluetoothName"], port["port"], port)
DeviceMatch(ProtocolType.SERIAL, port["bluetoothName"], port["port"], port)
for port in deviceInfoFetcher.comPorts
if "bluetoothName" in port
)
Expand All @@ -258,7 +286,7 @@ def getDriversForPossibleBluetoothDevices(
# device matches), if one is found early the iteration can stop.
btHidDevMatchesForHid, btHidDevMatchesForCustom = itertools.tee(
(
DeviceMatch(DeviceType.HID, port["hardwareID"], port["devicePath"], port)
DeviceMatch(ProtocolType.HID, port["hardwareID"], port["devicePath"], port)
for port in deviceInfoFetcher.hidDevices
if port["provider"] == "bluetooth"
)
Expand All @@ -267,7 +295,7 @@ def getDriversForPossibleBluetoothDevices(
for driver, devs in _driverDevices.items():
if limitToDevices and driver not in limitToDevices:
continue
matchFunc = devs[DeviceType.BLUETOOTH]
matchFunc = devs[CommunicationType.BLUETOOTH]
if not callable(matchFunc):
continue
if matchFunc(match):
Expand Down Expand Up @@ -517,16 +545,16 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]:
"""
usbDevs = itertools.chain(
(
DeviceMatch(DeviceType.CUSTOM, port["usbID"], port["devicePath"], port)
DeviceMatch(ProtocolType.CUSTOM, port["usbID"], port["devicePath"], port)
for port in deviceInfoFetcher.usbDevices
),
(
DeviceMatch(DeviceType.HID, port["usbID"], port["devicePath"], port)
DeviceMatch(ProtocolType.HID, port["usbID"], port["devicePath"], port)
for port in deviceInfoFetcher.hidDevices
if port["provider"] == "usb"
),
(
DeviceMatch(DeviceType.SERIAL, port["usbID"], port["port"], port)
DeviceMatch(ProtocolType.SERIAL, port["usbID"], port["port"], port)
for port in deviceInfoFetcher.usbComPorts
),
)
Expand All @@ -538,23 +566,10 @@ def getConnectedUsbDevicesForDriver(driver: str) -> Iterator[DeviceMatch]:
if _isHIDBrailleMatch(match):
yield match
else:
devs = _driverDevices[driver]
for type, typeDefs in devs.items():
if (
match.type == type
and (
typeDef := next(
(
t
for t in typeDefs
if isinstance(t, _UsbDeviceRegistryEntry) and t.id == match.id
),
None,
)
)
and (not callable(typeDef.matchFunc) or typeDef.matchFunc(match))
):
if typeDef.useAsFallback:
devs = _driverDevices[driver][CommunicationType.USB]
for dev in devs:
if dev.matches(match):
if dev.useAsFallback:
fallbackMatches.append(match)
else:
yield match
Expand All @@ -572,17 +587,17 @@ def getPossibleBluetoothDevicesForDriver(driver: str) -> Iterator[DeviceMatch]:
if driver == _getStandardHidDriverName():
matchFunc = _isHIDBrailleMatch
else:
matchFunc = _driverDevices[driver][DeviceType.BLUETOOTH]
matchFunc = _driverDevices[driver][CommunicationType.BLUETOOTH]
if not callable(matchFunc):
return
btDevs = itertools.chain(
(
DeviceMatch(DeviceType.SERIAL, port["bluetoothName"], port["port"], port)
DeviceMatch(ProtocolType.SERIAL, port["bluetoothName"], port["port"], port)
for port in deviceInfoFetcher.comPorts
if "bluetoothName" in port
),
(
DeviceMatch(DeviceType.HID, port["hardwareID"], port["devicePath"], port)
DeviceMatch(ProtocolType.HID, port["hardwareID"], port["devicePath"], port)
for port in deviceInfoFetcher.hidDevices
if port["provider"] == "bluetooth"
),
Expand Down Expand Up @@ -693,7 +708,7 @@ def _getDriverDict(self) -> DriverDictT:

def addUsbDevice(
self,
type: DeviceType,
type: ProtocolType,
id: str,
useAsFallback: bool = False,
matchFunc: MatchFuncT | None = None,
Expand All @@ -719,8 +734,8 @@ def addUsbDevice(
f"Invalid ID provided for driver {self._driver!r}, type {type!r}: " f"{id!r}",
)
devs = self._getDriverDict()
driverUsb = devs[type]
driverUsb.add(_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc))
driverUsb = devs[CommunicationType.USB]
driverUsb.add(_UsbDeviceRegistryEntry(type, id, useAsFallback, matchFunc))

def addUsbDevices(
self,
Expand Down Expand Up @@ -752,8 +767,8 @@ def addUsbDevices(
f"{', '.join(malformedIds)}",
)
devs = self._getDriverDict()
driverUsb = devs[type]
driverUsb.update((_UsbDeviceRegistryEntry(id, useAsFallback, matchFunc) for id in ids))
driverUsb = devs[CommunicationType.USB]
driverUsb.update((_UsbDeviceRegistryEntry(id, type, useAsFallback, matchFunc) for id in ids))

def addBluetoothDevices(self, matchFunc: MatchFuncT):
"""Associate Bluetooth HID or COM ports with the driver on this instance.
Expand All @@ -762,7 +777,7 @@ def addBluetoothDevices(self, matchFunc: MatchFuncT):
and returns a C{bool} indicating whether it matched.
"""
devs = self._getDriverDict()
devs[DeviceType.BLUETOOTH] = matchFunc
devs[CommunicationType.BLUETOOTH] = matchFunc

def addDeviceScanner(
self,
Expand Down
2 changes: 1 addition & 1 deletion source/braille.py
Original file line number Diff line number Diff line change
Expand Up @@ -3517,7 +3517,7 @@ def _getTryPorts(
pass
else:
yield bdDetect.DeviceMatch(
bdDetect.DeviceType.SERIAL,
bdDetect.ProtocolType.SERIAL,
portInfo["bluetoothName" if "bluetoothName" in portInfo else "friendlyName"],
portInfo["port"],
portInfo,
Expand Down
4 changes: 2 additions & 2 deletions source/brailleDisplayDrivers/albatross/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import time

from collections import deque
from bdDetect import DeviceType, DriverRegistrar
from bdDetect import DriverRegistrar, ProtocolType
from logHandler import log
from serial.win32 import (
PURGE_RXABORT,
Expand Down Expand Up @@ -85,7 +85,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
@classmethod
def registerAutomaticDetection(cls, driverRegistrar: DriverRegistrar):
driverRegistrar.addUsbDevice(
DeviceType.SERIAL,
ProtocolType.SERIAL,
VID_AND_PID, # Caiku Albatross 46/80
# Filter for bus reported device description, which should be "Albatross Braille Display".
matchFunc=lambda match: match.deviceInfo.get("busReportedDeviceDescription") == BUS_DEVICE_DESC,
Expand Down
4 changes: 2 additions & 2 deletions source/brailleDisplayDrivers/alva.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject):
@classmethod
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
driverRegistrar.addUsbDevices(
bdDetect.DeviceType.HID,
bdDetect.ProtocolType.HID,
{
"VID_0798&PID_0640", # BC640
"VID_0798&PID_0680", # BC680
Expand Down Expand Up @@ -216,7 +216,7 @@ def __init__(self, port="auto"):
self._deviceId = None

for portType, portId, port, portInfo in self._getTryPorts(port):
self.isHid = portType == bdDetect.DeviceType.HID
self.isHid = portType == bdDetect.ProtocolType.HID
# Try talking to the display.
try:
if self.isHid:
Expand Down
6 changes: 3 additions & 3 deletions source/brailleDisplayDrivers/baum.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
@classmethod
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
driverRegistrar.addUsbDevices(
bdDetect.DeviceType.HID,
bdDetect.ProtocolType.HID,
{
"VID_0904&PID_3001", # RefreshaBraille 18
"VID_0904&PID_6101", # VarioUltra 20
Expand All @@ -111,7 +111,7 @@ def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
)

driverRegistrar.addUsbDevices(
bdDetect.DeviceType.SERIAL,
bdDetect.ProtocolType.SERIAL,
{
"VID_0403&PID_FE70", # Vario 40
"VID_0403&PID_FE71", # PocketVario
Expand Down Expand Up @@ -164,7 +164,7 @@ def __init__(self, port="auto"):
for portType, portId, port, portInfo in self._getTryPorts(port):
# At this point, a port bound to this display has been found.
# Try talking to the display.
self.isHid = portType == bdDetect.DeviceType.HID
self.isHid = portType == bdDetect.ProtocolType.HID
try:
if self.isHid:
self._dev = hwIo.Hid(port, onReceive=self._onReceive)
Expand Down
2 changes: 1 addition & 1 deletion source/brailleDisplayDrivers/brailleNote.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver):
@classmethod
def registerAutomaticDetection(cls, driverRegistrar: bdDetect.DriverRegistrar):
driverRegistrar.addUsbDevices(
bdDetect.DeviceType.SERIAL,
bdDetect.ProtocolType.SERIAL,
{
"VID_1C71&PID_C004", # Apex
},
Expand Down
Loading