Skip to content

feat(theme): add a selectable source color mode for wallpaper palettes - #3020

Open
Conava wants to merge 6 commits into
AvengeMedia:masterfrom
Conava:source-color-extraction
Open

feat(theme): add a selectable source color mode for wallpaper palettes#3020
Conava wants to merge 6 commits into
AvengeMedia:masterfrom
Conava:source-color-extraction

Conversation

@Conava

@Conava Conava commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Description

DMS builds your whole color scheme out of a single color picked from the wallpaper. Today that's always the color covering the most pixels. Often this is a background color and not the color of the main subject of the image, for example a blue background from the sky. This can lead to many images resulting in a blue color palette.

There's no way to change that pick. This PR adds a Source Color setting in Theme & Colors that lets the user decide. DMS hands the picked color to matugen, the external tool that generates the actual scheme. Five of the options below are matugen's own, Colorful is not.

Option Picks
Dominant The most common color. Current behavior, and still the default
Colorful The most colorful prominent color, rather than the largest one
Darkest / Lightest The darkest or lightest of matugen's candidates
Most Saturated / Least Saturated Most or least saturated
Most Vivid The boldest of matugen's candidates

On one of the wallpapers below that's the difference between a #8ad0ee cold cyan accent and a #ffb690 warm peach one, from the same image.

The five matugen options map to its --prefer flag, through an exact-match allowlist so a hand-edited settings.json can't inject arbitrary arguments. Colorful has no matugen equivalent, so DMS computes it: the image is reduced to 128 representative colors, then each is scored on how much of the image it covers against how colorful it is. That scoring is inspired by caelestia-cli, which produces noticeably warmer palettes than DMS on the same images. Either way matugen receives a single color, so nothing downstream changes.

Notes for review

  • Nothing changes for existing users. Dominant is the default and produces byte-identical output to before. The flag isn't even added to matugen's command line unless the user picks something else
  • It can't break theming. If the image can't be read (DMS accepts jxl/avif/heif/exr, which Go can't decode), it logs a warning and falls back to today's behavior
  • --prefer arrived in matugen 4.1. On older versions those five options fall back to Dominant instead of failing on an unknown flag. Colorful works on any matugen version, since matugen only ever sees a single color
  • One new Go dependency, github.com/Nadim147c/material/v3, used only by core/internal/matugen/sourcecolor.go, for the color-space math and image quantization Colorful needs. Apache-2.0, and its own go.mod has no requires at all, so it pulls in nothing else.

Why this doesn't just call the library's own image quantizer: its QuantizeCelebi is broken: it stops after one unconverged pass, and its distance function returns a dot product rather than a distance, so "nearest color" is meaningless. On a 3840x2160 wallpaper it grouped 7,979,139 of 8,292,604 pixels into a single cluster. This PR does that step itself instead, with the reasoning in a comment at the call site.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behavior)
  • Refactor / internal cleanup
  • Documentation
  • Other

Related issues

None.

Screenshots / video

screenshot_2026-08-08_18-15-20 screenshot_2026-08-08_18-15-08

Validation

54 theme generations through the built binary against a scratch config, matugen 4.1.0.

What each option produces, across six wallpapers. Accent color, dark mode, scheme-tonal-spot:

Wallpaper Dominant Colorful Darkest Lightest Most Sat. Least Sat. Most Vivid
1 #8ad0ee #ffb690 #8ad0ee #ffb690 #ffb690 #95cdf7 #8ad0ee
2 #81d3df #faba73 #cdca75 #fdb976 #fdb976 #91d5ac #cdca75
3 #edb4eb #bec2ff #edb4eb #90cef4 #b8c4ff #edb4eb #edb4eb
4 #b9c3ff #bdc2ff #b9c3ff #9acbfa #9acbfa #f5b2e1 #b9c3ff
5 #edc06c #e7c26c #edc06c #89d6b8 #edc06c #a2d399 #a2d399
6 #feb877 #fcb974 #ffb692 #f1be6d #ffb692 #f1be6d #ffb692

