use the wpid to identify devices, when possible
This commit is contained in:
parent
c464e049bf
commit
a0b7d39f83
|
@ -63,12 +63,14 @@ def check_features(device, already_known):
|
|||
|
||||
from collections import namedtuple
|
||||
_DeviceDescriptor = namedtuple('_DeviceDescriptor',
|
||||
['name', 'kind', 'product_id', 'codename', 'protocol', 'registers', 'settings'])
|
||||
['name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings'])
|
||||
del namedtuple
|
||||
|
||||
DEVICES = {}
|
||||
|
||||
def _D(name, codename=None, kind=None, product_id=None, protocol=None, registers=None, settings=None):
|
||||
def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None):
|
||||
assert name
|
||||
|
||||
if kind is None:
|
||||
kind = (_hidpp10.DEVICE_KIND.mouse if 'Mouse' in name
|
||||
else _hidpp10.DEVICE_KIND.keyboard if 'Keyboard' in name
|
||||
|
@ -89,13 +91,15 @@ def _D(name, codename=None, kind=None, product_id=None, protocol=None, registers
|
|||
DEVICES[codename] = _DeviceDescriptor(
|
||||
name=name,
|
||||
kind=kind,
|
||||
product_id=product_id,
|
||||
wpid=wpid,
|
||||
codename=codename,
|
||||
protocol=protocol,
|
||||
registers=registers,
|
||||
settings=settings)
|
||||
if product_id:
|
||||
DEVICES[product_id] = DEVICES[codename]
|
||||
|
||||
if wpid:
|
||||
assert wpid not in DEVICES
|
||||
DEVICES[wpid] = DEVICES[codename]
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -125,41 +129,47 @@ def _D(name, codename=None, kind=None, product_id=None, protocol=None, registers
|
|||
# no known device uses both
|
||||
# 51 - leds
|
||||
# 63 - mice: DPI
|
||||
# F1 - firmware info
|
||||
# * F1 - firmware info
|
||||
# Some registers appear to be universally supported, no matter the HID++ version
|
||||
# (marked with *). The rest may or may not be supported, and their values may or
|
||||
# may not mean the same thing across different devices.
|
||||
|
||||
# The 'codename' and 'kind' fields are usually guessed from the device name,
|
||||
# but in some cases (like the Logitech Cube) that heuristic fails and they have
|
||||
# to be specified.
|
||||
#
|
||||
# The 'protocol' and 'wpid' fields are optional (they can be discovered at
|
||||
# runtime), but specifying them here speeds up device discovery and reduces the
|
||||
# USB traffic Solaar has to do to fully identify peripherals.
|
||||
# Same goes for HID++ 2.0 feature settings (like _feature_fn_swap).
|
||||
#
|
||||
# The 'registers' field indicates read-only registers, specifying a state.
|
||||
# The 'settings' field indicates a read/write register; based on them Solaar
|
||||
# generates, at runtime, the settings controls in the device panel.
|
||||
#
|
||||
# HID++ 2.0 features are not specified here, they are always discovered at
|
||||
# run-time.
|
||||
|
||||
# Keyboards
|
||||
|
||||
_D('Wireless Keyboard K230', protocol=2.0)
|
||||
_D('Wireless Keyboard K230', protocol=2.0, wpid='400D')
|
||||
_D('Wireless Keyboard K270')
|
||||
_D('Wireless Keyboard K350')
|
||||
_D('Wireless Keyboard K360', protocol=2.0,
|
||||
_D('Wireless Keyboard K360', protocol=2.0, wpid='4004',
|
||||
settings=[
|
||||
_feature_fn_swap()
|
||||
],
|
||||
)
|
||||
_D('Wireless Touch Keyboard K400', protocol=2.0)
|
||||
_D('Wireless Keyboard MK700', protocol=1.0,
|
||||
_D('Wireless Touch Keyboard K400', protocol=2.0, wpid='4024')
|
||||
_D('Wireless Keyboard MK700', protocol=1.0, wpid='2008',
|
||||
registers={'battery_charge': -0x0D, 'battery_status': 0x07},
|
||||
settings=[
|
||||
_register_fn_swap(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Solar Keyboard K750', protocol=2.0,
|
||||
_D('Wireless Solar Keyboard K750', protocol=2.0, wpid='4002',
|
||||
settings=[
|
||||
_feature_fn_swap()
|
||||
],
|
||||
)
|
||||
_D('Wireless Illuminated Keyboard K800', protocol=1.0,
|
||||
_D('Wireless Illuminated Keyboard K800', protocol=1.0, wpid='2010',
|
||||
registers={'battery_charge': -0x0D, 'battery_status': 0x07, 'leds': 0x51},
|
||||
settings=[
|
||||
_register_fn_swap(),
|
||||
|
@ -171,7 +181,7 @@ _D('Wireless Illuminated Keyboard K800', protocol=1.0,
|
|||
_D('Wireless Mouse M175', protocol=1.0)
|
||||
_D('Wireless Mouse M185', protocol=1.0)
|
||||
_D('Wireless Mouse M187', protocol=1.0)
|
||||
_D('Wireless Mouse M215', protocol=1.0)
|
||||
_D('Wireless Mouse M215', protocol=1.0, wpid='1020')
|
||||
_D('Wireless Mouse M235', protocol=1.0)
|
||||
_D('Wireless Mouse M305', protocol=1.0)
|
||||
_D('Wireless Mouse M310', protocol=1.0)
|
||||
|
@ -180,7 +190,7 @@ _D('Wireless Mouse M317')
|
|||
_D('Wireless Mouse M325')
|
||||
_D('Wireless Mouse M345')
|
||||
_D('Wireless Mouse M505')
|
||||
_D('Wireless Mouse M510', protocol=1.0,
|
||||
_D('Wireless Mouse M510', protocol=1.0, wpid='1025',
|
||||
registers={'battery_charge': -0x0D, 'battery_status': 0x07},
|
||||
settings=[
|
||||
_register_smooth_scroll(),
|
||||
|
@ -189,7 +199,7 @@ _D('Wireless Mouse M510', protocol=1.0,
|
|||
_D('Couch Mouse M515', protocol=2.0)
|
||||
_D('Wireless Mouse M525', protocol=2.0)
|
||||
_D('Touch Mouse M600')
|
||||
_D('Marathon Mouse M705', protocol=1.0,
|
||||
_D('Marathon Mouse M705', protocol=1.0, wpid='101B',
|
||||
registers={'battery_charge': 0x0D},
|
||||
settings=[
|
||||
_register_smooth_scroll(),
|
||||
|
@ -199,7 +209,7 @@ _D('Zone Touch Mouse T400')
|
|||
_D('Touch Mouse T620')
|
||||
_D('Logitech Cube', kind=_hidpp10.DEVICE_KIND.mouse, protocol=2.0)
|
||||
_D('Anywhere Mouse MX', codename='Anywhere MX')
|
||||
_D('Performance Mouse MX', codename='Performance MX', protocol=1.0,
|
||||
_D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A',
|
||||
registers={'battery_charge': -0x0D, 'battery_status': 0x07, 'leds': 0x51},
|
||||
settings=[
|
||||
_register_dpi(choices=_PERFORMANCE_MX_DPIS),
|
||||
|
@ -213,14 +223,14 @@ _D('Wireless Trackball M570')
|
|||
# Touchpads
|
||||
|
||||
_D('Wireless Rechargeable Touchpad T650', protocol=2.0)
|
||||
_D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0)
|
||||
_D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011')
|
||||
|
||||
#
|
||||
# classic Nano devices
|
||||
# a product_id is necessary to properly identify them
|
||||
# Classic Nano peripherals (that don't support the Unifying protocol).
|
||||
# A wpid is necessary to properly identify them.
|
||||
#
|
||||
|
||||
_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, product_id='c526',
|
||||
_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid='100F',
|
||||
registers={'battery_charge': 0x0D, 'battery_status': -0x07},
|
||||
settings=[
|
||||
_register_smooth_scroll(),
|
||||
|
|
|
@ -15,7 +15,10 @@ from . import base as _base
|
|||
from . import hidpp10 as _hidpp10
|
||||
from . import hidpp20 as _hidpp20
|
||||
from .common import strhex as _strhex
|
||||
from . import descriptors as _descriptors
|
||||
from .descriptors import (
|
||||
DEVICES as _DESCRIPTORS,
|
||||
check_features as _check_feature_settings,
|
||||
)
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -29,16 +32,26 @@ class PairedDevice(object):
|
|||
def __init__(self, receiver, number, link_notification=None):
|
||||
assert receiver
|
||||
self.receiver = receiver # _proxy(receiver)
|
||||
|
||||
assert number > 0 and number <= receiver.max_devices
|
||||
# Device number, 1..6 for unifying devices, 1 otherwise.
|
||||
self.number = number
|
||||
# 'device active' flag; requires manual management.
|
||||
self.online = None
|
||||
|
||||
# the Wireless PID is unique per device model
|
||||
self.wpid = None
|
||||
self._descriptor = None
|
||||
|
||||
# mose, keyboard, etc (see _hidpp10.DEVICE_KIND)
|
||||
self._kind = None
|
||||
# Unifying peripherals report a codename.
|
||||
self._codename = None
|
||||
# the full name of the model
|
||||
self._name = None
|
||||
# HID++ protocol version, 1.0 or 2.0
|
||||
self._protocol = None
|
||||
# serial number (an 8-char hex string)
|
||||
self._serial = None
|
||||
|
||||
self._firmware = None
|
||||
|
@ -46,6 +59,8 @@ class PairedDevice(object):
|
|||
self._registers = None
|
||||
self._settings = None
|
||||
|
||||
# Misc stuff that's irrelevant to any functionality, but may be
|
||||
# displayed in the UI and caching it here helps.
|
||||
self._polling_rate = None
|
||||
self._power_switch = None
|
||||
|
||||
|
@ -75,21 +90,22 @@ class PairedDevice(object):
|
|||
self._polling_rate = 0
|
||||
self._power_switch = '(unknown)'
|
||||
|
||||
descriptor = _descriptors.DEVICES.get(self.receiver.product_id)
|
||||
if descriptor is None:
|
||||
self._codename = self.receiver.product_id
|
||||
if self.wpid is None:
|
||||
device_info = self.receiver.read_register(0x2B5, 0x04)
|
||||
if device_info is None:
|
||||
_log.error("failed to read Nano wpid for device %d of %s", number, receiver)
|
||||
raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read Nano wpid")
|
||||
self.wpid = _strhex(device_info[3:5])
|
||||
|
||||
self._descriptor = _DESCRIPTORS.get(self.wpid)
|
||||
if self._descriptor is None:
|
||||
self._codename = self.receiver.wpid
|
||||
# actually there IS a device, just that we can't identify it
|
||||
# raise _base.NoSuchDevice(nuber=number, receiver=receiver, product_id=receiver.product_id, failed="no descriptor")
|
||||
self._name = 'Unknown device ' + self._codename
|
||||
else:
|
||||
self._codename = descriptor.codename
|
||||
self._name = descriptor.name
|
||||
|
||||
if self.wpid is None:
|
||||
device_info = self.receiver.read_register(0x2B5, 0x04)
|
||||
if device_info is None:
|
||||
raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read Nano wpid")
|
||||
self.wpid = _strhex(device_info[3:5])
|
||||
self._codename = self._descriptor.codename
|
||||
self._name = self._descriptor.name
|
||||
|
||||
# the wpid is necessary to properly identify wireless link on/off notifications
|
||||
# also it gets set to None when the device is unpaired
|
||||
|
@ -97,13 +113,11 @@ class PairedDevice(object):
|
|||
|
||||
# knowing the protocol as soon as possible helps reading all other info
|
||||
# and avoids an unecessary ping
|
||||
if self._codename is not None:
|
||||
descriptor = _descriptors.DEVICES.get(self._codename)
|
||||
if descriptor is None:
|
||||
_log.warn("device without descriptor found: %s (%d of %s)", self._codename, number, receiver)
|
||||
self._protocol = None if unifying else 1.0
|
||||
else:
|
||||
self._protocol = descriptor.protocol if unifying else 1.0 # may be None
|
||||
if self.descriptor:
|
||||
self._protocol = self.descriptor.protocol if unifying else 1.0 # may be None
|
||||
else:
|
||||
_log.warn("device without descriptor found: %s - %s (%d of %s)", self.wpid, self._codename, number, receiver)
|
||||
self._protocol = None if unifying else 1.0
|
||||
|
||||
if self._protocol is not None:
|
||||
self.features = _hidpp20.FeaturesArray(self) if self._protocol >= 2.0 else None
|
||||
|
@ -113,13 +127,20 @@ class PairedDevice(object):
|
|||
else:
|
||||
self.features = None
|
||||
|
||||
@property
|
||||
def descriptor(self):
|
||||
if self._descriptor is None:
|
||||
self._descriptor = _DESCRIPTORS.get(self.wpid)
|
||||
if self._descriptor is None and self._codename:
|
||||
self._descriptor = _DESCRIPTORS.get(self._codename)
|
||||
return self._descriptor
|
||||
|
||||
@property
|
||||
def protocol(self):
|
||||
if self._protocol is None:
|
||||
descriptor = _descriptors.DEVICES.get(self.codename)
|
||||
if descriptor:
|
||||
if descriptor.protocol:
|
||||
self._protocol = descriptor.protocol
|
||||
if self.descriptor:
|
||||
if self.descriptor.protocol:
|
||||
self._protocol = self.descriptor.protocol
|
||||
else:
|
||||
_log.warn("%s: descriptor has no protocol, should be %0.1f", self, self._protocol)
|
||||
|
||||
|
@ -134,7 +155,9 @@ class PairedDevice(object):
|
|||
@property
|
||||
def codename(self):
|
||||
if self._codename is None:
|
||||
if self.receiver.unifying_supported:
|
||||
if self.descriptor:
|
||||
self._codename = self.descriptor.codename
|
||||
elif self.receiver.unifying_supported:
|
||||
codename = self.receiver.read_register(0x2B5, 0x40 + self.number - 1)
|
||||
if codename:
|
||||
self._codename = codename[2:].rstrip(b'\x00').decode('utf-8')
|
||||
|
@ -144,28 +167,24 @@ class PairedDevice(object):
|
|||
@property
|
||||
def name(self):
|
||||
if self._name is None:
|
||||
if self.protocol >= 2.0 and self.online:
|
||||
if self.descriptor:
|
||||
self._name = self.descriptor.name
|
||||
elif self.protocol >= 2.0 and self.online:
|
||||
self._name = _hidpp20.get_name(self)
|
||||
if self._name is None:
|
||||
descriptor = _descriptors.DEVICES.get(self.codename)
|
||||
if descriptor and descriptor.name is not None:
|
||||
self._name = descriptor.name
|
||||
return self._name or self.codename or '?'
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
if self._kind is None:
|
||||
if self.receiver.unifying_supported:
|
||||
if self.descriptor:
|
||||
self._kind = self.descriptor.kind
|
||||
elif self.receiver.unifying_supported:
|
||||
pair_info = self.receiver.read_register(0x2B5, 0x20 + self.number - 1)
|
||||
if pair_info:
|
||||
kind = ord(pair_info[7:8]) & 0x0F
|
||||
self._kind = _hidpp10.DEVICE_KIND[kind]
|
||||
if self._kind is None and self.protocol >= 2.0 and self.online:
|
||||
self._kind = _hidpp20.get_kind(self)
|
||||
if self._kind is None:
|
||||
descriptor = _descriptors.DEVICES.get(self.codename)
|
||||
if descriptor and descriptor.kind is not None:
|
||||
self._kind = descriptor.kind
|
||||
return self._kind or '?'
|
||||
|
||||
@property
|
||||
|
@ -180,31 +199,33 @@ class PairedDevice(object):
|
|||
@property
|
||||
def serial(self):
|
||||
if self._serial is None:
|
||||
assert self.receiver.unifying_supported
|
||||
# otherwise it should have been set in the constructor
|
||||
self._serial = _hidpp10.get_serial(self)
|
||||
if self.receiver.unifying_supported:
|
||||
self._serial = _hidpp10.get_serial(self)
|
||||
else:
|
||||
self._serial = self.receiver.serial
|
||||
return self._serial or '?'
|
||||
|
||||
@property
|
||||
def power_switch_location(self):
|
||||
if self._power_switch is None:
|
||||
assert self.receiver.unifying_supported
|
||||
ps = self.receiver.read_register(0x2B5, 0x30 + self.number - 1)
|
||||
if ps is not None:
|
||||
ps = ord(ps[9:10]) & 0x0F
|
||||
self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps]
|
||||
if self.receiver.unifying_supported:
|
||||
ps = self.receiver.read_register(0x2B5, 0x30 + self.number - 1)
|
||||
if ps is not None:
|
||||
ps = ord(ps[9:10]) & 0x0F
|
||||
self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps]
|
||||
else:
|
||||
self._power_switch = '(unknown)'
|
||||
return self._power_switch
|
||||
|
||||
@property
|
||||
def polling_rate(self):
|
||||
if self._polling_rate is None:
|
||||
assert self.receiver.unifying_supported
|
||||
pair_info = self.receiver.read_register(0x2B5, 0x20 + self.number - 1)
|
||||
if pair_info is None:
|
||||
# wtf?
|
||||
self._polling_rate = 0
|
||||
if self.receiver.unifying_supported:
|
||||
pair_info = self.receiver.read_register(0x2B5, 0x20 + self.number - 1)
|
||||
if pair_info:
|
||||
self._polling_rate = ord(pair_info[2:3])
|
||||
else:
|
||||
self._polling_rate = ord(pair_info[2:3])
|
||||
self._polling_rate = 0
|
||||
return self._polling_rate
|
||||
|
||||
@property
|
||||
|
@ -217,24 +238,22 @@ class PairedDevice(object):
|
|||
@property
|
||||
def registers(self):
|
||||
if self._registers is None:
|
||||
descriptor = _descriptors.DEVICES.get(self.codename)
|
||||
if descriptor is None or descriptor.registers is None:
|
||||
self._registers = {}
|
||||
if self.descriptor and self.descriptor.registers:
|
||||
self._registers = dict(self.descriptor.registers)
|
||||
else:
|
||||
self._registers = descriptor.registers
|
||||
self._registers = {}
|
||||
return self._registers
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
if self._settings is None:
|
||||
descriptor = _descriptors.DEVICES.get(self.codename)
|
||||
if descriptor is None or descriptor.settings is None:
|
||||
self._settings = []
|
||||
if self.descriptor and self.descriptor.settings:
|
||||
self._settings = [s(self) for s in self.descriptor.settings]
|
||||
else:
|
||||
self._settings = [s(self) for s in descriptor.settings]
|
||||
self._settings = []
|
||||
|
||||
if self.online and self.features:
|
||||
_descriptors.check_features(self, self._settings)
|
||||
_check_feature_settings(self, self._settings)
|
||||
return self._settings
|
||||
|
||||
def enable_notifications(self, enable=True):
|
||||
|
|
|
@ -38,6 +38,14 @@ class _Setting(object):
|
|||
def __call__(self, device):
|
||||
assert not hasattr(self, '_value')
|
||||
assert self.device_kind is None or self.device_kind == device.kind
|
||||
p = device.protocol
|
||||
if p == 1.0:
|
||||
# HID++ 1.0 devices do not support features
|
||||
assert self._rw.kind == _RegisterRW.kind
|
||||
elif p >= 2.0:
|
||||
# HID++ 2.0 devices do not support registers
|
||||
assert self._rw.kind == _FeatureRW.kind
|
||||
|
||||
o = _copy(self)
|
||||
o._value = None
|
||||
o._device = device # _proxy(device)
|
||||
|
|
Loading…
Reference in New Issue