From 8b023115d2ed512a32a3c7b0e70f96e66a8e9af5 Mon Sep 17 00:00:00 2001 From: Alessio85 Date: Thu, 16 Apr 2026 17:38:25 +0200 Subject: [PATCH] device: Handle composite IntFlag.name on Python < 3.11 (#3187) * Handle composite IntFlag.name on Python < 3.11 On Python < 3.11, IntFlag.name returns None for composite flags (e.g. NotificationFlag(0x000900) = SOFTWARE_PRESENT|WIRELESS). The previous fix (#3184) returns an empty list in this case, losing the flag names. This commit decomposes composite flags manually by iterating over enum members, producing correct results on all supported Python versions (>= 3.8 as declared in setup.py). Add regression tests covering composite flags and None input. Tested on: - Python 3.9.2 (Debian 11): 56/56 passed - Python 3.11.15 (pyenv): 56/56 passed Refs #3184 * Fix line length style violation (E501) Move comment above the parametrize entry to stay under 127 chars. --------- Co-authored-by: avercelli --- lib/logitech_receiver/hidpp10_constants.py | 7 ++++-- tests/logitech_receiver/test_hidpp10.py | 26 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) 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)