receiver: add PERSISTENT_REMAPPABLE_ACTION to KeysArray classes
This commit is contained in:
parent
3d87f418cf
commit
1deb6c34e4
|
@ -35,6 +35,7 @@ from .common import NamedInt as _NamedInt
|
||||||
from .common import NamedInts as _NamedInts
|
from .common import NamedInts as _NamedInts
|
||||||
from .common import UnsortedNamedInts as _UnsortedNamedInts
|
from .common import UnsortedNamedInts as _UnsortedNamedInts
|
||||||
from .common import bytes2int as _bytes2int
|
from .common import bytes2int as _bytes2int
|
||||||
|
from .common import int2bytes as _int2bytes
|
||||||
from .common import pack as _pack
|
from .common import pack as _pack
|
||||||
from .common import unpack as _unpack
|
from .common import unpack as _unpack
|
||||||
|
|
||||||
|
@ -561,12 +562,73 @@ class ReprogrammableKeyV4(ReprogrammableKey):
|
||||||
_log.debug(f"REPROG_CONTROLS_v4 setCidReporting on device {self._device} didn't echo request packet.")
|
_log.debug(f"REPROG_CONTROLS_v4 setCidReporting on device {self._device} didn't echo request packet.")
|
||||||
|
|
||||||
|
|
||||||
|
class PersistentRemappableAction():
|
||||||
|
def __init__(self, device, index, cid, actionId, remapped, modifierMask, cidStatus):
|
||||||
|
self._device = device
|
||||||
|
self.index = index
|
||||||
|
self._cid = cid
|
||||||
|
self.actionId = actionId
|
||||||
|
self.remapped = remapped
|
||||||
|
self._modifierMask = modifierMask
|
||||||
|
self.cidStatus = cidStatus
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key(self) -> _NamedInt:
|
||||||
|
return special_keys.CONTROL[self._cid]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actionType(self) -> _NamedInt:
|
||||||
|
return special_keys.ACTIONID[self._actionId]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action(self):
|
||||||
|
if self.actionId == special_keys.ACTIONID.Empty:
|
||||||
|
return None
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Key:
|
||||||
|
return 'Key: ' + str(self.modifiers) + str(self.remapped)
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Mouse:
|
||||||
|
return 'Mouse Button'
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Xdisp:
|
||||||
|
return 'X Displacement'
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Ydisp:
|
||||||
|
return 'Y Displacement'
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Vscroll:
|
||||||
|
return 'Vertical Scroll'
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Hscroll:
|
||||||
|
return 'Horizontal Scroll'
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Consumer:
|
||||||
|
return 'Consumer: ' + str(self.remapped)
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Internal:
|
||||||
|
return 'Internal Action'
|
||||||
|
elif self.actionId == special_keys.ACTIONID.Internal:
|
||||||
|
return 'Power'
|
||||||
|
else:
|
||||||
|
return 'Unknown'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def modifiers(self):
|
||||||
|
return special_keys.modifiers[self._modifierMask]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data_bytes(self):
|
||||||
|
return _int2bytes(self.actionId, 1) + _int2bytes(self.remapped, 2) + _int2bytes(self._modifierMask, 1)
|
||||||
|
|
||||||
|
def remap(self, data_bytes):
|
||||||
|
cid = _int2bytes(self._cid, 2)
|
||||||
|
if _bytes2int(data_bytes) == special_keys.KEYS_Default: # map back to default
|
||||||
|
feature_request(self._device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x50, cid, 0xFF)
|
||||||
|
self._device.remap_keys._query_key(self.index)
|
||||||
|
return self._device.remap_keys.keys[self.index].data_bytes
|
||||||
|
else:
|
||||||
|
self._actionId, self._code, self._modifierMask = _unpack('!BHB', data_bytes)
|
||||||
|
self.cidStatus = 0x01
|
||||||
|
feature_request(self._device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class KeysArray:
|
class KeysArray:
|
||||||
"""A sequence of key mappings supported by a HID++ 2.0 device."""
|
"""A sequence of key mappings supported by a HID++ 2.0 device."""
|
||||||
|
def __init__(self, device, count, version):
|
||||||
__slots__ = ('device', 'keys', 'keyversion', 'cid_to_tid', 'group_cids', 'lock')
|
|
||||||
|
|
||||||
def __init__(self, device, count):
|
|
||||||
assert device is not None
|
assert device is not None
|
||||||
self.device = device
|
self.device = device
|
||||||
self.lock = _threading.Lock()
|
self.lock = _threading.Lock()
|
||||||
|
@ -579,17 +641,6 @@ class KeysArray:
|
||||||
_log.error(f'Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.')
|
_log.error(f'Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.')
|
||||||
self.keyversion = None
|
self.keyversion = None
|
||||||
self.keys = [None] * count
|
self.keys = [None] * count
|
||||||
"""The mapping from Control IDs to their native Task IDs.
|
|
||||||
For example, Control "Left Button" is mapped to Task "Left Click".
|
|
||||||
When remapping controls, we point the control we want to remap
|
|
||||||
at a target Control ID rather than a target Task ID. This has the
|
|
||||||
effect of performing the native task of the target control,
|
|
||||||
even if the target itself is also remapped. So remapping
|
|
||||||
is not recursive."""
|
|
||||||
self.cid_to_tid = {}
|
|
||||||
"""The mapping from Control ID groups to Controls IDs that belong to it.
|
|
||||||
A key k can only be remapped to targets in groups within k.group_mask."""
|
|
||||||
self.group_cids = {g: [] for g in special_keys.CID_GROUP}
|
|
||||||
|
|
||||||
def _query_key(self, index: int):
|
def _query_key(self, index: int):
|
||||||
"""Queries the device for a given key and stores it in self.keys."""
|
"""Queries the device for a given key and stores it in self.keys."""
|
||||||
|
@ -651,6 +702,92 @@ class KeysArray:
|
||||||
return len(self.keys)
|
return len(self.keys)
|
||||||
|
|
||||||
|
|
||||||
|
class KeysArrayV1(KeysArray):
|
||||||
|
def __init__(self, device, count, version=1):
|
||||||
|
super().__init__(device, count, version)
|
||||||
|
"""The mapping from Control IDs to their native Task IDs.
|
||||||
|
For example, Control "Left Button" is mapped to Task "Left Click".
|
||||||
|
When remapping controls, we point the control we want to remap
|
||||||
|
at a target Control ID rather than a target Task ID. This has the
|
||||||
|
effect of performing the native task of the target control,
|
||||||
|
even if the target itself is also remapped. So remapping
|
||||||
|
is not recursive."""
|
||||||
|
self.cid_to_tid = {}
|
||||||
|
"""The mapping from Control ID groups to Controls IDs that belong to it.
|
||||||
|
A key k can only be remapped to targets in groups within k.group_mask."""
|
||||||
|
self.group_cids = {g: [] for g in special_keys.CID_GROUP}
|
||||||
|
|
||||||
|
def _query_key(self, index: int):
|
||||||
|
if index < 0 or index >= len(self.keys):
|
||||||
|
raise IndexError(index)
|
||||||
|
keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, 0x10, index)
|
||||||
|
if keydata:
|
||||||
|
cid, tid, flags = _unpack('!HHB', keydata[:5])
|
||||||
|
self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags)
|
||||||
|
self.cid_to_tid[cid] = tid
|
||||||
|
elif _log.isEnabledFor(_WARNING):
|
||||||
|
_log.warn(f"Key with index {index} was expected to exist but device doesn't report it.")
|
||||||
|
|
||||||
|
|
||||||
|
class KeysArrayV4(KeysArrayV1):
|
||||||
|
def __init__(self, device, count):
|
||||||
|
super().__init__(device, count, 4)
|
||||||
|
|
||||||
|
def _query_key(self, index: int):
|
||||||
|
if index < 0 or index >= len(self.keys):
|
||||||
|
raise IndexError(index)
|
||||||
|
keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index)
|
||||||
|
if keydata:
|
||||||
|
cid, tid, flags1, pos, group, gmask, flags2 = _unpack('!HHBBBBB', keydata[:9])
|
||||||
|
flags = flags1 | (flags2 << 8)
|
||||||
|
self.keys[index] = ReprogrammableKeyV4(self.device, index, cid, tid, flags, pos, group, gmask)
|
||||||
|
self.cid_to_tid[cid] = tid
|
||||||
|
if group != 0: # 0 = does not belong to a group
|
||||||
|
self.group_cids[special_keys.CID_GROUP[group]].append(cid)
|
||||||
|
elif _log.isEnabledFor(_WARNING):
|
||||||
|
_log.warn(f"Key with index {index} was expected to exist but device doesn't report it.")
|
||||||
|
|
||||||
|
|
||||||
|
# we are only interested in the current host, so use 0xFF for the host throughout
|
||||||
|
class KeysArrayPersistent(KeysArray):
|
||||||
|
def __init__(self, device, count):
|
||||||
|
super().__init__(device, count, 5)
|
||||||
|
self._capabilities = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capabilities(self):
|
||||||
|
if self._capabilities is None and self.device.online:
|
||||||
|
capabilities = self.device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x00)
|
||||||
|
assert capabilities, 'Oops, persistent remappable key capabilities cannot be retrieved!'
|
||||||
|
self._capabilities = _unpack('!H', capabilities[:2])[0] # flags saying what the mappings are possible
|
||||||
|
return self._capabilities
|
||||||
|
|
||||||
|
def _query_key(self, index: int):
|
||||||
|
if index < 0 or index >= len(self.keys):
|
||||||
|
raise IndexError(index)
|
||||||
|
keydata = feature_request(self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xff)
|
||||||
|
if keydata:
|
||||||
|
key = _unpack('!H', keydata[:2])[0]
|
||||||
|
try:
|
||||||
|
mapped_data = feature_request(
|
||||||
|
self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x30, key & 0xff00, key & 0xff, 0xff
|
||||||
|
)
|
||||||
|
if mapped_data:
|
||||||
|
_ignore, _ignore, actionId, remapped, modifiers, status = _unpack('!HBBHBB', mapped_data[:8])
|
||||||
|
except Exception:
|
||||||
|
actionId = remapped = modifiers = status = 0
|
||||||
|
actionId = special_keys.ACTIONID[actionId]
|
||||||
|
if actionId == special_keys.ACTIONID.Key:
|
||||||
|
remapped = special_keys.USB_HID_KEYCODES[remapped]
|
||||||
|
elif actionId == special_keys.ACTIONID.Mouse:
|
||||||
|
remapped = special_keys.MOUSE_BUTTONS[remapped]
|
||||||
|
elif actionId == special_keys.ACTIONID.Consumer:
|
||||||
|
remapped = special_keys.HID_CONSUMERCODES[remapped]
|
||||||
|
self.keys[index] = PersistentRemappableAction(self.device, index, key, actionId, remapped, modifiers, status)
|
||||||
|
elif _log.isEnabledFor(_WARNING):
|
||||||
|
_log.warn(f"Key with index {index} was expected to exist but device doesn't report it.")
|
||||||
|
|
||||||
|
|
||||||
# Gesture Ids for feature GESTURE_2
|
# Gesture Ids for feature GESTURE_2
|
||||||
GESTURE = _NamedInts(
|
GESTURE = _NamedInts(
|
||||||
Tap1Finger=1, # task Left_Click
|
Tap1Finger=1, # task Left_Click
|
||||||
|
@ -1209,10 +1346,17 @@ def get_keys(device):
|
||||||
count = None
|
count = None
|
||||||
if FEATURE.REPROG_CONTROLS_V2 in device.features:
|
if FEATURE.REPROG_CONTROLS_V2 in device.features:
|
||||||
count = feature_request(device, FEATURE.REPROG_CONTROLS_V2)
|
count = feature_request(device, FEATURE.REPROG_CONTROLS_V2)
|
||||||
|
return KeysArrayV1(device, ord(count[:1]))
|
||||||
elif FEATURE.REPROG_CONTROLS_V4 in device.features:
|
elif FEATURE.REPROG_CONTROLS_V4 in device.features:
|
||||||
count = feature_request(device, FEATURE.REPROG_CONTROLS_V4)
|
count = feature_request(device, FEATURE.REPROG_CONTROLS_V4)
|
||||||
|
return KeysArrayV4(device, ord(count[:1]))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_remap_keys(device):
|
||||||
|
count = feature_request(device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x10)
|
||||||
if count:
|
if count:
|
||||||
return KeysArray(device, ord(count[:1]))
|
return KeysArrayPersistent(device, ord(count[:1]))
|
||||||
|
|
||||||
|
|
||||||
def get_gestures(device):
|
def get_gestures(device):
|
||||||
|
|
Loading…
Reference in New Issue