Every option produced a complete palette on every wallpaper, nothing failed or came back empty. The seven collapse to 4-5 distinct results per image, because matugen's candidate list is short and several --prefer values land on the same candidate.

Wallpapers 1 and 2 are the case this PR is for: Dominant picks a cold cyan, Colorful a warm peach or amber. On 4 to 6 nothing much moves. Across my own wallpaper collection of 207 images, which is whatever I happen to have rather than a set curated for this, the median hue shift between Dominant and Colorful is 7° on a 0-180° scale, with 14 wallpapers above 160°. It's a targeted fix for a specific failure, not a global repaint.

Which --prefer value lands closest to Colorful also changes per wallpaper (lightness, lightness, saturation, darkness, saturation, lightness), so it works as a per-image knob, not as one setting the user leaves alone.

The default is inert. Five generations of one wallpaper, differing only in the Source Color setting, all produced a byte-identical colors file (sha256 d615235985ca): setting absent, dominant, "", nonsense, and --exec. The last two confirm a hand-edited settings value can't inject arguments into matugen's command line.

Deterministic. The same image always produces the same color. This matters because DMS compares generated colors to decide whether to re-theme; a color that drifted between runs would re-theme the desktop on every wallpaper event.

Commands run:

go test ./...                             # 2169 passed, 88 packages
make fmt && go mod tidy                   # clean
GOOS=freebsd GOARCH=amd64 go build ./...  # clean
make lint-qml                             # PASS (2 entrypoints)
make build
prek run --files <changed files>

Checklist

  • My code follows the conventions in CONTRIBUTING.md
  • I have tested my changes locally
  • New user-facing strings are wrapped in I18n.tr() with translator context, reusing existing terms where possible
  • Go changes: ran make fmt, added/updated tests, make test passes, and go mod tidy is clean
  • QML changes: ran make lint-qml with no new warnings
  • I have opened a corresponding pull request in dlx-docs to document any new behaviors: docs(dms): document the Source Color setting DankLinux-Docs#129

DMS always seeds the palette from matugen's most dominant candidate
(--source-color-index 0, hardcoded at both call sites). When a large
muted region outvotes a smaller vivid one, a visibly warm wallpaper
produces a cold palette: one image here goes to #8ad0ee where the warm
subject is at hue 46.

Adds a Source Color setting with seven values. Dominant is the default
and is byte-identical to today. Colorful is computed in Go before
matugen runs: quantize the image (Wu for the starting clusters, then a
weighted k-means in Lab), score the result the way caelestia-cli does,
and hand matugen the resulting hex. Its scoring walks a chroma/tone bar
down from 20 until something clears it, which biases the pick toward a
colorful, well-lit color rather than whichever muted color covers the
most pixels. The remaining five map to matugen's --prefer.

- core/internal/matugen/sourcecolor.go: extraction, scoring, and the
  one helper both matugen call sites now share
- buildOnce resolves the seed once, before the StockColors branch, so
  the dry run and the real run cannot disagree about it
- extraction failure logs a warning and falls through to matugen's own
  extraction; it never fails a theme build
- --prefer arrived in matugen 4.1, so it is gated separately from the
  existing isV4 check; on 4.0.x those five modes degrade to the
  dominant color instead of aborting on an unknown argument
- --source-mode on generate and queue, plus sourceMode on the socket
  handler
- Source Color dropdown in Theme & Colors, defaulting to Dominant

Adds github.com/Nadim147c/material/v3 (Apache-2.0, no dependencies) for
HCT conversions, Wu quantization and the dislike analyzer. Its
QuantizeCelebi is not used: it returns after one unconverged iteration
and its Lab.DistanceSquared is a dot product rather than a distance, so
it collapses most of an image into a single cluster.
Copilot AI lite review requested due to automatic review settings August 8, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Source Color setting that lets users choose how the wallpaper seed color is selected for matugen-based palette generation, including a custom “Colorful” mode that computes a vivid seed color in-core and safely degrades on older matugen versions.

