From 51532252df02a67667243a719d3e24f4a069e852 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Thu, 12 Mar 2026 10:52:30 -0400 Subject: [PATCH 1/9] ui: better handling of missing devices --- lib/solaar/listener.py | 9 ++++++--- lib/solaar/ui/__init__.py | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index b2cab707..74d2cd89 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -314,10 +314,13 @@ def ping_all(resuming=False): for dev in listener_thread.receiver: if resuming: dev._active = None # ensure that settings are pushed - if dev.ping(): - dev.changed(active=True, push=True) - listener_thread._status_changed(dev) count -= 1 + try: # sometimes the device is not set up already, it should come back later + if dev.ping(): + dev.changed(active=True, push=True) + listener_thread._status_changed(dev) + except exceptions.NoSuchDevice: + logger.debug("can't ping device on resume: %s", dev) if not count: break diff --git a/lib/solaar/ui/__init__.py b/lib/solaar/ui/__init__.py index c97104cc..d3cc8136 100644 --- a/lib/solaar/ui/__init__.py +++ b/lib/solaar/ui/__init__.py @@ -122,7 +122,9 @@ def run_loop( def _status_changed(device, alert, reason, refresh=False): - assert device is not None + if device is None: + logger.debug("status changed on nil device: %s (%s) %s", device, alert, reason) + return logger.debug("status changed: %s (%s) %s", device, alert, reason) if alert is None: alert = Alert.NONE From 55a67c142c05d034ba54bbbf41d47f06ef831442 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sun, 8 Mar 2026 20:47:28 -0400 Subject: [PATCH 2/9] device: remove incorrect descriptor for WPID 4004 --- lib/logitech_receiver/descriptors.py | 2 +- lib/logitech_receiver/settings_templates.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 3541d520..fc81d494 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -213,7 +213,7 @@ _D( _D("Wireless Keyboard K520", codename="K520", protocol=1.0, wpid="2011", registers=(Reg.BATTERY_STATUS,)) _D("Wireless Solar Keyboard K750", codename="K750", protocol=2.0, wpid="4002") _D("Wireless Keyboard K270 (unifying)", codename="K270", protocol=2.0, wpid="4003") -_D("Wireless Keyboard K360", codename="K360", protocol=2.0, wpid="4004") +# incorrect _D("Wireless Keyboard K360", codename="K360", protocol=2.0, wpid="4004") _D("Wireless Keyboard K230", codename="K230", protocol=2.0, wpid="400D") _D("Wireless Touch Keyboard K400", codename="K400", protocol=2.0, wpid=("400E", "4024")) _D("Wireless Keyboard MK270", codename="MK270", protocol=2.0, wpid="4023") diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 9571723f..ea370017 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -210,7 +210,7 @@ descriptors.get_wpid("1013").settings = [RegisterSmoothScroll, RegisterSideScrol descriptors.get_wpid("1014").settings = [RegisterSmoothScroll, RegisterSideScroll] descriptors.get_wpid("1017").settings = [RegisterSmoothScroll, RegisterSideScroll] descriptors.get_wpid("1023").settings = [RegisterSmoothScroll, RegisterSideScroll] -descriptors.get_wpid("4004").settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +# somehow messed up ? descriptors.get_wpid("4004").settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] descriptors.get_wpid("101A").settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] descriptors.get_wpid("101B").settings = [RegisterSmoothScroll, RegisterSideScroll] descriptors.get_wpid("101D").settings = [RegisterSmoothScroll, RegisterSideScroll] From 94e94c1254397fbfc7608b07fe8f41f78aa04625 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sun, 8 Mar 2026 20:48:48 -0400 Subject: [PATCH 3/9] hidpp: add feature x1b04 flag sent by M510 4004 --- lib/logitech_receiver/hidpp20.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index 05295846..92d09dec 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -118,6 +118,7 @@ class MappingFlag(Flag): UNUSED_4000 = 0x4000 UNUSED_1000 = 0x1000 RAW_WHEEL = 0x400 + UNKNOWN_200 = 0x200 # seen on a Wireless Mouse M510 WPID 4004 ANALYTICS_KEY_EVENTS_REPORTING = 0x100 FORCE_RAW_XY_DIVERTED = 0x40 RAW_XY_DIVERTED = 0x10 From 7520c9cc284c036828a798e6224b79b4a6a084ab Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Mon, 9 Mar 2026 06:15:44 -0400 Subject: [PATCH 4/9] hidpp20: be defensive about no device features --- lib/logitech_receiver/device.py | 2 +- lib/logitech_receiver/hidpp20.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index d241249e..60611131 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -204,7 +204,7 @@ class Device: self.registers = self.descriptor.registers if self.descriptor.registers else [] if self._protocol is not None: - self.features = None if self._protocol < 2.0 else hidpp20.FeaturesArray(self) + self.features = {} if self._protocol < 2.0 else hidpp20.FeaturesArray(self) else: self.features = hidpp20.FeaturesArray(self) # may be a 2.0 device; if not, it will fix itself later diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index 92d09dec..c966981d 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -1690,10 +1690,10 @@ class Hidpp20: def get_keys(self, device: Device): # TODO: add here additional variants for other REPROG_CONTROLS count = None - if SupportedFeature.REPROG_CONTROLS_V2 in device.features: + if device.features and SupportedFeature.REPROG_CONTROLS_V2 in device.features: count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V2) return KeysArrayV2(device, ord(count[:1])) - elif SupportedFeature.REPROG_CONTROLS_V4 in device.features: + elif device.features and SupportedFeature.REPROG_CONTROLS_V4 in device.features: count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V4) return KeysArrayV4(device, ord(count[:1])) return None From dc9affe6fb5393817fb94dfa95be371c92981fd2 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Fri, 13 Mar 2026 17:58:51 -0400 Subject: [PATCH 5/9] hidpp: fix bug when showing device notification flags --- lib/logitech_receiver/hidpp10_constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/logitech_receiver/hidpp10_constants.py b/lib/logitech_receiver/hidpp10_constants.py index 1f52e411..62a725b3 100644 --- a/lib/logitech_receiver/hidpp10_constants.py +++ b/lib/logitech_receiver/hidpp10_constants.py @@ -16,8 +16,8 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import annotations -from enum import Flag from enum import IntEnum +from enum import IntFlag from typing import List from .common import NamedInts @@ -68,7 +68,7 @@ class PowerSwitchLocation(IntEnum): return cls.UNKNOWN -class NotificationFlag(Flag): +class NotificationFlag(IntFlag): """Some flags are used both by devices and receivers. The Logitech documentation mentions that the first and last (third) @@ -207,7 +207,7 @@ class InfoSubRegisters(IntEnum): BOLT_DEVICE_NAME = 0x60 # 0x6N01, by connected device -class DeviceFeature(Flag): +class DeviceFeature(IntFlag): """Features for devices. Flags taken from From ee25bc76c76e4ace788ff0c8c2ab8187cbe0fb56 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Wed, 18 Mar 2026 16:38:01 -0400 Subject: [PATCH 6/9] device: put lock around getting device name --- lib/logitech_receiver/device.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 60611131..fbfdfb29 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -153,6 +153,7 @@ class Device: self._gestures_lock = threading.Lock() self._settings_lock = threading.Lock() self._persister_lock = threading.Lock() + self._simple_lock = threading.Lock() self._notification_handlers = {} # See `add_notification_handler` self.cleanups = [] # functions to run on the device when it is closed @@ -243,8 +244,10 @@ class Device: @property def name(self): if not self._name: - if self.online and self.protocol >= 2.0: - self._name = _hidpp20.get_name(self) + with self._simple_lock: + if self._name is None: + if self.online and self.protocol >= 2.0: + self._name = _hidpp20.get_name(self) return self._name or self._codename or f"Unknown device {self.wpid or self.product_id}" def get_ids(self): From 230eaf242c0f8f7f3ab6bad710e9cf5ae77d5fce Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Thu, 19 Mar 2026 10:08:42 -0400 Subject: [PATCH 7/9] docs: add several device descriptions --- docs/devices/G502 X PLUS 4099.txt | 101 +++++++++++++++ ...reless Mechanical Gaming Keyboard 4065.txt | 116 +++++++++--------- ...tech G933 Gaming Wireless Headset 0A5B.txt | 41 +++++++ 3 files changed, 201 insertions(+), 57 deletions(-) create mode 100644 docs/devices/G502 X PLUS 4099.txt create mode 100644 docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt diff --git a/docs/devices/G502 X PLUS 4099.txt b/docs/devices/G502 X PLUS 4099.txt new file mode 100644 index 00000000..25482b4c --- /dev/null +++ b/docs/devices/G502 X PLUS 4099.txt @@ -0,0 +1,101 @@ +solaar version 1.1.19-25-g7520c9cc + + 1: G502 X PLUS + Device path : /dev/hidraw8 + WPID : 4099 + Codename : G502 X PLUS + Kind : mouse + Protocol : HID++ 4.2 + Report Rate : 1ms + Serial number: C6884511 + Model ID: 4099C0950000 + Unit ID: C6884511 + 1: BL1 42.00.B0016 + 0: MPM 27.00.B0016 + 3: + 3: + 3: + 3: + 3: + 3: + 3: + 3: + 3: + 3: + The power switch is located on the unknown. + Supports 32 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: 1 BL1 42.00.B0016 AB0BFBB13A33 + Firmware: 0 MPM 27.00.B0016 4099FBB13A33 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Unit ID: C6884511 Model ID: 4099C0950000 Transport IDs: {'wpid': '4099', 'usbid': 'C095'} + 3: DEVICE NAME {0005} V0 + Name: G502 X PLUS + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: UNIFIED BATTERY {1004} V3 + Battery: 77%, BatteryStatus.DISCHARGING. + 7: ADJUSTABLE DPI {2201} V2 + Sensitivity (DPI) (saved): 800 + Sensitivity (DPI) : 800 + 8: HIRES WHEEL {2121} V0 + Multiplier: 8 + Has invert: Normal wheel motion + Has ratchet switch: Normal wheel mode + Low resolution mode + HID notification + Scroll Wheel Direction (saved): False + Scroll Wheel Direction : False + Scroll Wheel Resolution (saved): False + Scroll Wheel Resolution : False + Scroll Wheel Diversion (saved): False + Scroll Wheel Diversion : False + 9: RGB EFFECTS {8071} V2 + LED Control (saved): Solaar + LED Control : Solaar + LEDs Primary (saved): !LEDEffectSetting {ID: 0, color: 10820909, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs Primary : !LEDEffectSetting {ID: 0, color: 10820909, intensity: 0, period: 100, ramp: 0, speed: 0} + 10: PER KEY LIGHTING V2 {8081} V2 + Per-key Lighting (saved): {A:No change, B:No change, C:No change, D:No change, E:No change, F:No change, G:No change, H:No change} + Per-key Lighting : {A:No change, B:No change, C:No change, D:No change, E:No change, F:No change, G:No change, H:No change} + 11: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles (saved): Profile 1 + Onboard Profiles : Profile 1 + 12: MOUSE BUTTON SPY {8110} V0 + 13: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 14: FORCE PAIRING {1500} V0 + 15: DFU {00D0} V3 + 16: DEVICE RESET {1802} V0 + 17: unknown:1803 {0318} V0 internal, hidden, unknown:000010 + 18: CONFIG DEVICE PROPS {1806} V8 + 19: unknown:1811 {1118} V0 internal, hidden, unknown:000010 + 20: OOBSTATE {1805} V0 + 21: unknown:1830 {3018} V0 internal, hidden, unknown:000010 + 22: unknown:1875 {7518} V0 internal, hidden, unknown:000010 + 23: unknown:1861 {6118} V0 internal, hidden, unknown:000010 + 24: unknown:1890 {9018} V0 internal, hidden, unknown:000008 + 25: unknown:18A1 {A118} V0 internal, hidden, unknown:000010 + 26: unknown:1801 {0118} V0 internal, hidden, unknown:000010 + 27: unknown:1E00 {001E} V0 hidden + 28: unknown:1E22 {221E} V0 internal, hidden, unknown:000010 + 29: unknown:1EB0 {B01E} V0 internal, hidden, unknown:000010 + 30: unknown:18B1 {B118} V0 internal, hidden, unknown:000010 + 31: unknown:18C0 {C018} V0 internal, hidden, unknown:000010 + Battery: 77%, BatteryStatus.DISCHARGING. diff --git a/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt b/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt index 3b1642bb..fcab437d 100644 --- a/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt +++ b/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt @@ -1,4 +1,4 @@ -Solaar version 1.1.3 +solaar version 1.1.19-25-g7520c9cc 1: G613 Wireless Mechanical Gaming Keyboard Device path : None @@ -6,69 +6,71 @@ Solaar version 1.1.3 Codename : G613 Kind : keyboard Protocol : HID++ 4.2 - Polling rate : 1 ms (1000Hz) - Serial number: 0DBC5FF6 + Report Rate : 1ms + Serial number: 710EC3A3 Model ID: B34F40650000 - Unit ID: 9AAB3225 - Bootloader: BOT 46.00.B0006 - Firmware: MPK 05.02.B0021 - Other: + Unit ID: 2A923B25 + 1: BOT 46.00.B0006 + 0: MPK 05.02.B0021 + 3: + The power switch is located on the unknown. Supports 32 HID++ 2.0 features: - 0: ROOT {0000} - 1: FEATURE SET {0001} - 2: DEVICE FW VERSION {0003} - Firmware: Bootloader BOT 46.00.B0006 00006E86A7BD - Firmware: Firmware MPK 05.02.B0021 40656E86A7BD - Firmware: Other - Unit ID: 9AAB3225 Model ID: B34F40650000 Transport IDs: {'btleid': 'B34F', 'wpid': '4065'} - 3: DEVICE NAME {0005} + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: 1 BOT 46.00.B0006 00006E86A7BD + Firmware: 0 MPK 05.02.B0021 40656E86A7BD + Firmware: 3 + Unit ID: 2A923B25 Model ID: B34F40650000 Transport IDs: {'btleid': 'B34F', 'wpid': '4065'} + 3: DEVICE NAME {0005} V0 Name: G613 Wireless Mechanical Gaming Keyboard Kind: keyboard - 4: WIRELESS DEVICE STATUS {1D4B} - 5: RESET {0020} - 6: DEVICE FRIENDLY NAME {0007} + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: G613 - 7: BATTERY STATUS {1000} - Battery: 50%, discharging, next level 20%. - 8: CHANGE HOST {1814} - Change Host : 1:video2 - 9: HOSTS INFO {1815} - Host 0 (paired): video2 - Host 1 (paired): CB73CG2 - 10: REPROG CONTROLS V4 {1B04} + 7: BATTERY STATUS {1000} V0 + Battery: 80%, BatteryStatus.DISCHARGING, next level 50%. + 8: CHANGE HOST {1814} V1 + Change Host : 1:cosmo + 9: HOSTS INFO {1815} V1 + Host 0 (paired): cosmo + Host 1 (paired): Mi 11 + 10: REPROG CONTROLS V4 {1B04} V3 Key/Button Diversion (saved): {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular} Key/Button Diversion : {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular} - 11: REPORT HID USAGE {1BC0} - 12: ENCRYPTION {4100} - 13: KEYBOARD DISABLE BY USAGE {4522} - 14: KEYBOARD LAYOUT 2 {4540} - 15: GKEY {8010} - Divert G Keys (saved): True - Divert G Keys : False - 16: REPORT RATE {8060} - Polling Rate (ms): 1 - Polling Rate (ms) (saved): 1 - Polling Rate (ms) : 1 - 17: DFUCONTROL SIGNED {00C2} - 18: DEVICE RESET {1802} internal, hidden - 19: unknown:1803 {1803} internal, hidden - 20: CONFIG DEVICE PROPS {1806} internal, hidden - 21: unknown:1813 {1813} internal, hidden - 22: OOBSTATE {1805} internal, hidden - 23: unknown:1830 {1830} internal, hidden - 24: unknown:1890 {1890} internal, hidden - 25: unknown:1891 {1891} internal, hidden - 26: unknown:18A1 {18A1} internal, hidden - 27: unknown:1DF3 {1DF3} internal, hidden - 28: unknown:1E00 {1E00} hidden - 29: unknown:1EB0 {1EB0} internal, hidden - 30: unknown:1861 {1861} internal, hidden - 31: unknown:18B1 {18B1} internal, hidden + 11: REPORT HID USAGE {1BC0} V1 + 12: ENCRYPTION {4100} V0 + 13: KEYBOARD DISABLE BY USAGE {4522} V0 + 14: KEYBOARD LAYOUT 2 {4540} V0 + 15: GKEY {8010} V0 + Divert G and M Keys (saved): False + Divert G and M Keys : False + 16: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 17: DFUCONTROL SIGNED {00C2} V0 + 18: DEVICE RESET {1802} V0 + 19: unknown:1803 {0318} V0 internal, hidden + 20: CONFIG DEVICE PROPS {1806} V3 + 21: unknown:1813 {1318} V0 internal, hidden + 22: OOBSTATE {1805} V0 + 23: unknown:1830 {3018} V0 internal, hidden + 24: unknown:1890 {9018} V0 internal, hidden + 25: unknown:1891 {9118} V0 internal, hidden + 26: unknown:18A1 {A118} V0 internal, hidden + 27: unknown:1DF3 {F31D} V0 internal, hidden + 28: unknown:1E00 {001E} V0 hidden + 29: unknown:1EB0 {B01E} V0 internal, hidden + 30: unknown:1861 {6118} V0 internal, hidden + 31: unknown:18B1 {B118} V0 internal, hidden Has 2 reprogrammable keys: - 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 - divertable, persistently divertable, pos:1, group:0, group mask:empty + 0: Host Switch Channel 1 , default: Hostswitch Channel 1 => Hostswitch Channel 1 + persistently_divertable, divertable, pos:1, group:0, group mask:empty reporting: default - 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 - divertable, persistently divertable, pos:2, group:0, group mask:empty + 1: Host Switch Channel 2 , default: Hostswitch Channel 2 => Hostswitch Channel 2 + persistently_divertable, divertable, pos:2, group:0, group mask:empty reporting: default - Battery: 50%, discharging, next level 20%. + Battery: 80%, BatteryStatus.DISCHARGING, next level 50%. diff --git a/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt b/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt new file mode 100644 index 00000000..d48fb58d --- /dev/null +++ b/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt @@ -0,0 +1,41 @@ +solaar version 1.1.19-25-g7520c9cc + +Logitech G933 Gaming Wireless Headset + Device path : /dev/hidraw4 + USB id : 046d:0A5B + Codename : Logitech + Kind : ? + Protocol : HID++ 4.2 + Serial number: + Model ID: 000000000A5B + Unit ID: FFFFFFFF + 0: U 98.03.B0027 + Supports 9 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: 0 U 98.03.B0027 0A5B + Unit ID: FFFFFFFF Model ID: 000000000A5B Transport IDs: {'btid': '0000', 'btleid': '0000'} + 3: DEVICE NAME {0005} V0 + Name: Logitech G933 Gaming Wireless Headset + Kind: None + 4: COLOR LED EFFECTS {8070} V3 + LED Control : HID++ error {'number': 255, 'request': 1147, 'error': 7, 'params': b''} + LEDs None (saved): !LEDEffectSetting {ID: 0} + LEDs None : !LEDEffectSetting {ID: 0} + LEDs None (saved): !LEDEffectSetting {ID: 0, color: 9519532, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs None : !LEDEffectSetting {ID: 1, color: 0, ramp: 0} + 5: GKEY {8010} V0 + Divert G and M Keys (saved): False + Divert G and M Keys : False + 6: EQUALIZER {8310} V1 + Equalizer (saved): {0: 8, 1: 8, 2: 4, 3: 2, 4: 1, 5: 4, 6: 7, 7: 10, 8: 5, 9: 11} + Equalizer : {0: 8, 1: 8, 2: 4, 3: 2, 4: 1, 5: 4, 6: 7, 7: 10, 8: 5, 9: 11} + 7: SIDETONE {8300} V0 + Sidetone (saved): 30 + Sidetone : 30 + 8: ADC MEASUREMENT {1F20} V3 + Battery: 100% 4183mV , BatteryStatus.RECHARGING. + Power Management (saved): 30 + Power Management : 30 + Battery: 100% 4183mV , BatteryStatus.RECHARGING. From a22ae124d9c96553f2ad8586954ebf1b0f707881 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Thu, 19 Mar 2026 10:09:13 -0400 Subject: [PATCH 8/9] device: don't use Logitech for codename --- lib/logitech_receiver/device.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index fbfdfb29..ca5de065 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -231,8 +231,9 @@ class Device: if not self._codename: if self.online and self.protocol >= 2.0: self._codename = _hidpp20.get_friendly_name(self) - if not self._codename: - self._codename = self.name.split(" ", 1)[0] if self.name else None + if not self._codename and self.name: + names = self.name.split(" ") + self._codename = names[1 if len(names) > 1 and names[0] == "Logitech" else 0] if not self._codename and self.receiver: codename = self.receiver.device_codename(self.number) if codename: From b9e0cf823543ba1dadc8eb188083b5c8db6280b0 Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Fri, 20 Mar 2026 06:07:11 -0700 Subject: [PATCH 9/9] hidpp: Add names for HID++ 2.0 features and sort by ID (#3153) Add 30 documented HID++ 2.0 feature names from LGHUB source analysis: keyboard/mouse (PROPERTY_ACCESS, BLE_PRO_PRE_PAIRING, FULL_KEY_CUSTOMIZATION, CONTROL_LIST, SWITCH_SWAPABILITY, DEVICE_MODE, ENABLE_HIDDEN_FEATURES, KEYBOARD_DISABLE_CONTROLS, LOGI_MODIFIERS), racing peripherals (RPM_INDICATOR, RPM_LED_PATTERN, LEGACY/AXIS_RESPONSE_CURVE, BANDED_AXIS, COMBINED_PEDALS, BUNNY_HOPPING, PROFILE_MANAGEMENT, DUAL_CLUTCH, WHEEL_CENTER_POSITION, DISPLAY_GAME_DATA, CENTER_SPRING, AXIS_MAPPING, GLOBAL_DAMPING, BRAKE_FORCE, PEDAL_STATUS, TORQUE_LIMIT, CONFIGURATION_PROFILES, OPERATING_RANGE, TRUE_FORCE, FFB_FILTER). Sort RPM_INDICATOR/RPM_LED_PATTERN (0x807A-B) before PER_KEY_LIGHTING (0x8080-81) to maintain ID ordering. --- lib/logitech_receiver/hidpp20_constants.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/logitech_receiver/hidpp20_constants.py b/lib/logitech_receiver/hidpp20_constants.py index e5c7f85d..33231f81 100644 --- a/lib/logitech_receiver/hidpp20_constants.py +++ b/lib/logitech_receiver/hidpp20_constants.py @@ -40,6 +40,7 @@ class SupportedFeature(IntEnum): DEVICE_GROUPS = 0x0006 DEVICE_FRIENDLY_NAME = 0x0007 KEEP_ALIVE = 0x0008 + PROPERTY_ACCESS = 0x0011 CONFIG_CHANGE = 0x0020 CRYPTO_ID = 0x0021 TARGET_SOFTWARE = 0x0030 @@ -61,6 +62,7 @@ class SupportedFeature(IntEnum): CONFIG_DEVICE_PROPS = 0x1806 CHANGE_HOST = 0x1814 HOSTS_INFO = 0x1815 + BLE_PRO_PRE_PAIRING = 0x1816 BACKLIGHT = 0x1981 BACKLIGHT2 = 0x1982 BACKLIGHT3 = 0x1983 @@ -74,10 +76,15 @@ class SupportedFeature(IntEnum): REPROG_CONTROLS_V2_2 = 0x1B02 # LogiOptions 2.10.73 features.xml REPROG_CONTROLS_V3 = 0x1B03 REPROG_CONTROLS_V4 = 0x1B04 + FULL_KEY_CUSTOMIZATION = 0x1B05 + CONTROL_LIST = 0x1B10 + SWITCH_SWAPABILITY = 0x1B20 + DEVICE_MODE = 0x1B30 REPORT_HID_USAGE = 0x1BC0 PERSISTENT_REMAPPABLE_ACTION = 0x1C00 WIRELESS_DEVICE_STATUS = 0x1D4B REMAINING_PAIRING = 0x1DF0 + ENABLE_HIDDEN_FEATURES = 0x1E00 FIRMWARE_PROPERTIES = 0x1F1F ADC_MEASUREMENT = 0x1F20 # Mouse @@ -110,6 +117,7 @@ class SupportedFeature(IntEnum): KEYBOARD_LAYOUT = 0x4520 KEYBOARD_DISABLE_KEYS = 0x4521 KEYBOARD_DISABLE_BY_USAGE = 0x4522 + KEYBOARD_DISABLE_CONTROLS = 0x4523 DUALPLATFORM = 0x4530 MULTIPLATFORM = 0x4531 KEYBOARD_LAYOUT_2 = 0x4540 @@ -132,18 +140,40 @@ class SupportedFeature(IntEnum): MKEYS = 0x8020 MR = 0x8030 BRIGHTNESS_CONTROL = 0x8040 + LOGI_MODIFIERS = 0x8051 REPORT_RATE = 0x8060 EXTENDED_ADJUSTABLE_REPORT_RATE = 0x8061 COLOR_LED_EFFECTS = 0x8070 RGB_EFFECTS = 0x8071 + RPM_INDICATOR = 0x807A + RPM_LED_PATTERN = 0x807B PER_KEY_LIGHTING = 0x8080 PER_KEY_LIGHTING_V2 = 0x8081 MODE_STATUS = 0x8090 + LEGACY_AXIS_RESPONSE_CURVE = 0x80A3 + AXIS_RESPONSE_CURVE = 0x80A4 + BANDED_AXIS = 0x80B1 + COMBINED_PEDALS = 0x80D0 + BUNNY_HOPPING = 0x80E0 ONBOARD_PROFILES = 0x8100 + PROFILE_MANAGEMENT = 0x8101 MOUSE_BUTTON_SPY = 0x8110 LATENCY_MONITORING = 0x8111 GAMING_ATTACHMENTS = 0x8120 FORCE_FEEDBACK = 0x8123 + DUAL_CLUTCH = 0x8127 + WHEEL_CENTER_POSITION = 0x812C + DISPLAY_GAME_DATA = 0x8130 + CENTER_SPRING = 0x8131 + AXIS_MAPPING = 0x8132 + GLOBAL_DAMPING = 0x8133 + BRAKE_FORCE = 0x8134 + PEDAL_STATUS = 0x8135 + TORQUE_LIMIT = 0x8136 + CONFIGURATION_PROFILES = 0x8137 + OPERATING_RANGE = 0x8138 + TRUE_FORCE = 0x8139 + FFB_FILTER = 0x8140 # Headsets SIDETONE = 0x8300 EQUALIZER = 0x8310