receiver: implement KEYBOARD_DISABLE_KEYS feature
(the UI needs some improvement)
This commit is contained in:
parent
c99f470dd5
commit
ef54a750dc
|
@ -54,11 +54,12 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None,
|
|||
|
||||
if protocol is not None:
|
||||
# ? 2.0 devices should not have any registers
|
||||
_kind = lambda s : s._rw.kind if hasattr(s, '_rw') else s._rw_kind
|
||||
if protocol < 2.0:
|
||||
assert settings is None or all(s._rw.kind == 1 for s in settings)
|
||||
assert settings is None or all(_kind(s) == 1 for s in settings)
|
||||
else:
|
||||
assert registers is None
|
||||
assert settings is None or all(s._rw.kind == 2 for s in settings)
|
||||
assert settings is None or all(_kind(s) == 2 for s in settings)
|
||||
|
||||
if wpid:
|
||||
for w in wpid if isinstance(wpid, tuple) else (wpid, ):
|
||||
|
@ -183,8 +184,10 @@ _D('Wireless Touch Keyboard K400', protocol=2.0, wpid=('400E', '4024'),
|
|||
)
|
||||
_D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D',
|
||||
settings=[
|
||||
_FS.new_fn_swap()
|
||||
],
|
||||
_FS.new_fn_swap(),
|
||||
_FS.reprogrammable_keys(),
|
||||
_FS.disable_keyboard_keys(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Keyboard K520', protocol=1.0, wpid='2011',
|
||||
registers=(_R.battery_status, ),
|
||||
|
|
|
@ -115,7 +115,7 @@ FEATURE = _NamedInts(
|
|||
LOCK_KEY_STATE=0x4220,
|
||||
SOLAR_DASHBOARD=0x4301,
|
||||
KEYBOARD_LAYOUT=0x4520,
|
||||
KEYBOARD_DISABLE=0x4521,
|
||||
KEYBOARD_DISABLE_KEYS=0x4521,
|
||||
KEYBOARD_DISABLE_BY_USAGE=0x4522,
|
||||
DUALPLATFORM=0x4530,
|
||||
MULTIPLATFORM=0x4531,
|
||||
|
|
|
@ -37,12 +37,12 @@ from .common import (
|
|||
#
|
||||
#
|
||||
|
||||
KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A)
|
||||
KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A, multiple_toggle=0x10)
|
||||
|
||||
class Setting(object):
|
||||
"""A setting descriptor.
|
||||
Needs to be instantiated for each specific device."""
|
||||
__slots__ = ('name', 'label', 'description', 'kind', 'device_kind', 'feature',
|
||||
__slots__ = ('name', 'label', 'description', 'kind', 'device_kind', 'feature',
|
||||
'_rw', '_validator', '_device', '_value')
|
||||
|
||||
def __init__(self, name, rw, validator, kind=None, label=None, description=None, device_kind=None, feature=None):
|
||||
|
@ -290,6 +290,120 @@ class Settings(Setting):
|
|||
return value
|
||||
|
||||
|
||||
class BitFieldSetting(Setting):
|
||||
"""A setting descriptor for a set of choices represented by one bit each, being a map from options to booleans.
|
||||
Needs to be instantiated for each specific device."""
|
||||
|
||||
def read(self, cached=True):
|
||||
assert hasattr(self, '_value')
|
||||
assert hasattr(self, '_device')
|
||||
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("%s: settings read %r from %s", self.name, self._value, self._device)
|
||||
|
||||
if self._value is None and getattr(self._device,'persister',None):
|
||||
# We haven't read a value from the device yet,
|
||||
# maybe we have something in the configuration.
|
||||
self._value = self._device.persister.get(self.name)
|
||||
|
||||
if cached and self._value is not None:
|
||||
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||
# If this is a new device (or a new setting for an old device),
|
||||
# make sure to save its current value for the next time.
|
||||
self._device.persister[self.name] = self._value
|
||||
return self._value
|
||||
|
||||
if self._device.online:
|
||||
reply_map = dict()
|
||||
reply = self._rw.read(self._device)
|
||||
if reply:
|
||||
# keys are ints, because that is what the device uses,
|
||||
# encoded into strings because JSON requires strings as keys
|
||||
reply_map = self._validator.validate_read(reply)
|
||||
self._value = reply_map
|
||||
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||
# Don't update the persister if it already has a value,
|
||||
# otherwise the first read might overwrite the value we wanted.
|
||||
self._device.persister[self.name] = self._value
|
||||
return self._value
|
||||
|
||||
def read_key(self, key, cached=True):
|
||||
assert hasattr(self, '_value')
|
||||
assert hasattr(self, '_device')
|
||||
assert key is not None
|
||||
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("%s: settings read %r key %r from %s", self.name, self._value, key, self._device)
|
||||
|
||||
if self._value is None and getattr(self._device,'persister',None):
|
||||
self._value = self._device.persister.get(self.name)
|
||||
|
||||
if cached and self._value is not None:
|
||||
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||
self._device.persister[self.name] = self._value
|
||||
return self._value[str(int(key))]
|
||||
|
||||
if self._device.online:
|
||||
reply = self._rw.read(self._device, key)
|
||||
if reply:
|
||||
self._value = self._validator.validate_read(reply)
|
||||
if getattr(self._device,'persister',None) and self.name not in self._device.persister:
|
||||
self._device.persister[self.name] = self._value
|
||||
return self._value[str(int(key))]
|
||||
|
||||
def write(self, map):
|
||||
assert hasattr(self, '_value')
|
||||
assert hasattr(self, '_device')
|
||||
assert map is not None
|
||||
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("%s: settings write %r to %s", self.name, map, self._device)
|
||||
|
||||
if self._device.online:
|
||||
# Remember the value we're trying to set, even if the write fails.
|
||||
# This way even if the device is offline or some other error occurs,
|
||||
# the last value we've tried to write is remembered in the configuration.
|
||||
self._value = map
|
||||
if self._device.persister:
|
||||
self._device.persister[self.name] = map
|
||||
data_bytes = self._validator.prepare_write(self._value)
|
||||
if data_bytes is not None:
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("%s: settings prepare map write(%s) => %r", self.name, self._value, data_bytes)
|
||||
reply = self._rw.write(self._device, data_bytes)
|
||||
if not reply:
|
||||
return None
|
||||
return map
|
||||
|
||||
def write_key_value(self, key, value):
|
||||
assert hasattr(self, '_value')
|
||||
assert hasattr(self, '_device')
|
||||
assert key is not None
|
||||
assert value is not None
|
||||
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("%s: settings write key %r value %r to %s", self.name, key, value, self._device)
|
||||
|
||||
if self._device.online:
|
||||
# Remember the value we're trying to set, even if the write fails.
|
||||
# This way even if the device is offline or some other error occurs,
|
||||
# the last value we've tried to write is remembered in the configuration.
|
||||
value = bool(value)
|
||||
self._value[str(key)] = value
|
||||
if self._device.persister:
|
||||
self._device.persister[self.name] = self._value
|
||||
|
||||
data_bytes = self._validator.prepare_write(self._value)
|
||||
if data_bytes is not None:
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("%s: settings prepare key value write(%s,%s) => %r", self.name, key, str(value), data_bytes)
|
||||
reply = self._rw.write(self._device, data_bytes)
|
||||
if not reply:
|
||||
# tell whomever is calling that the write failed
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
#
|
||||
# read/write low-level operators
|
||||
#
|
||||
|
@ -469,6 +583,38 @@ class BooleanValidator(object):
|
|||
return to_write
|
||||
|
||||
|
||||
class BitFieldValidator(object):
|
||||
__slots__ = ('byte_count', 'options')
|
||||
|
||||
kind = KIND.multiple_toggle
|
||||
|
||||
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
|
||||
if byte_count:
|
||||
assert(isinstance(byte_count, int) and byte_count >= self.byte_count)
|
||||
self.byte_count = byte_count
|
||||
|
||||
def validate_read(self, reply_bytes):
|
||||
r = _bytes2int(reply_bytes[:self.byte_count])
|
||||
value = {str(int(k)) : False for k in self.options}
|
||||
m = 1
|
||||
for i in range(8 * self.byte_count):
|
||||
if m in self.options:
|
||||
value[str(int(m))] = bool(r & m)
|
||||
m <<= 1
|
||||
return value
|
||||
|
||||
def prepare_write(self, new_value):
|
||||
assert(isinstance(new_value, dict))
|
||||
w = 0
|
||||
for k, v in new_value.items():
|
||||
if v:
|
||||
w |= int(k)
|
||||
return _int2bytes(w, self.byte_count)
|
||||
|
||||
|
||||
class ChoicesValidator(object):
|
||||
__slots__ = ('choices', 'flag', '_bytes_count', 'needs_current_value')
|
||||
|
||||
|
@ -516,7 +662,7 @@ class ChoicesValidator(object):
|
|||
assert isinstance(choice, _NamedInt)
|
||||
return choice.bytes(self._bytes_count)
|
||||
|
||||
class ChoicesMapValidator(ChoicesValidator):
|
||||
class ChoicesMapValidator(ChoicesValidator):
|
||||
kind = KIND.map_choice
|
||||
|
||||
def __init__(self, choices_map, key_bytes_count=None, skip_bytes_count=None, value_bytes_count=None, extra_default=None):
|
||||
|
|
|
@ -38,11 +38,13 @@ from .common import (
|
|||
from .settings import (
|
||||
KIND as _KIND,
|
||||
Setting as _Setting,
|
||||
BitFieldSetting as _BitFieldSetting,
|
||||
Settings as _Settings,
|
||||
RegisterRW as _RegisterRW,
|
||||
FeatureRW as _FeatureRW,
|
||||
FeatureRWMap as _FeatureRWMap,
|
||||
BooleanValidator as _BooleanV,
|
||||
BitFieldValidator as _BitFieldV,
|
||||
ChoicesValidator as _ChoicesV,
|
||||
ChoicesMapValidator as _ChoicesMapV,
|
||||
RangeValidator as _RangeV,
|
||||
|
@ -86,6 +88,30 @@ def feature_toggle(name, feature,
|
|||
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||
return _Setting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
|
||||
|
||||
def feature_bitfield_toggle(name, feature, options,
|
||||
read_function_id=_FeatureRW.default_read_fnid,
|
||||
write_function_id=_FeatureRW.default_write_fnid,
|
||||
label=None, description=None, device_kind=None):
|
||||
assert options
|
||||
validator = _BitFieldV(options)
|
||||
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||
return _BitFieldSetting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
|
||||
|
||||
def feature_bitfield_toggle_dynamic(name, feature, options_callback,
|
||||
read_function_id=_FeatureRW.default_read_fnid,
|
||||
write_function_id=_FeatureRW.default_write_fnid,
|
||||
label=None, description=None, device_kind=None):
|
||||
def instantiate(device):
|
||||
options = options_callback(device)
|
||||
setting = feature_bitfield_toggle(name, feature, options,
|
||||
read_function_id=read_function_id,
|
||||
write_function_id=write_function_id,
|
||||
label=label,
|
||||
description=description, device_kind=device_kind)
|
||||
return setting(device)
|
||||
instantiate._rw_kind = _FeatureRW.kind
|
||||
return instantiate
|
||||
|
||||
def feature_choices(name, feature, choices,
|
||||
read_function_id, write_function_id,
|
||||
bytes_count=None,
|
||||
|
@ -108,6 +134,7 @@ def feature_choices_dynamic(name, feature, choices_callback,
|
|||
bytes_count=bytes_count,
|
||||
label=label, description=description, device_kind=device_kind)
|
||||
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
|
||||
|
@ -136,6 +163,7 @@ def feature_map_choices_dynamic(name, feature, choices_callback,
|
|||
key_bytes_count=key_bytes_count, skip_bytes_count=skip_bytes_count, value_bytes_count=value_bytes_count,
|
||||
label=label, description=description, device_kind=device_kind, extra_default=extra_default)
|
||||
return setting(device)
|
||||
instantiate._rw_kind = _FeatureRWMap.kind
|
||||
return instantiate
|
||||
|
||||
def feature_range(name, feature, min_value, max_value,
|
||||
|
@ -175,6 +203,7 @@ _FN_SWAP = ('fn-swap', _("Swap Fx 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."))
|
||||
_DISABLE_KEYS = ('disable-keyboard-keys', _("Disable keys"), _("Disable specific keyboard keys."))
|
||||
_HAND_DETECTION = ('hand-detection', _("Hand Detection"),
|
||||
_("Turn on illumination when the hands hover over the keyboard."))
|
||||
_BACKLIGHT = ('backlight', _("Backlight"),
|
||||
|
@ -246,6 +275,7 @@ def _feature_lowres_smooth_scroll():
|
|||
return feature_toggle(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL,
|
||||
label=_LOW_RES_SCROLL[1], description=_LOW_RES_SCROLL[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
def _feature_hires_smooth_invert():
|
||||
return feature_toggle(_HIRES_INV[0], _F.HIRES_WHEEL,
|
||||
read_function_id=0x10,
|
||||
|
@ -379,6 +409,18 @@ def _feature_reprogrammable_keys():
|
|||
label=_REPROGRAMMABLE_KEYS[1], description=_REPROGRAMMABLE_KEYS[2],
|
||||
device_kind=(_DK.keyboard,), extra_default=0)
|
||||
|
||||
|
||||
def _feature_disable_keyboard_keys_key_list(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
|
||||
|
||||
def _feature_disable_keyboard_keys():
|
||||
return feature_bitfield_toggle_dynamic(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS,
|
||||
_feature_disable_keyboard_keys_key_list,
|
||||
read_function_id=0x10, write_function_id=0x20,
|
||||
label=_DISABLE_KEYS[1], description=_DISABLE_KEYS[2], device_kind=(_DK.keyboard,))
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -401,6 +443,7 @@ _SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [
|
|||
'typing_illumination',
|
||||
'smart_shift',
|
||||
'reprogrammable_keys',
|
||||
'disable_keyboard_keys',
|
||||
])
|
||||
del namedtuple
|
||||
|
||||
|
@ -421,6 +464,7 @@ RegisterSettings = _SETTINGS_LIST(
|
|||
typing_illumination=None,
|
||||
smart_shift=None,
|
||||
reprogrammable_keys=None,
|
||||
disable_keyboard_keys=None,
|
||||
)
|
||||
FeatureSettings = _SETTINGS_LIST(
|
||||
fn_swap=_feature_fn_swap,
|
||||
|
@ -439,6 +483,7 @@ FeatureSettings = _SETTINGS_LIST(
|
|||
typing_illumination=None,
|
||||
smart_shift=_feature_smart_shift,
|
||||
reprogrammable_keys=_feature_reprogrammable_keys,
|
||||
disable_keyboard_keys=_feature_disable_keyboard_keys,
|
||||
)
|
||||
|
||||
del _SETTINGS_LIST
|
||||
|
@ -479,6 +524,7 @@ def check_feature_settings(device, already_known):
|
|||
if detected:
|
||||
already_known.append(detected)
|
||||
except Exception as reason:
|
||||
raise reason
|
||||
_log.error("check_feature[%s] inconsistent feature %s", featureId, reason)
|
||||
|
||||
check_feature(_HI_RES_SCROLL[0], _F.HI_RES_SCROLLING)
|
||||
|
@ -493,4 +539,5 @@ def check_feature_settings(device, already_known):
|
|||
check_feature(_SMART_SHIFT[0], _F.SMART_SHIFT)
|
||||
check_feature(_BACKLIGHT[0], _F.BACKLIGHT2)
|
||||
check_feature(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4)
|
||||
check_feature(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS, 'disable_keyboard_keys')
|
||||
return True
|
||||
|
|
|
@ -377,3 +377,12 @@ KEY_FLAG = _NamedInts(
|
|||
is_FN=0x02,
|
||||
mse=0x01
|
||||
)
|
||||
|
||||
DISABLE = _NamedInts(
|
||||
Caps_Lock=0x01,
|
||||
Num_Lock=0x02,
|
||||
Scroll_Lock=0x04,
|
||||
Insert=0x08,
|
||||
Win=0x10, # aka Super
|
||||
)
|
||||
DISABLE._fallback = lambda x: 'unknown:%02X' % x
|
||||
|
|
|
@ -190,6 +190,20 @@ def _create_sbox(s):
|
|||
elif s.kind == _SETTING_KIND.map_choice:
|
||||
control = _create_map_choice_control(s)
|
||||
sbox.pack_end(control, True, True, 0)
|
||||
elif s.kind == _SETTING_KIND.multiple_toggle:
|
||||
# ugly temporary hack!
|
||||
choices = {k : [False, True] for k in s._validator.options}
|
||||
class X:
|
||||
def __init__(self, obj, ext):
|
||||
self.obj = obj
|
||||
self.ext = ext
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
return self.ext[attr]
|
||||
except KeyError:
|
||||
return getattr(self.obj, attr)
|
||||
control = _create_map_choice_control(X(s, {'choices': choices}))
|
||||
sbox.pack_end(control, True, True, 0)
|
||||
else:
|
||||
raise Exception("NotImplemented")
|
||||
|
||||
|
|
Loading…
Reference in New Issue