re-worked the settings classes
This commit is contained in:
parent
9db2a65b31
commit
f0c5046ccf
|
@ -72,6 +72,11 @@ class NamedInts(object):
|
|||
self._indexed = {int(v): v for v in self._values}
|
||||
self._fallback = None
|
||||
|
||||
@classmethod
|
||||
def range(cls, from_value, to_value, name_generator=lambda x: '_' + str(x), step=1):
|
||||
values = {name_generator(x): x for x in range(from_value, to_value + 1, step)}
|
||||
return NamedInts(**values)
|
||||
|
||||
def flag_names(self, value):
|
||||
unknown_bits = value
|
||||
for k in self._indexed:
|
||||
|
|
|
@ -7,7 +7,38 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||
from collections import namedtuple
|
||||
|
||||
from .common import NamedInts as _NamedInts
|
||||
from . import hidpp10
|
||||
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.'))
|
||||
|
||||
|
||||
def _register_smooth_scroll(register, true_value, mask):
|
||||
return _settings.register_toggle(_SMOOTH_SCROLL[0], register, true_value=true_value, mask=mask,
|
||||
label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2])
|
||||
|
||||
|
||||
def _register_dpi(register, choices):
|
||||
return _settings.register_choices(_DPI[0], register, choices,
|
||||
label=_DPI[1], description=_DPI[2])
|
||||
|
||||
|
||||
def check_features(device, already_known):
|
||||
if _hidpp20.FEATURE.FN_STATUS in device.features and not any(s.name == 'fn-swap' for s in already_known):
|
||||
tfn = _settings.feature_toggle(_FN_SWAP[0], _hidpp20.FEATURE.FN_STATUS, write_returns_value=True,
|
||||
label=_FN_SWAP[1], description=_FN_SWAP[2])
|
||||
already_known.append(tfn(device))
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -20,10 +51,10 @@ 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
|
||||
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
|
||||
|
||||
|
@ -43,7 +74,10 @@ _D('Wireless Mouse M525')
|
|||
_D('Wireless Trackball M570')
|
||||
_D('Touch Mouse M600')
|
||||
_D('Marathon Mouse M705',
|
||||
settings=[hidpp10.SmoothScroll_Setting(0x01)],
|
||||
settings=[
|
||||
_register_smooth_scroll(0x01, true_value=0x40, mask=0x40),
|
||||
_register_dpi(0x63, _NamedInts.range(9, 11, lambda x: '_' + str(x * 100))),
|
||||
],
|
||||
)
|
||||
_D('Wireless Keyboard K230')
|
||||
_D('Wireless Keyboard K270')
|
||||
|
@ -58,7 +92,7 @@ _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(**{str(x * 100): (0x80 + x) for x in range(1, 16)})),
|
||||
_register_dpi(0x63, _NamedInts.range(0x81, 0x8F, lambda x: '_' + str((x - 0x80) * 100))),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -4,15 +4,13 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from logging import getLogger, DEBUG as _DEBUG
|
||||
from logging import getLogger # , DEBUG as _DEBUG
|
||||
_log = getLogger('LUR').getChild('hidpp10')
|
||||
del getLogger
|
||||
|
||||
from .common import (strhex as _strhex,
|
||||
NamedInts as _NamedInts,
|
||||
NamedInt as _NamedInt,
|
||||
FirmwareInfo as _FirmwareInfo)
|
||||
from . import settings as _settings
|
||||
from .hidpp20 import FIRMWARE_KIND
|
||||
|
||||
#
|
||||
|
@ -65,65 +63,6 @@ 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, cached=True):
|
||||
if (self._value is None or not cached) 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:
|
||||
self._value = value
|
||||
return value
|
||||
# return self.read(False)
|
||||
|
||||
|
||||
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, cached=True):
|
||||
if (self._value is None or not cached) 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:
|
||||
self._value = value
|
||||
return value
|
||||
# return self.read(False)
|
||||
|
||||
#
|
||||
# functions
|
||||
#
|
||||
|
|
|
@ -11,7 +11,6 @@ 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,
|
||||
|
@ -300,33 +299,6 @@ class KeysArray(object):
|
|||
def __len__(self):
|
||||
return len(self.keys)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
class ToggleFN_Setting(_settings.Setting):
|
||||
def __init__(self):
|
||||
super(ToggleFN_Setting, self).__init__('fn-swap', _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, cached=True):
|
||||
if (self._value is None or not cached) 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
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
|
|
@ -15,7 +15,7 @@ from . import base as _base
|
|||
from . import hidpp10 as _hidpp10
|
||||
from . import hidpp20 as _hidpp20
|
||||
from .common import strhex as _strhex, NamedInts as _NamedInts
|
||||
from .descriptors import DEVICES as _DEVICES
|
||||
from . import descriptors as _descriptors
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -94,8 +94,8 @@ class PairedDevice(object):
|
|||
@property
|
||||
def name(self):
|
||||
if self._name is None:
|
||||
if self.codename in _DEVICES:
|
||||
self._name, self._kind = _DEVICES[self._codename][:2]
|
||||
if self.codename in _descriptors.DEVICES:
|
||||
self._name, self._kind = _descriptors.DEVICES[self._codename][:2]
|
||||
elif self.protocol >= 2.0:
|
||||
self._name = _hidpp20.get_name(self)
|
||||
return self._name or self.codename or '?'
|
||||
|
@ -110,8 +110,8 @@ class PairedDevice(object):
|
|||
if self._wpid is None:
|
||||
self._wpid = _strhex(pair_info[3:5])
|
||||
if self._kind is None:
|
||||
if self.codename in _DEVICES:
|
||||
self._name, self._kind = _DEVICES[self._codename][:2]
|
||||
if self.codename in _descriptors.DEVICES:
|
||||
self._name, self._kind = _descriptors.DEVICES[self._codename][:2]
|
||||
elif self.protocol >= 2.0:
|
||||
self._kind = _hidpp20.get_kind(self)
|
||||
return self._kind or '?'
|
||||
|
@ -141,7 +141,7 @@ class PairedDevice(object):
|
|||
@property
|
||||
def registers(self):
|
||||
if self._registers is None:
|
||||
descriptor = _DEVICES.get(self.codename)
|
||||
descriptor = _descriptors.DEVICES.get(self.codename)
|
||||
if descriptor is None or descriptor.registers is None:
|
||||
self._registers = _NamedInts()
|
||||
else:
|
||||
|
@ -151,16 +151,14 @@ class PairedDevice(object):
|
|||
@property
|
||||
def settings(self):
|
||||
if self._settings is None:
|
||||
descriptor = _DEVICES.get(self.codename)
|
||||
descriptor = _descriptors.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))
|
||||
|
||||
if self.features:
|
||||
_descriptors.check_features(self, self._settings)
|
||||
return self._settings
|
||||
|
||||
def request(self, request_id, *params):
|
||||
|
|
|
@ -7,24 +7,30 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||
from weakref import proxy as _proxy
|
||||
from copy import copy as _copy
|
||||
|
||||
from .common import NamedInts as _NamedInts
|
||||
from .common import NamedInt as _NamedInt, NamedInts as _NamedInts
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
KIND = _NamedInts(toggle=0x1, choice=0x02, range=0x03)
|
||||
KIND = _NamedInts(toggle=0x1, choice=0x02, range=0x12)
|
||||
|
||||
class Setting(object):
|
||||
__slots__ = ['name', 'kind', 'label', 'description', 'choices', '_device', '_value', 'register']
|
||||
class _Setting(object):
|
||||
__slots__ = ['name', 'label', 'description',
|
||||
'kind', '_rw', '_validator',
|
||||
'_device', '_value']
|
||||
|
||||
def __init__(self, name, kind, label, description=None, choices=None):
|
||||
def __init__(self, name, rw, validator, kind=None, label=None, description=None):
|
||||
assert name
|
||||
self.name = name
|
||||
self.kind = kind
|
||||
self.label = label
|
||||
self.label = label or name
|
||||
self.description = description
|
||||
self.choices = choices
|
||||
self.register = None
|
||||
|
||||
self._rw = rw
|
||||
self._validator = validator
|
||||
|
||||
assert kind is None or kind & validator.kind != 0
|
||||
self.kind = kind or validator.kind
|
||||
|
||||
def __call__(self, device):
|
||||
o = _copy(self)
|
||||
|
@ -32,21 +38,172 @@ class Setting(object):
|
|||
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)
|
||||
@property
|
||||
def choices(self):
|
||||
return self._validator.choices if self._validator.kind & KIND.choice else None
|
||||
|
||||
def read(self, cached=True):
|
||||
raise NotImplemented
|
||||
if self._device:
|
||||
if self._value is None or not cached:
|
||||
reply = self._rw.read(self._device)
|
||||
# print ("read reply", repr(reply))
|
||||
if reply:
|
||||
# print ("pre-read", self._value)
|
||||
self._value = self._validator.validate_read(reply)
|
||||
# print ("post-read", self._value)
|
||||
return self._value
|
||||
|
||||
def write(self, value):
|
||||
raise NotImplemented
|
||||
if self._device:
|
||||
data_bytes = self._validator.prepare_write(value)
|
||||
reply = self._rw.write(self._device, data_bytes)
|
||||
if reply:
|
||||
self._value = self._validator.validate_write(value, reply)
|
||||
return self._value
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, '_value'):
|
||||
assert hasattr(self, '_device')
|
||||
return'<%s(%s:%s=%s)>' % (self.__class__.__name__, self._device.codename, self.name, self._value)
|
||||
return '<%s(%s)>' % (self.__class__.__name__, self.name)
|
||||
return '<Setting([%s:%s] %s:%s=%s)>' % (self._rw.kind, self._validator.kind, self._device.codename, self.name, self._value)
|
||||
return '<Setting([%s:%s] %s)>' % (self._rw.kind, self._validator.kind, self.name)
|
||||
__unicode__ = __repr__ = __str__
|
||||
|
||||
|
||||
class _RegisterRW(object):
|
||||
__slots__ = ['register']
|
||||
|
||||
kind = _NamedInt(0x01, 'register')
|
||||
|
||||
def __init__(self, register):
|
||||
assert isinstance(register, int)
|
||||
self.register = register
|
||||
|
||||
def read(self, device):
|
||||
return device.request(0x8100 | (self.register & 0x2FF))
|
||||
|
||||
def write(self, device, data_bytes):
|
||||
return device.request(0x8000 | (self.register & 0x2FF), data_bytes)
|
||||
|
||||
|
||||
class _FeatureRW(object):
|
||||
__slots__ = ['feature', 'read_fnid', 'write_fnid']
|
||||
|
||||
kind = _NamedInt(0x02, 'feature')
|
||||
default_read_fnid = 0x00
|
||||
default_write_fnid = 0x10
|
||||
|
||||
def __init__(self, feature, read_fnid=default_read_fnid, write_fnid=default_write_fnid):
|
||||
assert isinstance(feature, _NamedInt)
|
||||
self.feature = feature
|
||||
self.read_fnid = read_fnid
|
||||
self.write_fnid = write_fnid
|
||||
|
||||
def read(self, device):
|
||||
assert self.feature is not None
|
||||
return device.feature_request(self.feature, self.read_fnid)
|
||||
|
||||
def write(self, device, data_bytes):
|
||||
assert self.feature is not None
|
||||
return device.feature_request(self.feature, self.write_fnid, data_bytes)
|
||||
|
||||
|
||||
class _BooleanValidator(object):
|
||||
__slots__ = ['true_value', 'false_value', 'mask', 'write_returns_value']
|
||||
|
||||
kind = KIND.toggle
|
||||
default_true = 0x01
|
||||
default_false = 0x00
|
||||
default_mask = 0xFF
|
||||
|
||||
def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask, write_returns_value=False):
|
||||
self.true_value = true_value
|
||||
self.false_value = false_value
|
||||
self.mask = mask
|
||||
self.write_returns_value = write_returns_value
|
||||
|
||||
def validate_read(self, reply_bytes):
|
||||
reply_value = ord(reply_bytes[:1]) & self.mask
|
||||
return reply_value == self.true_value
|
||||
|
||||
def prepare_write(self, value):
|
||||
# FIXME: this does not work right when there is more than one flag in
|
||||
# the same register!
|
||||
return self.true_value if value else self.false_value
|
||||
|
||||
def validate_write(self, value, reply_bytes):
|
||||
if self.write_returns_value:
|
||||
reply_value = ord(reply_bytes[:1]) & self.mask
|
||||
return reply_value == self.true_value
|
||||
|
||||
# just assume the value was written correctly, otherwise there would not
|
||||
# be any reply_bytes to check
|
||||
return bool(value)
|
||||
|
||||
|
||||
class _ChoicesValidator(object):
|
||||
__slots__ = ['choices', 'write_returns_value']
|
||||
|
||||
kind = KIND.choice
|
||||
|
||||
def __init__(self, choices, write_returns_value=False):
|
||||
assert isinstance(choices, _NamedInts)
|
||||
self.choices = choices
|
||||
self.write_returns_value = write_returns_value
|
||||
|
||||
def validate_read(self, reply_bytes):
|
||||
assert self.choices is not None
|
||||
reply_value = ord(reply_bytes[:1])
|
||||
valid_value = self.choices[reply_value]
|
||||
assert valid_value is not None, "%: failed to validate read value %02X" % (self.__class__.__name__, reply_value)
|
||||
return valid_value
|
||||
|
||||
def prepare_write(self, value):
|
||||
assert self.choices is not None
|
||||
choice = self.choices[value]
|
||||
if choice is None:
|
||||
raise ValueError("invalid choice " + repr(value))
|
||||
assert isinstance(choice, _NamedInt)
|
||||
return choice.bytes(1)
|
||||
|
||||
def validate_write(self, value, reply_bytes):
|
||||
assert self.choices is not None
|
||||
if self.write_returns_value:
|
||||
reply_value = ord(reply_bytes[:1])
|
||||
choice = self.choices[reply_value]
|
||||
assert choice is not None, "failed to validate write reply %02X" % reply_value
|
||||
return choice
|
||||
|
||||
# just assume the value was written correctly, otherwise there would not
|
||||
# be any reply_bytes to check
|
||||
return self.choices[value]
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
def register_choices(name, register, choices,
|
||||
kind=KIND.choice, write_returns_value=False,
|
||||
label=None, description=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)
|
||||
|
||||
|
||||
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):
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue