From 3569489ce7bc367b7d33b0e666b538ec9d1063df Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Fri, 7 Dec 2012 13:54:03 +0200 Subject: [PATCH] added registers and settings to device descriptors --- lib/logitech/unifying_receiver/descriptors.py | 63 +++++++++++++++ lib/logitech/unifying_receiver/devices.py | 66 ---------------- lib/logitech/unifying_receiver/hidpp10.py | 76 ++++++++++++++++--- lib/logitech/unifying_receiver/hidpp20.py | 27 +++++++ lib/logitech/unifying_receiver/receiver.py | 30 +++++++- lib/logitech/unifying_receiver/settings.py | 46 +++++++++++ 6 files changed, 232 insertions(+), 76 deletions(-) create mode 100644 lib/logitech/unifying_receiver/descriptors.py delete mode 100644 lib/logitech/unifying_receiver/devices.py create mode 100644 lib/logitech/unifying_receiver/settings.py diff --git a/lib/logitech/unifying_receiver/descriptors.py b/lib/logitech/unifying_receiver/descriptors.py new file mode 100644 index 00000000..90da1a69 --- /dev/null +++ b/lib/logitech/unifying_receiver/descriptors.py @@ -0,0 +1,63 @@ +# +# +# + +from collections import namedtuple + +from .common import NamedInts as _NamedInts +from . import hidpp10 + +# +# +# + +_DeviceDescriptor = namedtuple('_DeviceDescriptor', + ['name', 'kind', 'codename', 'registers', 'settings']) + +DEVICES = {} + +def _D(name, codename=None, kind=None, registers=None, settings=None): + if kind is None: + kind = (hidpp10.DEVICE_KIND.mouse if 'Mouse' in name + else hidpp10.DEVICE_KIND.keyboard if 'Keyboard' in name + else hidpp10.DEVICE_KIND.touchpad if 'Touchpad' in name + else hidpp10.DEVICE_KIND.trackball if 'Trackball' in name + else None) + assert kind is not None + + if codename is None: + codename = name.split(' ')[-1] + assert codename is not None + + DEVICES[codename] = _DeviceDescriptor(name, kind, codename, registers, settings) + + +_D('Wireless Mouse M315') +_D('Wireless Mouse M325') +_D('Wireless Mouse M505') +_D('Wireless Mouse M510') +_D('Couch Mouse M515') +_D('Wireless Mouse M525') +_D('Wireless Trackball M570') +_D('Touch Mouse M600') +_D('Marathon Mouse M705', + registers=_NamedInts(battery=0x0D), + settings=[hidpp10.SmoothScroll_Setting(0x01)] + ) +_D('Wireless Keyboard K270') +_D('Wireless Keyboard K350') +_D('Wireless Keyboard K360') +_D('Wireless Touch Keyboard K400') +_D('Wireless Solar Keyboard K750') +_D('Wireless Illuminated Keyboard K800') +_D('Zone Touch Mouse T400') +_D('Wireless Rechargeable Touchpad T650') +_D('Logitech Cube', kind='mouse') +_D('Anywhere Mouse MX', codename='Anywhere MX') +_D('Performance Mouse MX', codename='Performance MX', + settings=[ + hidpp10.MouseDPI_Setting(0x63, _NamedInts(**dict((str(x * 100), 0x80 + x) for x in range(1, 16)))), + ] + ) + +del namedtuple diff --git a/lib/logitech/unifying_receiver/devices.py b/lib/logitech/unifying_receiver/devices.py deleted file mode 100644 index 74f626fd..00000000 --- a/lib/logitech/unifying_receiver/devices.py +++ /dev/null @@ -1,66 +0,0 @@ -# -# -# - -from collections import namedtuple - -_DeviceDescriptor = namedtuple('_DeviceDescriptor', - ['name', 'kind', 'codename', 'settings']) - -DEVICES = {} - -def _D(name, codename=None, kind=None): - if kind is None: - kind = ('mouse' if 'Mouse' in name - else 'keyboard' if 'Keyboard' in name - else 'touchpad' if 'Touchpad' in name - else 'trackball' if 'Trackball' in name - else None) - assert kind is not None - - if codename is None: - codename = name.split(' ')[-1] - assert codename is not None - - DEVICES[codename] = _DeviceDescriptor(name, kind, codename, None) - - -_D('Wireless Mouse M315') -_D('Wireless Mouse M325') -_D('Wireless Mouse M505') -_D('Wireless Mouse M510') -_D('Couch Mouse M515') -_D('Wireless Mouse M525') -_D('Wireless Trackball M570') -_D('Touch Mouse M600') -_D('Marathon Mouse M705') -_D('Wireless Keyboard K270') -_D('Wireless Keyboard K350') -_D('Wireless Keyboard K360') -_D('Wireless Touch Keyboard K400') -_D('Wireless Solar Keyboard K750') -_D('Wireless Illuminated Keyboard K800') -_D('Zone Touch Mouse T400') -_D('Wireless Rechargeable Touchpad T650') -_D('Logitech Cube', kind='mouse') -_D('Anywhere Mouse MX', codename='Anywhere MX') -_D('Performance Mouse MX', codename='Performance MX') - # DPI=(0x64, {0x80: 100, - # 0x81: 200, - # 0x82: 300, - # 0x83: 400, - # 0x84: 500, - # 0x85: 600, - # 0x86: 800, - # 0x87: 900, - # 0x88: 1000, - # 0x89: 1100, - # 0x8A: 1200, - # 0x8B: 1300, - # 0x8C: 1400, - # 0x8D: 1500}), - # Leds=(0x51, {}), - -del _D -del _DeviceDescriptor -del namedtuple diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/lib/logitech/unifying_receiver/hidpp10.py index ffd3478f..b0fc93b0 100644 --- a/lib/logitech/unifying_receiver/hidpp10.py +++ b/lib/logitech/unifying_receiver/hidpp10.py @@ -5,6 +5,7 @@ from .common import (strhex as _strhex, NamedInts as _NamedInts, FirmwareInfo as _FirmwareInfo) +from . import settings as _settings from .hidpp20 import FIRMWARE_KIND # @@ -57,6 +58,60 @@ PAIRING_ERRORS = _NamedInts( too_many_devices=0x03, sequence_timeout=0x06) +# +# +# + + +class SmoothScroll_Setting(_settings.Setting): + def __init__(self, register): + super(SmoothScroll_Setting, self).__init__('smooth-scroll', _settings.KIND.toggle, + 'Smooth Scrolling', 'High-sensitivity mode for vertical scroll with the wheel.') + assert register is not None + self.register = register + + def read(self): + if self._value is None and self._device: + ss = self.read_register() + if ss: + self._value = (ss[:1] == b'\x40') + return self._value + + def write(self, value): + if self._device: + reply = self.write_register(0x40 if bool(value) else 0x00) + self._value = None + if reply: + return self.read() + + +class MouseDPI_Setting(_settings.Setting): + def __init__(self, register, choices): + super(MouseDPI_Setting, self).__init__('dpi', _settings.KIND.choice, + 'Sensitivity (DPI)', choices=choices) + assert choices + assert isinstance(choices, _NamedInts) + assert register is not None + self.register = register + + def read(self): + if self._value is None and self._device: + dpi = self.read_register() + if dpi: + value = ord(dpi[:1]) + self._value = self.choices[value] + assert self._value is not None + return self._value + + def write(self, value): + if self._device: + choice = self.choices[value] + if choice is None: + raise ValueError(repr(value)) + reply = self.write_register(value) + self._value = None + if reply: + return self.read() # # functions @@ -64,15 +119,18 @@ PAIRING_ERRORS = _NamedInts( def get_battery(device): """Reads a device's battery level, if provided by the HID++ 1.0 protocol.""" - reply = device.request(0x810D) - if reply: - charge = ord(reply[:1]) - status = ord(reply[2:3]) & 0xF0 - status = ('discharging' if status == 0x30 - else 'charging' if status == 0x50 - else 'fully charged' if status == 0x90 - else None) - return charge, status + if 'battery' in device.registers: + register = device.registers['battery'] + + reply = device.request(0x8100 + (register & 0xFF)) + if reply: + charge = ord(reply[:1]) + status = ord(reply[2:3]) & 0xF0 + status = ('discharging' if status == 0x30 + else 'charging' if status == 0x50 + else 'fully charged' if status == 0x90 + else None) + return charge, status def get_serial(device): diff --git a/lib/logitech/unifying_receiver/hidpp20.py b/lib/logitech/unifying_receiver/hidpp20.py index 0b99eb7b..1cce2981 100644 --- a/lib/logitech/unifying_receiver/hidpp20.py +++ b/lib/logitech/unifying_receiver/hidpp20.py @@ -9,6 +9,7 @@ from logging import getLogger, DEBUG as _DEBUG _log = getLogger('LUR').getChild('hidpp20') del getLogger +from . import settings as _settings from .common import (FirmwareInfo as _FirmwareInfo, ReprogrammableKeyInfo as _ReprogrammableKeyInfo, KwException as _KwException, @@ -290,6 +291,32 @@ class KeysArray(object): # # +class ToggleFN_Setting(_settings.Setting): + def __init__(self): + super(ToggleFN_Setting, self).__init__('fn-toggle', _settings.KIND.toggle, 'Swap Fx function', + 'When set, the F1..F12 keys will activate their special function,\n' + 'and you must hold the FN key to activate their standard function.\n' + '\n' + 'When unset, the F1..F12 keys will activate their standard function,\n' + 'and you must hold the FN key to activate their special function.') + + def read(self): + if self._value is None and self._device: + fn = self._device.feature_request(FEATURE.FN_STATUS) + if fn: + self._value = (fn[:1] == b'\x01') + return self._value + + def write(self, value): + if self._device: + reply = self._device.feature_request(FEATURE.FN_STATUS, 0x10, 0x01 if value else 0x00) + self._value = (reply[:1] == b'\x01') if reply else None + return self._value + +# +# +# + def feature_request(device, feature, function=0x00, *params): if device.features: if feature in device.features: diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index 9651bec2..0a257db5 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -4,6 +4,7 @@ import errno as _errno from weakref import proxy as _proxy +from collections import defaultdict as _defaultdict from logging import getLogger _log = getLogger('LUR').getChild('receiver') @@ -13,7 +14,7 @@ from . import base as _base from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 from .common import strhex as _strhex -from .devices import DEVICES as _DEVICES +from .descriptors import DEVICES as _DEVICES # # @@ -42,6 +43,8 @@ class PairedDevice(object): self._keys = None self.features = _hidpp20.FeaturesArray(self) + self._registers = None + self._settings = None @property def protocol(self): @@ -134,6 +137,31 @@ class PairedDevice(object): self._keys = _hidpp20.get_keys(self) or () return self._keys + @property + def registers(self): + if self._registers is None: + descriptor = _DEVICES.get(self.codename) + if descriptor is None or descriptor.registers is None: + self._registers = _defaultdict(lambda: None) + else: + self._registers = descriptor.registers + return self._registers + + @property + def settings(self): + if self._settings is None: + descriptor = _DEVICES.get(self.codename) + if descriptor is None or descriptor.settings is None: + self._settings = [] + else: + self._settings = [s(self) for s in descriptor.settings] + + if _hidpp20.FEATURE.FN_STATUS in self.features: + tfn = _hidpp20.ToggleFN_Setting() + self._settings.insert(0, tfn(self)) + + return self._settings + def request(self, request_id, *params): return _base.request(self.receiver.handle, self.number, request_id, *params) diff --git a/lib/logitech/unifying_receiver/settings.py b/lib/logitech/unifying_receiver/settings.py new file mode 100644 index 00000000..cc4ede20 --- /dev/null +++ b/lib/logitech/unifying_receiver/settings.py @@ -0,0 +1,46 @@ +# +# +# + +from weakref import proxy as _proxy +from copy import copy as _copy + +from .common import NamedInts as _NamedInts + +# +# +# + +KIND = _NamedInts(toggle=0x1, choice=0x02, range=0x03) + +class Setting(object): + __slots__ = ['name', 'kind', 'label', 'description', 'choices', '_device', '_value', 'register'] + + def __init__(self, name, kind, label, description=None, choices=None): + self.name = name + self.kind = kind + self.label = label + self.description = description + self.choices = choices + self.register = None + + def __call__(self, device): + o = _copy(self) + o._value = None + o._device = _proxy(device) + return o + + def read_register(self): + return self._device.request(0x8100 | (self.register & 0x2FF)) + + def write_register(self, value, value2=0): + return self._device.request(0x8000 | (self.register & 0x2FF), int(value) & 0xFF, int(value2) & 0xFF) + + def read(self): + raise NotImplemented + + def write(self, value): + raise NotImplemented + + def __str__(self): + return '<%s(%s=%s)>' % (self.__class__.__name__, self.name, self._value)