diff --git a/lib/logitech_receiver/hidpp10_constants.py b/lib/logitech_receiver/hidpp10_constants.py index fa3a8395..3861bcd4 100644 --- a/lib/logitech_receiver/hidpp10_constants.py +++ b/lib/logitech_receiver/hidpp10_constants.py @@ -91,9 +91,12 @@ class NotificationFlag(IntFlag): @classmethod def flag_names(cls, flags) -> List[str]: """Extract the names of the flags from the integer.""" - if flags is None or flags.name is None: + if flags is None: return [] - return flags.name.replace("_", " ").lower().split("|") + if flags.name is not None: + return flags.name.replace("_", " ").lower().split("|") + # Python < 3.11: .name is None for composite flags, decompose manually + return [m.name.replace("_", " ").lower() for m in cls if m.value and m in flags] NUMPAD_NUMERICAL_KEYS = 0x800000 F_LOCK_STATUS = 0x400000 diff --git a/tests/logitech_receiver/test_hidpp10.py b/tests/logitech_receiver/test_hidpp10.py index e4ef7818..368bc896 100644 --- a/tests/logitech_receiver/test_hidpp10.py +++ b/tests/logitech_receiver/test_hidpp10.py @@ -296,6 +296,32 @@ def test_notification_flag_str(flag_bits, expected_names): assert flag_names == expected_names +@pytest.mark.parametrize( + "flags, expected", + [ + (None, []), + # composite flag, .name is None on Python < 3.11 + (hidpp10_constants.NotificationFlag(0x000900), ["software present", "wireless"]), + (hidpp10_constants.NotificationFlag(0x100000), ["battery status"]), + (hidpp10_constants.NotificationFlag(0x080000), ["mouse extra buttons"]), + ], +) +def test_notification_flag_names(flags, expected): + result = hidpp10_constants.NotificationFlag.flag_names(flags) + assert result == expected + + +def test_notification_flag_names_none_does_not_crash(): + """Regression test for #3184: flag_names crashes with AttributeError when + flags.name is None, which happens with certain receiver firmware versions + (e.g. Nano C52F reporting 0x000900).""" + flags_with_none_name = hidpp10_constants.NotificationFlag(0x000900) + result = hidpp10_constants.NotificationFlag.flag_names(flags_with_none_name) + assert "software present" in result + assert "wireless" in result + assert isinstance(result, list) + + def test_get_device_features(): result = _hidpp10.get_device_features(device_standard)