Files
dotfiles/config/wal/generate.py
T

294 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Reads the active theme's colors.json and generates all themed config files."""
import json, os
scheme = os.path.expanduser('~/.active_theme/colors.json')
cache = os.path.expanduser('~/.cache/wal')
os.makedirs(cache, exist_ok=True)
with open(scheme) as f:
c = json.load(f)
bg = c['special']['background']
fg = c['special']['foreground']
pink = c['colors']['color1']
green = c['colors']['color2']
warn = c['colors']['color3']
muted = c['colors']['color8']
dark = c['colors']['color0']
def rgba(h, a):
h = h.lstrip('#')
r, g, b = int(h[0:2],16), int(h[2:4],16), int(h[4:6],16)
return f"rgba({r},{g},{b},{a})"
# ── Waybar ────────────────────────────────────────────────────────────────────
waybar = f"""/* Generated — edit ~/.active_theme/colors.json */
* {{
font-family: "MesloLGS Nerd Font Mono", "Font Awesome 7 Free Solid", "Font Awesome 7 Brands", monospace;
font-size: 11px;
font-weight: normal;
min-height: 0;
border: none;
border-radius: 0;
}}
window#waybar {{
background-color: {rgba(bg, 0.95)};
border-bottom: 1px solid {rgba(pink, 0.4)};
color: {green};
}}
window#waybar.bottom {{
border-bottom: none;
border-top: 1px solid {rgba(pink, 0.4)};
font-size: 11px;
}}
#custom-notification {{
padding: 1px 8px;
color: {green};
font-style: italic;
}}
#custom-notification.critical {{
color: {pink};
font-style: italic;
}}
#disk,
#network.speed {{
padding: 1px 8px;
margin: 3px 2px;
color: {green};
}}
#workspaces {{
margin: 2px 4px;
padding: 0 2px;
}}
#workspaces button {{
padding: 2px 10px;
background: transparent;
color: {muted};
border-radius: 10px;
margin: 2px;
transition: all 0.2s ease;
font-family: "Font Awesome 7 Brands", "MesloLGS Nerd Font Mono", "Font Awesome 7 Free Solid", monospace;
}}
#workspaces button:hover {{
color: {pink};
}}
#workspaces button.active {{
color: {pink};
font-weight: bold;
border-bottom: 1px solid {pink};
}}
#workspaces button.urgent {{
color: {pink};
animation: blink 0.6s steps(1) infinite;
}}
#workspaces button .windows-icon {{
font-size: 9px;
color: {muted};
margin-left: 2px;
}}
#window {{
color: {muted};
padding: 0 10px;
font-style: italic;
}}
#clock {{
color: {pink};
font-weight: bold;
padding: 1px 12px;
}}
#cpu,
#memory,
#temperature,
#battery,
#network,
#pulseaudio,
#tray {{
padding: 1px 8px;
margin: 2px 1px;
color: {green};
transition: all 0.2s ease;
}}
#battery.warning {{
color: {warn};
}}
#battery.critical {{
color: {pink};
transition: none;
animation: blink 1s steps(1) infinite;
}}
#cpu:hover,
#memory:hover,
#network:hover,
#pulseaudio:hover {{
color: {pink};
}}
#temperature.critical {{
color: {pink};
}}
#network.disconnected {{
color: {pink};
}}
#pulseaudio.muted {{
color: {muted};
}}
#tray > .passive {{
-gtk-icon-effect: dim;
}}
#tray > .needs-attention {{
-gtk-icon-effect: highlight;
}}
@keyframes blink {{
to {{ color: {muted}; }}
}}
"""
# ── Wofi ──────────────────────────────────────────────────────────────────────
wofi = f"""/* Generated — edit ~/.active_theme/colors.json */
window {{
background-color: {bg};
border: 1px solid {rgba(pink, 0.5)};
border-radius: 12px;
font-family: "MesloLGS Nerd Font Mono", monospace;
font-size: 13px;
color: {green};
}}
#input {{
background-color: {dark};
color: {green};
border: 1px solid {rgba(pink, 0.4)};
border-radius: 8px;
padding: 8px 12px;
margin: 8px;
outline: none;
caret-color: {pink};
}}
#input:focus {{
border-color: {pink};
}}
#inner-box {{
background-color: transparent;
margin: 0 8px 8px 8px;
}}
#outer-box {{
background-color: transparent;
padding: 4px;
}}
#scroll {{
background-color: transparent;
}}
#text {{
color: {green};
padding: 2px 8px;
}}
#entry {{
background-color: transparent;
border-radius: 8px;
padding: 6px 8px;
margin: 2px 0;
}}
#entry:selected {{
background-color: {rgba(pink, 0.2)};
border: 1px solid {rgba(pink, 0.5)};
}}
#entry:selected #text {{
color: {pink};
}}
"""
# ── Hyprland colors ───────────────────────────────────────────────────────────
def strip(h): return h.lstrip('#')
hypr = f"""# Generated — edit ~/.active_theme/colors.json
$wal_pink = rgba({strip(pink)}ee)
$wal_pink_b = rgba({strip(pink)}66)
$wal_green = rgba({strip(green)}ee)
$wal_muted = rgba({strip(muted)}aa)
$wal_shadow = rgba({strip(bg)}ee)
"""
# ── Hyprland keyword apply (no reload needed) ─────────────────────────────────
def a(h, s): return f"rgba({strip(h)}{s})"
hypr_apply = f"""#!/bin/bash
hyprctl keyword general:col.active_border "{a(pink,'ee')} {a(green,'ee')} 45deg"
hyprctl keyword general:col.inactive_border "{a(muted,'aa')}"
hyprctl keyword decoration:shadow:color "{a(bg,'ee')}"
"""
files = {
'waybar-style.css': waybar,
'wofi-style.css': wofi,
'colors-hypr.conf': hypr,
'hypr-apply.sh': hypr_apply,
}
for name, content in files.items():
with open(os.path.join(cache, name), 'w') as f:
f.write(content)
# Write wallpaper path for reload.sh (expand ~ so reload.sh gets an absolute path)
wallpaper = os.path.expanduser(c.get('wallpaper', 'None'))
with open(os.path.join(cache, 'wallpaper'), 'w') as f:
f.write(wallpaper)
# Update hyprpaper.conf to preload all theme wallpapers for the active profile
active_profile = os.path.expanduser('~/.active_profile')
themes_dir = os.path.join(active_profile, 'themes')
all_wallpapers = set()
if os.path.isdir(themes_dir):
for theme_name in sorted(os.listdir(themes_dir)):
theme_json = os.path.join(themes_dir, theme_name, 'colors.json')
if not os.path.isfile(theme_json):
continue
try:
with open(theme_json) as f:
j = json.load(f)
wp = os.path.expanduser(j.get('wallpaper', 'None'))
if wp and wp != 'None' and os.path.isfile(wp):
all_wallpapers.add(wp)
except Exception:
pass
if all_wallpapers:
initial = wallpaper if wallpaper in all_wallpapers else next(iter(sorted(all_wallpapers)))
hyprpaper_conf = ''.join(f'preload = {wp}\n' for wp in sorted(all_wallpapers))
hyprpaper_conf += f'\nwallpaper = ,{initial}\n'
with open(os.path.expanduser('~/.config/hypr/hyprpaper.conf'), 'w') as f:
f.write(hyprpaper_conf)
print("Generated: " + ", ".join(files.keys()))