Merge branch 'master' into feat/pro-x-2-superstrike

This commit is contained in:
Caio Quirino da Silva 2026-03-25 22:32:38 +01:00 committed by GitHub
commit 308dc7a1d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 257 additions and 73 deletions

View File

@ -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.

View File

@ -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%.

View File

@ -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.

View File

@ -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")

View File

@ -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
@ -204,7 +205,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
@ -230,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:
@ -243,8 +245,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):

View File

@ -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

View File

@ -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
@ -1689,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

View File

@ -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
@ -75,10 +77,15 @@ class SupportedFeature(IntEnum):
REPROG_CONTROLS_V3 = 0x1B03
REPROG_CONTROLS_V4 = 0x1B04
SUPERSTRIKE_TUNING = 0x1B0C # PRO X 2 Superstrike HITS tuning (actuation, rapid trigger, haptics)
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
@ -111,6 +118,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
@ -133,18 +141,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

View File

@ -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]

View File

@ -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

View File

@ -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