setting: add setting to divert gestures

This commit is contained in:
Peter F. Patel-Schneider 2022-03-10 17:55:28 -05:00
parent 681a06d8d7
commit d115ade2ea
4 changed files with 97 additions and 47 deletions

View File

@ -932,7 +932,7 @@ ACTION_ID._fallback = lambda x: 'unknown:%04X' % x
class Gesture: class Gesture:
def __init__(self, device, low, high, next_index): def __init__(self, device, low, high, next_index, next_diversion_index):
self._device = device self._device = device
self.id = low self.id = low
self.gesture = GESTURE[low] self.gesture = GESTURE[low]
@ -942,31 +942,51 @@ class Gesture:
self.desired_software_default = high & 0x08 self.desired_software_default = high & 0x08
self.persistent = high & 0x10 self.persistent = high & 0x10
self.default_enabled = high & 0x20 self.default_enabled = high & 0x20
self.index = None self.index = next_index if self.can_be_enabled or self.default_enabled else None
if self.can_be_enabled or self.default_enabled: self.diversion_index = next_diversion_index if self.can_be_diverted else None
self.index = next_index self._enabled = None
self.offset, self.mask = self._offset_mask() self._diverted = None
def _offset_mask(self): # offset and mask def _offset_mask(self, index): # offset and mask
if self.index is not None: if index is not None:
offset = self.index >> 3 # 8 gestures per byte offset = index >> 3 # 8 gestures per byte
mask = 0x1 << (self.index % 8) mask = 0x1 << (index % 8)
return (offset, mask) return (offset, mask)
else: else:
return (None, None) return (None, None)
enable_offset_mask = lambda gesture: gesture._offset_mask(gesture.index)
diversion_offset_mask = lambda gesture: gesture._offset_mask(gesture.diversion_index)
def enabled(self): # is the gesture enabled? def enabled(self): # is the gesture enabled?
if self.offset is not None: if self._enabled is None and self.index is not None:
result = feature_request(self._device, FEATURE.GESTURE_2, 0x10, self.offset, 0x01, self.mask) offset, mask = self.enable_offset_mask()
return bool(result[0] & self.mask) if result else None result = feature_request(self._device, FEATURE.GESTURE_2, 0x10, offset, 0x01, mask)
self._enabled = bool(result[0] & mask) if result else None
return self._enabled
def set(self, enable): # enable or disable the gesture def set(self, enable): # enable or disable the gesture
if not self.can_be_enabled: if not self.can_be_enabled:
return None return None
if self.offset is not None: if self.index is not None:
reply = feature_request( offset, mask = self.enable_offset_mask()
self._device, FEATURE.GESTURE_2, 0x20, self.offset, 0x01, self.mask, self.mask if enable else 0x00 reply = feature_request(self._device, FEATURE.GESTURE_2, 0x20, offset, 0x01, mask, mask if enable else 0x00)
) return reply
def diverted(self): # is the gesture diverted?
if self._diverted is None and self.diversion_index is not None:
offset, mask = self.diversion_offset_mask()
result = feature_request(self._device, FEATURE.GESTURE_2, 0x30, offset, 0x01, mask)
self._diverted = bool(result[0] & mask) if result else None
return self._diverted
def divert(self, diverted): # divert or undivert the gesture
if not self.can_be_diverted:
return None
if self.diversion_index is not None:
offset, mask = self.diversion_offset_mask()
reply = feature_request(self._device, FEATURE.GESTURE_2, 0x40, offset, 0x01, mask, mask if diverted else 0x00)
return reply return reply
def as_int(self): def as_int(self):
@ -976,7 +996,7 @@ class Gesture:
return self.id return self.id
def __repr__(self): def __repr__(self):
return f'<Gesture {self.gesture} offset={self.offset} mask={self.mask}>' return f'<Gesture {self.gesture} index={self.index} diversion_index={self.diversion_index}>'
# allow a gesture to be used as a settings reader/writer to enable and disable the gesture # allow a gesture to be used as a settings reader/writer to enable and disable the gesture
read = enabled read = enabled
@ -1072,7 +1092,7 @@ class Gestures:
self.params = {} self.params = {}
self.specs = {} self.specs = {}
index = 0 index = 0
next_gesture_index = 0 next_gesture_index = next_divsn_index = 0
field_high = 0x00 field_high = 0x00
while field_high != 0x01: # end of fields while field_high != 0x01: # end of fields
# retrieve the next eight fields # retrieve the next eight fields
@ -1085,8 +1105,9 @@ class Gestures:
if field_high == 0x1: # end of fields if field_high == 0x1: # end of fields
break break
elif field_high & 0x80: elif field_high & 0x80:
gesture = Gesture(device, field_low, field_high, next_gesture_index) gesture = Gesture(device, field_low, field_high, next_gesture_index, next_divsn_index)
next_gesture_index = next_gesture_index if gesture.index is None else next_gesture_index + 1 next_gesture_index = next_gesture_index if gesture.index is None else next_gesture_index + 1
next_divsn_index = next_divsn_index if gesture.diversion_index is None else next_divsn_index + 1
self.gestures[gesture.gesture] = gesture self.gestures[gesture.gesture] = gesture
elif field_high & 0xF0 == 0x30 or field_high & 0xF0 == 0x20: elif field_high & 0xF0 == 0x30 or field_high & 0xF0 == 0x20:
param = Param(device, field_low, field_high) param = Param(device, field_low, field_high)
@ -1100,7 +1121,6 @@ class Gestures:
else: else:
_log.warn(f'Unimplemented GESTURE_2 field {field_low} {field_high} found.') _log.warn(f'Unimplemented GESTURE_2 field {field_low} {field_high} found.')
index += 1 index += 1
device._gestures = self
def gesture(self, gesture): def gesture(self, gesture):
return self.gestures.get(gesture, None) return self.gestures.get(gesture, None)

