aboutsummaryrefslogtreecommitdiffstats
path: root/install.sh
blob: 849d7702b290e3242ab9d89ee256e1d5b6fc8fac (plain)
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
#!/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"

# Each consumer is a c_<name> function below, run in the order it is defined,
# with what it themes in a comment on the same line, prefixed `hardware:` for
# a device rather than an app. The list is read off the script itself, so
# adding a consumer is adding a function: quickshell's theme page asks for it
# with --list, which prints name<TAB>software|hardware<TAB>description.
listing() {
    awk '/^c_[a-z0-9_]+\(\) \{ *#/ {
        n = $1; sub(/^c_/, "", n); sub(/\(\).*/, "", n)
        d = $0; sub(/^[^#]*# */, "", d)
        k = "software"; if (sub(/^hardware: */, "", d)) k = "hardware"
        print n "\t" k "\t" d
    }' "${BASH_SOURCE[0]}"
}
consumers() { listing | cut -f1; }
if [ "${1:-}" = "--list" ]; then listing; exit 0; fi
# Names are checked before anything runs, palette generation included.
all="$(consumers)"
for c in "$@"; do
    grep -qx "$c" <<< "$all" || { echo "unknown consumer: $c (have: $(echo $all))" >&2; exit 2; }
done

# Everything this prints also becomes the install tab of quickshell's steps
# window when there is something to do, from whichever caller: the usual one
# is the appearance panel, not a terminal, so the hints are assumed wanted on
# screen. The script reruns itself as a child so the output can be both shown
# live and kept whole.
if [ -z "${UDT_INSTALL_CHILD:-}" ]; then
    log="$(mktemp)"
    trap 'rm -f "$log"' EXIT
    rc=0
    UDT_INSTALL_CHILD=1 "$repo/install.sh" "$@" 2>&1 | tee "$log" || rc=$?
    # Only a failure or a command to run (a line indented two spaces) is
    # something to do; otherwise the tab is cleared, so a login's wallpaper
    # restore, which pokes the window, does not reopen a stale recap.
    if [ "$rc" -eq 0 ] && ! grep -q '^  ' "$log"; then : > "$log"; fi
    "$repo/bin/udt-steps" install < "$log"
    exit "$rc"
fi

mkdir -p "$target" "$HOME/bin" "$HOME/.cache/udt"

# Merge an ini template into a live file key by key: every key the template
# sets is written, every other line of the live file is kept as it was. Keys
# missing from a section are appended to it, missing sections to the end. The
# live file is written through rather than replaced, so a symlink survives.
ini_merge() {
    local tmpl="$1" live="$2"
    mkdir -p "$(dirname "$live")"
    [ -e "$live" ] || : > "$live"
    awk '
    function flush(   i, p) {
        for (i = 1; i <= n; i++) {
            split(order[i], p, SUBSEP)
            if (p[1] == sec && !(order[i] in done)) { print want[order[i]]; done[order[i]] = 1 }
        }
        for (; blank > 0; blank--) print ""
    }
    FNR == NR {
        if (/^\[/) ts = $0
        else if (/^[^#;].*=/) { k = ts SUBSEP substr($0, 1, index($0, "=") - 1); want[k] = $0; order[++n] = k }
        next
    }
    /^[[:space:]]*$/ { blank++; next }
    /^\[/ { flush(); sec = $0; print; next }
    { for (; blank > 0; blank--) print "" }
    index($0, "=") && ((sec SUBSEP substr($0, 1, index($0, "=") - 1)) in want) {
        k = sec SUBSEP substr($0, 1, index($0, "=") - 1)
        print want[k]; done[k] = 1; next
    }
    { print }
    END {
        flush()
        for (i = 1; i <= n; i++) if (!(order[i] in done)) {
            split(order[i], p, SUBSEP)
            if (!(p[1] in hdr)) { print p[1]; hdr[p[1]] = 1 }
            print want[order[i]]
        }
    }' "$tmpl" "$live" > "$live.udt-tmp"
    cat "$live.udt-tmp" > "$live"
    rm -f "$live.udt-tmp"
}

# 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
# Only the summary line: the list of every generated file, 86 of them, buried
# the hints in the steps window. sed, not head: head would close the pipe
# early, and pipefail would read the SIGPIPE as a failed generation.
"$repo/bin/udt-palette" | sed -n 1p

# The rofi menus' scripts live in the sibling rofi-scripts repo, which links
# its own.
for f in udt-accent udt-grub udt-keyboard udt-ledstrip udt-bulb udt-tv udt-steps udt-palette udt-machine-name; do
    ln -sfn "$repo/bin/$f" "$HOME/bin/$f"
done

reloaded=""

# udt-accent regenerates the accent. It needs a wallpaper to read; without one
# the accent keeps its current value. Quickshell watches the palette it writes,
# so notifications and the rest of the shell follow without a restart. It runs
# before every consumer, since udt-palette above has just rewritten conky's,
# GRUB's and the homepage's accent lines with the scheme's own, and the
# hardware consumers below send what it writes. --no-helpers keeps it from
# starting those itself: each is a consumer here, so re-applying conky does
# not also repaint the LED strip.
if [ -e "$HOME/.cache/udt/wpaper" ]; then
    "$repo/bin/udt-accent" --no-helpers "$HOME/.cache/udt/wpaper" >/dev/null 2>&1 &&
        reloaded="$reloaded accent"
fi

c_rofi() { # launcher and menus
# 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 screenshot.rasi ssh.rasi; do
    ln -sfn "$repo/rofi/udt/$f" "$target/$f"
done

# launcher.rasi and header.rasi are generated rather than symlinked: their
# wallpaper path comes from the wal cache and cannot be committed. Regenerating
# them is why re-running this script after moving the repo is safe.
for f in launcher.rasi header.rasi; do
    rm -f "$target/$f"
    sed "s|@WPAPER@|$HOME/.cache/udt/wpaper|" "$repo/rofi/udt/$f" > "$target/$f"
done

# 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/udt/udt-accent.rasi" ]; then
    cp "$repo/rofi/udt/accent.rasi" "$HOME/.cache/udt/udt-accent.rasi"
fi
ln -sfn "$HOME/.cache/udt/udt-accent.rasi" "$target/accent.rasi"
}

c_hyprlock() { # lock screen
# hyprlock. The palette is scheme-level, so it is copied like the other
# generated configs. The border is wallpaper-level: seeded from the static
# fallback so the lock parses before any wallpaper is processed, then rewritten
# by udt-accent below. Both are sourced by hyprlock.conf, which carries inline
# lavender fallbacks too, so a wiped cache degrades to lavender rather than
# failing to lock.
install -Dm644 "$repo/templates/hyprlock/hyprlock-palette.conf" \
    "$HOME/.cache/udt/hyprlock-palette.conf"
if [ ! -f "$HOME/.cache/udt/hyprlock-border.conf" ]; then
    cp "$repo/templates/hyprlock/hyprlock-border.conf" \
        "$HOME/.cache/udt/hyprlock-border.conf"
fi
}

c_firefox() { # Firefox, through pywalfox
# Firefox's bridge is the one consumer that cannot follow: pywalfox hardcodes
# <cache>/wal/colors.json and honours only XDG_CACHE_HOME, which would move
# every cache on the system. So udt-accent writes the real file with the rest
# of the palette and this points pywalfox at it, leaving ~/.cache/wal holding
# nothing but the link.
mkdir -p "$HOME/.cache/wal"
ln -sfn "$HOME/.cache/udt/colors.json" "$HOME/.cache/wal/colors.json"
}

# 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.

c_kitty() { # terminal
# Written through, not replaced: current-theme.conf is a symlink into the
# kitty-conf repo, and install -D would unlink it and leave a plain file behind.
mkdir -p "$HOME/.config/kitty"
cat "$repo/templates/terminal/kitty-theme.conf" \
    > "$HOME/.config/kitty/current-theme.conf"
# kitty rereads its config on a signal. Guarded, like every reload here,
# because it is not necessarily running, and a missing one is not an error.
if pgrep -x kitty >/dev/null 2>&1 && pkill -USR1 -x kitty 2>/dev/null; then
    reloaded="$reloaded kitty"
fi
}

c_sublime() { # Sublime Text editor and UI
# Sublime Text. Copied into Packages/User, which Sublime watches, so writing it
# reaches a running editor. The template carries @ACCENT@, filled here from the
# accent udt-accent wrote above, and replaced on later wallpaper changes by
# udt-accent itself, matching the line; 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"
    accent="$(sed -n 's/.*accent: \(#[0-9a-fA-F]\{6\}\).*/\1/p' "$HOME/.cache/udt/udt-accent.rasi" 2>/dev/null)"
    [ -n "$accent" ] && sed -i "s|@ACCENT@|$accent|" "$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
}

c_kvantum() { # Qt widget style
# 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"
}

c_kde() { # KDE Frameworks apps (kcalc and friends)
# KDE Frameworks apps (kcalc and friends) bypass qt6ct twice over off Plasma:
# KStyleManager forces Breeze unless kdeglobals names a widgetStyle, and
# KColorSchemeManager applies BreezeLight unless kdeglobals names a scheme.
# kdeglobals is edited in place, two keys only, like the gsettings calls below.
install -Dm644 "$repo/templates/kde/udt.colors" \
    "$HOME/.local/share/color-schemes/udt.colors"
if command -v kwriteconfig6 >/dev/null 2>&1; then
    kwriteconfig6 --file kdeglobals --group UiSettings --key ColorScheme udt
    kwriteconfig6 --file kdeglobals --group KDE --key widgetStyle kvantum-dark
fi
}

c_gtk() { # GTK3 and GTK4
# GTK3 and GTK4. Installed under one fixed name for every scheme, like Kvantum:
# the theme name is set in gsettings and in a symlink, so a per-scheme name
# would mean rewriting both on every switch and leaving stale themes behind.
#
# Only the two GTK directories are installed. The upstream theme this is
# derived from also ships cinnamon, gnome-shell, metacity, xfwm4 and plank
# directories, none of which anything on this desktop reads.
gtk_theme="$HOME/.themes/udt"
install -Dm644 "$repo/templates/gtk/index.theme" "$gtk_theme/index.theme"
for v in 3.0 4.0; do
    src="gtk3.css"
    [ "$v" = "4.0" ] && src="gtk4.css"
    install -Dm644 "$repo/templates/gtk/$src" "$gtk_theme/gtk-$v/gtk.css"
    # gtk-dark.css is byte-identical to gtk.css upstream; apps ask for one or
    # the other depending on the prefer-dark setting, so both must exist.
    install -Dm644 "$repo/templates/gtk/$src" "$gtk_theme/gtk-$v/gtk-dark.css"
    rm -rf "$gtk_theme/gtk-$v/assets"
    mkdir -p "$gtk_theme/gtk-$v/assets"
    cp "$repo"/templates/gtk/assets/*.svg "$gtk_theme/gtk-$v/assets/"
done

# GTK3 reads the theme name from gsettings on Wayland, not settings.ini: the
# file is written too, but gsettings is what actually wins. It was once pinned
# to Breeze, silently overriding the file. Every scheme is dark, so
# prefer-dark is set too; the portal passes it to libadwaita apps.
ini_merge "$repo/templates/qt-gtk/gtk3-settings.ini" "$HOME/.config/gtk-3.0/settings.ini"
if command -v gsettings >/dev/null 2>&1; then
    gsettings set org.gnome.desktop.interface gtk-theme 'udt' 2>/dev/null || true
    gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null || true
fi

# GTK4 ignores gtk-theme-name entirely; its stylesheet is imported by
# ~/.config/gtk-4.0/gtk.css through this symlink. gtk.css is seeded when
# missing, and otherwise only gets the import prepended if it lacks one.
mkdir -p "$HOME/.config/gtk-4.0"
ln -sfn "$gtk_theme/gtk-4.0" "$HOME/.config/gtk-4.0/theme"
gtk4_css="$HOME/.config/gtk-4.0/gtk.css"
if [ ! -f "$gtk4_css" ]; then
    cp "$repo/templates/qt-gtk/gtk4-gtk.css" "$gtk4_css"
elif ! grep -q '@import url("theme/gtk.css")' "$gtk4_css"; then
    { echo '@import url("theme/gtk.css");'; cat "$gtk4_css"; } > "$gtk4_css.udt-tmp"
    cat "$gtk4_css.udt-tmp" > "$gtk4_css"
    rm -f "$gtk4_css.udt-tmp"
fi
}

c_qt() { # qt5ct and qt6ct
# qt5ct and qt6ct hand Qt apps to Kvantum. Theme keys only: both files also
# hold settings and personal lists that are none of this repo's business.
ini_merge "$repo/templates/qt-gtk/qt5ct.conf" "$HOME/.config/qt5ct/qt5ct.conf"
ini_merge "$repo/templates/qt-gtk/qt6ct.conf" "$HOME/.config/qt6ct/qt6ct.conf"
}

c_nvim() { # neovim colorscheme
# neovim. The colorscheme is generated, and selected with one line in
# init.vim, rewritten only if it names something else. Like Sublime's
# color_scheme key: the rest of init.vim is none of this repo's business.
mkdir -p "$HOME/.config/nvim/colors"
ln -sfn "$repo/templates/nvim/udt.lua" "$HOME/.config/nvim/colors/udt.lua"
nvim_init="$HOME/.config/nvim/init.vim"
if [ -f "$nvim_init" ] && ! grep -qx 'colorscheme udt' "$nvim_init"; then
    sed -i 's/^colorscheme .*/colorscheme udt/' "$nvim_init"
    grep -qx 'colorscheme udt' "$nvim_init" && echo "nvim: colorscheme switched to udt"
fi
}

c_apps() { # Obsidian, Typora, btop, Joplin, Feishin
# 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
}

c_conky() { # desktop dashboard
# conky rereads its config when it is written in place, but the links below
# have to exist for it to find the file at all, and the restart further down
# covers a first install and a config replaced by rename.
#
# The Lua dashboard lives in its own repo; udt-palette renders its colours into
# conky.conf there. Both the config and the Lua it loads are linked, since
# lua_load points at ~/.config/conky/dashboard.lua.
# A sibling checkout, like waybar-theme-udt, and skipped when absent.
conky_repo="$(realpath -m "$repo/../conky-theme-udt")"
if [ -d "$conky_repo" ]; then
    mkdir -p "$HOME/.config/conky"
    # bin/ holds the weather fetch script, which conky.text runs on an execi.
    for f in conky.conf dashboard.lua lib widgets bin; do
        ln -sfn "$conky_repo/$f" "$HOME/.config/conky/$f"
    done
fi

# conky reloads an in-place write on its own, but its inotify watch is on the
# inode, so a first install or a config replaced by rename goes unseen, and a
# widget module stays cached until a restart. 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
    # Name the config explicitly rather than relying on conky's default lookup.
    # It resolves to the same file, but the dashboard's own docs tell the user
    # to launch it this way from autostart.lua, and two launch paths that
    # disagree in form are how they later drift apart in effect.
    # `[workspace special:dash silent]`: without it the dashboard's own window
    # rule puts it on that workspace but cannot suppress the switch, so every
    # reinstall pops the dashboard open over whatever is on screen. Matches
    # how hypr-theme's autostart launches it.
    # Launched THROUGH hyprctl so the workspace rule applies; falls back to a
    # plain exec where Hyprland is not running, since conky itself does not
    # need it.
    if command -v hyprctl >/dev/null 2>&1 && [ -n "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]; then
        hyprctl dispatch 'hl.dsp.exec_cmd("[workspace special:dash silent] conky -c ~/.config/conky/conky.conf -d")' >/dev/null 2>&1
    else
        (conky -c "$HOME/.config/conky/conky.conf" -d >/dev/null 2>&1 &)
    fi
    reloaded="$reloaded conky"
fi
}

c_hypr() { # Hyprland, window border
# Hyprland. hypr-theme is the whole of ~/.config/hypr, linked as one
# directory, and it reads the border this repo generates. A real directory
# there is someone's config: it is left alone rather than replaced.
hypr_repo="$(realpath -m "$repo/../hypr-theme")"
if [ -d "$hypr_repo" ]; then
    if [ -L "$HOME/.config/hypr" ] || [ ! -e "$HOME/.config/hypr" ]; then
        ln -sfn "$hypr_repo" "$HOME/.config/hypr"
    elif [ "$(realpath "$HOME/.config/hypr")" != "$hypr_repo" ]; then
        echo "hypr: ~/.config/hypr is a real directory, left alone; move it aside to link hypr-theme" >&2
    fi
fi
command -v hyprctl >/dev/null 2>&1 && hyprctl reload >/dev/null 2>&1 && reloaded="$reloaded hyprland"
}

c_sddm() { # login greeter, root steps
# SDDM. The theme code is root-installed, but the palette the greeter reads has
# to stay live, so the root block below symlinks the generated file in. Never
# run here: /usr/share/sddm is root-owned and this script is not. Only the
# steps still needed are printed and handed to the steps window; none clears
# its tab.
sddm_repo="$(realpath -m "$repo/../sddm-theme-udt")"
if [ -d "$sddm_repo/theme" ]; then
    sddm=/usr/share/sddm/themes/udt
    sddm_conf="$HOME/.local/share/udt/sddm-theme.conf"
    sddm_steps="$(
        diff -rq -x theme.conf.user "$sddm_repo/theme" "$sddm" >/dev/null 2>&1 ||
            printf '  install -d %s\n  cp -r "%s/theme/." %s/\n' "$sddm" "$sddm_repo" "$sddm"
        [ "$(readlink "$sddm/theme.conf.user")" = "$sddm_conf" ] ||
            printf '  ln -sfn "%s" %s/theme.conf.user\n' "$sddm_conf" "$sddm"
        grep -qx 'Current=udt' /etc/sddm.conf.d/udt.conf 2>/dev/null ||
            printf "  printf '[Theme]\\\\nCurrent=udt\\\\n' > /etc/sddm.conf.d/udt.conf\n"
    )"
    [ -n "$sddm_steps" ] && sddm_steps="The SDDM theme is out of date. As root:
$sddm_steps"
    echo
    echo "sddm theme: ${sddm_steps:-up to date}"
    printf '%s' "$sddm_steps" | "$repo/bin/udt-steps" sddm
fi
}

c_grub() { # boot menu, root steps
# GRUB. Same shape as SDDM: udt-palette has rendered theme.txt, and udt-grub
# builds the theme as the user and prints the root half. udt-accent also runs
# it, detached, so a wallpaper change alone rebuilds it too.
echo
"$repo/bin/udt-grub"
}

# The rest are what udt-accent starts detached on a wallpaper change. Here they
# run in the foreground, so a re-apply reports how it went; each is a no-op
# without its device or its config, and none of them is fatal to the install.
c_homepage() { # dashboard host, staged for a root copy
"$repo/bin/udt-homepage" || true
}

c_keyboard() { # hardware: Ajazz AK820 Pro lighting
"$repo/bin/udt-keyboard" || true
}

c_ledstrip() { # hardware: Tuya LED strip
"$repo/bin/udt-ledstrip" || true
}

c_bulb() { # hardware: IKEA TRADFRI colour bulbs
"$repo/bin/udt-bulb" || true
}

c_tv() { # hardware: TV launcher focus highlight
"$repo/bin/udt-tv" || true
}

# No arguments installs every consumer; names install only those, which is how
# the theme page re-applies one.
for c in ${*:-$all}; do "c_$c"; done

echo
[ -n "$reloaded" ] && echo "reloaded:$reloaded"
echo "Qt, GTK, btop and Joplin only reread a theme when they restart; neovim needs :colorscheme udt."