diff options
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/udt-appthemes | 88 | ||||
| -rwxr-xr-x | bin/udt-palette | 85 |
2 files changed, 173 insertions, 0 deletions
diff --git a/bin/udt-appthemes b/bin/udt-appthemes new file mode 100755 index 0000000..eedc144 --- /dev/null +++ b/bin/udt-appthemes @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# udt-appthemes: install the generated app themes that need more than a copy. +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# Licensed under the GNU General Public License v2 only. +"""Install the Obsidian snippet into every vault, and the Typora theme. + +Separate from install.sh because enabling an Obsidian snippet means editing +JSON that the user also owns: appearance.json lists every enabled snippet, so +the entry has to be appended rather than the file rewritten, or a vault loses +whatever layout snippets it had. +""" + +import json +import shutil +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +SNIPPET = "udt" +VAULT_ROOT = Path.home() / "Documents" / "Obsidian" +TYPORA_THEMES = Path.home() / ".config" / "Typora" / "themes" + + +def vaults(): + """Every Obsidian vault under the vault root. + + A vault is any directory holding a .obsidian config dir. Nested vaults are + real: a vault inside another vault's folder is still its own vault. + """ + if not VAULT_ROOT.is_dir(): + return [] + return sorted(p for p in VAULT_ROOT.rglob(".obsidian") if p.is_dir()) + + +def install_obsidian(source): + """Copy the snippet into each vault and enable it, preserving the rest.""" + done = [] + for config in vaults(): + snippets = config / "snippets" + snippets.mkdir(exist_ok=True) + shutil.copyfile(source, snippets / f"{SNIPPET}.css") + + appearance = config / "appearance.json" + try: + data = json.loads(appearance.read_text()) + except (OSError, json.JSONDecodeError): + data = {} + + enabled = data.get("enabledCssSnippets", []) + if SNIPPET not in enabled: + # Append: a vault may already have snippets on, and replacing the + # list would silently switch them off. + enabled.append(SNIPPET) + data["enabledCssSnippets"] = enabled + appearance.write_text(json.dumps(data, indent=2) + "\n") + + done.append(config.parent.name or str(config.parent)) + return done + + +def install_typora(source): + """Drop the theme in. Typora picks it from Themes once the file exists.""" + if not TYPORA_THEMES.is_dir(): + return False + shutil.copyfile(source, TYPORA_THEMES / "udt.css") + return True + + +def main(): + obsidian = REPO / "templates" / "obsidian" / "udt.css" + typora = REPO / "templates" / "typora" / "udt.css" + + if not obsidian.exists() or not typora.exists(): + print("udt-appthemes: run udt-palette first", file=sys.stderr) + return 1 + + names = install_obsidian(obsidian) + if names: + print(f"obsidian: {len(names)} vaults ({', '.join(names)})") + + if install_typora(typora): + print("typora: udt.css installed (select it in Themes)") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/udt-palette b/bin/udt-palette index edde7f3..f2807af 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -223,6 +223,89 @@ def gen_kitty(res, scheme, palette): return "\n".join(lines) + "\n" +def gen_obsidian(res, scheme, palette): + """Obsidian: a CSS snippet overriding the theme variables. + + A snippet rather than a theme, because a theme replaces the user's choice + outright while a snippet layers over whatever they have enabled. Obsidian + loads snippets after the theme, so these win without the theme having to go. + + The variables are Obsidian's documented public API for this. Only colour is + set: spacing and typography belong to whatever theme the vault uses. + """ + def c(role): + return hex6(res[role]) + + return f"""/* {BANNER.format(scheme=scheme)} + * + * Enabled per vault in Appearance > CSS snippets. install.sh writes this into + * every vault it finds and enables it without disturbing the snippets already + * on, so a vault keeps its layout snippets and gains these colours. + */ + +.theme-dark {{ + --background-primary: {c('bg')}; + --background-primary-alt: {c('bg_alt')}; + --background-secondary: {c('bg_alt')}; + --background-secondary-alt: {c('bg_deep')}; + --background-modifier-border: {c('border')}; + --background-modifier-hover: {c('surface')}; + --background-modifier-error: {c('critical')}; + --background-modifier-success:{c('success')}; + + --text-normal: {c('fg')}; + --text-muted: {c('fg_dim')}; + --text-faint: {c('fg_faint')}; + --text-error: {c('critical')}; + --text-success: {c('success')}; + --text-accent: {c('accent')}; + --text-accent-hover:{c('accent_bright')}; + --text-on-accent: {c('bg')}; + --text-selection: {hex8((*res['accent'][:3], 30))}; + --text-highlight-bg:{hex8((*res['warning'][:3], 40))}; + + --interactive-normal: {c('surface')}; + --interactive-hover: {c('surface_alt')}; + --interactive-accent: {c('accent')}; + --interactive-accent-hover: {c('accent_bright')}; + + --h1-color: {c('accent')}; + --h2-color: {c('accent')}; + --h3-color: {c('info')}; + --h4-color: {c('info')}; + --h5-color: {c('fg_dim')}; + --h6-color: {c('fg_dim')}; + + --code-normal: {c('warning')}; + --code-background: {c('bg_alt')}; + --blockquote-border-color: {c('accent')}; + --hr-color: {c('border')}; + --checkbox-color: {c('accent')}; + --tag-color: {c('info')}; + --tag-background: {hex8((*res['info'][:3], 20))}; +}} +""" + + +def gen_typora(res, scheme, palette, template): + """Typora: substitute the palette into the theme's :root block. + + Typora themes are one self-contained stylesheet, but this one routes all + 350 of its rules through variables in :root, so only that block is a + template. Derived from the dracula theme already installed here. + """ + out = template + for role in TYPORA_ROLES: + out = out.replace(f"@{role.upper()}@", hex6(res[role])) + return out + + +# Exactly the placeholders templates/typora/udt.css.in carries. +TYPORA_ROLES = ["bg", "bg_deep", "surface_alt", "fg", "fg_dim", "fg_faint", + "accent", "accent_bright", "critical", "warning", "success", + "info", "highlight"] + + KVANTUM_ROLES = ["bg", "bg_alt", "surface", "surface_alt", "surface_high", "fg", "fg_dim", "fg_faint", "mid_high", "accent", "accent_dim", "accent_bright", "highlight", "critical"] @@ -436,6 +519,8 @@ TARGETS = [ ("templates/conky.conf", gen_conky, "templates/conky.conf.in"), ("bin/udt_colors.py", gen_accent_py, None), ("templates/homepage/custom.css", gen_homepage, None), + ("templates/obsidian/udt.css", gen_obsidian, None), + ("templates/typora/udt.css", gen_typora, "templates/typora/udt.css.in"), ("templates/kvantum/theme.kvconfig", gen_kvantum, "templates/kvantum/theme.kvconfig.in"), ("templates/kvantum/theme.svg", gen_kvantum, "templates/kvantum/theme.svg.in"), ] |