View File

@ -811,28 +811,32 @@ class BitFieldWithOffsetAndMaskValidator(Validator):
kind = KIND.multiple_toggle kind = KIND.multiple_toggle
sep = 0x01 sep = 0x01
def __init__(self, options, byte_count=None): def __init__(self, options, om_method=None, byte_count=None):
assert (isinstance(options, list)) assert (isinstance(options, list))
# each element of options must have .offset and .mask, # each element of options is an instance of a class
# and its int representation must be its id (not its index) # that has an id (which is used as an index in other dictionaries)
# and where om_method is a method that returns a byte offset and byte mask
# that says how to access and modify the bit toogle for the option
self.options = options self.options = options
self.om_method = om_method
# to retrieve the options efficiently: # to retrieve the options efficiently:
self._option_from_key = {} self._option_from_key = {}
self._mask_from_offset = {} self._mask_from_offset = {}
self._option_from_offset_mask = {} self._option_from_offset_mask = {}
for opt in options: for opt in options:
offset, mask = om_method(opt)
self._option_from_key[int(opt)] = opt self._option_from_key[int(opt)] = opt
try: try:
self._mask_from_offset[opt.offset] |= opt.mask self._mask_from_offset[offset] |= mask
except KeyError: except KeyError:
self._mask_from_offset[opt.offset] = opt.mask self._mask_from_offset[offset] = mask
try: try:
mask_to_opt = self._option_from_offset_mask[opt.offset] mask_to_opt = self._option_from_offset_mask[offset]
except KeyError: except KeyError:
mask_to_opt = {} mask_to_opt = {}
self._option_from_offset_mask[opt.offset] = mask_to_opt self._option_from_offset_mask[offset] = mask_to_opt
mask_to_opt[opt.mask] = opt mask_to_opt[mask] = opt
self.byte_count = (max(x.mask.bit_length() for x in options) + 7) // 8 self.byte_count = (max(om_method(x)[1].bit_length() for x in options) + 7) // 8 # is this correct??
if byte_count: if byte_count:
assert (isinstance(byte_count, int) and byte_count >= self.byte_count) assert (isinstance(byte_count, int) and byte_count >= self.byte_count)
self.byte_count = byte_count self.byte_count = byte_count
@ -849,8 +853,9 @@ class BitFieldWithOffsetAndMaskValidator(Validator):
option = self._option_from_key.get(key, None) option = self._option_from_key.get(key, None)
if option is None: if option is None:
return None return None
b = option.offset << (8 * (self.byte_count + 1)) offset, mask = option.om_method(option)
b |= (self.sep << (8 * self.byte_count)) | option.mask b = offset << (8 * (self.byte_count + 1))
b |= (self.sep << (8 * self.byte_count)) | mask
return _int2bytes(b, self.byte_count + 2) return _int2bytes(b, self.byte_count + 2)
def validate_read(self, reply_bytes_dict): def validate_read(self, reply_bytes_dict):
@ -872,10 +877,11 @@ class BitFieldWithOffsetAndMaskValidator(Validator):
w = {} w = {}
for k, v in new_value.items(): for k, v in new_value.items():
option = self._option_from_key[int(k)] option = self._option_from_key[int(k)]
if option.offset not in w: offset, mask = self.om_method(option)
w[option.offset] = 0 if offset not in w:
w[offset] = 0
if v: if v:
w[option.offset] |= option.mask w[offset] |= mask
return [ return [
_int2bytes((offset << (8 * (2 * self.byte_count + 1))) _int2bytes((offset << (8 * (2 * self.byte_count + 1)))
| (self.sep << (16 * self.byte_count)) | (self.sep << (16 * self.byte_count))

View File

@ -459,10 +459,11 @@ class ReprogrammableKeys(_Settings):
@classmethod @classmethod
def build(cls, setting_class, device): def build(cls, setting_class, device):
choices = {} choices = {}
for k in device.keys: if device.keys:
tgts = k.remappable_to for k in device.keys:
if len(tgts) > 1: tgts = k.remappable_to
choices[k.key] = tgts if len(tgts) > 1:
choices[k.key] = tgts
return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) if choices else None return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) if choices else None
@ -494,9 +495,10 @@ class DivertKeys(_Settings):
@classmethod @classmethod
def build(cls, setting_class, device): def build(cls, setting_class, device):
choices = {} choices = {}
for k in device.keys: if device.keys:
if 'divertable' in k.flags and 'virtual' not in k.flags: for k in device.keys:
choices[k.key] = setting_class.choices_universe if 'divertable' in k.flags and 'virtual' not in k.flags:
choices[k.key] = setting_class.choices_universe
if not choices: if not choices:
return None return None
return cls(choices, key_byte_count=2, byte_count=1, mask=0x01) return cls(choices, key_byte_count=2, byte_count=1, mask=0x01)
@ -982,14 +984,32 @@ class Gesture2Gestures(_BitFieldOMSetting):
description = _('Tweak the mouse/touchpad behaviour.') description = _('Tweak the mouse/touchpad behaviour.')
feature = _F.GESTURE_2 feature = _F.GESTURE_2
rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20}
validator_options = {'om_method': _hidpp20.Gesture.enable_offset_mask}
choices_universe = _hidpp20.GESTURE choices_universe = _hidpp20.GESTURE
_labels = _GESTURE2_GESTURES_LABELS _labels = _GESTURE2_GESTURES_LABELS
class validator_class(_BitFieldOMV): class validator_class(_BitFieldOMV):
@classmethod @classmethod
def build(cls, setting_class, device): def build(cls, setting_class, device, om_method=None):
options = [g for g in _hidpp20.get_gestures(device).gestures.values() if g.can_be_enabled or g.default_enabled] options = [g for g in device.gestures.gestures.values() if g.can_be_enabled or g.default_enabled]
return cls(options) if options else None return cls(options, om_method=om_method) if options else None
class Gesture2Divert(_BitFieldOMSetting):
name = 'gesture2-divert'
label = _('Gestures Diversion')
description = _('Divert mouse/touchpad gestures.')
feature = _F.GESTURE_2
rw_options = {'read_fnid': 0x30, 'write_fnid': 0x40}
validator_options = {'om_method': _hidpp20.Gesture.diversion_offset_mask}
choices_universe = _hidpp20.GESTURE
_labels = _GESTURE2_GESTURES_LABELS
class validator_class(_BitFieldOMV):
@classmethod
def build(cls, setting_class, device, om_method=None):
options = [g for g in device.gestures.gestures.values() if g.can_be_diverted]
return cls(options, om_method=om_method) if options else None
class Gesture2Params(_LongSettings): class Gesture2Params(_LongSettings):
@ -1147,6 +1167,7 @@ SETTINGS = [
DualPlatform, # simple DualPlatform, # simple
ChangeHost, # working ChangeHost, # working
Gesture2Gestures, # working Gesture2Gestures, # working
Gesture2Divert,
Gesture2Params, # working Gesture2Params, # working
] ]

View File

@ -262,11 +262,14 @@ def _print_device(dev, num=None):
(len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs)) (len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs))
) )
for k in dev.gestures.gestures.values(): for k in dev.gestures.gestures.values():
print(' %-26s Enabled (%4s): %s' % (k.gesture, k.index, k.enabled())) print(
' %-26s Enabled(%4s): %-5s Diverted:(%4s) %s' %
(k.gesture, k.index, k.enabled(), k.diversion_index, k.diverted())
)
for k in dev.gestures.params.values(): for k in dev.gestures.params.values():
print(' %-26s Value (%4s): %s [Default: %s]' % (k.param, k.index, k.value, k.default_value)) print(' %-26s Value (%4s): %s [Default: %s]' % (k.param, k.index, k.value, k.default_value))
for k in dev.gestures.specs.values(): for k in dev.gestures.specs.values():
print(' %-26s Spec (%4s): %s' % (k.spec, k.id, k.value)) print(' %-26s Spec (%4s): %s' % (k.spec, k.id, k.value))
if dev.online: if dev.online:
_battery_line(dev) _battery_line(dev)
else: else: