From ceba698678656acf136e2359eefa042b4792fef4 Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Mon, 1 Jul 2013 15:24:30 +0200 Subject: [PATCH] moved settings templates into separate .py --- lib/logitech/unifying_receiver/descriptors.py | 90 ++++-------- lib/logitech/unifying_receiver/settings.py | 62 ++++----- .../unifying_receiver/settings_templates.py | 131 ++++++++++++++++++ 3 files changed, 184 insertions(+), 99 deletions(-) create mode 100644 lib/logitech/unifying_receiver/settings_templates.py diff --git a/lib/logitech/unifying_receiver/descriptors.py b/lib/logitech/unifying_receiver/descriptors.py index 77d958b5..21166511 100644 --- a/lib/logitech/unifying_receiver/descriptors.py +++ b/lib/logitech/unifying_receiver/descriptors.py @@ -4,58 +4,9 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from .common import NamedInts as _NamedInts from . import hidpp10 as _hidpp10 -from . import hidpp20 as _hidpp20 -from . import settings as _settings - -# -# common strings for settings -# - -_SMOOTH_SCROLL = ('smooth-scroll', 'Smooth Scrolling', - 'High-sensitivity mode for vertical scroll with the wheel.') -_DPI = ('dpi', 'Sensitivity (DPI)', None) -_FN_SWAP = ('fn-swap', '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.')) - -# this register is only applicable to HID++ 1.0 devices, it should not exist with HID++ 2.0 devices -# using Features -def _register_fn_swap(register=0x09, true_value=b'\x00\x01', mask=b'\x00\x01'): - return _settings.register_toggle(_FN_SWAP[0], register, true_value=true_value, mask=mask, - label=_FN_SWAP[1], description=_FN_SWAP[2], - device_kind=_hidpp10.DEVICE_KIND.keyboard) - -def _register_smooth_scroll(register=0x01, true_value=0x40, mask=0x40): - return _settings.register_toggle(_SMOOTH_SCROLL[0], register, true_value=true_value, mask=mask, - label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2], - device_kind=_hidpp10.DEVICE_KIND.mouse) - - -_PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) -def _register_dpi(register=0x63, choices=None): - return _settings.register_choices(_DPI[0], register, choices, - label=_DPI[1], description=_DPI[2], - device_kind=_hidpp10.DEVICE_KIND.mouse) - - -def _feature_fn_swap(): - return _settings.feature_toggle(_FN_SWAP[0], _hidpp20.FEATURE.FN_INVERSION, - write_returns_value=True, - label=_FN_SWAP[1], description=_FN_SWAP[2], - device_kind=_hidpp10.DEVICE_KIND.keyboard) - - -def check_features(device, already_known): - """Try to auto-detect device settings by the HID++ 2.0 features they have.""" - if device.protocol is not None and device.protocol < 2.0: - return - if not any(s.name == _FN_SWAP[0] for s in already_known) and _hidpp20.FEATURE.FN_INVERSION in device.features: - already_known.append(_feature_fn_swap()) +from .common import NamedInts as _NamedInts +from .settings_templates import Register as _R, Feature as _F # # @@ -86,7 +37,11 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, if protocol is not None: # ? 2.0 devices should not have any registers - assert protocol < 2.0 or registers is None + if protocol < 2.0: + assert settings is None or all(s._rw.kind == 1 for s in settings) + else: + assert registers is None + assert settings is None or all(s._rw.kind == 2 for s in settings) DEVICES[codename] = _DeviceDescriptor( name=name, @@ -105,6 +60,12 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, # # +_PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) + +# +# +# + # Some HID++1.0 registers and HID++2.0 features can be discovered at run-time, # so they are not specified here. # @@ -143,9 +104,12 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, # 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 'registers' field indicates read-only registers, specifying a state. These +# are valid (AFAIK) only to HID+= 1.0 devices. # The 'settings' field indicates a read/write register; based on them Solaar -# generates, at runtime, the settings controls in the device panel. +# generates, at runtime, the settings controls in the device panel. HID++ 1.0 +# devices may only have register-based settings; HID++ 2.0 devices may only have +# feature-based settings. # Keyboards @@ -154,29 +118,29 @@ _D('Wireless Keyboard K270') _D('Wireless Keyboard K350') _D('Wireless Keyboard K360', protocol=2.0, wpid='4004', settings=[ - _feature_fn_swap() + _F.fn_swap() ], ) _D('Wireless Touch Keyboard K400', protocol=2.0, wpid='4024', settings=[ - _feature_fn_swap() + _F.fn_swap() ], ) _D('Wireless Keyboard MK700', protocol=1.0, wpid='2008', registers={'battery_charge': -0x0D, 'battery_status': 0x07}, settings=[ - _register_fn_swap(), + _R.fn_swap(), ], ) _D('Wireless Solar Keyboard K750', protocol=2.0, wpid='4002', settings=[ - _feature_fn_swap() + _F.fn_swap() ], ) _D('Wireless Illuminated Keyboard K800', protocol=1.0, wpid='2010', registers={'battery_charge': -0x0D, 'battery_status': 0x07, '3leds': 0x51}, settings=[ - _register_fn_swap(), + _R.fn_swap(), ], ) @@ -199,7 +163,7 @@ _D('Wireless Mouse M505') _D('Wireless Mouse M510', protocol=1.0, wpid='1025', registers={'battery_charge': -0x0D, 'battery_status': 0x07}, settings=[ - _register_smooth_scroll(), + _R.smooth_scroll(), ], ) _D('Couch Mouse M515', protocol=2.0) @@ -208,7 +172,7 @@ _D('Touch Mouse M600') _D('Marathon Mouse M705', protocol=1.0, wpid='101B', registers={'battery_charge': 0x0D}, settings=[ - _register_smooth_scroll(), + _R.smooth_scroll(), ], ) _D('Zone Touch Mouse T400') @@ -218,7 +182,7 @@ _D('Anywhere Mouse MX', codename='Anywhere MX') _D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A', registers={'battery_charge': -0x0D, 'battery_status': 0x07, '3leds': 0x51}, settings=[ - _register_dpi(choices=_PERFORMANCE_MX_DPIS), + _R.dpi(choices=_PERFORMANCE_MX_DPIS), ], ) @@ -239,6 +203,6 @@ _D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011') _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(), + _R.smooth_scroll(), ], ) diff --git a/lib/logitech/unifying_receiver/settings.py b/lib/logitech/unifying_receiver/settings.py index 7f94e311..431171b6 100644 --- a/lib/logitech/unifying_receiver/settings.py +++ b/lib/logitech/unifying_receiver/settings.py @@ -15,7 +15,7 @@ from .common import NamedInt as _NamedInt, NamedInts as _NamedInts KIND = _NamedInts(toggle=0x1, choice=0x02, range=0x12) -class _Setting(object): +class Setting(object): """A setting descriptor. Needs to be instantiated for each specific device.""" __slots__ = ['name', 'label', 'description', 'kind', 'persister', 'device_kind', @@ -41,10 +41,10 @@ class _Setting(object): p = device.protocol if p == 1.0: # HID++ 1.0 devices do not support features - assert self._rw.kind == _RegisterRW.kind + assert self._rw.kind == RegisterRW.kind elif p >= 2.0: # HID++ 2.0 devices do not support registers - assert self._rw.kind == _FeatureRW.kind + assert self._rw.kind == FeatureRW.kind o = _copy(self) o._value = None @@ -53,14 +53,24 @@ class _Setting(object): @property def choices(self): + assert hasattr(self, '_value') + assert hasattr(self, '_device') + return self._validator.choices if self._validator.kind & KIND.choice else None def read(self, cached=True): + assert hasattr(self, '_value') + assert hasattr(self, '_device') + if self._value is None and self.persister: + # We haven't read a value from the device yet, + # maybe we have something in the configuration. self._value = self.persister.get(self.name) if cached and self._value is not None: if self.persister and self.name not in self.persister: + # If this is a new device (or a new setting for an old device), + # make sure to save its current value for the next time. self.persister[self.name] = self._value return self._value @@ -69,10 +79,15 @@ class _Setting(object): if reply: self._value = self._validator.validate_read(reply) if self.persister and self.name not in self.persister: + # Don't update the persister if it already has a value, + # otherwise the first read might overwrite the value we wanted. self.persister[self.name] = self._value return self._value def write(self, value): + assert hasattr(self, '_value') + assert hasattr(self, '_device') + if self._device: data_bytes = self._validator.prepare_write(value) reply = self._rw.write(self._device, data_bytes) @@ -83,6 +98,9 @@ class _Setting(object): return self._value def apply(self): + assert hasattr(self, '_value') + assert hasattr(self, '_device') + if self._value is not None: self.write(self._value) @@ -97,7 +115,7 @@ class _Setting(object): # read/write low-level operators # -class _RegisterRW(object): +class RegisterRW(object): __slots__ = ['register'] kind = _NamedInt(0x01, 'register') @@ -113,7 +131,7 @@ class _RegisterRW(object): return device.write_register(self.register, data_bytes) -class _FeatureRW(object): +class FeatureRW(object): __slots__ = ['feature', 'read_fnid', 'write_fnid'] kind = _NamedInt(0x02, 'feature') @@ -139,7 +157,7 @@ class _FeatureRW(object): # handle the conversion from read bytes, to setting value, and back # -class _BooleanValidator(object): +class BooleanValidator(object): __slots__ = ['true_value', 'false_value', 'mask', 'write_returns_value'] kind = KIND.toggle @@ -181,7 +199,7 @@ class _BooleanValidator(object): return bool(value) -class _ChoicesValidator(object): +class ChoicesValidator(object): __slots__ = ['choices', 'write_returns_value'] kind = KIND.choice @@ -218,33 +236,5 @@ class _ChoicesValidator(object): # be any reply_bytes to check return self.choices[value] -# -# pre-defined basic setting descriptors -# -def register_toggle(name, register, - true_value=_BooleanValidator.default_true, false_value=_BooleanValidator.default_false, - mask=_BooleanValidator.default_mask, write_returns_value=False, - label=None, description=None, device_kind=None): - rw = _RegisterRW(register) - validator = _BooleanValidator(true_value=true_value, false_value=false_value, mask=mask, write_returns_value=write_returns_value) - return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind) - - -def register_choices(name, register, choices, - kind=KIND.choice, write_returns_value=False, - label=None, description=None, device_kind=None): - assert choices - rw = _RegisterRW(register) - validator = _ChoicesValidator(choices, write_returns_value=write_returns_value) - return _Setting(name, rw, validator, kind=kind, label=label, description=description, device_kind=device_kind) - - -def feature_toggle(name, feature, - read_function_id=_FeatureRW.default_read_fnid, write_function_id=_FeatureRW.default_write_fnid, - true_value=_BooleanValidator.default_true, false_value=_BooleanValidator.default_false, - mask=_BooleanValidator.default_mask, write_returns_value=False, - label=None, description=None, device_kind=None): - rw = _FeatureRW(feature, read_function_id, write_function_id) - validator = _BooleanValidator(true_value=true_value, false_value=false_value, mask=mask, write_returns_value=write_returns_value) - return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind) +__all__ = ('KIND', 'Setting', 'RegisterRW', 'FeatureRW', 'BooleanValidator', 'ChoicesValidator') diff --git a/lib/logitech/unifying_receiver/settings_templates.py b/lib/logitech/unifying_receiver/settings_templates.py new file mode 100644 index 00000000..057e3992 --- /dev/null +++ b/lib/logitech/unifying_receiver/settings_templates.py @@ -0,0 +1,131 @@ +# +# +# + +from __future__ import absolute_import, division, print_function, unicode_literals + +from . import hidpp10 as _hidpp10 +from . import hidpp20 as _hidpp20 +from .settings import ( + KIND as _KIND, + Setting as _Setting, + RegisterRW as _RegisterRW, + FeatureRW as _FeatureRW, + BooleanValidator as _BooleanV, + ChoicesValidator as _ChoicesV, + ) + +# +# pre-defined basic setting descriptors +# + +def register_toggle(name, register, + true_value=_BooleanV.default_true, false_value=_BooleanV.default_false, + mask=_BooleanV.default_mask, write_returns_value=False, + label=None, description=None, device_kind=None): + rw = _RegisterRW(register) + validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask, write_returns_value=write_returns_value) + return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind) + + +def register_choices(name, register, choices, + kind=_KIND.choice, write_returns_value=False, + label=None, description=None, device_kind=None): + assert choices + rw = _RegisterRW(register) + validator = _ChoicesV(choices, write_returns_value=write_returns_value) + return _Setting(name, rw, validator, kind=kind, label=label, description=description, device_kind=device_kind) + + +def feature_toggle(name, feature, + read_function_id=_FeatureRW.default_read_fnid, write_function_id=_FeatureRW.default_write_fnid, + true_value=_BooleanV.default_true, false_value=_BooleanV.default_false, + mask=_BooleanV.default_mask, write_returns_value=False, + label=None, description=None, device_kind=None): + rw = _FeatureRW(feature, read_function_id, write_function_id) + validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask, write_returns_value=write_returns_value) + return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind) + +# +# common strings for settings +# + +_SMOOTH_SCROLL = ('smooth-scroll', 'Smooth Scrolling', + 'High-sensitivity mode for vertical scroll with the wheel.') +_DPI = ('dpi', 'Sensitivity (DPI)', None) +_FN_SWAP = ('fn-swap', '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 _register_fn_swap(register=0x09, true_value=b'\x00\x01', mask=b'\x00\x01'): + return register_toggle(_FN_SWAP[0], register, true_value=true_value, mask=mask, + label=_FN_SWAP[1], description=_FN_SWAP[2], + device_kind=_hidpp10.DEVICE_KIND.keyboard) + +def _register_smooth_scroll(register=0x01, true_value=0x40, mask=0x40): + return register_toggle(_SMOOTH_SCROLL[0], register, true_value=true_value, mask=mask, + label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2], + device_kind=_hidpp10.DEVICE_KIND.mouse) + +def _register_dpi(register=0x63, choices=None): + return register_choices(_DPI[0], register, choices, + label=_DPI[1], description=_DPI[2], + device_kind=_hidpp10.DEVICE_KIND.mouse) + + +def _feature_fn_swap(): + return feature_toggle(_FN_SWAP[0], _hidpp20.FEATURE.FN_INVERSION, + write_returns_value=True, + label=_FN_SWAP[1], description=_FN_SWAP[2], + device_kind=_hidpp10.DEVICE_KIND.keyboard) + + +# +# +# + +from collections import namedtuple +_SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [ + 'fn_swap', + 'smooth_scroll', + 'dpi', + 'hand_detection', + 'typing_illumination', + ]) +del namedtuple + +Register = _SETTINGS_LIST( + fn_swap=_register_fn_swap, + smooth_scroll=_register_smooth_scroll, + dpi=_register_dpi, + hand_detection=None, + typing_illumination=None, + ) +Feature = _SETTINGS_LIST( + fn_swap=_feature_fn_swap, + smooth_scroll=None, + dpi=None, + hand_detection=None, + typing_illumination=None, + ) + +del _SETTINGS_LIST + +# +# +# + +def check_feature_settings(device, already_known): + """Try to auto-detect device settings by the HID++ 2.0 features they have.""" + if device.protocol is not None and device.protocol < 2.0: + return + if not any(s.name == _FN_SWAP[0] for s in already_known) and _hidpp20.FEATURE.FN_INVERSION in device.features: + fn_swap = Feature.fn_swap() + already_known.append(fn_swap(device))