24-bit RGB values stored in PerKeyLighting per-key maps and in
LEDEffectSetting `.color` fields currently dump as decimal integers in
both the YAML config file and `solaar show` output:
per-key-lighting: {1: -1, 2: 16733440, 3: 16755200, ...}
LEDs Keys: {1:-1, 2:16733440, 3:16755200, ...}
Hex is the canonical RGB representation. Render colors as `0xrrggbb`
everywhere: solaar show output, repr, and YAML config dumps. Both new
values and legacy values from pre-existing YAML configs migrate
transparently.
Implementation: a `ColorInt(int)` subclass in `common.py`.
- `str(c)` / `repr(c)` → `'0xrrggbb'` for 0..0xFFFFFF; falls back to
decimal for out-of-range values so sentinels like
`COLORSPLUS["No change"] = -1` still display naturally.
- Constructor accepts ints AND hex strings (`'0xrrggbb'` or
`'#rrggbb'`) so pre-existing configs that wrote decimal continue
to load.
- YAML representer emits a hex int literal (`tag:yaml.org,2002:int`
with style `'0xrrggbb'`). YAML 1.1 parses hex int literals back as
plain ints with no custom loader registration — values round-trip
cleanly without a custom YAML tag.
Wiring:
- `Range` gains a `value_type=int` field; `MapRangeValidator.validate_read`
wraps results through `rng.value_type(...)`. PerKeyLighting's
`_COLOR_RANGE` sets `value_type=ColorInt`; other Range users keep
the int default with no behavior change.
- PerKeyLighting overrides `update()` and `update_key_value()` to wrap
raw ints in `ColorInt` at write time. `type(v) is int` (exact
match, not isinstance) deliberately excludes NamedInt sentinels and
avoids re-wrapping existing ColorInts.
- `LEDEffectSetting.__init__` wraps the `color` param in `ColorInt`
with the same guard, so zone-effect color round-trips as hex
through `yaml.dump(setting)` / `val_to_string`.
- `MapRangeValidator.to_string` re-wraps raw ints loaded from YAML
(which `yaml.safe_load` returns as plain Python ints regardless of
the choice's `value_type`) through `rng.value_type` before
formatting. Without this, `solaar show` would render legacy saved
values as decimal even after the rest of the pipeline is hex-aware.
Coverage: 12 new unit tests across `test_common.py` and
`test_settings_validator.py`:
- ColorInt str/repr, equality with plain int, hex-string constructor
(0x / 0X / # prefixes), out-of-range fallback to decimal, YAML
dump format, plain-int round-trip on load, dict-value formatting.
- MapRangeValidator.to_string: plain-int re-wrap via value_type,
pass-through for already-wrapped ColorInts, NamedInt sentinel
preservation, and no behavior change for int-typed Ranges.
Existing test fixtures updated: _PERKEY_COLOR_RANGE now carries
value_type=ColorInt to match runtime _COLOR_RANGE.
702 tests pass; pre-commit clean.