diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index f7a78530..21a0a081 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -45,6 +45,7 @@ from .settings import BitFieldWithOffsetAndMaskValidator as _BitFieldOMV from .settings import ChoicesMapValidator as _ChoicesMapV from .settings import ChoicesValidator as _ChoicesV from .settings import FeatureRW as _FeatureRW +from .settings import FeatureRWMap as _FeatureRWMap from .settings import HeteroValidator as _HeteroV from .settings import LongSettings as _LongSettings from .settings import MultipleRangeValidator as _MultipleRangeV @@ -1573,6 +1574,61 @@ class RGBEffectSetting(LEDZoneSetting): return cls.setup(device, 0xE0, 0x10, b"\x01") +class PerKeyLighting(_Settings): + name = "per-key-lighting" + label = _("Per-key Lighting") + description = _("Control per-key lighting.") + feature = _F.PER_KEY_LIGHTING_V2 + keys_universe = _NamedInts.range(1, 254) + choices_universe = _special_keys.COLORS + + def read(self, cached=True): + self._pre_read(cached) + if cached and self._value is not None: + return self._value + reply_map = {} + for key in self._validator.choices: + reply_map[int(key)] = 0xFFFFFF # can't read so fake a value of white + self._value = reply_map + return reply_map + + def write(self, map, save=True): + if self._device.online: + self.update(map, save) + data_bytes = b"" + for key, value in map.items(): + data_bytes += key.to_bytes(1, "big") + value.to_bytes(3, "big") + if len(data_bytes) >= 16: # up to four values are packed into a request + self._device.feature_request(self.feature, 0x10, data_bytes) + data_bytes = b"" + if len(data_bytes) > 0: + self._device.feature_request(self.feature, 0x10, data_bytes) + self._device.feature_request(self.feature, 0x70, 0x00) # signal device to make the changes + return map + + def write_key_value(self, key, value, save=True): + result = super().write_key_value(key, value, save) + if self._device.online: + self._device.feature_request(self.feature, 0x70, 0x00) # signal device to make the change + return result + + class rw_class(_FeatureRWMap): + pass + + class validator_class(_ChoicesMapV): + @classmethod + def build(cls, setting_class, device): + choices_map = {} + key_bitmap = device.feature_request(setting_class.feature, 0x00, 0x00, 0x00)[2:] + key_bitmap += device.feature_request(setting_class.feature, 0x00, 0x00, 0x01)[2:] + key_bitmap += device.feature_request(setting_class.feature, 0x00, 0x00, 0x02)[2:] + for i in range(1, 255): + if (key_bitmap[i // 8] >> i % 8) & 0x01: + choices_map[setting_class.keys_universe[i]] = setting_class.choices_universe + result = cls(choices_map) if choices_map else None + return result + + SETTINGS = [ RegisterHandDetection, # simple RegisterSmoothScroll, # simple @@ -1607,6 +1663,7 @@ SETTINGS = [ RGBControl, RGBEffectSetting, BrightnessControl, + PerKeyLighting, FnSwap, # simple NewFnSwap, # simple K375sFnSwap, # working diff --git a/tests/logitech_receiver/test_setting_templates.py b/tests/logitech_receiver/test_setting_templates.py index ff9f447e..468a5b6e 100644 --- a/tests/logitech_receiver/test_setting_templates.py +++ b/tests/logitech_receiver/test_setting_templates.py @@ -610,6 +610,7 @@ responses_remappable_action = responses_reprog_controls + [ hidpp.Response("0051FF01005100", 0x0440, "0051FF01005100"), # right button set ] + tests = [ [ FeatureTest( @@ -642,6 +643,23 @@ tests = [ }, ] + responses_remappable_action, + [ + FeatureTest(settings_templates.PerKeyLighting, {0x01: 0xFFFFFF, 0x02: 0xFFFFFF}, {0x02: 0xFF0000}, 0x70, "00"), + { + common.NamedInt(1, "1"): special_keys.COLORS, + common.NamedInt(2, "2"): special_keys.COLORS, + common.NamedInt(9, "9"): special_keys.COLORS, + common.NamedInt(10, "10"): special_keys.COLORS, + common.NamedInt(113, "113"): special_keys.COLORS, + common.NamedInt(114, "114"): special_keys.COLORS, + }, + hidpp.Response("040001", 0x0000, "8081"), # PER_KEY_LIGHTING_V2 + hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"), + hidpp.Response("00000600000000000000000000000000", 0x0400, "0001"), + hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"), + hidpp.Response("02FF0000", 0x0410, "02FF0000"), + hidpp.Response("00", 0x0470, "00"), + ], ] @@ -651,7 +669,6 @@ def test_key_template(test, mocker): spy_feature_request = mocker.spy(device, "feature_request") setting = settings_templates.check_feature(device, test[0].sclass) - print("SETTING", setting) assert setting is not None if isinstance(setting, list): setting = setting[0] @@ -664,6 +681,7 @@ def test_key_template(test, mocker): value = setting.read(cached=False) for k, v in test[0].initial_value.items(): assert value[k] == v + setting.write(setting._value) for key, value in test[0].write_value.items(): write_value = setting.write_key_value(key, value)