From e34bcbe0b80f79012e27cb595966515640da71a2 Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Mon, 27 Apr 2026 20:24:11 -0700 Subject: [PATCH] HeadsetMicMute: apply correct fnids feature-wide, not per-device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0x0601 mic-mute feature was being treated as if it followed the typical BooleanValidator default of fn 0 GetState / fn 1 SetState, but that's not how this feature works: fn 0 and fn 1 are state-change events, fn 2 is the actual SetState. That's a feature-level wire convention, not a G522 firmware quirk — the previous PID-gated override implied the standard defaults were correct for other headsets, which we have no evidence of. Set the rw_options at the class level so every device using this feature gets the right fnids. If a future device emerges that genuinely uses the BooleanValidator defaults for 0x0601 we can add a per-PID override at that point — default to what we've actually verified. --- lib/logitech_receiver/settings_templates.py | 29 +++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 89b67918..58854ec1 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -1620,23 +1620,18 @@ class HeadsetMicMute(settings.Setting): description = _("Mute the microphone.") feature = _F.HEADSET_MIC_MUTE validator_class = settings_validator.BooleanValidator - - @classmethod - def build(cls, device): - # G522 firmware uses non-standard fnids for HEADSET_MIC_MUTE: - # fn 0x10 (function 1) emits state-change events from the device - # (physical mute switch + host writes); also serves as GetState. - # fn 0x20 (function 2) is SetState — the standard fn 0x10 SetState - # returns 0x0A UNSUPPORTED on this firmware. - # Other headsets are presumably standard so we only override the - # rw_options on the known-quirky G522 PIDs (0x0B18 wireless, 0x0B19 - # wired-mode firmware). product_id is the uppercase hex string set - # by hidapi_impl (`f"{pid:04X}"`), so compare against strings. - if getattr(device, "product_id", None) in ("0B18", "0B19"): - rw = settings.FeatureRW(cls.feature, read_fnid=0x10, write_fnid=0x20) - validator = settings_validator.BooleanValidator() - return cls(device, rw, validator) - return super().build(device) + # HEADSET_MIC_MUTE (0x0601) doesn't follow the typical fn 0 GetState / + # fn 1 SetState pattern that BooleanValidator defaults to. Function + # layout (confirmed via G HUB pcap on G522): + # fn 0 — physical-mute-switch state-change events from the device + # fn 1 — state-change events emitted as the device's echo of a + # host-driven SetState; also serves as the host-callable + # GetState read + # fn 2 — host-callable SetState (single byte: 0=unmuted, 1=muted) + # The standard fn 0/1 write path returns 0x0A UNSUPPORTED. State-change + # events from both fn 0 and fn 1 are handled by _process_feature_notification + # so the toggle reflects physical mute presses too. + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} class HeadsetMicSNR(settings.Setting):