From e844d0611db4bb45d469863dc438bf6ccf50a35c Mon Sep 17 00:00:00 2001 From: grechmarlon <55017324+grechmarlon@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:22:25 +0200 Subject: [PATCH] Fix crash writing per-key lighting from the CLI PerKeyLighting.write_key_value dereferenced self._value without the initialization guard that the base Settings.write_key_value has. On a fresh CLI invocation (solaar config per-key-lighting ) nothing has read the setting yet, so _value is still None and the write crashes with 'NoneType' object does not support item assignment. The GUI never hits this because it reads all settings on attach. Initialize via read(), which for this write-only 0x8081 setting loads the persisted map or fabricates the all-"No change" sentinel map. Co-Authored-By: Claude Fable 5 --- lib/logitech_receiver/settings_templates.py | 2 ++ tests/logitech_receiver/test_rgb_power.py | 33 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index a3891b35..fbaf109f 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -4101,6 +4101,8 @@ class PerKeyLighting(settings.Settings): no_change = special_keys.COLORSPLUS["No change"] zone_id = int(key) if value != no_change: + if self._value is None: + self.read() # 0x8081 is write-only — read() loads persisted state or the sentinel map self.update_key_value(zone_id, value, save) if not self._device.online: return value diff --git a/tests/logitech_receiver/test_rgb_power.py b/tests/logitech_receiver/test_rgb_power.py index cafe6d59..45dbae42 100644 --- a/tests/logitech_receiver/test_rgb_power.py +++ b/tests/logitech_receiver/test_rgb_power.py @@ -672,3 +672,36 @@ def test_perkey_write_key_value_skipped_when_zone_is_animation(monkeypatch): s.write_key_value(7, 0xFF0000) s._send_zone_color.assert_not_called() + + +def test_perkey_write_key_value_initializes_unread_value(monkeypatch): + """The CLI writes a single key without ever reading the setting, so + _value is still None. write_key_value must initialize it via read() + (persisted state or the sentinel map — 0x8081 is write-only) instead + of crashing with a None dereference in update_key_value.""" + from unittest.mock import MagicMock + + from logitech_receiver import settings_templates + + static_zone = _FakeZoneSetting("rgb_zone_1", _ValueWithID(0x01)) + + s = settings_templates.PerKeyLighting.__new__(settings_templates.PerKeyLighting) + device = MagicMock() + device.online = True + device.settings = [static_zone] + s._device = device + s._value = None # fresh CLI invocation: nothing has read the setting + s._validator = MagicMock() + s._validator.choices = [7] + s._pre_read = lambda cached=True: None + s._has_rgb_effects = True + s._send_with_retry = MagicMock(return_value=True) + s._send_zone_color = MagicMock(return_value=True) + s._fill_unset_zones_with_base_color = MagicMock(return_value=True) + monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) + monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) + + s.write_key_value(7, 0xFF0000) + + assert s._value[7] == 0xFF0000 + s._send_zone_color.assert_called_once()