From 484b097664af41099cefad83513a35b61f10e56f Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Thu, 10 Mar 2022 06:10:56 -0500 Subject: [PATCH] settings: handle PERSISTENT REMAPPABLE ACTION for M720 mouse --- lib/logitech_receiver/hidpp20.py | 16 +-- lib/logitech_receiver/settings_templates.py | 17 ++- lib/logitech_receiver/special_keys.py | 132 ++++++++++++-------- 3 files changed, 104 insertions(+), 61 deletions(-) diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index 74f8d92c..015a5235 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -587,21 +587,21 @@ class PersistentRemappableAction(): 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' + return 'Mouse Button: ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Xdisp: - return 'X Displacement' + return 'X Displacement ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Ydisp: - return 'Y Displacement' + return 'Y Displacement ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Vscroll: - return 'Vertical Scroll' + return 'Vertical Scroll ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Hscroll: - return 'Horizontal Scroll' + return 'Horizontal Scroll: ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Consumer: return 'Consumer: ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Internal: - return 'Internal Action' + return 'Internal Action ' + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Internal: - return 'Power' + return 'Power ' + str(self.remapped) else: return 'Unknown' @@ -781,6 +781,8 @@ class KeysArrayPersistent(KeysArray): remapped = special_keys.USB_HID_KEYCODES[remapped] elif actionId == special_keys.ACTIONID.Mouse: remapped = special_keys.MOUSE_BUTTONS[remapped] + elif actionId == special_keys.ACTIONID.Hscroll: + remapped = special_keys.HORIZONTAL_SCROLL[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) diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 6a6093ad..3c3bbdcd 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -18,6 +18,7 @@ from logging import DEBUG as _DEBUG from logging import INFO as _INFO +from logging import WARN as _WARN from logging import getLogger from time import time as _time @@ -1042,7 +1043,8 @@ class MRKeyLED(_Setting): return b'\x00' -## Only implemented for devices that can produce HID and Consumer Key Codes +## Only implemented for devices that can produce Key and Consumer Codes (e.g., Craft) +## and devices that can produce Key, Mouse, and Horizontal Scroll (e.g., M720) ## Only interested in current host, so use 0xFF for it class PersistentRemappableAction(_Settings): name = 'persistent-remappable-keys' @@ -1074,12 +1076,21 @@ class PersistentRemappableAction(_Settings): @classmethod def build(cls, setting_class, device): remap_keys = device.remap_keys - if not remap_keys or not device.remap_keys.capabilities & 0x0041 == 0x0041: # HID and Consumer Key Codes + if not remap_keys: + return None + capabilities = device.remap_keys.capabilities + if capabilities & 0x0041 == 0x0041: # Key and Consumer Codes + keys = _special_keys.KEYS_KEYS_CONSUMER + elif capabilities & 0x0023 == 0x0023: # Key, Mouse, and HScroll Codes + keys = _special_keys.KEYS_KEYS_MOUSE_HSCROLL + else: + if _log.isEnabledFor(_WARN): + _log.warn('%s: unimplemented Persistent Remappable capability %s', device.name, hex(capabilities)) return None choices = {} for k in remap_keys: key = _special_keys.CONTROL[k.key] - choices[key] = _special_keys.KEYS + choices[key] = keys # TO RECOVER FROM BAD VALUES use _special_keys.KEYS return cls(choices, key_byte_count=2, byte_count=4) if choices else None diff --git a/lib/logitech_receiver/special_keys.py b/lib/logitech_receiver/special_keys.py index 30917d45..18b7ffe5 100644 --- a/lib/logitech_receiver/special_keys.py +++ b/lib/logitech_receiver/special_keys.py @@ -597,34 +597,6 @@ DISABLE = _NamedInts( ) DISABLE._fallback = lambda x: 'unknown:%02X' % x -## -## Information for x1c00 Persistent from https://drive.google.com/drive/folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28 -## - -KEYMOD = _NamedInts(CTRL=0x01, SHIFT=0x02, ALT=0x04, META=0x08, RCTRL=0x10, RSHIFT=0x20, RALT=0x40, RMETA=0x80) - -ACTIONID = _NamedInts( - Empty=0x00, - Key=0x01, - Mouse=0x02, - Xdisp=0x03, - Ydisp=0x04, - Vscroll=0x05, - Hscroll=0x06, - Consumer=0x07, - Internal=0x08, - Power=0x09 -) - -MOUSE_BUTTONS = _NamedInts( - Left=0x01, - Right=0x02, - Middle=0x04, - Back=0x08, - Forward=0x10, -) -MOUSE_BUTTONS._fallback = lambda x: 'unknown:%02X' % x - # HID USB Keycodes from https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf # Modified by information from Linux HID driver linux/drivers/hid/hid-input.c USB_HID_KEYCODES = _NamedInts( @@ -798,28 +770,6 @@ USB_HID_KEYCODES[0x26] = '9' USB_HID_KEYCODES[0x27] = '0' USB_HID_KEYCODES[0x64] = '102ND' -# Construct keys plus modifiers -modifiers = { - 0x0: '', - 0x1: 'Cntrl+', - 0x2: 'Shift+', - 0x4: 'Alt+', - 0x8: 'Meta+', - 0x3: 'Cntrl+Shift+', - 0x5: 'Alt+Cntrl+', - 0x9: 'Meta+Cntrl+', - 0x6: 'Alt+Shift+', - 0xA: 'Meta+Shift+', - 0xC: 'Meta+Alt+' -} -KEYS_Default = 0x7FFFFFFF # Special value to reset key to default - has to be different from all others -KEYS = _UnsortedNamedInts() -KEYS[KEYS_Default] = 'Default' # Value to reset to default -KEYS[0] = 'None' # Value for no output -for val, name in modifiers.items(): - for key in USB_HID_KEYCODES: - KEYS[0x01000000 + (int(key) << 8) + val] = name + str(key) - HID_CONSUMERCODES = _NamedInts( # Unassigned=0x00, # Consumer_Control=0x01, @@ -1203,5 +1153,85 @@ HID_CONSUMERCODES[0x20] = '+10' HID_CONSUMERCODES[0x21] = '+100' HID_CONSUMERCODES._fallback = lambda x: 'unknown:%04X' % x +## Information for x1c00 Persistent from https://drive.google.com/drive/folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28 + +KEYMOD = _NamedInts(CTRL=0x01, SHIFT=0x02, ALT=0x04, META=0x08, RCTRL=0x10, RSHIFT=0x20, RALT=0x40, RMETA=0x80) + +ACTIONID = _NamedInts( + Empty=0x00, + Key=0x01, + Mouse=0x02, + Xdisp=0x03, + Ydisp=0x04, + Vscroll=0x05, + Hscroll=0x06, + Consumer=0x07, + Internal=0x08, + Power=0x09 +) + +MOUSE_BUTTONS = _NamedInts( + Mouse_Button_Left=0x0001, + Mouse_Button_Right=0x0002, + Mouse_Button_Middle=0x0004, + Mouse_Button_Back=0x0008, + Mouse_Button_Forward=0x0010, +) +MOUSE_BUTTONS._fallback = lambda x: 'unknown mouse button:%04X' % x + +HORIZONTAL_SCROLL = _NamedInts( + Horizontal_Scroll_Left=0x4000, + Horizontal_Scroll_Right=0x8000, +) +HORIZONTAL_SCROLL._fallback = lambda x: 'unknown horizontal scroll:%04X' % x + +# Construct universe for Persistent Remappable Keys setting (only for supported values) +KEYS = _UnsortedNamedInts() +KEYS_Default = 0x7FFFFFFF # Special value to reset key to default - has to be different from all others +KEYS[KEYS_Default] = 'Default' # Value to reset to default +KEYS[0] = 'None' # Value for no output + +# Add HID keys plus modifiers +modifiers = { + 0x00: '', + 0x01: 'Cntrl+', + 0x02: 'Shift+', + 0x04: 'Alt+', + 0x08: 'Meta+', + 0x03: 'Cntrl+Shift+', + 0x05: 'Alt+Cntrl+', + 0x09: 'Meta+Cntrl+', + 0x06: 'Alt+Shift+', + 0x0A: 'Meta+Shift+', + 0x0C: 'Meta+Alt+' +} +for val, name in modifiers.items(): + for key in USB_HID_KEYCODES: + KEYS[(ACTIONID.Key << 24) + (int(key) << 8) + val] = name + str(key) + +# Add HID Consumer Codes for code in HID_CONSUMERCODES: - KEYS[0x07000000 + (int(code) << 8)] = str(code) + KEYS[(ACTIONID.Consumer << 24) + (int(code) << 8)] = str(code) + +# Add Mouse Buttons +for code in MOUSE_BUTTONS: + KEYS[(ACTIONID.Mouse << 24) + (int(code) << 8)] = str(code) + +# Add Horizontal Scroll +for code in HORIZONTAL_SCROLL: + KEYS[(ACTIONID.Hscroll << 24) + (int(code) << 8)] = str(code) + + +# Construct subsets for known devices +def persistent_keys(action_ids): + keys = _UnsortedNamedInts() + keys[KEYS_Default] = 'Default' # Value to reset to default + keys[0] = 'None' # Value for no output + for key in KEYS: + if (int(key) >> 24) in action_ids: + keys[int(key)] = str(key) + return keys + + +KEYS_KEYS_CONSUMER = persistent_keys([ACTIONID.Key, ACTIONID.Consumer]) +KEYS_KEYS_MOUSE_HSCROLL = persistent_keys([ACTIONID.Key, ACTIONID.Mouse, ACTIONID.Hscroll])