device: seed Centurion device kind=headset at construction

Centurion-transport devices have no static descriptor and are not
receiver-paired, so their kind was only resolved by an online feature
scan. A headset powered off when Solaar started showed no kind, hence
no headphone icon in the tray/window — unlike receiver-paired mice and
keyboards, whose kind comes from the receiver's persistent pairing
registers.

Every Centurion-transport device seen so far is a headset, and the
centurion flag is known at construction time. Seed _kind=headset there
so the icon is correct offline and on first run. Drop the now-dead
online _infer_kind_centurion() feature scan and its Centurion branch
in the kind property.
This commit is contained in:
Ken Sanislo 2026-05-15 21:01:31 -07:00
parent 9780e730d8
commit f22b542daf
4 changed files with 9 additions and 34 deletions

View File

@ -146,6 +146,8 @@ class Device:
self.status_callback = None # for changes to other potentially visible aspects
self.wpid = pairing_info["wpid"] if pairing_info else None # the Wireless PID is unique per device model
self._kind = pairing_info["kind"] if pairing_info else None # mouse, keyboard, etc (see hidpp10.DEVICE_KIND)
if self._kind is None and self.centurion:
self._kind = hidpp10_constants.DEVICE_KIND.headset # every Centurion-transport device so far is a headset
self._serial = pairing_info["serial"] if pairing_info else None # serial number (an 8-char hex string)
self._polling_rate = pairing_info["polling"] if pairing_info else None
self._power_switch = pairing_info["power_switch"] if pairing_info else None
@ -322,27 +324,12 @@ class Device:
@property
def kind(self):
# Centurion devices are seeded with kind=headset at construction, so
# this online lookup only runs for descriptor-less HID++ 2.0 devices.
if not self._kind and self.online and self.protocol >= 2.0:
if self.centurion:
self._kind = self._infer_kind_centurion()
else:
self._kind = _hidpp20.get_kind(self)
self._kind = _hidpp20.get_kind(self)
return self._kind or "?"
def _infer_kind_centurion(self):
"""Infer device kind from Centurion features (sub-device or top-level)."""
# Check sub-device features (wireless via bridge)
for feature in getattr(self, "_centurion_sub_features", ()):
if isinstance(feature, int) and 0x0600 <= feature <= 0x06FF:
return hidpp10_constants.DEVICE_KIND.headset
# Check top-level features (direct USB connection, no bridge)
if self.features:
for feature, _index in self.features.enumerate():
feat_int = int(feature) if isinstance(feature, int) else 0
if 0x0600 <= feat_int <= 0x06FF:
return hidpp10_constants.DEVICE_KIND.headset
return None
@property
def firmware(self) -> tuple[common.FirmwareInfo]:
if self._firmware is None and self.online:

View File

@ -403,7 +403,6 @@ class Device:
gestures = device.Device.gestures
__hash__ = device.Device.__hash__
feature_request = device.Device.feature_request
_infer_kind_centurion = device.Device._infer_kind_centurion
def __post_init__(self):
self._name = self.name

View File

@ -108,6 +108,10 @@ def test_create_centurion_device():
assert test_device.hidpp_long is True
assert int(test_device.handle) in base._centurion_handles
# kind is seeded at construction, so the headset icon shows even offline
test_device.online = False
assert test_device.kind == "headset"
# Clean up
base._centurion_handles.discard(int(test_device.handle))

View File

@ -1116,21 +1116,6 @@ def test_centurion_sub_device_hardware_info():
assert product_id == 0x0AF7
def test_centurion_kind_inference():
"""Centurion device with 0x06xx audio features infers kind=headset."""
dev = fake_hidpp.Device("CENT_KIND", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True)
dev._centurion_sub_features = {
hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE,
hidpp20_constants.SupportedFeature.HEADSET_MIC_MUTE,
}
kind = dev._infer_kind_centurion()
from logitech_receiver import hidpp10_constants
assert kind == hidpp10_constants.DEVICE_KIND.headset
# --- CenturionReceiver tests ---