aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-29 11:56:14 +0200
committerDanilo M. <danix@danix.xyz>2026-09-29 11:56:14 +0200
commit3bcadc0fd35cb1e077a77c847bbc044ca2fabce3 (patch)
tree9cc8bb74806eb266b0244c40858dfa0b9e9026e2
parentff7dcf3ef2338a0aef1856a33da06d1a92c2434c (diff)
downloadunified-desktop-theme-3bcadc0fd35cb1e077a77c847bbc044ca2fabce3.tar.gz
unified-desktop-theme-3bcadc0fd35cb1e077a77c847bbc044ca2fabce3.zip
feat(steps): hand pending root steps to a quickshell window
Notification balloons carried the GRUB and homepage root commands, but a balloon cannot stay open while its commands are pasted into a root shell. bin/udt-steps NAME writes a consumer's pending steps to ~/.cache/udt/steps/NAME and pokes quickshell's appearance shell, which shows one tab per file on DP-3. Empty stdin clears the tab, so producers report on every run and a consumer that caught up drops out by itself. udt-grub and udt-homepage now route through it. install.sh's SDDM block checks what is installed and reports only the steps still missing, rather than printing the full install on every run. udt-homepage remembers the last staged pair, since udt-accent now also runs on icon switches, which change neither file and would re-raise the finish steps for nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS.md22
-rwxr-xr-xbin/udt-grub48
-rwxr-xr-xbin/udt-homepage33
-rwxr-xr-xbin/udt-steps36
-rwxr-xr-xinstall.sh29
5 files changed, 125 insertions, 43 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 6218fb6..07ca2b3 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -28,7 +28,7 @@ one symlink. A real `~/.config/hypr` directory is left alone with a warning.
not a link, because GRUB reads `/boot` before `/home` is mounted, so it goes
stale on a scheme or wallpaper change. `install.sh` runs `udt-grub` in the
foreground and `udt-accent` starts it detached, and when an installed theme
-differs from the new build the root command arrives as a notification.
+differs from the new build the root command lands in the steps window.
Its panel title is `PRETTY_HOSTNAME` from `/etc/machine-info`, which
`bin/udt-machine-name` writes (root, run by the user; there is no
hostnamectl without systemd). It rewrites that one key and keeps the rest.
@@ -143,8 +143,10 @@ as `background.<ext>` with its own extension, and the finish command rewrites
change and a scheme switch without holding either up; a five second timeout
and silence when the host is down keep it harmless off that network. It stages
only: `/opt/homepage` is root-owned, and the final copy plus a `systemctl
-restart homepage` is a manual step, whose command arrives as a desktop
-notification. homepage's `settings.yaml` must keep `color: gray`, the
+restart homepage` is a manual step, whose commands land in the steps window.
+An identical stylesheet and wallpaper pair is not staged twice
+(`~/.cache/udt/homepage.staged`), so a run that changed neither, an icon
+switch for one, raises no tab. homepage's `settings.yaml` must keep `color: gray`, the
theme class the generated CSS targets. The last is
imported by `udt-accent`, which is why its accent table follows the scheme.
@@ -162,6 +164,20 @@ because both failures shipped: a role set can drift silently, and a palette
that resolves can still emit a theme rofi refuses to parse, which stops every
launcher on the desktop.
+## The steps window
+
+What this repo cannot do itself, it hands over: `bin/udt-steps NAME` takes a
+consumer's pending steps on stdin, writes them to `~/.cache/udt/steps/NAME`
+and pokes quickshell's appearance shell (`ipc call appearance steps`), which
+shows one tab per file in a window centred on DP-3. Empty stdin clears the
+tab, so each producer reports on every run and a consumer that caught up
+disappears by itself. The producers are `udt-grub`, `udt-homepage`,
+`install.sh`'s SDDM block (only the steps still missing), and the appearance
+panel's Theme tab, which pipes all of `install.sh`'s output into an `install`
+tab. A line indented two spaces is a command with a copy button; the rest is
+prose. It replaced notification balloons, which could not be kept open while
+pasting into a root shell.
+
## The accent
One colour moves: `@accent`. `bin/udt-accent` reads the current wallpaper,
diff --git a/bin/udt-grub b/bin/udt-grub
index 20fd296..273389c 100755
--- a/bin/udt-grub
+++ b/bin/udt-grub
@@ -10,8 +10,8 @@
# and a scheme switch leave the copy stale.
#
# udt-accent starts this detached on every run; install.sh runs it in the
-# foreground. A notification goes out only when an installed theme is stale:
-# a machine that never installed it has nothing to refresh.
+# foreground. The root steps go to the steps window through udt-steps, and
+# the tab clears itself once /boot matches the build.
repo="$(dirname "$(readlink -f "$0")")/.."
grub_repo="$(realpath -m "$repo/../grub-theme-udt")"
@@ -28,23 +28,33 @@ flock 9
theme=/boot/grub/themes/udt
copy="rm -rf $theme && cp -r \"$grub_repo/dist/udt\" /boot/grub/themes/"
-if diff -rq "$grub_repo/dist/udt" "$theme" >/dev/null 2>&1; then
- echo "grub theme: up to date"
-elif [ -d "$theme" ]; then
- echo "grub theme: refresh as root"
+steps() {
+ if diff -rq "$grub_repo/dist/udt" "$theme" >/dev/null 2>&1; then
+ return
+ elif [ -d "$theme" ]; then
+ echo "The GRUB theme was rebuilt. Refresh the copy in /boot as root:"
+ else
+ echo "The GRUB theme is built but not installed. As root:"
+ fi
echo " $copy"
- command -v notify-send >/dev/null 2>&1 &&
- notify-send -a udt "grub: theme rebuilt" "As root:
-$copy"
-else
- echo "grub theme: run these as root to install it"
- echo " $copy"
-fi
-grep -q "^GRUB_THEME=\"$theme/theme.txt\"" /etc/default/grub 2>/dev/null || {
- echo " # once: GRUB_THEME=\"$theme/theme.txt\" in /etc/default/grub, then"
- echo " grub-mkconfig -o /boot/grub/grub.cfg"
+ grep -q "^GRUB_THEME=\"$theme/theme.txt\"" /etc/default/grub 2>/dev/null || {
+ echo
+ echo "Once, set GRUB_THEME=\"$theme/theme.txt\" in /etc/default/grub, then:"
+ echo " grub-mkconfig -o /boot/grub/grub.cfg"
+ }
+ # The panel title falls back to the OS name until one is set.
+ [ -n "$("$repo/bin/udt-machine-name")" ] || {
+ echo
+ echo "Optional, the panel title:"
+ echo " \"$repo/bin/udt-machine-name\" \"My Machine\""
+ }
}
-# The panel title falls back to the OS name until one is set.
-[ -n "$("$repo/bin/udt-machine-name")" ] ||
- echo " # optional, the panel title: sudo \"$repo/bin/udt-machine-name\" \"My Machine\""
+
+out="$(steps)"
+echo "grub theme: ${out:-up to date}"
+# A theme that was never installed stays out of the steps window: a wallpaper
+# change would otherwise keep asking for an install nobody wanted.
+if [ -d "$theme" ] || [ -z "$out" ]; then
+ printf '%s' "$out" | "$repo/bin/udt-steps" grub
+fi
exit 0
diff --git a/bin/udt-homepage b/bin/udt-homepage
index 7be5d16..a814d16 100755
--- a/bin/udt-homepage
+++ b/bin/udt-homepage
@@ -4,13 +4,17 @@
# Licensed under the GNU General Public License v2 only.
#
# Staged, not installed: /opt/homepage is root-owned and this script does not
-# hold root there, so it copies into ~/hp-stage/ and a notification carries
-# the command that finishes the job.
+# hold root there, so it copies into ~/hp-stage/ and the steps window
+# (udt-steps) carries the commands that finish the job.
#
# udt-accent starts this detached on every run, which covers both a wallpaper
# change (wallp) and a scheme switch (install.sh). Never slow and never loud
# when the host is away: a laptop off that network gets a short connect
-# timeout and no notification, since there is nothing for the user to do.
+# timeout and no steps tab, since there is nothing for the user to do.
+#
+# udt-accent also runs for reasons that change neither file, an icon theme
+# switch for one, so what was last staged is remembered and an identical pair
+# is not staged again: the finish steps would reappear for nothing.
repo="$(dirname "$(readlink -f "$0")")/.."
css="$repo/templates/homepage/custom.css"
@@ -31,6 +35,10 @@ case "$ext" in
esac
bg="background.$ext"
+stamp="$HOME/.cache/udt/homepage.staged"
+sum="$(cat "$css" "$real" | sha256sum)"
+[ "$sum" = "$(cat "$stamp" 2>/dev/null)" ] && exit 0
+
scp "${opts[@]}" "$css" homepage:hp-stage/custom.css &&
scp "${opts[@]}" "$real" "homepage:hp-stage/$bg" || {
echo "homepage: unreachable, skipped" >&2
@@ -41,13 +49,14 @@ scp "${opts[@]}" "$css" homepage:hp-stage/custom.css &&
# is not the staging directory, so it is named through the ssh login user,
# which ssh -G resolves from the config without connecting.
stage="~$(ssh -G homepage 2>/dev/null | awk '/^user /{print $2}')/hp-stage"
-finish="cp $stage/custom.css /opt/homepage/config/
-cp $stage/$bg /opt/homepage/public/images/
-sed -i 's|/images/background\.[a-z]*|/images/$bg|' /opt/homepage/config/settings.yaml
-systemctl restart homepage"
-echo "homepage: staged, finish as root on homepage:"
-echo "$finish"
-command -v notify-send >/dev/null 2>&1 &&
- notify-send -a udt "homepage: theme staged" "As root on homepage:
-$finish"
+echo "$sum" > "$stamp"
+out="$(
+ echo "The homepage theme is staged in $stage. Finish as root on homepage:"
+ echo " cp $stage/custom.css /opt/homepage/config/"
+ echo " cp $stage/$bg /opt/homepage/public/images/"
+ echo " sed -i 's|/images/background\\.[a-z]*|/images/$bg|' /opt/homepage/config/settings.yaml"
+ echo " systemctl restart homepage"
+)"
+echo "$out"
+printf '%s' "$out" | "$repo/bin/udt-steps" homepage
exit 0
diff --git a/bin/udt-steps b/bin/udt-steps
new file mode 100755
index 0000000..da4aca0
--- /dev/null
+++ b/bin/udt-steps
@@ -0,0 +1,36 @@
+#!/bin/bash
+# udt-steps: hand a consumer's pending manual steps to the steps window.
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+# Licensed under the GNU General Public License v2 only.
+#
+# Usage: <producer> | udt-steps NAME
+#
+# stdin becomes the NAME tab of quickshell's steps window, one file per
+# consumer under ~/.cache/udt/steps/. Empty stdin clears the tab: a consumer
+# that is up to date has nothing to ask for. Either way the running shell is
+# poked over IPC, which reloads the tabs and opens the window when any remain.
+#
+# Format: a line indented two spaces is a command, and a run of them is one
+# block with its own copy button. Everything else is prose. So the root
+# commands producers already printed for a terminal read the same here.
+
+name="${1:?usage: udt-steps NAME}"
+case "$name" in
+ *[!a-z0-9-]*) echo "udt-steps: bad name '$name'" >&2; exit 2 ;;
+esac
+
+dir="$HOME/.cache/udt/steps"
+mkdir -p "$dir"
+text="$(cat)"
+if [ -z "$text" ]; then
+ rm -f "$dir/$name"
+else
+ # Atomic, so the shell never reads half a file.
+ printf '%s\n' "$text" > "$dir/.$name.tmp" && mv "$dir/.$name.tmp" "$dir/$name"
+fi
+
+repo="$(dirname "$(readlink -f "$0")")/.."
+qs_dir="$(realpath -m "$repo/../quickshell/appearance")"
+[ -f "$qs_dir/shell.qml" ] && command -v qs >/dev/null 2>&1 &&
+ timeout 5 qs -p "$qs_dir" ipc call appearance steps >/dev/null 2>&1
+exit 0
diff --git a/install.sh b/install.sh
index 33e6f0c..ff08dba 100755
--- a/install.sh
+++ b/install.sh
@@ -104,7 +104,7 @@ ln -sfn "$HOME/.cache/udt/colors.json" "$HOME/.cache/wal/colors.json"
# The rofi menus are bound by name in hypr-theme's keybindings, so they keep
# the names they had when they lived loose in ~/bin.
-for f in udt-accent udt-grub udt-palette udt-machine-name udt-powermenu.sh udt-launcher.sh qar-scrotmenu.sh; do
+for f in udt-accent udt-grub udt-steps udt-palette udt-machine-name udt-powermenu.sh udt-launcher.sh qar-scrotmenu.sh; do
ln -sfn "$repo/bin/$f" "$HOME/bin/$f"
done
@@ -327,16 +327,27 @@ if pgrep -x conky >/dev/null 2>&1; then
fi
# 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. Printed,
-# never run: /usr/share/sddm is root-owned and this script is not.
-sddm_repo="$repo/../sddm-theme-udt"
+# 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: run these as root to install or refresh it"
- echo " install -d /usr/share/sddm/themes/udt"
- echo " cp -r \"$sddm_repo/theme/.\" /usr/share/sddm/themes/udt/"
- echo " ln -sfn \"$HOME/.local/share/udt/sddm-theme.conf\" /usr/share/sddm/themes/udt/theme.conf.user"
- echo " printf '[Theme]\\nCurrent=udt\\n' > /etc/sddm.conf.d/udt.conf"
+ echo "sddm theme: ${sddm_steps:-up to date}"
+ printf '%s' "$sddm_steps" | "$repo/bin/udt-steps" sddm
fi
# GRUB. Same shape as SDDM: udt-palette has rendered theme.txt, and udt-grub