feat: Nomarchy ground-up rewrite on NixOS 26.05

Full replacement of the previous iteration, rebuilt around three ideas:

- Pure evaluation: theme-state.json lives inside the flake and is read
  via the nomarchy.stateFile option — no --impure, ever.
- All-Home-Manager theming: `nomarchy-theme-sync apply` writes the JSON
  and runs `home-manager switch`; every theme change is one atomic,
  rollbackable generation. Wallpaper (swww) is the sole runtime piece.
- Flat, downstream-first layout: modules/{nixos,home} with one
  options.nix each, exported as nixosModules/homeModules + overlay +
  flake template; system (nixos-rebuild) and desktop (home-manager
  switch) rebuild paths are fully split.

Ships 21 themes imported from the previous iteration (palettes,
wallpapers, btop themes, six whole-swap Waybar identities), Stylix for
the GTK/Qt/cursor long tail, a live ISO target with offline theme
switching, and docs/TESTING.md with the QEMU verification workflow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-10 10:59:13 +01:00
commit f211ef0d09
131 changed files with 4844 additions and 0 deletions

96
tools/import-palettes.py Normal file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Convert old-distro palettes (colors.toml) into Nomarchy theme JSON presets.
Usage: tools/import-palettes.py <old-palettes-dir> <output-themes-dir>
Old format (per theme directory):
colors.toml kitty-style keys: background, foreground, accent,
selection_background, color0..color15
light.mode marker file: theme is light
icons.theme icon theme name (informational, ignored for now)
backgrounds/ wallpapers
apps/ hand-made per-app theme files (btop.theme, ...)
Output, per theme:
<slug>.json the palette (Nomarchy theme schema)
<slug>/backgrounds/ wallpapers, copied verbatim
<slug>/btop.theme copied from apps/ when present
"""
import json
import shutil
import sys
import tomllib
from pathlib import Path
def darken(hex_color: str, factor: float = 0.85) -> str:
"""Scale a #rrggbb color toward black (used to derive 'mantle')."""
h = hex_color.lstrip("#")
r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
return "#{:02x}{:02x}{:02x}".format(*(round(v * factor) for v in (r, g, b)))
def convert(theme_dir: Path) -> dict:
toml = tomllib.loads((theme_dir / "colors.toml").read_text())
slug = theme_dir.name
ansi = [toml[f"color{i}"] for i in range(16)]
return {
"version": 1,
"name": slug.replace("-", " ").title(),
"slug": slug,
"mode": "light" if (theme_dir / "light.mode").exists() else "dark",
# Empty = auto: nomarchy-theme-sync falls back to the first file in
# the theme's backgrounds/ directory.
"wallpaper": "",
"colors": {
"base": toml["background"],
"mantle": darken(toml["background"]),
"surface": toml["color0"],
"overlay": toml["color8"],
"text": toml["foreground"],
"subtext": toml["color7"],
"muted": toml["color8"],
"accent": toml["accent"],
"accentAlt": toml["color5"],
"good": toml["color2"],
"warn": toml["color3"],
"bad": toml["color1"],
},
"ansi": ansi,
}
def main() -> None:
if len(sys.argv) != 3:
sys.exit(__doc__)
src, out = Path(sys.argv[1]), Path(sys.argv[2])
out.mkdir(parents=True, exist_ok=True)
converted = 0
for theme_dir in sorted(src.iterdir()):
if not (theme_dir / "colors.toml").is_file():
continue
theme = convert(theme_dir)
slug = theme["slug"]
(out / f"{slug}.json").write_text(json.dumps(theme, indent=2) + "\n")
# Assets: wallpapers and hand-made per-app theme files.
extras = []
if (theme_dir / "backgrounds").is_dir():
shutil.copytree(theme_dir / "backgrounds", out / slug / "backgrounds",
dirs_exist_ok=True)
extras.append(f"{len(list((theme_dir / 'backgrounds').iterdir()))} wallpapers")
if (theme_dir / "apps" / "btop.theme").is_file():
(out / slug).mkdir(parents=True, exist_ok=True)
shutil.copyfile(theme_dir / "apps" / "btop.theme", out / slug / "btop.theme")
extras.append("btop")
print(f" {slug:<20} ({theme['mode']}{', ' + ', '.join(extras) if extras else ''})")
converted += 1
print(f"converted {converted} palettes -> {out}")
if __name__ == "__main__":
main()