aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 13:09:54 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 13:09:54 +0200
commit394eb7d1eb3e0366afa8bfbba4e579ab4cb66a46 (patch)
treefc82caae4a69239aea64fe4ff25671eb5a4874f7
parentec6ffd7a905de8c90512e16633c9741a9fe61c5b (diff)
downloadunified-desktop-theme-394eb7d1eb3e0366afa8bfbba4e579ab4cb66a46.tar.gz
unified-desktop-theme-394eb7d1eb3e0366afa8bfbba4e579ab4cb66a46.zip
feat: write the SDDM theme palette for sddm-theme-udt
-rwxr-xr-xbin/udt-accent58
1 files changed, 58 insertions, 0 deletions
diff --git a/bin/udt-accent b/bin/udt-accent
index 391664b..610b04e 100755
--- a/bin/udt-accent
+++ b/bin/udt-accent
@@ -47,6 +47,18 @@ PALETTE_RASI = Path.home() / ".config" / "rofi" / "udt" / "palette.rasi"
# with the rest of the palette and ~/.cache/wal holds nothing but that link.
COLORS_JSON = CACHE / "colors.json"
+# The SDDM theme's live palette. ~/.cache is 0700 and unreachable to the sddm
+# user, ~/.local/share is 0755, so the file has to live here. Root symlinks
+# /usr/share/sddm/themes/udt/theme.conf.user to it. See the sddm-theme-udt spec.
+LIBEXEC = Path.home() / ".local" / "share" / "udt"
+SDDM_OUTPUT = LIBEXEC / "sddm-theme.conf"
+
+# The keys the theme asks for. The names are the same Catppuccin-compat names
+# palette.rasi and udt-palette.qml use, so one palette has one vocabulary.
+SDDM_KEYS = ["base", "mantle", "crust", "surface0", "surface1", "surface2",
+ "text", "subtext0", "subtext1", "overlay0", "overlay1", "overlay2",
+ "red", "yellow", "green", "teal", "blue", "lavender"]
+
def write_atomic(path, content):
"""Write a file atomically, so no reader ever sees it half-written.
@@ -286,6 +298,39 @@ def write_sublime(name):
write_atomic(SUBLIME, new)
+def sddm_conf(palette, name, image):
+ """The SDDM theme config, as an INI fragment.
+
+ theme.conf.user overrides theme.conf and is exposed to the theme as the
+ `config` object. It is data parsed by QSettings, never executed, so nothing
+ the login user can write reaches the greeter as code.
+ """
+ lines = [
+ "# Generated by udt-accent. Do not edit.",
+ "[General]",
+ "type=image",
+ f"background={os.path.realpath(image)}",
+ f"accent={ACCENTS[name]}",
+ ]
+ lines += [f"{key}={palette.get(key, '#000000')}" for key in SDDM_KEYS]
+ return "\n".join(lines) + "\n"
+
+
+def write_sddm(name, image):
+ """Write the live SDDM palette, atomically, world-readable.
+
+ World-readable because the greeter runs as the sddm user. The atomic writer
+ creates the file 0600, so the mode is fixed up here.
+ """
+ palette = read_palette()
+ if not palette:
+ print("udt-accent: palette.rasi unreadable, skipping SDDM theme",
+ file=sys.stderr)
+ return
+ write_atomic(SDDM_OUTPUT, sddm_conf(palette, name, image))
+ os.chmod(SDDM_OUTPUT, 0o644)
+
+
def main(image):
try:
name = snap(signature_color(image))
@@ -298,6 +343,7 @@ def main(image):
write_border(name)
write_qml(name)
write_sublime(name)
+ write_sddm(name, image)
write_colors_json(name, image)
# Hyprland only rereads its config on request, and may not be running.
@@ -351,6 +397,18 @@ def selftest():
delta = abs(_hue(ACCENTS[neighbour]) - _hue(ACCENTS[name]))
assert min(delta, 2 * math.pi - delta) < math.pi, (name, neighbour)
+ # The SDDM theme reads this file through a symlink, so it has to carry every
+ # key the theme asks for, and a second run has to overwrite cleanly rather
+ # than append.
+ palette = {k: "#1e2030" for k in SDDM_KEYS}
+ probe_image = Path(tempfile.mkdtemp()) / "wall.png"
+ probe_image.write_bytes(b"")
+ text = sddm_conf(palette, FALLBACK, str(probe_image))
+ for key in ["type", "background", "accent", *SDDM_KEYS]:
+ assert f"\n{key}=" in "\n" + text, f"sddm conf missing {key}"
+ assert text.count("accent=") == 1
+ assert "background=" + str(probe_image) in text
+
print("selftest OK")