nandi/frqpublic Fork 0
63365cd044006966e9fa5e61b63941ee476db3bc
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

cosmic2cljd.py · 132 lines · 5.3 KBPython Blame HistoryRaw
Paint the phone in COSMIC's own theme, read from cosmic-config ba4e71b nandi 7d ago1#!/usr/bin/env python3
2"""Read the COSMIC theme out of cosmic-config and write it as ClojureDart.
3
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago4libcosmic asks cosmic-config for the user's theme at run time, so
5`just cosmic run` already paints frq in whatever accent and surfaces are set in COSMIC Settings.
Paint the phone in COSMIC's own theme, read from cosmic-config ba4e71b nandi 7d ago6A phone has no cosmic-config, so the APK cannot ask — the values are read here
7instead, on the machine that has them, and compiled in.
8
9That is a real difference and worth naming: the desktop follows the theme as
10it changes, and the APK carries the theme as it was when the APK was built.
11`just theme` moves it.
12
13The config is RON. Not parsed as RON: every value this needs is either a
14`key: "#RRGGBBAA"` line or a `key: N` line inside a named block, and a general
15RON parser to read colours out of a flat file is a dependency nobody needs.
16"""
17
18import pathlib
19import re
20import sys
21
22CONFIG = pathlib.Path.home() / ".config" / "cosmic"
23
24# What frq asks of a theme, as (token, file, field). `component` fields are one
25# block deep — see the `component: (` in background/primary/secondary.
26COLOURS = [
27 ("accent", "accent", "base"),
28 ("on-accent", "accent", "on"),
29 ("bg", "background", "base"),
30 ("on-bg", "background", "component.on"),
31 ("component", "background", "component.base"),
32 ("component-hover", "background", "component.hover"),
33 ("divider", "background", "component.divider"),
34 ("card", "primary", "base"),
35 ("card-component", "primary", "component.base"),
36 ("on-card", "primary", "component.on"),
37 ("destructive", "destructive", "base"),
38 ("on-destructive", "destructive", "on"),
39 ("success", "success", "base"),
40]
41
42
43def read(mode: str, name: str) -> str:
44 for version in ("v2", "v1"):
45 p = CONFIG / f"com.system76.CosmicTheme.{mode}" / version / name
46 if p.exists():
47 return p.read_text()
48 raise SystemExit(f"cosmic2cljd: no {name} under {CONFIG} — is COSMIC installed?")
49
50
51def field(text: str, path: str) -> str:
52 """`base`, or `component.base` for the one inside the component block."""
53 if "." in path:
54 outer, inner = path.split(".", 1)
55 m = re.search(rf"\b{outer}:\s*\(", text)
56 if not m:
57 raise SystemExit(f"cosmic2cljd: no {outer} block")
58 text = text[m.end():]
59 path = inner
60 m = re.search(rf'^\s*{path}:\s*"(#[0-9A-Fa-f]{{6,8}})"', text, re.M)
61 if not m:
62 raise SystemExit(f"cosmic2cljd: no {path}")
63 return m.group(1)
64
65
66def argb(hex_rgba: str) -> str:
67 """#RRGGBBAA (COSMIC) to 0xAARRGGBB (Flutter's Color)."""
68 h = hex_rgba.lstrip("#")
69 if len(h) == 6:
70 h += "FF"
71 r, g, b, a = h[0:2], h[2:4], h[4:6], h[6:8]
72 return f"0x{a}{r}{g}{b}".upper().replace("0X", "0x")
73
74
75def number(text: str, key: str, default: float) -> float:
76 m = re.search(rf"^\s*{key}:\s*\(?\s*([0-9.]+)", text, re.M)
77 return float(m.group(1)) if m else default
78
79
80def main() -> None:
81 out = pathlib.Path(sys.argv[1] if len(sys.argv) > 1
82 else "flutter/src/frq/theme/cosmic.cljd")
83 dark = (CONFIG / "com.system76.CosmicTheme.Mode" / "v1" / "is_dark")
84 is_dark = dark.exists() and dark.read_text().strip() == "true"
85 mode = "Dark" if is_dark else "Light"
86
87 radii = read(mode, "corner_radii")
88 spacing = read(mode, "spacing")
89 files = {n: read(mode, n) for n in {f for _, f, _ in COLOURS}}
90
91 lines = [
92 "(ns frq.theme.cosmic",
93 ' "The COSMIC theme, as it was on the machine that built this.',
94 "",
95 " GENERATED by tools/cosmic2cljd.py — `just theme`. Do not edit.",
96 "",
Name the libcosmic recipe for its backend: `just cosmic run` 5ba9382 nandi 7d ago97 " libcosmic asks cosmic-config for these at run time, so `just cosmic run`",
98 " follows COSMIC Settings as it changes. A phone has no cosmic-config, so",
99 " the APK carries them instead. That is the one real difference between",
100 f' the two, and it is why this file is in git."',
Paint the phone in COSMIC's own theme, read from cosmic-config ba4e71b nandi 7d ago101 ' (:require ["package:flutter/material.dart" :as m]))',
102 "",
103 f";; {mode} theme, from ~/.config/cosmic.",
104 f"(def dark? {str(is_dark).lower()})",
105 "",
106 ]
107 for token, fname, path in COLOURS:
108 val = field(files[fname], path)
109 lines.append(f";; {fname}.{path} = {val}")
110 lines.append(f"(def {token} (m/Color. {argb(val)}))")
111 lines.append("")
112 lines.append(";; corner_radii")
113 for token, key, default in [("radius-xs", "radius_xs", 2.0),
114 ("radius-s", "radius_s", 8.0),
115 ("radius-m", "radius_m", 8.0)]:
116 lines.append(f"(def {token} {number(radii, key, default)})")
117 lines.append("")
118 lines.append(";; spacing")
119 for token, key, default in [("space-xxxs", "space_xxxs", 4.0),
120 ("space-xxs", "space_xxs", 8.0),
121 ("space-xs", "space_xs", 12.0),
122 ("space-s", "space_s", 16.0),
123 ("space-m", "space_m", 24.0)]:
124 lines.append(f"(def {token} {number(spacing, key, default)})")
125
126 out.parent.mkdir(parents=True, exist_ok=True)
127 out.write_text("\n".join(lines) + "\n")
128 print(f"wrote {out} ({mode}, accent {field(files['accent'], 'base')})")
129
130
131if __name__ == "__main__":
132 main()