Changes:

  • Adds UI + settings plumbing for matugenSourceMode, and forwards it to the core via --source-mode only when non-default.
  • Implements “Colorful” wallpaper seed extraction in Go (image sampling + quantization + scoring) and forwards matugen --prefer values via an allowlist with version-gated fallback.
  • Extends matugen invocation/version detection, plus adds unit tests and a new Go dependency (github.com/Nadim147c/material/v3).

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
quickshell/translations/settings_search_index.json Adds search metadata for the new “Source Color” setting.
quickshell/Modules/Settings/ThemeColorsTab.qml Adds the Source Color dropdown UI and mode description display.
quickshell/Common/Theme.qml Defines available source modes and conditionally appends --source-mode to matugen queue args.
quickshell/Common/SettingsData.qml Persists matugenSourceMode and provides a setter used by the UI.
quickshell/Common/settings/SettingsSpec.js Adds matugenSourceMode to the persisted settings spec with regen hook.
core/internal/server/matugen_handler.go Plumbs sourceMode through the request handler into matugen options.
core/internal/matugen/sourcecolor.go New implementation for “Colorful” source color extraction and --prefer allowlisting.
core/internal/matugen/matugen.go Wires SourceMode into matugen arg building with version-gated --prefer behavior.
core/internal/matugen/matugen_test.go Adds tests for source-mode arg selection and “Colorful” extraction behavior.
core/go.sum Records checksums for the new material/v3 dependency.
core/go.mod Adds github.com/Nadim147c/material/v3 dependency.
core/cmd/dms/commands_matugen.go Adds the --source-mode CLI flag and forwards it through the queue request.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread core/internal/matugen/sourcecolor.go Outdated
Comment thread quickshell/Common/SettingsData.qml
Conava added 2 commits August 8, 2026 18:30
Two points from review on AvengeMedia#3020.

The quantize comment pointed at a FOLLOWUPS file that does not exist in
this repository. Replaced the reference with the measurement it was
standing in for, so the claim is self-contained.

setMatugenSourceMode called Theme.generateSystemThemesFromCurrentTheme()
directly on top of the regenSystemThemes onChange hook that Spec.set
already dispatches. Both feed a 100ms debounce so it was one generation
either way, but the direct call was redundant and is gone.
Comment thread core/internal/matugen/matugen.go
Comment thread quickshell/Common/Theme.qml Outdated
Conava added 3 commits August 17, 2026 22:46
Colorful resolves the seed itself and calls matugen with a hex source, which
leaves matugen's {{image}} unset: the builtin pywalfox template and any user
template using the keyword render the literal string "Null".

Pass the wallpaper path alongside dank16 in --import-json-string, as suggested
in review, so those templates keep working unedited. The path is made absolute
to match what matugen renders for {{image}} in the other modes.
Per-mode descriptions were seven of the fifteen new terms this branch adds,
and five of them differed by a single adjective. Drop them: the dropdown row
already describes what the setting does, and the docs cover each mode.
matugen_test.go: both sides added imports to the same block, kept both.
@Conava
Conava requested a review from bbedward August 17, 2026 21:08
@bbedward

Copy link
Copy Markdown
Collaborator

/claude review

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude review

No issues found.

Checked: the Colorful extraction path (decode/downscale/quantize/score, determinism, empty/transparent/all-black/1px edge cases, no panics on nil population), the --prefer allowlist and its 4.0.x/v3 version gating, matugenFlags positional construction and redetect logic, the opts.Kind/opts.Value rewrite ordering vs. buildMergedConfig/execDryRun/the real run (no stale reads, Options passed by value so the mutation is call-scoped), the {{image}} injection (correctly gated to the colorful-succeeded case only, path json.Marshaled rather than interpolated), CLI flag plumbing through queue/generate/the server handler, and the QML side (setter relies on the regenSystemThemes onChange hook which set() does dispatch, --source-mode omitted at default for older binaries, dropdown/label mapping and availableSourceModes follow the existing availableMatugenSchemes pattern, search-index entry is alphabetically placed with the right tabIndex, en.json/template.json untouched). Model: claude-opus-5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants