initial dotfiles setup
Profile-based dotfiles with switchable color themes. Structure: profiles/ with themes/ and config/ per profile.
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
#!/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']
|
||||
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", 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;
|
||||
}}
|
||||
|
||||
#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;
|
||||
}}
|
||||
|
||||
#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;
|
||||
}}
|
||||
|
||||
#window {{
|
||||
color: {muted};
|
||||
padding: 0 10px;
|
||||
font-style: italic;
|
||||
}}
|
||||
|
||||
#clock {{
|
||||
color: {pink};
|
||||
font-weight: bold;
|
||||
padding: 1px 12px;
|
||||
}}
|
||||
|
||||
#cpu,
|
||||
#memory,
|
||||
#temperature,
|
||||
#network,
|
||||
#pulseaudio,
|
||||
#tray {{
|
||||
padding: 1px 8px;
|
||||
margin: 2px 1px;
|
||||
color: {green};
|
||||
transition: all 0.2s ease;
|
||||
}}
|
||||
|
||||
#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()))
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
THEMES_DIR="$HOME/.active_profile/themes"
|
||||
|
||||
mapfile -t themes < <(ls -d "$THEMES_DIR"/*/ 2>/dev/null | xargs -n1 basename | sort)
|
||||
|
||||
if [[ ${#themes[@]} -eq 0 ]]; then
|
||||
echo "No themes found in $THEMES_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current=$(basename "$(readlink "$HOME/.active_theme" 2>/dev/null)" || echo "${themes[0]}")
|
||||
next_index=0
|
||||
for i in "${!themes[@]}"; do
|
||||
if [[ "${themes[$i]}" == "$current" ]]; then
|
||||
next_index=$(( (i + 1) % ${#themes[@]} ))
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
next="${themes[$next_index]}"
|
||||
ln -sfn "$HOME/.active_profile/themes/$next" "$HOME/.active_theme"
|
||||
~/.config/wal/reload.sh
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Preloads all theme wallpapers at startup so switching is instant.
|
||||
THEME_DIR="$HOME/.config/wal/colorschemes/dark"
|
||||
|
||||
for json in "$THEME_DIR"/*.json; do
|
||||
wp=$(python3 -c "import json,sys; c=json.load(open('$json')); print(c.get('wallpaper','None'))")
|
||||
if [[ -n "$wp" && "$wp" != "None" && -f "$wp" ]]; then
|
||||
hyprctl hyprpaper preload "$wp" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
THEME=$(basename "$(readlink "$HOME/.active_theme" 2>/dev/null)" 2>/dev/null || echo "unknown")
|
||||
echo "$THEME" > ~/.cache/wal/current-theme
|
||||
|
||||
python3 ~/.config/wal/generate.py
|
||||
|
||||
# Symlinks (harmless to repeat)
|
||||
ln -sf ~/.cache/wal/colors-hypr.conf ~/.config/hypr/colors.conf
|
||||
ln -sf ~/.cache/wal/waybar-style.css ~/.config/waybar/style.css
|
||||
ln -sf ~/.cache/wal/wofi-style.css ~/.config/wofi/style.css
|
||||
|
||||
# Apply Hyprland colors without full reload
|
||||
bash ~/.cache/wal/hypr-apply.sh
|
||||
|
||||
# Restart waybar
|
||||
pkill waybar; sleep 0.3; waybar &disown
|
||||
|
||||
# Switch wallpaper only if it changed
|
||||
WALLPAPER=$(cat ~/.cache/wal/wallpaper 2>/dev/null)
|
||||
PREV=$(cat ~/.cache/wal/wallpaper-active 2>/dev/null)
|
||||
if [[ -n "$WALLPAPER" && "$WALLPAPER" != "None" && -f "$WALLPAPER" && "$WALLPAPER" != "$PREV" ]]; then
|
||||
awww img "$WALLPAPER" --transition-type fade
|
||||
echo "$WALLPAPER" > ~/.cache/wal/wallpaper-active
|
||||
fi
|
||||
@@ -0,0 +1,6 @@
|
||||
# Generated by pywal — edit ~/.config/wal/colorschemes/dark/tekki.json
|
||||
$wal_pink = rgba({color1.strip}ee)
|
||||
$wal_pink_b = rgba({color1.strip}66)
|
||||
$wal_green = rgba({color2.strip}ee)
|
||||
$wal_muted = rgba({color8.strip}aa)
|
||||
$wal_shadow = rgba({background.strip}ee)
|
||||
Reference in New Issue
Block a user