1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
#!/bin/bash
# Install udt by symlink, so the repo stays canonical.
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
# Licensed under the GNU General Public License v2 only.
set -euo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
target="$HOME/.config/rofi/udt"
mkdir -p "$target" "$HOME/bin" "$HOME/.cache/wal"
# Generate every themed config from the selected scheme first: the rofi palette
# symlinked below is one of its outputs, so this has to run before anything is
# linked. It fails loudly if a role names a colour the scheme does not define,
# which is the whole point of keeping the colours in one place.
selector="$HOME/.config/udt/roles.conf"
if [ ! -f "$selector" ]; then
install -Dm644 "$repo/palette/roles.conf.default" "$selector"
echo "seeded $selector"
fi
"$repo/bin/udt-palette"
# Theme files are symlinked individually; accent.rasi is NOT, it points at the
# generated file in the wal cache instead.
for f in palette.rasi common.rasi menu.rasi list.rasi grid.rasi powermenu.rasi confirm.rasi; do
ln -sfn "$repo/rofi/udt/$f" "$target/$f"
done
# launcher.rasi is generated rather than symlinked: its wallpaper path comes
# from the wal cache and cannot be committed. Regenerating it is why re-running
# this script after moving the repo is safe.
rm -f "$target/launcher.rasi"
sed "s|@WPAPER@|$HOME/.cache/wal/wpaper|" \
"$repo/rofi/udt/launcher.rasi" > "$target/launcher.rasi"
# Seed the generated accent if it does not exist yet, so themes parse on a
# fresh install before any wallpaper has been set.
if [ ! -f "$HOME/.cache/wal/udt-accent.rasi" ]; then
cp "$repo/rofi/udt/accent.rasi" "$HOME/.cache/wal/udt-accent.rasi"
fi
ln -sfn "$HOME/.cache/wal/udt-accent.rasi" "$target/accent.rasi"
ln -sfn "$repo/bin/udt-accent" "$HOME/bin/udt-accent"
ln -sfn "$repo/bin/udt-palette" "$HOME/bin/udt-palette"
# The generated configs for everything that is not rofi. These are copied
# rather than symlinked: each app owns its config file and mixes theme keys
# with settings that are none of this repo's business.
scheme="$(sed -n 's/^scheme *= *//p' "$selector")"
# waybar. The bar is waybar-theme-udt, a sibling checkout with its own
# install.sh, and that script is called here rather than having this one copy
# the stylesheet into place.
#
# The reason is that the palette reaches the bar as THREE generated files, not
# one: styles/theme.css, dots.colors for the two dot scripts, which are bash
# and cannot read @define-color, and two tints of the Slackware mark, which
# has to contrast with the accent pill at rest and the dark pill on hover.
# Copying only the stylesheet left the dots and the mark on the previous
# scheme's colours after a switch, and a second writer for those files here
# would drift from the one over there.
#
# Called with the palette already generated, and never fatal: a failure in the
# bar's install must not stop kitty, Kvantum or Sublime from being themed. The
# same reasoning as udt-appthemes below.
#
# The stock ~/.config/waybar/ is deliberately left alone. Nothing launches it
# any more, and its stylesheets still reference the per-module role names this
# generator no longer emits, so writing a new theme in there would leave it
# referencing colours that do not exist rather than simply sitting unused.
waybar_repo="$repo/../waybar-theme-udt"
if [ -x "$waybar_repo/install.sh" ]; then
UDT_PALETTE_DONE=1 "$waybar_repo/install.sh" >/dev/null || \
echo "waybar: its install.sh failed, bar left on the old colours" >&2
fi
install -Dm644 "$repo/templates/terminal/kitty-theme.conf" \
"$HOME/.config/kitty/current-theme.conf"
# Sublime Text. Copied into Packages/User, which Sublime watches, so writing it
# reaches a running editor. The file still carries @ACCENT@, which udt-accent
# substitutes below; selecting it is a separate step because color_scheme lives
# in Preferences.sublime-settings alongside settings that are none of our
# business, so only that one key is rewritten, and only if it is not ours yet.
subl="$HOME/.config/sublime-text/Packages/User"
if [ -d "$subl" ]; then
install -Dm644 "$repo/templates/sublime/udt.sublime-color-scheme" \
"$subl/udt.sublime-color-scheme"
# The UI chrome is an override, and Sublime merges .sublime-theme files by
# FILENAME across packages with User last. So it has to be installed under
# the name of the theme it layers over, not under a name of its own: called
# udt.sublime-theme it would be an unselected theme that never applies.
# Renaming it here rather than in the repo keeps the source name obvious.
behave="$subl/Material Behave.sublime-theme"
if [ -f "$behave" ] && ! grep -q 'Generated by udt-palette' "$behave"; then
cp "$behave" "$behave.pre-udt"
echo "sublime: kept your theme override as $(basename "$behave").pre-udt"
fi
install -Dm644 "$repo/templates/sublime/udt.sublime-theme" "$behave"
prefs="$subl/Preferences.sublime-settings"
if [ -f "$prefs" ] && ! grep -q '"color_scheme".*udt\.sublime-color-scheme' "$prefs"; then
sed -i 's|"color_scheme":[[:space:]]*"[^"]*"|"color_scheme": "Packages/User/udt.sublime-color-scheme"|' "$prefs"
echo "sublime: color_scheme switched to udt"
fi
fi
# Kvantum wants a directory per theme, named after itself, and qt5ct/qt6ct
# reach Qt apps through it. The theme is installed under one fixed name rather
# than one per scheme: Kvantum caches by name, so reusing it means a switch
# needs no change to kvantum.kvconfig and no second theme left behind.
kv="$HOME/.config/Kvantum/udt"
install -Dm644 "$repo/templates/kvantum/theme.kvconfig" "$kv/udt.kvconfig"
install -Dm644 "$repo/templates/kvantum/theme.svg" "$kv/udt.svg"
printf '[General]\ntheme=udt\n' > "$HOME/.config/Kvantum/kvantum.kvconfig"
# Obsidian and Typora need more than a copy: the Obsidian snippet has to be
# enabled in each vault's appearance.json without disturbing the snippets
# already listed there. Skipped silently when neither app is installed.
"$repo/bin/udt-appthemes" || true
# Stage the homepage stylesheet on the dashboard host. Staged, not installed:
# /opt/homepage/config is root-owned and this script does not hold root there.
# Never fatal, and never slow: a laptop away from that network must still be
# able to install the local theme, so the connect timeout is short and any
# failure is a warning.
if command -v scp >/dev/null 2>&1; then
if scp -q -o ConnectTimeout=5 -o BatchMode=yes \
"$repo/templates/homepage/custom.css" \
homepage:hp-stage/custom.css 2>/dev/null; then
echo "homepage: staged custom.css ($scheme)"
echo " finish with: ssh homepage 'sudo cp ~/hp-stage/custom.css /opt/homepage/config/custom.css'"
else
echo "homepage: unreachable, skipped" >&2
fi
fi
# Reload what is running. Writing the configs is not enough: waybar, kitty and
# Hyprland reread on a signal, dunst and conky only on a restart, and rofi
# reads its theme per launch so it needs nothing. Each is guarded, because none
# of them is necessarily running, and a missing one is not an error.
reloaded=""
pgrep -x waybar >/dev/null 2>&1 && killall -SIGUSR2 waybar 2>/dev/null && reloaded="$reloaded waybar"
pgrep -x kitty >/dev/null 2>&1 && pkill -USR1 -x kitty 2>/dev/null && reloaded="$reloaded kitty"
command -v hyprctl >/dev/null 2>&1 && hyprctl reload >/dev/null 2>&1 && reloaded="$reloaded hyprland"
# dunst reads ~/.cache/wal/dunstrc, which pywal renders from its own template
# dir. Install the generated config into both: the template so a later `wal`
# run keeps it, and the cache file so this install reaches dunst now. Both
# still carry @ACCENT@, which udt-accent substitutes just below.
if [ -f templates/dunstrc ]; then
mkdir -p "$HOME/.config/wal/templates"
cp templates/dunstrc "$HOME/.config/wal/templates/dunstrc"
cp templates/dunstrc "$HOME/.cache/wal/dunstrc"
fi
# udt-accent regenerates the accent and restarts dunst itself. It needs a
# wallpaper to read; without one the accent keeps its current value.
if command -v udt-accent >/dev/null 2>&1 && [ -e "$HOME/.cache/wal/wpaper" ]; then
udt-accent "$HOME/.cache/wal/wpaper" >/dev/null 2>&1 && reloaded="$reloaded accent+dunst"
fi
# conky has no hot-reload and nothing respawns it, so it is restarted only if
# it was already running.
if pgrep -x conky >/dev/null 2>&1; then
pkill -x conky 2>/dev/null
sleep 0.5
(conky -d >/dev/null 2>&1 &)
reloaded="$reloaded conky"
fi
[ -n "$reloaded" ] && echo "reloaded:$reloaded"
echo "installed:"
ls -l "$target" "$HOME/bin/udt-accent"
|