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
|
||||
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
|
||||
|
||||
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"))
|
||||
```
|
||||
|
||||
Implement a register interface for the setting (if you are very brave and
|
||||
some devices have a register interface for the setting).
|
||||
Next implement an interface for the setting by creating
|
||||
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
|
||||
for each device with the register interface.
|
||||
|
||||
Implement a feature interface for the setting. There are several possible kinds of
|
||||
feature interfaces, ranging from simple toggles, to ranges, to fixed lists, to
|
||||
dynamic choices, to maps of dynamic choices, each created by a macro function.
|
||||
Pointer speed is a setting
|
||||
whose values are integers in a range so `feature_range` is used.
|
||||
The arguments to this macro are
|
||||
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 minimum and maximum values for the setting,
|
||||
the HID++ 2.0 function IDs to read and write the setting (left-shifted four bits),
|
||||
the byte size of the setting value,
|
||||
a label and description for the setting (from the common strings tuple),
|
||||
and which kinds of devices can have this setting.
|
||||
(This last is no longer used because keyboards with integrated pointers only
|
||||
The reader/writer instance is responsible for reading raw values
|
||||
from the device and writing values to it.
|
||||
There are different classes for feature interfaces and register interfaces.
|
||||
Pointer speed is a feature so the _FeatureRW reader/writer is used.
|
||||
Reader/writers take the register or feature ID and the command numbers for reading and writing,
|
||||
plus other arguments for complex interfaces.
|
||||
|
||||
The validator instance is responsible for turning read raw values into Python data
|
||||
and Python data into raw values to be written and validating that the Python data is
|
||||
acceptable for the setting.
|
||||
There are several possible kinds of Python data for setting interfaces,
|
||||
ranging from simple toggles, to ranges, to fixed lists, to
|
||||
dynamic choices, to maps of dynamic choices.
|
||||
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.)
|
||||
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
|
||||
def _feature_pointer_speed():
|
||||
"""Pointer Speed feature"""
|
||||
return feature_range(_POINTER_SPEED[0],
|
||||
_F.POINTER_SPEED,
|
||||
0x002e,
|
||||
0x01ff,
|
||||
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))
|
||||
"""Pointer Speed feature"""
|
||||
# min and max values taken from usb traces of Win software
|
||||
validator = _RangeV(0x002e, 0x01ff, 2)
|
||||
rw = _FeatureRW(_F.POINTER_SPEED)
|
||||
return _Setting(_POINTER_SPEED, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
```
|
||||
|
||||
Settings that are toggles or choices work very similarly.
|
||||
Settings where the choices are determined from the device
|
||||
Settings where the acceptable values are determined from the device
|
||||
need an auxiliary function to receive and decipher the permissible choices.
|
||||
See `_feature_adjustable_dpi_choices` for an example.
|
||||
|
||||
Add an element to _SETTINGS_TABLE with
|
||||
the setting name (from the common strings),
|
||||
Finally, add an element to _SETTINGS_TABLE with
|
||||
the common strings for the setting,
|
||||
the feature ID (if any),
|
||||
the feature implementation (if any),
|
||||
the register implementation (if any).
|
||||
and
|
||||
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 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
|
||||
and is also used to auto-discover feature implementations.
|
||||
|
||||
```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.
|
||||
Needs to be instantiated for each specific device."""
|
||||
__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
|
||||
self.name = name[0]
|
||||
self.label = name[1]
|
||||
self.description = name[2]
|
||||
self.device_kind = device_kind
|
||||
self.feature = feature
|
||||
self.feature = getattr(rw, 'feature', None)
|
||||
self.persist = persist
|
||||
|
||||
self._rw = rw
|
||||
assert (validator and not callback) or (not validator and callback)
|
||||
self._validator = validator
|
||||
self._callback = callback
|
||||
|
||||
assert kind is None or kind & validator.kind != 0
|
||||
self.kind = kind or validator.kind
|
||||
assert kind is None or validator is None or kind & validator.kind != 0
|
||||
self.kind = kind or getattr(validator, 'kind', None)
|
||||
|
||||
def __call__(self, device):
|
||||
assert not hasattr(self, '_value')
|
||||
|
@ -73,8 +75,13 @@ class Setting(object):
|
|||
elif p >= 2.0:
|
||||
# HID++ 2.0 devices do not support registers
|
||||
assert self._rw.kind == FeatureRW.kind
|
||||
|
||||
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._device = device
|
||||
return o
|
||||
|
@ -84,7 +91,7 @@ class Setting(object):
|
|||
assert hasattr(self, '_value')
|
||||
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
|
||||
def range(self):
|
||||
|
@ -171,9 +178,9 @@ class Setting(object):
|
|||
if hasattr(self, '_value'):
|
||||
assert hasattr(self, '_device')
|
||||
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__
|
||||
|
||||
|
@ -438,7 +445,7 @@ class FeatureRW(object):
|
|||
default_read_fnid = 0x00
|
||||
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)
|
||||
self.feature = feature
|
||||
self.read_fnid = read_fnid
|
||||
|
@ -459,32 +466,31 @@ class FeatureRWMap(FeatureRW):
|
|||
kind = _NamedInt(0x02, 'feature')
|
||||
default_read_fnid = 0x00
|
||||
default_write_fnid = 0x10
|
||||
default_key_bytes_count = 1
|
||||
default_key_byte_count = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
feature,
|
||||
read_fnid=default_read_fnid,
|
||||
write_fnid=default_write_fnid,
|
||||
key_bytes_count=default_key_bytes_count,
|
||||
no_reply=False,
|
||||
**_ignore
|
||||
key_byte_count=default_key_byte_count,
|
||||
no_reply=False
|
||||
):
|
||||
assert isinstance(feature, _NamedInt)
|
||||
self.feature = feature
|
||||
self.read_fnid = read_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
|
||||
|
||||
def read(self, device, key):
|
||||
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)
|
||||
|
||||
def write(self, device, key, data_bytes):
|
||||
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)
|
||||
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
|
||||
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):
|
||||
assert isinstance(false_value, int)
|
||||
if mask is None:
|
||||
|
@ -609,7 +615,7 @@ class BitFieldValidator(object):
|
|||
|
||||
kind = KIND.multiple_toggle
|
||||
|
||||
def __init__(self, options, byte_count=None, **kwargs):
|
||||
def __init__(self, options, byte_count=None):
|
||||
assert (isinstance(options, list))
|
||||
self.options = options
|
||||
self.byte_count = (max(x.bit_length() for x in options) + 7) // 8
|
||||
|
@ -640,9 +646,9 @@ class ChoicesValidator(object):
|
|||
kind = KIND.choice
|
||||
"""Translates between NamedInts and a byte sequence.
|
||||
: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."""
|
||||
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 isinstance(choices, _NamedInts)
|
||||
assert len(choices) > 1
|
||||
|
@ -650,18 +656,18 @@ class ChoicesValidator(object):
|
|||
self.needs_current_value = False
|
||||
|
||||
max_bits = max(x.bit_length() for x in choices)
|
||||
self._bytes_count = (max_bits // 8) + (1 if max_bits % 8 else 0)
|
||||
if bytes_count:
|
||||
assert self._bytes_count <= bytes_count
|
||||
self._bytes_count = bytes_count
|
||||
assert self._bytes_count < 8
|
||||
self._read_skip_bytes_count = read_skip_bytes_count if read_skip_bytes_count else 0
|
||||
self._byte_count = (max_bits // 8) + (1 if max_bits % 8 else 0)
|
||||
if byte_count:
|
||||
assert self._byte_count <= byte_count
|
||||
self._byte_count = byte_count
|
||||
assert self._byte_count < 8
|
||||
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''
|
||||
assert self._bytes_count + self._read_skip_bytes_count <= 14
|
||||
assert self._bytes_count + len(self._write_prefix_bytes) <= 14
|
||||
assert self._byte_count + self._read_skip_byte_count <= 14
|
||||
assert self._byte_count + len(self._write_prefix_bytes) <= 14
|
||||
|
||||
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]
|
||||
assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
||||
return valid_value
|
||||
|
@ -682,7 +688,7 @@ class ChoicesValidator(object):
|
|||
if choice is None:
|
||||
raise ValueError('invalid choice %r' % new_value)
|
||||
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):
|
||||
|
@ -691,12 +697,11 @@ class ChoicesMapValidator(ChoicesValidator):
|
|||
def __init__(
|
||||
self,
|
||||
choices_map,
|
||||
key_bytes_count=None,
|
||||
bytes_count=None,
|
||||
read_skip_bytes_count=0,
|
||||
key_byte_count=None,
|
||||
byte_count=None,
|
||||
read_skip_byte_count=0,
|
||||
write_prefix_bytes=b'',
|
||||
extra_default=None,
|
||||
**kwargs
|
||||
extra_default=None
|
||||
):
|
||||
assert choices_map is not None
|
||||
assert isinstance(choices_map, dict)
|
||||
|
@ -709,25 +714,25 @@ class ChoicesMapValidator(ChoicesValidator):
|
|||
for key_value in choices:
|
||||
assert isinstance(key_value, _NamedInt)
|
||||
max_value_bits = max(max_value_bits, key_value.bit_length())
|
||||
self._key_bytes_count = (max_key_bits + 7) // 8
|
||||
if key_bytes_count:
|
||||
assert self._key_bytes_count <= key_bytes_count
|
||||
self._key_bytes_count = key_bytes_count
|
||||
self._bytes_count = (max_value_bits + 7) // 8
|
||||
if bytes_count:
|
||||
assert self._bytes_count <= bytes_count
|
||||
self._bytes_count = bytes_count
|
||||
self._key_byte_count = (max_key_bits + 7) // 8
|
||||
if key_byte_count:
|
||||
assert self._key_byte_count <= key_byte_count
|
||||
self._key_byte_count = key_byte_count
|
||||
self._byte_count = (max_value_bits + 7) // 8
|
||||
if byte_count:
|
||||
assert self._byte_count <= byte_count
|
||||
self._byte_count = byte_count
|
||||
self.choices = choices_map
|
||||
self.needs_current_value = False
|
||||
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''
|
||||
assert self._bytes_count + self._read_skip_bytes_count + self._key_bytes_count <= 14
|
||||
assert self._bytes_count + len(self._write_prefix_bytes) + self._key_bytes_count <= 14
|
||||
assert self._byte_count + self._read_skip_byte_count + self._key_byte_count <= 14
|
||||
assert self._byte_count + len(self._write_prefix_bytes) + self._key_byte_count <= 14
|
||||
|
||||
def validate_read(self, reply_bytes, key):
|
||||
start = self._key_bytes_count + self._read_skip_bytes_count
|
||||
end = start + self._bytes_count
|
||||
start = self._key_byte_count + self._read_skip_byte_count
|
||||
end = start + self._byte_count
|
||||
reply_value = _bytes2int(reply_bytes[start:end])
|
||||
# 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:
|
||||
|
@ -740,32 +745,32 @@ class ChoicesMapValidator(ChoicesValidator):
|
|||
choices = self.choices[key]
|
||||
if new_value not in choices and new_value != self.extra_default:
|
||||
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):
|
||||
__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
|
||||
"""Translates between integers and a byte sequence.
|
||||
:param min_value: minimum 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."""
|
||||
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
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.needs_current_value = False
|
||||
|
||||
self._bytes_count = math.ceil(math.log(max_value + 1, 256))
|
||||
if bytes_count:
|
||||
assert self._bytes_count <= bytes_count
|
||||
self._bytes_count = bytes_count
|
||||
assert self._bytes_count < 8
|
||||
self._byte_count = math.ceil(math.log(max_value + 1, 256))
|
||||
if byte_count:
|
||||
assert self._byte_count <= byte_count
|
||||
self._byte_count = byte_count
|
||||
assert self._byte_count < 8
|
||||
|
||||
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.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
|
||||
return reply_value
|
||||
|
@ -773,4 +778,4 @@ class RangeValidator(object):
|
|||
def prepare_write(self, new_value, current_value=None):
|
||||
if new_value < self.min_value or new_value > self.max_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 unpack as _unpack
|
||||
from .i18n import _
|
||||
from .settings import KIND as _KIND
|
||||
from .settings import BitFieldSetting as _BitFieldSetting
|
||||
from .settings import BitFieldValidator as _BitFieldV
|
||||
from .settings import BooleanValidator as _BooleanV
|
||||
|
@ -51,292 +50,165 @@ _DK = _hidpp10.DEVICE_KIND
|
|||
_R = _hidpp10.REGISTERS
|
||||
_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
|
||||
#
|
||||
|
||||
# yapf: disable
|
||||
_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.'))
|
||||
_SIDE_SCROLL = (
|
||||
'side-scroll', _('Side Scrolling'),
|
||||
_(
|
||||
'When disabled, pushing the wheel sideways sends custom button events\n'
|
||||
'instead of the standard side-scrolling events.'
|
||||
)
|
||||
)
|
||||
_HI_RES_SCROLL = (
|
||||
'hi-res-scroll', _('High Resolution Scrolling'), _('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' + _('Effectively turns off wheel scrolling in Linux.')
|
||||
)
|
||||
_HIRES_INV = (
|
||||
'hires-smooth-invert', _('High Resolution Wheel Invert'),
|
||||
_('High-sensitivity wheel invert direction for vertical scroll.')
|
||||
)
|
||||
_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.'
|
||||
)
|
||||
)
|
||||
_SIDE_SCROLL = ('side-scroll', _('Side Scrolling'),
|
||||
_('When disabled, pushing the wheel sideways sends custom button events\n'
|
||||
'instead of the standard side-scrolling events.'))
|
||||
_HI_RES_SCROLL = ('hi-res-scroll', _('High Resolution Scrolling'),
|
||||
_('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' +
|
||||
_('Effectively turns off wheel scrolling in Linux.'))
|
||||
_HIRES_INV = ('hires-smooth-invert', _('High Resolution Wheel Invert'),
|
||||
_('High-sensitivity wheel invert direction for vertical scroll.'))
|
||||
_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)
|
||||
_POINTER_SPEED = (
|
||||
'pointer_speed', _('Sensitivity (Pointer Speed)'), _('Speed multiplier for mouse (256 is normal multiplier).')
|
||||
)
|
||||
_SMART_SHIFT = (
|
||||
'smart-shift', _('Smart Shift'),
|
||||
_(
|
||||
'Automatically switch the mouse wheel between ratchet and freespin mode.\n'
|
||||
'The mouse wheel is always free at 0, and always locked at 50'
|
||||
)
|
||||
)
|
||||
_POINTER_SPEED = ('pointer_speed', _('Sensitivity (Pointer Speed)'),
|
||||
_('Speed multiplier for mouse (256 is normal multiplier).'))
|
||||
_SMART_SHIFT = ('smart-shift', _('Smart Shift'),
|
||||
_('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.'))
|
||||
_REPROGRAMMABLE_KEYS = (
|
||||
'reprogrammable-keys', _('Actions'), _('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.')
|
||||
)
|
||||
_REPROGRAMMABLE_KEYS = ('reprogrammable-keys', _('Actions'),
|
||||
_('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.'))
|
||||
_DISABLE_KEYS = ('disable-keyboard-keys', _('Disable keys'), _('Disable specific keyboard keys.'))
|
||||
_PLATFORM = ('multiplatform', _('Set OS'), _('Change keys to match OS.'))
|
||||
_CHANGE_HOST = ('change-host', _('Change Host'), _('Switch connection to a different host'))
|
||||
_THUMB_SCROLL_MODE = (
|
||||
'thumb-scroll-mode', _('HID++ Thumb Scrolling'),
|
||||
_('HID++ mode for horizontal scroll with the thumb wheel.') + '\n' + _('Effectively turns off thumb scrolling in Linux.')
|
||||
)
|
||||
_THUMB_SCROLL_MODE = ('thumb-scroll-mode', _('HID++ Thumb Scrolling'),
|
||||
_('HID++ mode for horizontal scroll with the thumb wheel.') + '\n' +
|
||||
_('Effectively turns off thumb scrolling in Linux.'))
|
||||
_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:
|
||||
# persist=True - whether to store the values and reapply them from now on
|
||||
# device_kind - the kinds of devices that setting is suitable for (NOT CURRENTLY USED)
|
||||
# read_fnid=0x00, write_fnid=0x10 - default 0x00 and 0x10 function numbers (times 16) to read and write setting
|
||||
# bytes_count=1 - number of bytes for the data (ignoring the key, if any)
|
||||
# only for boolean settings
|
||||
# true_value=0x01, false_value=0x00, mask=0xFF - integer or byte strings for boolean settings
|
||||
# only for map choices
|
||||
# key_bytes_count=1 - number of bytes in the key
|
||||
# extra_default - extra value that cannot be set but means same as default value
|
||||
# only for choices and map choices
|
||||
# read_skip_bytes_count=0 - number of bytes to ignore before the data when reading
|
||||
# write_prefix_bytes=b'' - bytes to put before the data writing
|
||||
# The _Setting class is for settings with simple values (booleans, numbers in a range, and symbolic choices).
|
||||
# Its positional arguments are the strings for the setting and the reader/writer.
|
||||
# The validator keyword (or third) argument is the validator, if the validator does not depend on information from the device.
|
||||
# The callback keyword argument is a function that given a device as argument returns the validator or None.
|
||||
# If the callback function returns None the setting is not created for the device.
|
||||
# Either a validator or callback must be specified, but not both.
|
||||
# The device_kind keyword argument (default None) says what kinds of devices can use the setting.
|
||||
# (This argument is currently not used because keyboards with integrated trackpads break its abstraction.)
|
||||
# The persist keyword argument (default True) says whether to store the value and apply it when setting up the device.
|
||||
#
|
||||
# There are two simple reader/writers - _RegisterRW and _FeatureRW.
|
||||
# _RegisterRW is for register-based settings and takes the register name as argument.
|
||||
# _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(
|
||||
register=_R.keyboard_hand_detection, true_value=b'\x00\x00\x00', false_value=b'\x00\x00\x30', mask=b'\x00\x00\xFF'
|
||||
):
|
||||
return register_toggle(
|
||||
_HAND_DETECTION, register, true_value=true_value, false_value=false_value, device_kind=(_DK.keyboard, )
|
||||
)
|
||||
def _register_hand_detection():
|
||||
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, ))
|
||||
|
||||
|
||||
def _register_fn_swap(register=_R.keyboard_fn_swap, true_value=b'\x00\x01', mask=b'\x00\x01'):
|
||||
return register_toggle(_FN_SWAP, register, true_value=true_value, mask=mask, device_kind=(_DK.keyboard, ))
|
||||
def _register_fn_swap():
|
||||
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):
|
||||
return register_toggle(_SMOOTH_SCROLL, register, true_value=true_value, mask=mask, device_kind=(_DK.mouse, _DK.trackball))
|
||||
def _register_smooth_scroll():
|
||||
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):
|
||||
return register_toggle(_SIDE_SCROLL, register, true_value=true_value, mask=mask, device_kind=(_DK.mouse, _DK.trackball))
|
||||
def _register_side_scroll():
|
||||
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):
|
||||
return register_choices(_DPI, register, choices, device_kind=(_DK.mouse, _DK.trackball))
|
||||
def _register_dpi(choices=None):
|
||||
return _Setting(_DPI, _RegisterRW(_R.mouse_dpi), _ChoicesV(choices), device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
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():
|
||||
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
|
||||
# just use the current host (first byte = 0xFF) part of the feature to read and set the Fn state
|
||||
def _feature_k375s_fn_swap():
|
||||
return feature_toggle(
|
||||
_FN_SWAP, _F.K375S_FN_INVERSION, true_value=b'\xFF\x01', false_value=b'\xFF\x00', device_kind=(_DK.keyboard, )
|
||||
)
|
||||
validator = _BooleanV(true_value=b'\xFF\x01', false_value=b'\xFF\x00')
|
||||
return _Setting(_FN_SWAP, _FeatureRW(_F.K375S_FN_INVERSION), validator, device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
# FIXME: This will enable all supported backlight settings,
|
||||
# we should allow the users to select which settings they want to enable.
|
||||
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():
|
||||
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():
|
||||
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():
|
||||
return feature_toggle(
|
||||
_HIRES_INV,
|
||||
_F.HIRES_WHEEL,
|
||||
read_fnid=0x10,
|
||||
write_fnid=0x20,
|
||||
true_value=0x04,
|
||||
mask=0x04,
|
||||
device_kind=(_DK.mouse, _DK.trackball)
|
||||
)
|
||||
rw = _FeatureRW(_F.HIRES_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||
validator = _BooleanV(true_value=0x04, mask=0x04)
|
||||
return _Setting(_HIRES_INV, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_hires_smooth_resolution():
|
||||
return feature_toggle(
|
||||
_HIRES_RES,
|
||||
_F.HIRES_WHEEL,
|
||||
read_fnid=0x10,
|
||||
write_fnid=0x20,
|
||||
true_value=0x02,
|
||||
mask=0x02,
|
||||
device_kind=(_DK.mouse, _DK.trackball)
|
||||
)
|
||||
rw = _FeatureRW(_F.HIRES_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||
validator = _BooleanV(true_value=0x02, mask=0x02)
|
||||
return _Setting(_HIRES_RES, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_smart_shift():
|
||||
|
@ -361,26 +233,17 @@ def _feature_smart_shift():
|
|||
threshold = _bytes2int(data_bytes)
|
||||
# Freespin at minimum
|
||||
mode = 1 if threshold == _MIN_SMART_SHIFT_VALUE else 2
|
||||
|
||||
# Ratchet at maximum
|
||||
if threshold == _MAX_SMART_SHIFT_VALUE:
|
||||
threshold = 255
|
||||
|
||||
data = _int2bytes(mode, count=1) + _int2bytes(threshold, count=1) * 2
|
||||
return super(_SmartShiftRW, self).write(device, data)
|
||||
|
||||
return feature_range(
|
||||
_SMART_SHIFT,
|
||||
_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)
|
||||
)
|
||||
validator = _RangeV(_MIN_SMART_SHIFT_VALUE, _MAX_SMART_SHIFT_VALUE, 1)
|
||||
return _Setting(_SMART_SHIFT, _SmartShiftRW(_F.SMART_SHIFT), validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_adjustable_dpi_choices(device):
|
||||
def _feature_adjustable_dpi_callback(device):
|
||||
# [1] getSensorDpiList(sensorIdx)
|
||||
reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10)
|
||||
# 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:
|
||||
assert len(dpi_list) == 2, 'Invalid DPI list range: %r' % dpi_list
|
||||
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():
|
||||
|
@ -408,77 +271,49 @@ def _feature_adjustable_dpi():
|
|||
# Assume sensorIdx 0 (there is only one sensor)
|
||||
# [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB
|
||||
# [3] setSensorDpi(sensorIdx, dpi)
|
||||
return feature_choices_dynamic(
|
||||
_DPI,
|
||||
_F.ADJUSTABLE_DPI,
|
||||
_feature_adjustable_dpi_choices,
|
||||
read_fnid=0x20,
|
||||
write_fnid=0x30,
|
||||
bytes_count=3,
|
||||
device_kind=(_DK.mouse, _DK.trackball)
|
||||
)
|
||||
rw = _FeatureRW(_F.ADJUSTABLE_DPI, read_fnid=0x20, write_fnid=0x30)
|
||||
return _Setting(_DPI, rw, callback=_feature_adjustable_dpi_callback, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_pointer_speed():
|
||||
"""Pointer Speed feature"""
|
||||
# min and max values taken from usb traces of Win software
|
||||
return feature_range(
|
||||
_POINTER_SPEED,
|
||||
_F.POINTER_SPEED,
|
||||
0x002e,
|
||||
0x01ff,
|
||||
read_fnid=0x0,
|
||||
write_fnid=0x10,
|
||||
bytes_count=2,
|
||||
device_kind=(_DK.mouse, _DK.trackball)
|
||||
)
|
||||
validator = _RangeV(0x002e, 0x01ff, 2)
|
||||
rw = _FeatureRW(_F.POINTER_SPEED)
|
||||
return _Setting(_POINTER_SPEED, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
# 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)
|
||||
# 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
|
||||
def _feature_reprogrammable_keys_choices(device):
|
||||
def _feature_reprogrammable_keys_callback(device):
|
||||
choices = {}
|
||||
for k in device.keys:
|
||||
tgts = k.remappable_to
|
||||
if len(tgts) > 1:
|
||||
choices[k.key] = tgts
|
||||
|
||||
return choices
|
||||
if not 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():
|
||||
return feature_map_choices_dynamic(
|
||||
_REPROGRAMMABLE_KEYS,
|
||||
_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, ),
|
||||
)
|
||||
rw = _FeatureRWMap(_F.REPROG_CONTROLS_V4, read_fnid=0x20, write_fnid=0x30, key_byte_count=2)
|
||||
return _Settings(_REPROGRAMMABLE_KEYS, rw, callback=_feature_reprogrammable_keys_callback, 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]
|
||||
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():
|
||||
return feature_bitfield_toggle_dynamic(
|
||||
_DISABLE_KEYS,
|
||||
_F.KEYBOARD_DISABLE_KEYS,
|
||||
_feature_disable_keyboard_keys_key_list,
|
||||
read_fnid=0x10,
|
||||
write_fnid=0x20,
|
||||
device_kind=(_DK.keyboard, )
|
||||
)
|
||||
rw = _FeatureRW(_F.KEYBOARD_DISABLE_KEYS, read_fnid=0x10, write_fnid=0x20)
|
||||
return _BitFieldSetting(_DISABLE_KEYS, rw, callback=_feature_disable_keyboard_keys_callback, device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
# muultiplatform OS bits
|
||||
|
@ -486,7 +321,7 @@ OSS = [('Linux', 0x0400), ('MacOS', 0x2000), ('Windows', 0x0100), ('iOS', 0x4000
|
|||
('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_version(version):
|
||||
if version == 0:
|
||||
|
@ -514,19 +349,12 @@ def _feature_multiplatform_choices(device):
|
|||
os = os_name + _str_os_versions(low, high)
|
||||
if os_bit & os_flags and platform not in choices and os not in choices:
|
||||
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():
|
||||
return feature_choices_dynamic(
|
||||
_PLATFORM,
|
||||
_F.MULTIPLATFORM,
|
||||
_feature_multiplatform_choices,
|
||||
read_fnid=0x00,
|
||||
read_skip_bytes_count=6,
|
||||
write_fnid=0x30,
|
||||
write_prefix_bytes=b'\xff'
|
||||
)
|
||||
rw = _FeatureRW(_F.MULTIPLATFORM, read_fnid=0x00, write_fnid=0x30)
|
||||
return _Setting(_PLATFORM, rw, callback=_feature_multiplatform_callback)
|
||||
|
||||
|
||||
PLATFORMS = _NamedInts()
|
||||
|
@ -535,12 +363,12 @@ PLATFORMS[0x01] = 'Android, Windows'
|
|||
|
||||
|
||||
def _feature_dualplatform():
|
||||
return feature_choices(
|
||||
_PLATFORM, _F.DUALPLATFORM, PLATFORMS, read_fnid=0x10, write_fnid=0x20, device_kind=(_DK.keyboard, )
|
||||
)
|
||||
validator = _ChoicesV(PLATFORMS)
|
||||
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)
|
||||
assert infos, 'Oops, host count cannot be retrieved!'
|
||||
numHosts, currentHost = _unpack('!BB', infos[:2])
|
||||
|
@ -553,46 +381,24 @@ def _feature_change_host_choices(device):
|
|||
for host in range(0, numHosts):
|
||||
_ignore, hostName = hostNames.get(host, (False, ''))
|
||||
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():
|
||||
return feature_choices_dynamic(
|
||||
_CHANGE_HOST,
|
||||
_F.CHANGE_HOST,
|
||||
_feature_change_host_choices,
|
||||
persist=False,
|
||||
no_reply=True,
|
||||
read_fnid=0x00,
|
||||
read_skip_bytes_count=1,
|
||||
write_fnid=0x10
|
||||
)
|
||||
rw = _FeatureRW(_F.CHANGE_HOST, read_fnid=0x00, write_fnid=0x10, no_reply=True)
|
||||
return _Setting(_CHANGE_HOST, rw, callback=_feature_change_host_callback, persist=False)
|
||||
|
||||
|
||||
def _feature_thumb_mode():
|
||||
return feature_toggle(
|
||||
_THUMB_SCROLL_MODE,
|
||||
_F.THUMB_WHEEL,
|
||||
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)
|
||||
)
|
||||
rw = _FeatureRW(_F.THUMB_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||
validator = _BooleanV(true_value=b'\x01\x00', false_value=b'\x00\x00', mask=b'\x01\x00')
|
||||
return _Setting(_THUMB_SCROLL_MODE, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_thumb_invert():
|
||||
return feature_toggle(
|
||||
_THUMB_SCROLL_INVERT,
|
||||
_F.THUMB_WHEEL,
|
||||
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)
|
||||
)
|
||||
rw = _FeatureRW(_F.THUMB_WHEEL, read_fnid=0x10, write_fnid=0x20)
|
||||
validator = _BooleanV(true_value=b'\x00\x01', false_value=b'\x00\x00', mask=b'\x00\x01')
|
||||
return _Setting(_THUMB_SCROLL_INVERT, rw, validator, device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
#
|
||||
|
|
Loading…
Reference in New Issue