re-worked the settings classes

This commit is contained in:
Daniel Pavel 2012-12-14 06:44:44 +02:00
parent 9db2a65b31
commit f0c5046ccf
6 changed files with 231 additions and 126 deletions

View File

@ -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:

View File

@ -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))),
],
)

View File

@ -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
#

View File

@ -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
#
#
#

View File

@ -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):

View File

@ -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)