receiver: simplify settings interface
This commit is contained in:
parent
1516daa6d0
commit
718c2d4039
|
@ -119,7 +119,7 @@ A “read only” note means the feature is a read-only feature and cannot be ch
|
||||||
|
|
||||||
Features are implemented as settable features in
|
Features are implemented as settable features in
|
||||||
lib/logitech_receiver/settings_templates.py
|
lib/logitech_receiver/settings_templates.py
|
||||||
Some features also have direct implementation in
|
some features also have direct implementation in
|
||||||
lib/logitech_receiver/hidpp20.py
|
lib/logitech_receiver/hidpp20.py
|
||||||
|
|
||||||
In most cases it should suffice to only implement the settable feature
|
In most cases it should suffice to only implement the settable feature
|
||||||
|
@ -143,62 +143,72 @@ _POINTER_SPEED = ('pointer_speed',
|
||||||
_("How fast the pointer moves"))
|
_("How fast the pointer moves"))
|
||||||
```
|
```
|
||||||
|
|
||||||
Implement a register interface for the setting (if you are very brave and
|
Next implement an interface for the setting by creating
|
||||||
some devices have a register interface for the setting).
|
a reader/writer, a validator, and a setting instance for it.
|
||||||
|
Most settings use device features and thus need feature interfaces.
|
||||||
|
Some settings use device register and thus need register interfaces.
|
||||||
|
Only implement a register interface for the setting if you are very brave and
|
||||||
|
you have access to a device that has a register interface for the setting.
|
||||||
Register interfaces cannot be auto-discovered and need to be stated in descriptors.py
|
Register interfaces cannot be auto-discovered and need to be stated in descriptors.py
|
||||||
for each device with the register interface.
|
for each device with the register interface.
|
||||||
|
|
||||||
Implement a feature interface for the setting. There are several possible kinds of
|
The reader/writer instance is responsible for reading raw values
|
||||||
feature interfaces, ranging from simple toggles, to ranges, to fixed lists, to
|
from the device and writing values to it.
|
||||||
dynamic choices, to maps of dynamic choices, each created by a macro function.
|
There are different classes for feature interfaces and register interfaces.
|
||||||
Pointer speed is a setting
|
Pointer speed is a feature so the _FeatureRW reader/writer is used.
|
||||||
whose values are integers in a range so `feature_range` is used.
|
Reader/writers take the register or feature ID and the command numbers for reading and writing,
|
||||||
The arguments to this macro are
|
plus other arguments for complex interfaces.
|
||||||
the name of the setting (use the name from the common strings tuple),
|
|
||||||
the HID++ 2.0 feature ID for the setting (from the FEATURE structure in hidpp20.py),
|
The validator instance is responsible for turning read raw values into Python data
|
||||||
the minimum and maximum values for the setting,
|
and Python data into raw values to be written and validating that the Python data is
|
||||||
the HID++ 2.0 function IDs to read and write the setting (left-shifted four bits),
|
acceptable for the setting.
|
||||||
the byte size of the setting value,
|
There are several possible kinds of Python data for setting interfaces,
|
||||||
a label and description for the setting (from the common strings tuple),
|
ranging from simple toggles, to ranges, to fixed lists, to
|
||||||
and which kinds of devices can have this setting.
|
dynamic choices, to maps of dynamic choices.
|
||||||
(This last is no longer used because keyboards with integrated pointers only
|
Pointer speed is a setting whose values are integers in a range so a _RangeV validator is used.
|
||||||
|
The arguments to this class are the
|
||||||
|
the minimum and maximum values for the value
|
||||||
|
and the byte size of the value on the device.
|
||||||
|
Settings that are toggles or choices work similarly,
|
||||||
|
but their validators have different arguments.
|
||||||
|
Map settings have more complicated validators.
|
||||||
|
|
||||||
|
The setting instance keeps everything together and provides control.
|
||||||
|
It takes the strings for the setting, the reader/writer, the validator, and
|
||||||
|
which kinds of devices can have this setting.
|
||||||
|
(This last is no longer used because keyboards with integrated trackpads only
|
||||||
report that they are keyboards.)
|
report that they are keyboards.)
|
||||||
The values to be used need to be determined from documentation of the
|
|
||||||
feature or from reverse-engineering behavior of Logitech software under
|
|
||||||
Windows or MacOS.
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def _feature_pointer_speed():
|
def _feature_pointer_speed():
|
||||||
"""Pointer Speed feature"""
|
"""Pointer Speed feature"""
|
||||||
return feature_range(_POINTER_SPEED[0],
|
# min and max values taken from usb traces of Win software
|
||||||
_F.POINTER_SPEED,
|
validator = _RangeV(0x002e, 0x01ff, 2)
|
||||||
0x002e,
|
rw = _FeatureRW(_F.POINTER_SPEED)
|
||||||
0x01ff,
|
return _Setting(_POINTER_SPEED, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
read_function_id=0x0,
|
|
||||||
write_function_id=0x10,
|
|
||||||
bytes_count=2,
|
|
||||||
label=_POINTER_SPEED[1],
|
|
||||||
description=_POINTER_SPEED[2],
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Settings that are toggles or choices work very similarly.
|
Settings where the acceptable values are determined from the device
|
||||||
Settings where the choices are determined from the device
|
|
||||||
need an auxiliary function to receive and decipher the permissible choices.
|
need an auxiliary function to receive and decipher the permissible choices.
|
||||||
See `_feature_adjustable_dpi_choices` for an example.
|
See `_feature_adjustable_dpi_choices` for an example.
|
||||||
|
|
||||||
Add an element to _SETTINGS_TABLE with
|
Finally, add an element to _SETTINGS_TABLE with
|
||||||
the setting name (from the common strings),
|
the common strings for the setting,
|
||||||
the feature ID (if any),
|
the feature ID (if any),
|
||||||
the feature implementation (if any),
|
the feature implementation (if any),
|
||||||
the register implementation (if any).
|
the register implementation (if any).
|
||||||
and
|
and
|
||||||
the identifier for the setting implementation if different from the setting name.
|
the identifier for the setting implementation if different from the setting name.
|
||||||
The identifier is used in descriptors.py to say that a device has the register or feature implementation.
|
The identifier is used in descriptors.py to say that a device has the register or feature implementation.
|
||||||
The identifier can be the same as the setting name if there is only one implementation for the setting.
|
|
||||||
This table is used to generate the data structures for describing devices in descriptors.py
|
This table is used to generate the data structures for describing devices in descriptors.py
|
||||||
and is also used to auto-discover feature implementations.
|
and is also used to auto-discover feature implementations.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
_S( _POINTER_SPEED[0], _F.POINTER_SPEED, _feature_pointer_speed ),
|
_S( _POINTER_SPEED, _F.POINTER_SPEED, _feature_pointer_speed ),
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The values to be used need to be determined from documentation of the
|
||||||
|
feature or from reverse-engineering behavior of Logitech software under
|
||||||
|
Windows or MacOS.
|
||||||
|
For more information on implementing feature settings
|
||||||
|
see the comments in lib/logitech_receiver/settings_templates.py.
|
||||||
|
|
|
@ -44,23 +44,25 @@ class Setting(object):
|
||||||
"""A setting descriptor.
|
"""A setting descriptor.
|
||||||
Needs to be instantiated for each specific device."""
|
Needs to be instantiated for each specific device."""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'name', 'label', 'description', 'kind', 'device_kind', 'feature', 'persist', '_rw', '_validator', '_device', '_value'
|
'name', 'label', 'description', 'kind', 'device_kind', 'feature', 'persist', '_rw', '_validator', '_callback',
|
||||||
|
'_device', '_value'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, name, rw, validator, kind=None, device_kind=None, feature=None, persist=True, **kwargs):
|
def __init__(self, name, rw, validator=None, callback=None, kind=None, device_kind=None, feature=None, persist=True):
|
||||||
assert name
|
assert name
|
||||||
self.name = name[0]
|
self.name = name[0]
|
||||||
self.label = name[1]
|
self.label = name[1]
|
||||||
self.description = name[2]
|
self.description = name[2]
|
||||||
self.device_kind = device_kind
|
self.device_kind = device_kind
|
||||||
self.feature = feature
|
self.feature = getattr(rw, 'feature', None)
|
||||||
self.persist = persist
|
self.persist = persist
|
||||||
|
|
||||||
self._rw = rw
|
self._rw = rw
|
||||||
|
assert (validator and not callback) or (not validator and callback)
|
||||||
self._validator = validator
|
self._validator = validator
|
||||||
|
self._callback = callback
|
||||||
|
|
||||||
assert kind is None or kind & validator.kind != 0
|
assert kind is None or validator is None or kind & validator.kind != 0
|
||||||
self.kind = kind or validator.kind
|
self.kind = kind or getattr(validator, 'kind', None)
|
||||||
|
|
||||||
def __call__(self, device):
|
def __call__(self, device):
|
||||||
assert not hasattr(self, '_value')
|
assert not hasattr(self, '_value')
|
||||||
|
@ -73,8 +75,13 @@ class Setting(object):
|
||||||
elif p >= 2.0:
|
elif p >= 2.0:
|
||||||
# HID++ 2.0 devices do not support registers
|
# HID++ 2.0 devices do not support registers
|
||||||
assert self._rw.kind == FeatureRW.kind
|
assert self._rw.kind == FeatureRW.kind
|
||||||
|
|
||||||
o = _copy(self)
|
o = _copy(self)
|
||||||
|
if o._callback:
|
||||||
|
o._validator = o._callback(device)
|
||||||
|
if o._validator is None:
|
||||||
|
return None
|
||||||
|
assert o.kind is None or o.kind & o._validator.kind != 0
|
||||||
|
o.kind = o.kind or o._validator.kind
|
||||||
o._value = None
|
o._value = None
|
||||||
o._device = device
|
o._device = device
|
||||||
return o
|
return o
|
||||||
|
@ -84,7 +91,7 @@ class Setting(object):
|
||||||
assert hasattr(self, '_value')
|
assert hasattr(self, '_value')
|
||||||
assert hasattr(self, '_device')
|
assert hasattr(self, '_device')
|
||||||
|
|
||||||
return self._validator.choices if self._validator.kind & KIND.choice else None
|
return self._validator.choices if self._validator and self._validator.kind & KIND.choice else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def range(self):
|
def range(self):
|
||||||
|
@ -171,9 +178,9 @@ class Setting(object):
|
||||||
if hasattr(self, '_value'):
|
if hasattr(self, '_value'):
|
||||||
assert hasattr(self, '_device')
|
assert hasattr(self, '_device')
|
||||||
return '<Setting([%s:%s] %s:%s=%s)>' % (
|
return '<Setting([%s:%s] %s:%s=%s)>' % (
|
||||||
self._rw.kind, self._validator.kind, self._device.codename, self.name, self._value
|
self._rw.kind, self._validator.kind if self._validator else None, self._device.codename, self.name, self._value
|
||||||
)
|
)
|
||||||
return '<Setting([%s:%s] %s)>' % (self._rw.kind, self._validator.kind, self.name)
|
return '<Setting([%s:%s] %s)>' % (self._rw.kind, self._validator.kind if self._validator else None, self.name)
|
||||||
|
|
||||||
__unicode__ = __repr__ = __str__
|
__unicode__ = __repr__ = __str__
|
||||||
|
|
||||||
|
@ -438,7 +445,7 @@ class FeatureRW(object):
|
||||||
default_read_fnid = 0x00
|
default_read_fnid = 0x00
|
||||||
default_write_fnid = 0x10
|
default_write_fnid = 0x10
|
||||||
|
|
||||||
def __init__(self, feature, read_fnid=default_read_fnid, write_fnid=default_write_fnid, no_reply=False, **kwargs):
|
def __init__(self, feature, read_fnid=default_read_fnid, write_fnid=default_write_fnid, no_reply=False):
|
||||||
assert isinstance(feature, _NamedInt)
|
assert isinstance(feature, _NamedInt)
|
||||||
self.feature = feature
|
self.feature = feature
|
||||||
self.read_fnid = read_fnid
|
self.read_fnid = read_fnid
|
||||||
|
@ -459,32 +466,31 @@ class FeatureRWMap(FeatureRW):
|
||||||
kind = _NamedInt(0x02, 'feature')
|
kind = _NamedInt(0x02, 'feature')
|
||||||
default_read_fnid = 0x00
|
default_read_fnid = 0x00
|
||||||
default_write_fnid = 0x10
|
default_write_fnid = 0x10
|
||||||
default_key_bytes_count = 1
|
default_key_byte_count = 1
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
feature,
|
feature,
|
||||||
read_fnid=default_read_fnid,
|
read_fnid=default_read_fnid,
|
||||||
write_fnid=default_write_fnid,
|
write_fnid=default_write_fnid,
|
||||||
key_bytes_count=default_key_bytes_count,
|
key_byte_count=default_key_byte_count,
|
||||||
no_reply=False,
|
no_reply=False
|
||||||
**_ignore
|
|
||||||
):
|
):
|
||||||
assert isinstance(feature, _NamedInt)
|
assert isinstance(feature, _NamedInt)
|
||||||
self.feature = feature
|
self.feature = feature
|
||||||
self.read_fnid = read_fnid
|
self.read_fnid = read_fnid
|
||||||
self.write_fnid = write_fnid
|
self.write_fnid = write_fnid
|
||||||
self.key_bytes_count = key_bytes_count
|
self.key_byte_count = key_byte_count
|
||||||
self.no_reply = no_reply
|
self.no_reply = no_reply
|
||||||
|
|
||||||
def read(self, device, key):
|
def read(self, device, key):
|
||||||
assert self.feature is not None
|
assert self.feature is not None
|
||||||
key_bytes = _int2bytes(key, self.key_bytes_count)
|
key_bytes = _int2bytes(key, self.key_byte_count)
|
||||||
return device.feature_request(self.feature, self.read_fnid, key_bytes)
|
return device.feature_request(self.feature, self.read_fnid, key_bytes)
|
||||||
|
|
||||||
def write(self, device, key, data_bytes):
|
def write(self, device, key, data_bytes):
|
||||||
assert self.feature is not None
|
assert self.feature is not None
|
||||||
key_bytes = _int2bytes(key, self.key_bytes_count)
|
key_bytes = _int2bytes(key, self.key_byte_count)
|
||||||
reply = device.feature_request(self.feature, self.write_fnid, key_bytes, data_bytes, no_reply=self.no_reply)
|
reply = device.feature_request(self.feature, self.write_fnid, key_bytes, data_bytes, no_reply=self.no_reply)
|
||||||
return reply if not self.no_reply else True
|
return reply if not self.no_reply else True
|
||||||
|
|
||||||
|
@ -504,7 +510,7 @@ class BooleanValidator(object):
|
||||||
# mask specifies all the affected bits in the value
|
# mask specifies all the affected bits in the value
|
||||||
default_mask = 0xFF
|
default_mask = 0xFF
|
||||||
|
|
||||||
def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask, **kwargs):
|
def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask):
|
||||||
if isinstance(true_value, int):
|
if isinstance(true_value, int):
|
||||||
assert isinstance(false_value, int)
|
assert isinstance(false_value, int)
|
||||||
if mask is None:
|
if mask is None:
|
||||||
|
@ -609,7 +615,7 @@ class BitFieldValidator(object):
|
||||||
|
|
||||||
kind = KIND.multiple_toggle
|
kind = KIND.multiple_toggle
|
||||||
|
|
||||||
def __init__(self, options, byte_count=None, **kwargs):
|
def __init__(self, options, byte_count=None):
|
||||||
assert (isinstance(options, list))
|
assert (isinstance(options, list))
|
||||||
self.options = options
|
self.options = options
|
||||||
self.byte_count = (max(x.bit_length() for x in options) + 7) // 8
|
self.byte_count = (max(x.bit_length() for x in options) + 7) // 8
|
||||||
|
@ -640,9 +646,9 @@ class ChoicesValidator(object):
|
||||||
kind = KIND.choice
|
kind = KIND.choice
|
||||||
"""Translates between NamedInts and a byte sequence.
|
"""Translates between NamedInts and a byte sequence.
|
||||||
:param choices: a list of NamedInts
|
:param choices: a list of NamedInts
|
||||||
:param bytes_count: the size of the derived byte sequence. If None, it
|
:param byte_count: the size of the derived byte sequence. If None, it
|
||||||
will be calculated from the choices."""
|
will be calculated from the choices."""
|
||||||
def __init__(self, choices, bytes_count=None, read_skip_bytes_count=None, write_prefix_bytes=b'', **_ignore):
|
def __init__(self, choices, byte_count=None, read_skip_byte_count=None, write_prefix_bytes=b''):
|
||||||
assert choices is not None
|
assert choices is not None
|
||||||
assert isinstance(choices, _NamedInts)
|
assert isinstance(choices, _NamedInts)
|
||||||
assert len(choices) > 1
|
assert len(choices) > 1
|
||||||
|
@ -650,18 +656,18 @@ class ChoicesValidator(object):
|
||||||
self.needs_current_value = False
|
self.needs_current_value = False
|
||||||
|
|
||||||
max_bits = max(x.bit_length() for x in choices)
|
max_bits = max(x.bit_length() for x in choices)
|
||||||
self._bytes_count = (max_bits // 8) + (1 if max_bits % 8 else 0)
|
self._byte_count = (max_bits // 8) + (1 if max_bits % 8 else 0)
|
||||||
if bytes_count:
|
if byte_count:
|
||||||
assert self._bytes_count <= bytes_count
|
assert self._byte_count <= byte_count
|
||||||
self._bytes_count = bytes_count
|
self._byte_count = byte_count
|
||||||
assert self._bytes_count < 8
|
assert self._byte_count < 8
|
||||||
self._read_skip_bytes_count = read_skip_bytes_count if read_skip_bytes_count else 0
|
self._read_skip_byte_count = read_skip_byte_count if read_skip_byte_count else 0
|
||||||
self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b''
|
self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b''
|
||||||
assert self._bytes_count + self._read_skip_bytes_count <= 14
|
assert self._byte_count + self._read_skip_byte_count <= 14
|
||||||
assert self._bytes_count + len(self._write_prefix_bytes) <= 14
|
assert self._byte_count + len(self._write_prefix_bytes) <= 14
|
||||||
|
|
||||||
def validate_read(self, reply_bytes):
|
def validate_read(self, reply_bytes):
|
||||||
reply_value = _bytes2int(reply_bytes[self._read_skip_bytes_count:self._read_skip_bytes_count + self._bytes_count])
|
reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count:self._read_skip_byte_count + self._byte_count])
|
||||||
valid_value = self.choices[reply_value]
|
valid_value = self.choices[reply_value]
|
||||||
assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
||||||
return valid_value
|
return valid_value
|
||||||
|
@ -682,7 +688,7 @@ class ChoicesValidator(object):
|
||||||
if choice is None:
|
if choice is None:
|
||||||
raise ValueError('invalid choice %r' % new_value)
|
raise ValueError('invalid choice %r' % new_value)
|
||||||
assert isinstance(choice, _NamedInt)
|
assert isinstance(choice, _NamedInt)
|
||||||
return self._write_prefix_bytes + choice.bytes(self._bytes_count)
|
return self._write_prefix_bytes + choice.bytes(self._byte_count)
|
||||||
|
|
||||||
|
|
||||||
class ChoicesMapValidator(ChoicesValidator):
|
class ChoicesMapValidator(ChoicesValidator):
|
||||||
|
@ -691,12 +697,11 @@ class ChoicesMapValidator(ChoicesValidator):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
choices_map,
|
choices_map,
|
||||||
key_bytes_count=None,
|
key_byte_count=None,
|
||||||
bytes_count=None,
|
byte_count=None,
|
||||||
read_skip_bytes_count=0,
|
read_skip_byte_count=0,
|
||||||
write_prefix_bytes=b'',
|
write_prefix_bytes=b'',
|
||||||
extra_default=None,
|
extra_default=None
|
||||||
**kwargs
|
|
||||||
):
|
):
|
||||||
assert choices_map is not None
|
assert choices_map is not None
|
||||||
assert isinstance(choices_map, dict)
|
assert isinstance(choices_map, dict)
|
||||||
|
@ -709,25 +714,25 @@ class ChoicesMapValidator(ChoicesValidator):
|
||||||
for key_value in choices:
|
for key_value in choices:
|
||||||
assert isinstance(key_value, _NamedInt)
|
assert isinstance(key_value, _NamedInt)
|
||||||
max_value_bits = max(max_value_bits, key_value.bit_length())
|
max_value_bits = max(max_value_bits, key_value.bit_length())
|
||||||
self._key_bytes_count = (max_key_bits + 7) // 8
|
self._key_byte_count = (max_key_bits + 7) // 8
|
||||||
if key_bytes_count:
|
if key_byte_count:
|
||||||
assert self._key_bytes_count <= key_bytes_count
|
assert self._key_byte_count <= key_byte_count
|
||||||
self._key_bytes_count = key_bytes_count
|
self._key_byte_count = key_byte_count
|
||||||
self._bytes_count = (max_value_bits + 7) // 8
|
self._byte_count = (max_value_bits + 7) // 8
|
||||||
if bytes_count:
|
if byte_count:
|
||||||
assert self._bytes_count <= bytes_count
|
assert self._byte_count <= byte_count
|
||||||
self._bytes_count = bytes_count
|
self._byte_count = byte_count
|
||||||
self.choices = choices_map
|
self.choices = choices_map
|
||||||
self.needs_current_value = False
|
self.needs_current_value = False
|
||||||
self.extra_default = extra_default
|
self.extra_default = extra_default
|
||||||
self._read_skip_bytes_count = read_skip_bytes_count if read_skip_bytes_count else 0
|
self._read_skip_byte_count = read_skip_byte_count if read_skip_byte_count else 0
|
||||||
self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b''
|
self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b''
|
||||||
assert self._bytes_count + self._read_skip_bytes_count + self._key_bytes_count <= 14
|
assert self._byte_count + self._read_skip_byte_count + self._key_byte_count <= 14
|
||||||
assert self._bytes_count + len(self._write_prefix_bytes) + self._key_bytes_count <= 14
|
assert self._byte_count + len(self._write_prefix_bytes) + self._key_byte_count <= 14
|
||||||
|
|
||||||
def validate_read(self, reply_bytes, key):
|
def validate_read(self, reply_bytes, key):
|
||||||
start = self._key_bytes_count + self._read_skip_bytes_count
|
start = self._key_byte_count + self._read_skip_byte_count
|
||||||
end = start + self._bytes_count
|
end = start + self._byte_count
|
||||||
reply_value = _bytes2int(reply_bytes[start:end])
|
reply_value = _bytes2int(reply_bytes[start:end])
|
||||||
# reprogrammable keys starts out as 0, which is not a choice, so don't use assert here
|
# reprogrammable keys starts out as 0, which is not a choice, so don't use assert here
|
||||||
if self.extra_default is not None and self.extra_default == reply_value:
|
if self.extra_default is not None and self.extra_default == reply_value:
|
||||||
|
@ -740,32 +745,32 @@ class ChoicesMapValidator(ChoicesValidator):
|
||||||
choices = self.choices[key]
|
choices = self.choices[key]
|
||||||
if new_value not in choices and new_value != self.extra_default:
|
if new_value not in choices and new_value != self.extra_default:
|
||||||
raise ValueError('invalid choice %r' % new_value)
|
raise ValueError('invalid choice %r' % new_value)
|
||||||
return self._write_prefix_bytes + new_value.to_bytes(self._bytes_count, 'big')
|
return self._write_prefix_bytes + new_value.to_bytes(self._byte_count, 'big')
|
||||||
|
|
||||||
|
|
||||||
class RangeValidator(object):
|
class RangeValidator(object):
|
||||||
__slots__ = ('min_value', 'max_value', 'flag', '_bytes_count', 'needs_current_value')
|
__slots__ = ('min_value', 'max_value', 'flag', '_byte_count', 'needs_current_value')
|
||||||
|
|
||||||
kind = KIND.range
|
kind = KIND.range
|
||||||
"""Translates between integers and a byte sequence.
|
"""Translates between integers and a byte sequence.
|
||||||
:param min_value: minimum accepted value (inclusive)
|
:param min_value: minimum accepted value (inclusive)
|
||||||
:param max_value: maximum accepted value (inclusive)
|
:param max_value: maximum accepted value (inclusive)
|
||||||
:param bytes_count: the size of the derived byte sequence. If None, it
|
:param byte_count: the size of the derived byte sequence. If None, it
|
||||||
will be calculated from the range."""
|
will be calculated from the range."""
|
||||||
def __init__(self, min_value, max_value, bytes_count=None, **kwargs):
|
def __init__(self, min_value, max_value, byte_count=None):
|
||||||
assert max_value > min_value
|
assert max_value > min_value
|
||||||
self.min_value = min_value
|
self.min_value = min_value
|
||||||
self.max_value = max_value
|
self.max_value = max_value
|
||||||
self.needs_current_value = False
|
self.needs_current_value = False
|
||||||
|
|
||||||
self._bytes_count = math.ceil(math.log(max_value + 1, 256))
|
self._byte_count = math.ceil(math.log(max_value + 1, 256))
|
||||||
if bytes_count:
|
if byte_count:
|
||||||
assert self._bytes_count <= bytes_count
|
assert self._byte_count <= byte_count
|
||||||
self._bytes_count = bytes_count
|
self._byte_count = byte_count
|
||||||
assert self._bytes_count < 8
|
assert self._byte_count < 8
|
||||||
|
|
||||||
def validate_read(self, reply_bytes):
|
def validate_read(self, reply_bytes):
|
||||||
reply_value = _bytes2int(reply_bytes[:self._bytes_count])
|
reply_value = _bytes2int(reply_bytes[:self._byte_count])
|
||||||
assert reply_value >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
assert reply_value >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
||||||
assert reply_value <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
assert reply_value <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
||||||
return reply_value
|
return reply_value
|
||||||
|
@ -773,4 +778,4 @@ class RangeValidator(object):
|
||||||
def prepare_write(self, new_value, current_value=None):
|
def prepare_write(self, new_value, current_value=None):
|
||||||
if new_value < self.min_value or new_value > self.max_value:
|
if new_value < self.min_value or new_value > self.max_value:
|
||||||
raise ValueError('invalid choice %r' % new_value)
|
raise ValueError('invalid choice %r' % new_value)
|
||||||
return _int2bytes(new_value, self._bytes_count)
|
return _int2bytes(new_value, self._byte_count)
|
||||||
|
|
|
@ -31,7 +31,6 @@ from .common import bytes2int as _bytes2int
|
||||||
from .common import int2bytes as _int2bytes
|
from .common import int2bytes as _int2bytes
|
||||||
from .common import unpack as _unpack
|
from .common import unpack as _unpack
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .settings import KIND as _KIND
|
|
||||||
from .settings import BitFieldSetting as _BitFieldSetting
|
from .settings import BitFieldSetting as _BitFieldSetting
|
||||||
from .settings import BitFieldValidator as _BitFieldV
|
from .settings import BitFieldValidator as _BitFieldV
|
||||||
from .settings import BooleanValidator as _BooleanV
|
from .settings import BooleanValidator as _BooleanV
|
||||||
|
@ -51,292 +50,165 @@ _DK = _hidpp10.DEVICE_KIND
|
||||||
_R = _hidpp10.REGISTERS
|
_R = _hidpp10.REGISTERS
|
||||||
_F = _hidpp20.FEATURE
|
_F = _hidpp20.FEATURE
|
||||||
|
|
||||||
#
|
|
||||||
# pre-defined basic setting descriptors
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
def register_toggle(
|
|
||||||
name,
|
|
||||||
register,
|
|
||||||
true_value=_BooleanV.default_true,
|
|
||||||
false_value=_BooleanV.default_false,
|
|
||||||
mask=_BooleanV.default_mask,
|
|
||||||
device_kind=None
|
|
||||||
):
|
|
||||||
validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
|
|
||||||
rw = _RegisterRW(register)
|
|
||||||
return _Setting(name, rw, validator, device_kind=device_kind)
|
|
||||||
|
|
||||||
|
|
||||||
def register_choices(name, register, choices, kind=_KIND.choice, device_kind=None):
|
|
||||||
assert choices
|
|
||||||
validator = _ChoicesV(choices)
|
|
||||||
rw = _RegisterRW(register)
|
|
||||||
return _Setting(name, rw, validator, kind=kind, device_kind=device_kind)
|
|
||||||
|
|
||||||
|
|
||||||
def feature_toggle(
|
|
||||||
name,
|
|
||||||
feature,
|
|
||||||
read_fnid=_FeatureRW.default_read_fnid,
|
|
||||||
write_fnid=_FeatureRW.default_write_fnid,
|
|
||||||
true_value=_BooleanV.default_true,
|
|
||||||
false_value=_BooleanV.default_false,
|
|
||||||
mask=_BooleanV.default_mask,
|
|
||||||
device_kind=None
|
|
||||||
):
|
|
||||||
validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
|
|
||||||
rw = _FeatureRW(feature, read_fnid=read_fnid, write_fnid=write_fnid)
|
|
||||||
return _Setting(name, rw, validator, feature=feature, device_kind=device_kind)
|
|
||||||
|
|
||||||
|
|
||||||
def feature_bitfield_toggle(
|
|
||||||
name,
|
|
||||||
feature,
|
|
||||||
options,
|
|
||||||
read_fnid=_FeatureRW.default_read_fnid,
|
|
||||||
write_fnid=_FeatureRW.default_write_fnid,
|
|
||||||
device_kind=None
|
|
||||||
):
|
|
||||||
assert options
|
|
||||||
validator = _BitFieldV(options)
|
|
||||||
rw = _FeatureRW(feature, read_fnid=read_fnid, write_fnid=write_fnid)
|
|
||||||
return _BitFieldSetting(name, rw, validator, feature=feature, device_kind=device_kind)
|
|
||||||
|
|
||||||
|
|
||||||
def feature_bitfield_toggle_dynamic(
|
|
||||||
name,
|
|
||||||
feature,
|
|
||||||
options_callback,
|
|
||||||
read_fnid=_FeatureRW.default_read_fnid,
|
|
||||||
write_fnid=_FeatureRW.default_write_fnid,
|
|
||||||
device_kind=None
|
|
||||||
):
|
|
||||||
def instantiate(device):
|
|
||||||
options = options_callback(device)
|
|
||||||
setting = feature_bitfield_toggle(
|
|
||||||
name, feature, options, read_fnid=read_fnid, write_fnid=write_fnid, device_kind=device_kind
|
|
||||||
)
|
|
||||||
return setting(device)
|
|
||||||
|
|
||||||
instantiate._rw_kind = _FeatureRW.kind
|
|
||||||
return instantiate
|
|
||||||
|
|
||||||
|
|
||||||
def feature_choices(name, feature, choices, **kwargs):
|
|
||||||
assert choices
|
|
||||||
validator = _ChoicesV(choices, **kwargs)
|
|
||||||
rw = _FeatureRW(feature, **kwargs)
|
|
||||||
return _Setting(name, rw, validator, feature=feature, kind=_KIND.choice, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def feature_choices_dynamic(name, feature, choices_callback, **kwargs):
|
|
||||||
# Proxy that obtains choices dynamically from a device
|
|
||||||
def instantiate(device):
|
|
||||||
# Obtain choices for this feature
|
|
||||||
choices = choices_callback(device)
|
|
||||||
if not choices: # no choices, so don't create a setting
|
|
||||||
return None
|
|
||||||
setting = feature_choices(name, feature, choices, **kwargs)
|
|
||||||
return setting(device)
|
|
||||||
|
|
||||||
instantiate._rw_kind = _FeatureRW.kind
|
|
||||||
return instantiate
|
|
||||||
|
|
||||||
|
|
||||||
# maintain a mapping from keys (NamedInts) to one of a list of choices (NamedInts), default is first one
|
|
||||||
# the setting is stored as a JSON-compatible object mapping the key int (as a string) to the choice int
|
|
||||||
def feature_map_choices(name, feature, choicesmap, **kwargs):
|
|
||||||
assert choicesmap
|
|
||||||
validator = _ChoicesMapV(choicesmap, **kwargs)
|
|
||||||
rw = _FeatureRWMap(feature, **kwargs)
|
|
||||||
return _Settings(name, rw, validator, feature=feature, kind=_KIND.map_choice, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def feature_map_choices_dynamic(name, feature, choices_callback, **kwargs):
|
|
||||||
# Proxy that obtains choices dynamically from a device
|
|
||||||
def instantiate(device):
|
|
||||||
choices = choices_callback(device)
|
|
||||||
if not choices: # no choices, so don't create a Setting
|
|
||||||
return None
|
|
||||||
setting = feature_map_choices(name, feature, choices, **kwargs)
|
|
||||||
return setting(device)
|
|
||||||
|
|
||||||
instantiate._rw_kind = _FeatureRWMap.kind
|
|
||||||
return instantiate
|
|
||||||
|
|
||||||
|
|
||||||
def feature_range(
|
|
||||||
name,
|
|
||||||
feature,
|
|
||||||
min_value,
|
|
||||||
max_value,
|
|
||||||
read_fnid=_FeatureRW.default_read_fnid,
|
|
||||||
write_fnid=_FeatureRW.default_write_fnid,
|
|
||||||
rw=None,
|
|
||||||
bytes_count=None,
|
|
||||||
device_kind=None
|
|
||||||
):
|
|
||||||
validator = _RangeV(min_value, max_value, bytes_count=bytes_count)
|
|
||||||
if rw is None:
|
|
||||||
rw = _FeatureRW(feature, read_fnid=read_fnid, write_fnid=write_fnid)
|
|
||||||
return _Setting(name, rw, validator, feature=feature, kind=_KIND.range, device_kind=device_kind)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# common strings for settings - name, string to display in main window, tool tip for main window
|
# common strings for settings - name, string to display in main window, tool tip for main window
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# yapf: disable
|
||||||
_HAND_DETECTION = ('hand-detection', _('Hand Detection'), _('Turn on illumination when the hands hover over the keyboard.'))
|
_HAND_DETECTION = ('hand-detection', _('Hand Detection'), _('Turn on illumination when the hands hover over the keyboard.'))
|
||||||
_SMOOTH_SCROLL = ('smooth-scroll', _('Smooth Scrolling'), _('High-sensitivity mode for vertical scroll with the wheel.'))
|
_SMOOTH_SCROLL = ('smooth-scroll', _('Smooth Scrolling'), _('High-sensitivity mode for vertical scroll with the wheel.'))
|
||||||
_SIDE_SCROLL = (
|
_SIDE_SCROLL = ('side-scroll', _('Side Scrolling'),
|
||||||
'side-scroll', _('Side Scrolling'),
|
_('When disabled, pushing the wheel sideways sends custom button events\n'
|
||||||
_(
|
'instead of the standard side-scrolling events.'))
|
||||||
'When disabled, pushing the wheel sideways sends custom button events\n'
|
_HI_RES_SCROLL = ('hi-res-scroll', _('High Resolution Scrolling'),
|
||||||
'instead of the standard side-scrolling events.'
|
_('High-sensitivity mode for vertical scroll with the wheel.'))
|
||||||
)
|
_LOW_RES_SCROLL = ('lowres-smooth-scroll', _('HID++ Scrolling'),
|
||||||
)
|
_('HID++ mode for vertical scroll with the wheel.') + '\n' +
|
||||||
_HI_RES_SCROLL = (
|
_('Effectively turns off wheel scrolling in Linux.'))
|
||||||
'hi-res-scroll', _('High Resolution Scrolling'), _('High-sensitivity mode for vertical scroll with the wheel.')
|
_HIRES_INV = ('hires-smooth-invert', _('High Resolution Wheel Invert'),
|
||||||
)
|
_('High-sensitivity wheel invert direction for vertical scroll.'))
|
||||||
_LOW_RES_SCROLL = (
|
_HIRES_RES = ('hires-smooth-resolution', _('Wheel Resolution'),
|
||||||
'lowres-smooth-scroll', _('HID++ Scrolling'),
|
_('High-sensitivity mode for vertical scroll with the wheel.'))
|
||||||
_('HID++ mode for vertical scroll with the wheel.') + '\n' + _('Effectively turns off wheel scrolling in Linux.')
|
_FN_SWAP = ('fn-swap', _('Swap Fx function'),
|
||||||
)
|
_('When set, the F1..F12 keys will activate their special function,\n'
|
||||||
_HIRES_INV = (
|
'and you must hold the FN key to activate their standard function.') + '\n\n' +
|
||||||
'hires-smooth-invert', _('High Resolution Wheel Invert'),
|
_('When unset, the F1..F12 keys will activate their standard function,\n'
|
||||||
_('High-sensitivity wheel invert direction for vertical scroll.')
|
'and you must hold the FN key to activate their special function.'))
|
||||||
)
|
|
||||||
_HIRES_RES = ('hires-smooth-resolution', _('Wheel Resolution'), _('High-sensitivity mode for vertical scroll with the wheel.'))
|
|
||||||
_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.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_DPI = ('dpi', _('Sensitivity (DPI)'), None)
|
_DPI = ('dpi', _('Sensitivity (DPI)'), None)
|
||||||
_POINTER_SPEED = (
|
_POINTER_SPEED = ('pointer_speed', _('Sensitivity (Pointer Speed)'),
|
||||||
'pointer_speed', _('Sensitivity (Pointer Speed)'), _('Speed multiplier for mouse (256 is normal multiplier).')
|
_('Speed multiplier for mouse (256 is normal multiplier).'))
|
||||||
)
|
_SMART_SHIFT = ('smart-shift', _('Smart Shift'),
|
||||||
_SMART_SHIFT = (
|
_('Automatically switch the mouse wheel between ratchet and freespin mode.\n'
|
||||||
'smart-shift', _('Smart Shift'),
|
'The mouse wheel is always free at 0, and always locked at 50'))
|
||||||
_(
|
|
||||||
'Automatically switch the mouse wheel between ratchet and freespin mode.\n'
|
|
||||||
'The mouse wheel is always free at 0, and always locked at 50'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_BACKLIGHT = ('backlight', _('Backlight'), _('Turn illumination on or off on keyboard.'))
|
_BACKLIGHT = ('backlight', _('Backlight'), _('Turn illumination on or off on keyboard.'))
|
||||||
_REPROGRAMMABLE_KEYS = (
|
_REPROGRAMMABLE_KEYS = ('reprogrammable-keys', _('Actions'),
|
||||||
'reprogrammable-keys', _('Actions'), _('Change the action for the key or button.') + '\n' +
|
_('Change the action for the key or button.') + '\n' +
|
||||||
_('Changing important actions (such as for the left mouse button) can result in an unusable system.')
|
_('Changing important actions (such as for the left mouse button) can result in an unusable system.'))
|
||||||
)
|
|
||||||
_DISABLE_KEYS = ('disable-keyboard-keys', _('Disable keys'), _('Disable specific keyboard keys.'))
|
_DISABLE_KEYS = ('disable-keyboard-keys', _('Disable keys'), _('Disable specific keyboard keys.'))
|
||||||
_PLATFORM = ('multiplatform', _('Set OS'), _('Change keys to match OS.'))
|
_PLATFORM = ('multiplatform', _('Set OS'), _('Change keys to match OS.'))
|
||||||
_CHANGE_HOST = ('change-host', _('Change Host'), _('Switch connection to a different host'))
|
_CHANGE_HOST = ('change-host', _('Change Host'), _('Switch connection to a different host'))
|
||||||
_THUMB_SCROLL_MODE = (
|
_THUMB_SCROLL_MODE = ('thumb-scroll-mode', _('HID++ Thumb Scrolling'),
|
||||||
'thumb-scroll-mode', _('HID++ Thumb Scrolling'),
|
_('HID++ mode for horizontal scroll with the thumb wheel.') + '\n' +
|
||||||
_('HID++ mode for horizontal scroll with the thumb wheel.') + '\n' + _('Effectively turns off thumb scrolling in Linux.')
|
_('Effectively turns off thumb scrolling in Linux.'))
|
||||||
)
|
|
||||||
_THUMB_SCROLL_INVERT = ('thumb-scroll-invert', _('Thumb Scroll Invert'), _('Invert thumb scroll direction.'))
|
_THUMB_SCROLL_INVERT = ('thumb-scroll-invert', _('Thumb Scroll Invert'), _('Invert thumb scroll direction.'))
|
||||||
|
# yapf: enable
|
||||||
|
|
||||||
|
# Setting template functions need to set up the setting itself, the validator, and the reader/writer.
|
||||||
|
# The reader/writer is responsible for reading raw values from the device and writing values to it.
|
||||||
|
# The validator is responsible for turning read raw values into Python data and Python data into raw values to be written.
|
||||||
|
# The setting keeps everything together and provides control.
|
||||||
#
|
#
|
||||||
# Keyword arguments for setting template functions:
|
# The _Setting class is for settings with simple values (booleans, numbers in a range, and symbolic choices).
|
||||||
# persist=True - whether to store the values and reapply them from now on
|
# Its positional arguments are the strings for the setting and the reader/writer.
|
||||||
# device_kind - the kinds of devices that setting is suitable for (NOT CURRENTLY USED)
|
# The validator keyword (or third) argument is the validator, if the validator does not depend on information from the device.
|
||||||
# read_fnid=0x00, write_fnid=0x10 - default 0x00 and 0x10 function numbers (times 16) to read and write setting
|
# The callback keyword argument is a function that given a device as argument returns the validator or None.
|
||||||
# bytes_count=1 - number of bytes for the data (ignoring the key, if any)
|
# If the callback function returns None the setting is not created for the device.
|
||||||
# only for boolean settings
|
# Either a validator or callback must be specified, but not both.
|
||||||
# true_value=0x01, false_value=0x00, mask=0xFF - integer or byte strings for boolean settings
|
# The device_kind keyword argument (default None) says what kinds of devices can use the setting.
|
||||||
# only for map choices
|
# (This argument is currently not used because keyboards with integrated trackpads break its abstraction.)
|
||||||
# key_bytes_count=1 - number of bytes in the key
|
# The persist keyword argument (default True) says whether to store the value and apply it when setting up the device.
|
||||||
# extra_default - extra value that cannot be set but means same as default value
|
#
|
||||||
# only for choices and map choices
|
# There are two simple reader/writers - _RegisterRW and _FeatureRW.
|
||||||
# read_skip_bytes_count=0 - number of bytes to ignore before the data when reading
|
# _RegisterRW is for register-based settings and takes the register name as argument.
|
||||||
# write_prefix_bytes=b'' - bytes to put before the data writing
|
# _FeatureRW is for feature-based settings and takes the feature name as positional argument plus the following:
|
||||||
|
# read_fnid is the feature function (times 16) to read the value (default 0x00),
|
||||||
|
# write_fnid is the feature function (times 16) to write the value (default 0x10),
|
||||||
|
# no_reply is whether to wait for a reply (default false) (USE WITH EXTREME CAUTION).
|
||||||
|
#
|
||||||
|
# There are three simple validators - _BooleanV, _RangeV, and _ChoicesV
|
||||||
|
# _BooleanV is for boolean values. It takes three keyword arguments that can be integers or byte strings:
|
||||||
|
# true_value is the raw value for true (default 0x01),
|
||||||
|
# false_value is the raw value for false (default 0x00),
|
||||||
|
# mask is used to keep only some bits from a sequence of bits.
|
||||||
|
# _RangeV is for an integer in a range. It takes three keyword arguments:
|
||||||
|
# min_value is the minimum value for the setting,
|
||||||
|
# max_value is the maximum value for the setting,
|
||||||
|
# byte_count is number of bytes that the value is stored in (defaults to size of max_value).
|
||||||
|
# _ChoicesV is for symbolic choices. It takes one positional and three keyword arguments:
|
||||||
|
# the positional argument is a list of named integers that are the valid choices,
|
||||||
|
# byte_count is the number of bytes for the integer (default size of largest choice integer),
|
||||||
|
# read_skip_byte_count is the number of bytes to ignore at the beginning of the read value (default 0),
|
||||||
|
# write_prefix_bytes is a byte string to write before the value (default empty).
|
||||||
|
#
|
||||||
|
# The _Settings class is for settings that are maps from keys to values.
|
||||||
|
# The _BitFieldSetting class is for settings that have multiple boolean values packed into a bit field.
|
||||||
|
# They have has same arguments as the _Setting class.
|
||||||
|
#
|
||||||
|
# _ChoicesMapV validator is for map settings that map onto symbolic choices. It takes four keyword arguments:
|
||||||
|
# the positional argument is the choices map
|
||||||
|
# byte_count is as for _ChoicesV,
|
||||||
|
# read_skip_byte_count is as for _ChoicesV,
|
||||||
|
# write_prefix_bytes is as for _ChoicesV,
|
||||||
|
# key_byte_count is the number of bytes for the key integer (default size of largest key),
|
||||||
|
# extra_default is an extra raw value that is used as a default value (default None).
|
||||||
|
# _BitFieldV validator is for bit field settings. It takes one positional and one keyword argument
|
||||||
|
# the positional argument is the number of bits in the bit field
|
||||||
|
# byte_count is the size of the bit field (default size of the bit field)
|
||||||
|
|
||||||
|
|
||||||
def _register_hand_detection(
|
def _register_hand_detection():
|
||||||
register=_R.keyboard_hand_detection, true_value=b'\x00\x00\x00', false_value=b'\x00\x00\x30', mask=b'\x00\x00\xFF'
|
validator = _BooleanV(true_value=b'\x00\x00\x00', false_value=b'\x00\x00\x30', mask=b'\x00\x00\xFF')
|
||||||
):
|
return _Setting(_HAND_DETECTION, _RegisterRW(_R.keyboard_hand_detection), validator, device_kind=(_DK.keyboard, ))
|
||||||
return register_toggle(
|
|
||||||
_HAND_DETECTION, register, true_value=true_value, false_value=false_value, device_kind=(_DK.keyboard, )
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _register_fn_swap(register=_R.keyboard_fn_swap, true_value=b'\x00\x01', mask=b'\x00\x01'):
|
def _register_fn_swap():
|
||||||
return register_toggle(_FN_SWAP, register, true_value=true_value, mask=mask, device_kind=(_DK.keyboard, ))
|
validator = _BooleanV(true_value=b'\x00\x01', mask=b'\x00\x01')
|
||||||
|
return _Setting(_FN_SWAP, _RegisterRW(_R.keyboard_fn_swap), validator, device_kind=(_DK.keyboard, ))
|
||||||
|
|
||||||
|
|
||||||
def _register_smooth_scroll(register=_R.mouse_button_flags, true_value=0x40, mask=0x40):
|
def _register_smooth_scroll():
|
||||||
return register_toggle(_SMOOTH_SCROLL, register, true_value=true_value, mask=mask, device_kind=(_DK.mouse, _DK.trackball))
|
validator = _BooleanV(true_value=0x40, mask=0x40)
|
||||||
|
return _Setting(_SMOOTH_SCROLL, _RegisterRW(_R.mouse_button_flags), validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
|
|
||||||
|
|
||||||
def _register_side_scroll(register=_R.mouse_button_flags, true_value=0x02, mask=0x02):
|
def _register_side_scroll():
|
||||||
return register_toggle(_SIDE_SCROLL, register, true_value=true_value, mask=mask, device_kind=(_DK.mouse, _DK.trackball))
|
validator = _BooleanV(true_value=0x02, mask=0x02)
|
||||||
|
return _Setting(_SIDE_SCROLL, _RegisterRW(_R.mouse_button_flags), validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
|
|
||||||
|
|
||||||
def _register_dpi(register=_R.mouse_dpi, choices=None):
|
def _register_dpi(choices=None):
|
||||||
return register_choices(_DPI, register, choices, device_kind=(_DK.mouse, _DK.trackball))
|
return _Setting(_DPI, _RegisterRW(_R.mouse_dpi), _ChoicesV(choices), device_kind=(_DK.mouse, _DK.trackball))
|
||||||
|
|
||||||
|
|
||||||
def _feature_fn_swap():
|
def _feature_fn_swap():
|
||||||
return feature_toggle(_FN_SWAP, _F.FN_INVERSION, device_kind=(_DK.keyboard, ))
|
return _Setting(_FN_SWAP, _FeatureRW(_F.FN_INVERSION), _BooleanV(), device_kind=(_DK.keyboard, ))
|
||||||
|
|
||||||
|
|
||||||
# this might not be correct for this feature
|
|
||||||
def _feature_new_fn_swap():
|
def _feature_new_fn_swap():
|
||||||
return feature_toggle(_FN_SWAP, _F.NEW_FN_INVERSION, device_kind=(_DK.keyboard, ))
|
return _Setting(_FN_SWAP, _FeatureRW(_F.NEW_FN_INVERSION), _BooleanV(), device_kind=(_DK.keyboard, ))
|
||||||
|
|
||||||
|
|
||||||
# ignore the capabilities part of the feature - all devices should be able to swap Fn state
|
# ignore the capabilities part of the feature - all devices should be able to swap Fn state
|
||||||
# just use the current host (first byte = 0xFF) part of the feature to read and set the Fn state
|
# just use the current host (first byte = 0xFF) part of the feature to read and set the Fn state
|
||||||
def _feature_k375s_fn_swap():
|
def _feature_k375s_fn_swap():
|
||||||
return feature_toggle(
|
validator = _BooleanV(true_value=b'\xFF\x01', false_value=b'\xFF\x00')
|
||||||
_FN_SWAP, _F.K375S_FN_INVERSION, true_value=b'\xFF\x01', false_value=b'\xFF\x00', device_kind=(_DK.keyboard, )
|
return _Setting(_FN_SWAP, _FeatureRW(_F.K375S_FN_INVERSION), validator, device_kind=(_DK.keyboard, ))
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: This will enable all supported backlight settings,
|
# FIXME: This will enable all supported backlight settings,
|
||||||
# we should allow the users to select which settings they want to enable.
|
# we should allow the users to select which settings they want to enable.
|
||||||
def _feature_backlight2():
|
def _feature_backlight2():
|
||||||
return feature_toggle(_BACKLIGHT, _F.BACKLIGHT2, device_kind=(_DK.keyboard, ))
|
return _Setting(_BACKLIGHT, _FeatureRW(_F.BACKLIGHT2), _BooleanV(), device_kind=(_DK.keyboard, ))
|
||||||
|
|
||||||
|
|
||||||
def _feature_hi_res_scroll():
|
def _feature_hi_res_scroll():
|
||||||
return feature_toggle(_HI_RES_SCROLL, _F.HI_RES_SCROLLING, device_kind=(_DK.mouse, _DK.trackball))
|
return _Setting(_HI_RES_SCROLL, _FeatureRW(_F.HI_RES_SCROLLING), _BooleanV(), device_kind=(_DK.mouse, _DK.trackball))
|
||||||
|
|
||||||
|
|
||||||
def _feature_lowres_smooth_scroll():
|
def _feature_lowres_smooth_scroll():
|
||||||
return feature_toggle(_LOW_RES_SCROLL, _F.LOWRES_WHEEL, device_kind=(_DK.mouse, _DK.trackball))
|
return _Setting(_LOW_RES_SCROLL, _FeatureRW(_F.LOWRES_WHEEL), _BooleanV(), device_kind=(_DK.mouse, _DK.trackball))
|
||||||
|
|
||||||
|
|
||||||
def _feature_hires_smooth_invert():
|
def _feature_hires_smooth_invert():
|
||||||
return feature_toggle(
|
rw = _FeatureRW(_F.HIRES_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||||
_HIRES_INV,
|
validator = _BooleanV(true_value=0x04, mask=0x04)
|
||||||
_F.HIRES_WHEEL,
|
return _Setting(_HIRES_INV, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
read_fnid=0x10,
|
|
||||||
write_fnid=0x20,
|
|
||||||
true_value=0x04,
|
|
||||||
mask=0x04,
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_hires_smooth_resolution():
|
def _feature_hires_smooth_resolution():
|
||||||
return feature_toggle(
|
rw = _FeatureRW(_F.HIRES_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||||
_HIRES_RES,
|
validator = _BooleanV(true_value=0x02, mask=0x02)
|
||||||
_F.HIRES_WHEEL,
|
return _Setting(_HIRES_RES, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
read_fnid=0x10,
|
|
||||||
write_fnid=0x20,
|
|
||||||
true_value=0x02,
|
|
||||||
mask=0x02,
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_smart_shift():
|
def _feature_smart_shift():
|
||||||
|
@ -361,26 +233,17 @@ def _feature_smart_shift():
|
||||||
threshold = _bytes2int(data_bytes)
|
threshold = _bytes2int(data_bytes)
|
||||||
# Freespin at minimum
|
# Freespin at minimum
|
||||||
mode = 1 if threshold == _MIN_SMART_SHIFT_VALUE else 2
|
mode = 1 if threshold == _MIN_SMART_SHIFT_VALUE else 2
|
||||||
|
|
||||||
# Ratchet at maximum
|
# Ratchet at maximum
|
||||||
if threshold == _MAX_SMART_SHIFT_VALUE:
|
if threshold == _MAX_SMART_SHIFT_VALUE:
|
||||||
threshold = 255
|
threshold = 255
|
||||||
|
|
||||||
data = _int2bytes(mode, count=1) + _int2bytes(threshold, count=1) * 2
|
data = _int2bytes(mode, count=1) + _int2bytes(threshold, count=1) * 2
|
||||||
return super(_SmartShiftRW, self).write(device, data)
|
return super(_SmartShiftRW, self).write(device, data)
|
||||||
|
|
||||||
return feature_range(
|
validator = _RangeV(_MIN_SMART_SHIFT_VALUE, _MAX_SMART_SHIFT_VALUE, 1)
|
||||||
_SMART_SHIFT,
|
return _Setting(_SMART_SHIFT, _SmartShiftRW(_F.SMART_SHIFT), validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
_F.SMART_SHIFT,
|
|
||||||
_MIN_SMART_SHIFT_VALUE,
|
|
||||||
_MAX_SMART_SHIFT_VALUE,
|
|
||||||
bytes_count=1,
|
|
||||||
rw=_SmartShiftRW(_F.SMART_SHIFT),
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_adjustable_dpi_choices(device):
|
def _feature_adjustable_dpi_callback(device):
|
||||||
# [1] getSensorDpiList(sensorIdx)
|
# [1] getSensorDpiList(sensorIdx)
|
||||||
reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10)
|
reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10)
|
||||||
# Should not happen, but might happen when the user unplugs device while the
|
# Should not happen, but might happen when the user unplugs device while the
|
||||||
|
@ -400,7 +263,7 @@ def _feature_adjustable_dpi_choices(device):
|
||||||
if step:
|
if step:
|
||||||
assert len(dpi_list) == 2, 'Invalid DPI list range: %r' % dpi_list
|
assert len(dpi_list) == 2, 'Invalid DPI list range: %r' % dpi_list
|
||||||
dpi_list = range(dpi_list[0], dpi_list[1] + 1, step)
|
dpi_list = range(dpi_list[0], dpi_list[1] + 1, step)
|
||||||
return _NamedInts.list(dpi_list)
|
return _ChoicesV(_NamedInts.list(dpi_list), byte_count=3) if dpi_list else None
|
||||||
|
|
||||||
|
|
||||||
def _feature_adjustable_dpi():
|
def _feature_adjustable_dpi():
|
||||||
|
@ -408,77 +271,49 @@ def _feature_adjustable_dpi():
|
||||||
# Assume sensorIdx 0 (there is only one sensor)
|
# Assume sensorIdx 0 (there is only one sensor)
|
||||||
# [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB
|
# [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB
|
||||||
# [3] setSensorDpi(sensorIdx, dpi)
|
# [3] setSensorDpi(sensorIdx, dpi)
|
||||||
return feature_choices_dynamic(
|
rw = _FeatureRW(_F.ADJUSTABLE_DPI, read_fnid=0x20, write_fnid=0x30)
|
||||||
_DPI,
|
return _Setting(_DPI, rw, callback=_feature_adjustable_dpi_callback, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
_F.ADJUSTABLE_DPI,
|
|
||||||
_feature_adjustable_dpi_choices,
|
|
||||||
read_fnid=0x20,
|
|
||||||
write_fnid=0x30,
|
|
||||||
bytes_count=3,
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_pointer_speed():
|
def _feature_pointer_speed():
|
||||||
"""Pointer Speed feature"""
|
"""Pointer Speed feature"""
|
||||||
# min and max values taken from usb traces of Win software
|
# min and max values taken from usb traces of Win software
|
||||||
return feature_range(
|
validator = _RangeV(0x002e, 0x01ff, 2)
|
||||||
_POINTER_SPEED,
|
rw = _FeatureRW(_F.POINTER_SPEED)
|
||||||
_F.POINTER_SPEED,
|
return _Setting(_POINTER_SPEED, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
0x002e,
|
|
||||||
0x01ff,
|
|
||||||
read_fnid=0x0,
|
|
||||||
write_fnid=0x10,
|
|
||||||
bytes_count=2,
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# the keys for the choice map are Logitech controls (from special_keys)
|
# the keys for the choice map are Logitech controls (from special_keys)
|
||||||
# each choice value is a NamedInt with the string from a task (to be shown to the user)
|
# each choice value is a NamedInt with the string from a task (to be shown to the user)
|
||||||
# and the integer being the control number for that task (to be written to the device)
|
# and the integer being the control number for that task (to be written to the device)
|
||||||
# Solaar only remaps keys (controlled by key gmask and group), not other key reprogramming
|
# Solaar only remaps keys (controlled by key gmask and group), not other key reprogramming
|
||||||
def _feature_reprogrammable_keys_choices(device):
|
def _feature_reprogrammable_keys_callback(device):
|
||||||
choices = {}
|
choices = {}
|
||||||
for k in device.keys:
|
for k in device.keys:
|
||||||
tgts = k.remappable_to
|
tgts = k.remappable_to
|
||||||
if len(tgts) > 1:
|
if len(tgts) > 1:
|
||||||
choices[k.key] = tgts
|
choices[k.key] = tgts
|
||||||
|
if not choices:
|
||||||
return choices
|
return None
|
||||||
|
return _ChoicesMapV(
|
||||||
|
choices, key_byte_count=2, byte_count=2, read_skip_byte_count=1, write_prefix_bytes=b'\x00', extra_default=0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _feature_reprogrammable_keys():
|
def _feature_reprogrammable_keys():
|
||||||
return feature_map_choices_dynamic(
|
rw = _FeatureRWMap(_F.REPROG_CONTROLS_V4, read_fnid=0x20, write_fnid=0x30, key_byte_count=2)
|
||||||
_REPROGRAMMABLE_KEYS,
|
return _Settings(_REPROGRAMMABLE_KEYS, rw, callback=_feature_reprogrammable_keys_callback, device_kind=(_DK.keyboard, ))
|
||||||
_F.REPROG_CONTROLS_V4,
|
|
||||||
_feature_reprogrammable_keys_choices,
|
|
||||||
read_fnid=0x20,
|
|
||||||
write_fnid=0x30,
|
|
||||||
key_bytes_count=2,
|
|
||||||
bytes_count=2,
|
|
||||||
read_skip_bytes_count=1,
|
|
||||||
write_prefix_bytes=b'\x00',
|
|
||||||
extra_default=0,
|
|
||||||
device_kind=(_DK.keyboard, ),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_disable_keyboard_keys_key_list(device):
|
def _feature_disable_keyboard_keys_callback(device):
|
||||||
mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS)[0]
|
mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS)[0]
|
||||||
options = [_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)]
|
options = [_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)]
|
||||||
return options
|
return _BitFieldV(options) if options else None
|
||||||
|
|
||||||
|
|
||||||
def _feature_disable_keyboard_keys():
|
def _feature_disable_keyboard_keys():
|
||||||
return feature_bitfield_toggle_dynamic(
|
rw = _FeatureRW(_F.KEYBOARD_DISABLE_KEYS, read_fnid=0x10, write_fnid=0x20)
|
||||||
_DISABLE_KEYS,
|
return _BitFieldSetting(_DISABLE_KEYS, rw, callback=_feature_disable_keyboard_keys_callback, device_kind=(_DK.keyboard, ))
|
||||||
_F.KEYBOARD_DISABLE_KEYS,
|
|
||||||
_feature_disable_keyboard_keys_key_list,
|
|
||||||
read_fnid=0x10,
|
|
||||||
write_fnid=0x20,
|
|
||||||
device_kind=(_DK.keyboard, )
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# muultiplatform OS bits
|
# muultiplatform OS bits
|
||||||
|
@ -486,7 +321,7 @@ OSS = [('Linux', 0x0400), ('MacOS', 0x2000), ('Windows', 0x0100), ('iOS', 0x4000
|
||||||
('Chrome', 0x0800), ('WinEmb', 0x0200), ('Tizen', 0x0001)]
|
('Chrome', 0x0800), ('WinEmb', 0x0200), ('Tizen', 0x0001)]
|
||||||
|
|
||||||
|
|
||||||
def _feature_multiplatform_choices(device):
|
def _feature_multiplatform_callback(device):
|
||||||
def _str_os_versions(low, high):
|
def _str_os_versions(low, high):
|
||||||
def _str_os_version(version):
|
def _str_os_version(version):
|
||||||
if version == 0:
|
if version == 0:
|
||||||
|
@ -514,19 +349,12 @@ def _feature_multiplatform_choices(device):
|
||||||
os = os_name + _str_os_versions(low, high)
|
os = os_name + _str_os_versions(low, high)
|
||||||
if os_bit & os_flags and platform not in choices and os not in choices:
|
if os_bit & os_flags and platform not in choices and os not in choices:
|
||||||
choices[platform] = os
|
choices[platform] = os
|
||||||
return choices
|
return _ChoicesV(choices, read_skip_byte_count=6, write_prefix_bytes=b'\xff') if choices else None
|
||||||
|
|
||||||
|
|
||||||
def _feature_multiplatform():
|
def _feature_multiplatform():
|
||||||
return feature_choices_dynamic(
|
rw = _FeatureRW(_F.MULTIPLATFORM, read_fnid=0x00, write_fnid=0x30)
|
||||||
_PLATFORM,
|
return _Setting(_PLATFORM, rw, callback=_feature_multiplatform_callback)
|
||||||
_F.MULTIPLATFORM,
|
|
||||||
_feature_multiplatform_choices,
|
|
||||||
read_fnid=0x00,
|
|
||||||
read_skip_bytes_count=6,
|
|
||||||
write_fnid=0x30,
|
|
||||||
write_prefix_bytes=b'\xff'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
PLATFORMS = _NamedInts()
|
PLATFORMS = _NamedInts()
|
||||||
|
@ -535,12 +363,12 @@ PLATFORMS[0x01] = 'Android, Windows'
|
||||||
|
|
||||||
|
|
||||||
def _feature_dualplatform():
|
def _feature_dualplatform():
|
||||||
return feature_choices(
|
validator = _ChoicesV(PLATFORMS)
|
||||||
_PLATFORM, _F.DUALPLATFORM, PLATFORMS, read_fnid=0x10, write_fnid=0x20, device_kind=(_DK.keyboard, )
|
rw = _FeatureRW(_F.DUALPLATFORM, read_fnid=0x00, write_fnid=0x20)
|
||||||
)
|
return _Setting(_PLATFORM, rw, validator)
|
||||||
|
|
||||||
|
|
||||||
def _feature_change_host_choices(device):
|
def _feature_change_host_callback(device):
|
||||||
infos = device.feature_request(_F.CHANGE_HOST)
|
infos = device.feature_request(_F.CHANGE_HOST)
|
||||||
assert infos, 'Oops, host count cannot be retrieved!'
|
assert infos, 'Oops, host count cannot be retrieved!'
|
||||||
numHosts, currentHost = _unpack('!BB', infos[:2])
|
numHosts, currentHost = _unpack('!BB', infos[:2])
|
||||||
|
@ -553,46 +381,24 @@ def _feature_change_host_choices(device):
|
||||||
for host in range(0, numHosts):
|
for host in range(0, numHosts):
|
||||||
_ignore, hostName = hostNames.get(host, (False, ''))
|
_ignore, hostName = hostNames.get(host, (False, ''))
|
||||||
choices[host] = str(host + 1) + ':' + hostName if hostName else str(host + 1)
|
choices[host] = str(host + 1) + ':' + hostName if hostName else str(host + 1)
|
||||||
return choices
|
return _ChoicesV(choices, read_skip_byte_count=1) if choices else None
|
||||||
|
|
||||||
|
|
||||||
def _feature_change_host():
|
def _feature_change_host():
|
||||||
return feature_choices_dynamic(
|
rw = _FeatureRW(_F.CHANGE_HOST, read_fnid=0x00, write_fnid=0x10, no_reply=True)
|
||||||
_CHANGE_HOST,
|
return _Setting(_CHANGE_HOST, rw, callback=_feature_change_host_callback, persist=False)
|
||||||
_F.CHANGE_HOST,
|
|
||||||
_feature_change_host_choices,
|
|
||||||
persist=False,
|
|
||||||
no_reply=True,
|
|
||||||
read_fnid=0x00,
|
|
||||||
read_skip_bytes_count=1,
|
|
||||||
write_fnid=0x10
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_thumb_mode():
|
def _feature_thumb_mode():
|
||||||
return feature_toggle(
|
rw = _FeatureRW(_F.THUMB_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||||
_THUMB_SCROLL_MODE,
|
validator = _BooleanV(true_value=b'\x01\x00', false_value=b'\x00\x00', mask=b'\x01\x00')
|
||||||
_F.THUMB_WHEEL,
|
return _Setting(_THUMB_SCROLL_MODE, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
read_fnid=0x10,
|
|
||||||
write_fnid=0x20,
|
|
||||||
true_value=b'\x01\x00',
|
|
||||||
false_value=b'\x00\x00',
|
|
||||||
mask=b'\x01\x00',
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _feature_thumb_invert():
|
def _feature_thumb_invert():
|
||||||
return feature_toggle(
|
rw = _FeatureRW(_F.THUMB_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||||
_THUMB_SCROLL_INVERT,
|
validator = _BooleanV(true_value=b'\x00\x01', false_value=b'\x00\x00', mask=b'\x00\x01')
|
||||||
_F.THUMB_WHEEL,
|
return _Setting(_THUMB_SCROLL_INVERT, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||||
read_fnid=0x10,
|
|
||||||
write_fnid=0x20,
|
|
||||||
true_value=b'\x00\x01',
|
|
||||||
false_value=b'\x00\x00',
|
|
||||||
mask=b'\x00\x01',
|
|
||||||
device_kind=(_DK.mouse, _DK.trackball)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in New Issue