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:
def __init__(self, device, low, high, next_index):
def __init__(self, device, low, high, next_index, next_diversion_index):
self._device = device
self.id = low
self.gesture = GESTURE[low]
@ -942,31 +942,51 @@ class Gesture:
self.desired_software_default = high & 0x08
self.persistent = high & 0x10
self.default_enabled = high & 0x20
self.index = None
if self.can_be_enabled or self.default_enabled:
self.index = next_index
self.offset, self.mask = self._offset_mask()
self.index = next_index if self.can_be_enabled or self.default_enabled else None
self.diversion_index = next_diversion_index if self.can_be_diverted else None
self._enabled = None
self._diverted = None
def _offset_mask(self): # offset and mask
if self.index is not None:
offset = self.index >> 3 # 8 gestures per byte
mask = 0x1 << (self.index % 8)
def _offset_mask(self, index): # offset and mask
if index is not None:
offset = index >> 3 # 8 gestures per byte
mask = 0x1 << (index % 8)
return (offset, mask)
else:
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?
if self.offset is not None:
result = feature_request(self._device, FEATURE.GESTURE_2, 0x10, self.offset, 0x01, self.mask)
return bool(result[0] & self.mask) if result else None
if self._enabled is None and self.index is not None:
offset, mask = self.enable_offset_mask()
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
if not self.can_be_enabled:
return None
if self.offset is not None:
reply = feature_request(
self._device, FEATURE.GESTURE_2, 0x20, self.offset, 0x01, self.mask, self.mask if enable else 0x00
)
if self.index is not None:
offset, mask = self.enable_offset_mask()
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
def as_int(self):
@ -976,7 +996,7 @@ class Gesture:
return self.id
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
read = enabled
@ -1072,7 +1092,7 @@ class Gestures:
self.params = {}
self.specs = {}
index = 0
next_gesture_index = 0
next_gesture_index = next_divsn_index = 0
field_high = 0x00
while field_high != 0x01: # end of fields
# retrieve the next eight fields
@ -1085,8 +1105,9 @@ class Gestures:
if field_high == 0x1: # end of fields
break
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_divsn_index = next_divsn_index if gesture.diversion_index is None else next_divsn_index + 1
self.gestures[gesture.gesture] = gesture
elif field_high & 0xF0 == 0x30 or field_high & 0xF0 == 0x20:
param = Param(device, field_low, field_high)
@ -1100,7 +1121,6 @@ class Gestures:
else:
_log.warn(f'Unimplemented GESTURE_2 field {field_low} {field_high} found.')
index += 1
device._gestures = self
def gesture(self, gesture):
return self.gestures.get(gesture, None)

View File

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

View File

@ -459,10 +459,11 @@ class ReprogrammableKeys(_Settings):
@classmethod
def build(cls, setting_class, device):
choices = {}
for k in device.keys:
tgts = k.remappable_to
if len(tgts) > 1:
choices[k.key] = tgts
if device.keys:
for k in device.keys:
tgts = k.remappable_to
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
@ -494,9 +495,10 @@ class DivertKeys(_Settings):
@classmethod
def build(cls, setting_class, device):
choices = {}
for k in device.keys:
if 'divertable' in k.flags and 'virtual' not in k.flags:
choices[k.key] = setting_class.choices_universe
if device.keys:
for k in device.keys:
if 'divertable' in k.flags and 'virtual' not in k.flags:
choices[k.key] = setting_class.choices_universe
if not choices:
return None
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.')
feature = _F.GESTURE_2
rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20}
validator_options = {'om_method': _hidpp20.Gesture.enable_offset_mask}
choices_universe = _hidpp20.GESTURE
_labels = _GESTURE2_GESTURES_LABELS
class validator_class(_BitFieldOMV):
@classmethod
def build(cls, setting_class, device):
options = [g for g in _hidpp20.get_gestures(device).gestures.values() if g.can_be_enabled or g.default_enabled]
return cls(options) if options else None
def build(cls, setting_class, device, om_method=None):
options = [g for g in device.gestures.gestures.values() if g.can_be_enabled or g.default_enabled]
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):
@ -1147,6 +1167,7 @@ SETTINGS = [
DualPlatform, # simple
ChangeHost, # working
Gesture2Gestures, # working
Gesture2Divert,
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))
)
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():
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():
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:
_battery_line(dev)
else: