From f297a029751f6150cba668f039bdfc808bbe4a4c Mon Sep 17 00:00:00 2001 From: aman-sanin Date: Sat, 7 Mar 2026 12:51:01 +0530 Subject: [PATCH] fix cava and add nvim support fix the issue with bars in cava. also add color template for nvim --- src/caelestia/data/templates/cava.conf | 2 +- src/caelestia/data/templates/discord.scss | 22 +- src/caelestia/data/templates/gtk.css | 4 - src/caelestia/data/templates/nvim.lua | 17 ++ src/caelestia/utils/theme.py | 253 +++++----------------- 5 files changed, 80 insertions(+), 218 deletions(-) create mode 100644 src/caelestia/data/templates/nvim.lua diff --git a/src/caelestia/data/templates/cava.conf b/src/caelestia/data/templates/cava.conf index 1b7fcd30..b88d51d7 100644 --- a/src/caelestia/data/templates/cava.conf +++ b/src/caelestia/data/templates/cava.conf @@ -3,7 +3,7 @@ [general] # Number of bars (20-200) - fewer bars = better performance -bars = 64 +#bars = 64 # Framerate (1-144) - higher = smoother but more CPU intensive framerate = 60 diff --git a/src/caelestia/data/templates/discord.scss b/src/caelestia/data/templates/discord.scss index 5582741f..91d0cfb1 100644 --- a/src/caelestia/data/templates/discord.scss +++ b/src/caelestia/data/templates/discord.scss @@ -15,10 +15,8 @@ @import url("https://refact0r.github.io/midnight-discord/build/midnight.css"); body { - /* font options */ - --font: "figtree"; /* change to '' for default discord font */ - --code-font: "JetBrainsMono NF"; /* change to '' for default discord font */ - font-weight: 400; /* normal text font weight. DOES NOT AFFECT BOLD TEXT */ + /* font, change to '' for default discord font */ + --font: "figtree"; /* sizes */ --gap: 12px; /* spacing between panels */ @@ -29,14 +27,13 @@ body { --animations: on; /* turn off to disable all midnight animations/transitions */ --list-item-transition: 0.2s ease; /* transition for list items */ --dms-icon-svg-transition: 0.4s ease; /* transition for the dms icon */ - --border-hover-transition: 0.2s ease; /* transition for borders when hovered */ /* top bar options */ --top-bar-height: var( --gap ); /* height of the titlebar/top bar (discord default is 36px, 24px recommended if moving/hiding top bar buttons) */ - --top-bar-button-position: titlebar; /* off: default position, hide: hide inbox/support buttons completely, serverlist: move inbox button to server list, titlebar: move inbox button to titlebar (will hide title) */ - --top-bar-title-position: off; /* off: default centered position, hide: hide title completely, left: left align title (like old discord) */ + --top-bar-button-position: hide; /* off: default position, hide: hide inbox/support buttons completely, serverlist: move inbox button to server list, titlebar: move inbox button to titlebar (will hide title) */ + --top-bar-title-position: hide; /* off: default centered position, hide: hide title completely, left: left align title (like old discord) */ --subtle-top-bar-title: off; /* off: default, on: hide the icon and use subtle text color (like old discord) */ /* window controls */ @@ -45,9 +42,9 @@ body { /* dms button icon options */ --custom-dms-icon: custom; /* off: use default discord icon, hide: remove icon entirely, custom: use custom icon */ - --dms-icon-svg-url: url("https://refact0r.github.io/midnight-discord/assets/Font_Awesome_5_solid_moon.svg"); /* icon svg url. MUST BE A SVG. */ + --dms-icon-svg-url: url("https://upload.wikimedia.org/wikipedia/commons/c/c4/Font_Awesome_5_solid_moon.svg"); /* icon svg url. MUST BE A SVG. */ --dms-icon-svg-size: 90%; /* size of the svg (css mask-size) */ - --dms-icon-color-before: var(--icon-subtle); /* normal icon color */ + --dms-icon-color-before: var(--icon-secondary); /* normal icon color */ --dms-icon-color-after: var(--white); /* icon color when button is hovered/selected */ /* dms button background options */ @@ -74,11 +71,12 @@ body { --bg-floating: #{c.$surface}; /* you can set this to a more opaque color if floating panels look too transparent */ /* chatbar options */ - --custom-chatbar: off; /* off: default chatbar, separated: chatbar separated from chat */ - --chatbar-height: 47px; /* height of the chatbar (56px by default, 47px to align with user panel, 56px recommended for separated) */ + --custom-chatbar: aligned; /* off: default chatbar, aligned: chatbar aligned with the user panel, separated: chatbar separated from chat */ + --chatbar-height: 47px; /* height of the chatbar (52px by default, 47px recommended for aligned, 56px recommended for separated) */ + --chatbar-padding: 8px; /* padding of the chatbar. only applies in aligned mode. */ /* other options */ - --small-user-panel: off; /* off: default user panel, on: smaller user panel like in old discord */ + --small-user-panel: off; /* turn on to make the user panel smaller like in old discord */ } /* color options */ diff --git a/src/caelestia/data/templates/gtk.css b/src/caelestia/data/templates/gtk.css index 35d16a11..bc9e5581 100644 --- a/src/caelestia/data/templates/gtk.css +++ b/src/caelestia/data/templates/gtk.css @@ -15,7 +15,3 @@ @define-color sidebar_fg_color @window_fg_color; @define-color sidebar_border_color @window_bg_color; @define-color sidebar_backdrop_color @window_bg_color; -@define-color theme_selected_bg_color alpha(@accent_color, 0.15); -@define-color theme_selected_fg_color @primary; - -@import "thunar.css"; diff --git a/src/caelestia/data/templates/nvim.lua b/src/caelestia/data/templates/nvim.lua new file mode 100644 index 00000000..33469627 --- /dev/null +++ b/src/caelestia/data/templates/nvim.lua @@ -0,0 +1,17 @@ +return { + background = "{{ $surface }}", + foreground = "{{ $onSurface }}", + cursor = "{{ $primary }}", + selection = "{{ $surfaceContainer }}", + line_nr = "{{ $outline }}", + comment = "{{ $outlineVariant }}", -- Make sure this exists! + + -- Syntax colors + keyword = "{{ $primary }}", + func = "{{ $blue }}", + string = "{{ $green }}", + type = "{{ $mauve }}", + const = "{{ $peach }}", + error = "{{ $red }}", + warn = "{{ $yellow }}" +} diff --git a/src/caelestia/utils/theme.py b/src/caelestia/utils/theme.py index 7d174330..3c0680be 100644 --- a/src/caelestia/utils/theme.py +++ b/src/caelestia/utils/theme.py @@ -2,10 +2,6 @@ import re import subprocess from pathlib import Path -import tempfile -import shutil -import fcntl -import sys from caelestia.utils.colour import get_dynamic_colours from caelestia.utils.logging import log_exception @@ -41,7 +37,7 @@ def gen_replace(colours: dict[str, str], template: Path, hash: bool = False) -> return template -def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> str: +def gen_replace_dynamic(colours: dict[str, str], template: Path) -> str: def fill_colour(match: re.Match) -> str: data = match.group(1).strip().split(".") if len(data) != 2: @@ -52,16 +48,10 @@ def fill_colour(match: re.Match) -> str: return getattr(colours_dyn[col], form) # match atomic {{ . }} pairs - dotField = r"\{\{((?:(?!\{\{|\}\}).)*)\}\}" - - # match {{ mode }} - modeField = r"\{\{\s*mode\s*\}\}" - + field = r"\{\{((?:(?!\{\{|\}\}).)*)\}\}" colours_dyn = get_dynamic_colours(colours) template_content = template.read_text() - - template_filled = re.sub(dotField, fill_colour, template_content) - template_filled = re.sub(modeField, mode, template_filled) + template_filled = re.sub(field, fill_colour, template_content) return template_filled @@ -111,11 +101,8 @@ def gen_sequences(colours: dict[str, str]) -> str: def write_file(path: Path, content: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) - with tempfile.NamedTemporaryFile("w") as f: - f.write(content) - f.flush() - shutil.move(f.name, path) @log_exception def apply_terms(sequences: str) -> None: @@ -127,15 +114,9 @@ def apply_terms(sequences: str) -> None: for pt in pts_path.iterdir(): if pt.name.isdigit(): try: - # Use non-blocking write with timeout to prevent hangs - import os - fd = os.open(str(pt), os.O_WRONLY | os.O_NONBLOCK | os.O_NOCTTY) - try: - os.write(fd, sequences.encode()) - finally: - os.close(fd) - except (PermissionError, OSError, BlockingIOError): - # Skip terminals that are busy, closed, or inaccessible + with pt.open("a") as f: + f.write(sequences) + except PermissionError: pass @@ -155,12 +136,6 @@ def apply_discord(scss: str) -> None: for client in "Equicord", "Vencord", "BetterDiscord", "equibop", "vesktop", "legcord": write_file(config_dir / client / "themes/caelestia.theme.css", conf) -@log_exception -def apply_pandora(colours: dict[str, str], mode: str) -> None: - template = gen_replace(colours, templates_dir / "pandora.json", hash=True) - template = template.replace("{{ $mode }}", mode) - write_file(data_dir / "PandoraLauncher/themes/caelestia.json", template) - @log_exception def apply_spicetify(colours: dict[str, str], mode: str) -> None: @@ -194,130 +169,23 @@ def apply_htop(colours: dict[str, str]) -> None: subprocess.run(["killall", "-USR2", "htop"], stderr=subprocess.DEVNULL) -def sync_papirus_colors(hex_color: str) -> None: - """Sync Papirus folder icon colors using hue/saturation analysis""" - try: - result = subprocess.run( - ["which", "papirus-folders"], - capture_output=True, - check=False - ) - if result.returncode != 0: - return - except Exception: - return - - papirus_paths = [ - Path("/usr/share/icons/Papirus"), - Path("/usr/share/icons/Papirus-Dark"), - Path.home() / ".local/share/icons/Papirus", - Path.home() / ".icons/Papirus", - ] - - if not any(p.exists() for p in papirus_paths): - return - - r = int(hex_color[0:2], 16) - g = int(hex_color[2:4], 16) - b = int(hex_color[4:6], 16) - - # Brightness and saturation - max_val = max(r, g, b) - min_val = min(r, g, b) - brightness = max_val - saturation = 0 if max_val == 0 else ((max_val - min_val) * 100) // max_val - - # Low saturation = grayscale - if saturation < 20: - if brightness < 85: - color = "black" - elif brightness < 170: - color = "grey" - else: - color = "white" - # Medium-low saturation with high brightness = pale variants - elif saturation < 60 and brightness > 180: - use_pale = True - color = _determine_hue_color(r, g, b, brightness, use_pale) - else: - color = _determine_hue_color(r, g, b, brightness, False) - - try: - subprocess.Popen( - ["sudo", "-n", "papirus-folders", "-C", color, "-u"], - stderr=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - start_new_session=True - ) - except Exception: - pass - - -def _determine_hue_color(r: int, g: int, b: int, brightness: int, use_pale: bool) -> str: - if b > r and b > g: - # Blue dominant - r_ratio = (r * 100) // b if b > 0 else 0 - g_ratio = (g * 100) // b if b > 0 else 0 - rg_diff = abs(r - g) - - if r_ratio > 70 and g_ratio > 70: - # Both R and G high relative to B = light blue/periwinkle - if rg_diff < 15: - return "blue" - elif r > g: - return "violet" - else: - return "cyan" - elif r_ratio > 60 and r > g: - return "violet" - elif g_ratio > 60 and g > r: - return "cyan" - else: - return "blue" - elif r > g and r > b: - # Red dominant - if g > b + 30: - # Orange/yellow-ish/brown - rg_ratio = (g * 100) // r if r > 0 else 0 - if use_pale: - if rg_ratio > 70 and brightness < 220: - return "palebrown" - else: - return "paleorange" - else: - if rg_ratio > 70 and brightness < 180: - return "brown" - else: - return "orange" - elif b > g + 20: - return "pink" - else: - return "pink" if use_pale else "red" - elif g > r and g > b: - # Green dominant - if r > b + 30: - return "yellow" - else: - return "green" - else: - return "grey" +@log_exception +def apply_nvim(colours: dict[str, str]) -> None: + # Generates lua file with # hashes (hash=True) + template = gen_replace(colours, templates_dir / "nvim.lua", hash=True) + # Writes to ~/.config/nvim/lua/caelestia_colors.lua + write_file(config_dir / "nvim/lua/caelestia_colors.lua", template) @log_exception def apply_gtk(colours: dict[str, str], mode: str) -> None: - gtk_template = gen_replace(colours, templates_dir / "gtk.css", hash=True) - thunar_template = gen_replace(colours, templates_dir / "thunar.css", hash=True) - - for gtk_version in ["gtk-3.0", "gtk-4.0"]: - gtk_config_dir = config_dir / gtk_version - write_file(gtk_config_dir / "gtk.css", gtk_template) - write_file(gtk_config_dir / "thunar.css", thunar_template) + template = gen_replace(colours, templates_dir / "gtk.css", hash=True) + write_file(config_dir / "gtk-3.0/gtk.css", template) + write_file(config_dir / "gtk-4.0/gtk.css", template) subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/gtk-theme", "'adw-gtk3-dark'"]) subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/color-scheme", f"'prefer-{mode}'"]) subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/icon-theme", f"'Papirus-{mode.capitalize()}'"]) - - sync_papirus_colors(colours["primary"]) @log_exception @@ -364,66 +232,49 @@ def apply_cava(colours: dict[str, str]) -> None: @log_exception -def apply_user_templates(colours: dict[str, str], mode: str) -> None: +def apply_user_templates(colours: dict[str, str]) -> None: if not user_templates_dir.is_dir(): return for file in user_templates_dir.iterdir(): if file.is_file(): - content = gen_replace_dynamic(colours, file, mode) + content = gen_replace_dynamic(colours, file) write_file(theme_dir / file.name, content) def apply_colours(colours: dict[str, str], mode: str) -> None: - # Use file-based lock to prevent concurrent theme changes - lock_file = c_state_dir / "theme.lock" - c_state_dir.mkdir(parents=True, exist_ok=True) - try: - with open(lock_file, 'w') as lock_fd: - try: - fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) - except BlockingIOError: - return - - try: - cfg = json.loads(user_config_path.read_text())["theme"] - except (FileNotFoundError, json.JSONDecodeError, KeyError): - cfg = {} - - def check(key: str) -> bool: - return cfg[key] if key in cfg else True - - if check("enableTerm"): - apply_terms(gen_sequences(colours)) - if check("enableHypr"): - apply_hypr(gen_conf(colours)) - if check("enableDiscord"): - apply_discord(gen_scss(colours)) - if check("enableSpicetify"): - apply_spicetify(colours, mode) - if check("enablePandora"): - apply_pandora(colours, mode) - if check("enableFuzzel"): - apply_fuzzel(colours) - if check("enableBtop"): - apply_btop(colours) - if check("enableNvtop"): - apply_nvtop(colours) - if check("enableHtop"): - apply_htop(colours) - if check("enableGtk"): - apply_gtk(colours, mode) - if check("enableQt"): - apply_qt(colours, mode) - if check("enableWarp"): - apply_warp(colours, mode) - if check("enableCava"): - apply_cava(colours) - apply_user_templates(colours, mode) - - finally: - try: - lock_file.unlink() - except FileNotFoundError: - pass + cfg = json.loads(user_config_path.read_text())["theme"] + except (FileNotFoundError, json.JSONDecodeError, KeyError): + cfg = {} + + def check(key: str) -> bool: + return cfg[key] if key in cfg else True + + if check("enableTerm"): + apply_terms(gen_sequences(colours)) + if check("enableHypr"): + apply_hypr(gen_conf(colours)) + if check("enableDiscord"): + apply_discord(gen_scss(colours)) + if check("enableSpicetify"): + apply_spicetify(colours, mode) + if check("enableFuzzel"): + apply_fuzzel(colours) + if check("enableBtop"): + apply_btop(colours) + if check("enableNvtop"): + apply_nvtop(colours) + if check("enableHtop"): + apply_htop(colours) + if check("enableNvim"): + apply_nvim(colours) + if check("enableGtk"): + apply_gtk(colours, mode) + if check("enableQt"): + apply_qt(colours, mode) + if check("enableWarp"): + apply_warp(colours, mode) + if check("enableCava"): + apply_cava(colours) + apply_user_templates(colours)