mirror of
https://github.com/omacom/omarchy.git
synced 2026-09-28 06:13:13 +08:00
Add opt-in theme sync to herdr machines (#13430)
* Add a theme-set sample hook that mirrors themes to herdr machines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Mirror theme changes to herdr machines by default Replaces the sample hook with omarchy-theme-sync, which omarchy-theme-set starts detached after every theme change, and a theme-sync-off toggle that stops a machine from both sending and receiving. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Rename omarchy-theme-sync to omarchy-theme-set-herdr-machines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Label the toggle Herdr Theme Sync and extract its menu guard Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Answer the Herdr Theme Sync menu guard from omarchy-theme-set-herdr-machines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sync herdr machines one at a time and drop the dry run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check the theme sync toggle under the lock and stop special-casing this machine Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Make herdr theme sync opt-in Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
387fcf599a
commit
616feda103
@@ -441,6 +441,9 @@ if [[ $THEME_HEADLESS != "1" ]]; then
|
||||
# Call hook on theme set
|
||||
omarchy-hook theme-set "$THEME_NAME" >/dev/null
|
||||
|
||||
# Mirror the theme to herdr machines. SSH can take a while, so this never holds up the theme change.
|
||||
setsid omarchy-theme-set-herdr-machines >>"$HOME/.local/state/omarchy/theme-set-herdr-machines.log" 2>&1 </dev/null &
|
||||
|
||||
# Warm the background selector's thumbnails. The theme picker keeps its own
|
||||
# rows in the shell and refreshes them each time it opens.
|
||||
omarchy-theme-bg-cache >/dev/null 2>&1 &
|
||||
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Mirror the current theme to your herdr machines that run Omarchy
|
||||
# omarchy:args=[--available]
|
||||
# omarchy:hidden=true
|
||||
|
||||
# omarchy-theme-set runs this in the background after every theme change. Sync is off until
|
||||
# the herdr-theme-sync toggle is turned on, and a machine only accepts themes with it on too.
|
||||
|
||||
# Columns: id, label, SSH target, session, state.
|
||||
# This machine may be listed too, and it is already on the theme, so it needs no special case.
|
||||
targets=$(herdr machine list 2>/dev/null | awk -F '\t' '$5 == "enabled" { print $3 }')
|
||||
|
||||
# The menu offers the toggle whenever there is a machine to sync to, whether or not sync is on.
|
||||
if [[ $1 == "--available" ]]; then
|
||||
[[ -n $targets ]]
|
||||
exit
|
||||
fi
|
||||
|
||||
# Serialize runs and send whichever theme is current once the lock is held,
|
||||
# so a slow earlier run can never overwrite a later theme change.
|
||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/omarchy-theme-set-herdr-machines.lock"
|
||||
flock 9
|
||||
|
||||
# A theme that arrived from another machine stops here, so machines never echo it back.
|
||||
# The toggle is checked once the lock is held, so turning sync off also stops a queued run.
|
||||
if [[ -n $OMARCHY_THEME_SYNC_FROM ]] || ! omarchy-toggle-enabled herdr-theme-sync; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
theme=$(cat "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null)
|
||||
|
||||
# Runs on the remote machine under a login shell, so OMARCHY_PATH and PATH are set there.
|
||||
# SSH carries no desktop session, so the environment comes from the live Hyprland instance.
|
||||
# The flag is read directly, so an Omarchy that predates theme sync simply counts as off.
|
||||
# Braces make bash read the whole script before running it, so nothing it runs can consume the rest.
|
||||
read -r -d '' REMOTE_SCRIPT <<'EOF'
|
||||
{
|
||||
if [[ ! -f ~/.local/state/omarchy/toggles/herdr-theme-sync ]]; then
|
||||
echo "theme sync is off"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [[ $(cat ~/.local/state/omarchy/current/theme.name 2>/dev/null) == "$theme" ]]; then
|
||||
echo "already on $theme"
|
||||
exit
|
||||
fi
|
||||
|
||||
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
||||
instance=$(hyprctl instances -j 2>/dev/null | jq -r 'max_by(.time) // empty | "\(.instance) \(.wl_socket)"')
|
||||
if [[ -z $instance ]]; then
|
||||
echo "no Hyprland session"
|
||||
exit
|
||||
fi
|
||||
|
||||
read -r HYPRLAND_INSTANCE_SIGNATURE WAYLAND_DISPLAY <<<"$instance"
|
||||
export HYPRLAND_INSTANCE_SIGNATURE WAYLAND_DISPLAY
|
||||
export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
|
||||
export OMARCHY_THEME_SYNC_FROM="$from"
|
||||
|
||||
if omarchy theme set "$theme" >/dev/null 2>&1; then
|
||||
echo "set $theme"
|
||||
else
|
||||
echo "failed to set $theme"
|
||||
fi
|
||||
} </dev/null
|
||||
EOF
|
||||
|
||||
# One machine at a time, so an SSH agent like 1Password asks for approval at most once.
|
||||
# SSH joins its arguments into a shell command, so the theme travels on stdin as a quoted assignment.
|
||||
for target in $targets; do
|
||||
result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" |
|
||||
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 "$target" bash -l -s 2>&1 | tail -n 1)
|
||||
echo "$(date '+%F %T') $target: ${result:-unreachable}"
|
||||
done
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Toggle mirroring theme changes to and from your herdr machines
|
||||
|
||||
omarchy-toggle herdr-theme-sync
|
||||
|
||||
if omarchy-toggle-enabled herdr-theme-sync; then
|
||||
omarchy-notification-send -g "Herdr theme sync enabled"
|
||||
else
|
||||
omarchy-notification-send -g "Herdr theme sync disabled"
|
||||
fi
|
||||
@@ -91,6 +91,7 @@
|
||||
"trigger.toggle.notifications": {"icon":"","label":"Notifications","action":"omarchy-toggle-notification-silencing"},
|
||||
"trigger.toggle.crash-capture": {"icon":"","label":"Crash Capture","action":"omarchy-toggle-crash-capture"},
|
||||
"trigger.toggle.screensaver": {"icon":"","label":"Screensaver","action":"omarchy-toggle-screensaver"},
|
||||
"trigger.toggle.theme-sync": {"icon":"","label":"Herdr Theme Sync","when":"omarchy-theme-set-herdr-machines --available","action":"omarchy-toggle-theme-sync"},
|
||||
"trigger.toggle.nightlight": {"icon":"","label":"Nightlight","action":"omarchy-toggle-nightlight"},
|
||||
"trigger.toggle.top-bar": {"icon":"","label":"Menu Bar","action":"omarchy-toggle-bar"},
|
||||
"trigger.toggle.battery-percentage": {"icon":"","label":"Battery Percentage","when":"omarchy-hw-laptop","action":"omarchy-shell omarchy.power togglePercentage"},
|
||||
|
||||
@@ -45,6 +45,8 @@ Making a new app follow theme changes means adding its restart/retint command
|
||||
to that list. Runs serialize on a `flock`, so scripted theme changes queue
|
||||
instead of racing.
|
||||
|
||||
Last, it starts `omarchy-theme-set-herdr-machines` detached. That command sets the same theme on every enabled `herdr machine list` target that runs Omarchy, over SSH inside the remote's live Hyprland session, and logs each machine's result to `~/.local/state/omarchy/theme-set-herdr-machines.log`. Sync is off by default. The `herdr-theme-sync` toggle turns it on, and a machine only sends and accepts themes while it is on. A mirrored change carries `OMARCHY_THEME_SYNC_FROM`, so the receiving machine never sends it on.
|
||||
|
||||
## What an installed theme may not ship
|
||||
|
||||
`themes/<name>/` in this repo is Omarchy's own code and is trusted. So is a theme the user wrote by hand in `~/.config/omarchy/themes/<name>/`: it is their machine and their file, and both stage in full.
|
||||
|
||||
@@ -15,6 +15,7 @@ From the terminal, the same switches are `omarchy toggle <thing>`. Run `omarchy
|
||||
| Stay awake (no idle lock) | `Super + Ctrl + I` | `omarchy toggle idle` |
|
||||
| Crash capture | — | `omarchy toggle crash-capture` |
|
||||
| Screensaver | — | `omarchy toggle screensaver` |
|
||||
| [Herdr](https://herdr.dev) theme sync | — | `omarchy toggle theme sync` |
|
||||
| Menu bar | `Super + Shift + Space` | `omarchy toggle bar` |
|
||||
| Touchpad | `XF86TouchpadToggle` | `omarchy toggle touchpad` |
|
||||
| Touchscreen | — | `omarchy toggle touchscreen` |
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
require_command jq
|
||||
|
||||
SYNC_TEST=$(mktemp -d)
|
||||
trap 'rm -rf "$SYNC_TEST"' EXIT
|
||||
export SYNC_TEST
|
||||
|
||||
local_home="$SYNC_TEST/home"
|
||||
stub_bin="$SYNC_TEST/bin"
|
||||
remote_bin="$SYNC_TEST/remote-bin"
|
||||
mkdir -p "$local_home/.local/state/omarchy/current" "$stub_bin" "$remote_bin" "$SYNC_TEST/run"
|
||||
|
||||
# herdr lists one unreachable machine first, this machine, and a disabled one.
|
||||
cat >"$stub_bin/herdr" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf '%s\t%s\t%s\t%s\t%s\n' \
|
||||
1 down down default enabled \
|
||||
2 alpha alpha default enabled \
|
||||
3 beta beta default enabled \
|
||||
4 gamma gamma default enabled \
|
||||
5 local-box local-box default enabled \
|
||||
6 retired retired default disabled
|
||||
EOF
|
||||
|
||||
cat >"$stub_bin/hostname" <<'EOF'
|
||||
#!/bin/bash
|
||||
echo local-box
|
||||
EOF
|
||||
|
||||
# ssh runs the script it receives against a fake home for that machine, as the remote would.
|
||||
cat >"$stub_bin/ssh" <<'EOF'
|
||||
#!/bin/bash
|
||||
while [[ $1 == "-o" ]]; do
|
||||
shift 2
|
||||
done
|
||||
target=$1
|
||||
echo "$target" >>"$SYNC_TEST/ssh-calls"
|
||||
remote="$SYNC_TEST/remotes/$target"
|
||||
if [[ ! -d $remote ]]; then
|
||||
echo "ssh: connect to host $target port 22: Connection refused" >&2
|
||||
exit 255
|
||||
fi
|
||||
HOME="$remote" PATH="$SYNC_TEST/remote-bin:/usr/bin:/bin" bash -s
|
||||
EOF
|
||||
|
||||
cat >"$remote_bin/omarchy" <<'EOF'
|
||||
#!/bin/bash
|
||||
echo "$* from=$OMARCHY_THEME_SYNC_FROM session=$WAYLAND_DISPLAY" >>"$HOME/set.log"
|
||||
EOF
|
||||
|
||||
cat >"$remote_bin/hyprctl" <<'EOF'
|
||||
#!/bin/bash
|
||||
echo '[{"instance":"old","time":1,"wl_socket":"wayland-0"},{"instance":"live","time":2,"wl_socket":"wayland-1"}]'
|
||||
EOF
|
||||
|
||||
chmod +x "$stub_bin"/* "$remote_bin"/*
|
||||
|
||||
reset_remotes() {
|
||||
rm -rf "$SYNC_TEST/remotes" "$SYNC_TEST/ssh-calls"
|
||||
local machine
|
||||
for machine in alpha beta gamma local-box; do
|
||||
mkdir -p "$SYNC_TEST/remotes/$machine/.local/state/omarchy/current"
|
||||
echo "tokyo-night" >"$SYNC_TEST/remotes/$machine/.local/state/omarchy/current/theme.name"
|
||||
mkdir -p "$SYNC_TEST/remotes/$machine/.local/state/omarchy/toggles"
|
||||
touch "$SYNC_TEST/remotes/$machine/.local/state/omarchy/toggles/herdr-theme-sync"
|
||||
done
|
||||
}
|
||||
|
||||
local_toggle="$local_home/.local/state/omarchy/toggles/herdr-theme-sync"
|
||||
|
||||
set_local_theme() {
|
||||
echo "$1" >"$local_home/.local/state/omarchy/current/theme.name"
|
||||
}
|
||||
|
||||
run_sync() {
|
||||
HOME="$local_home" XDG_RUNTIME_DIR="$SYNC_TEST/run" PATH="$stub_bin:$ROOT/bin:$PATH" omarchy-theme-set-herdr-machines "$@"
|
||||
}
|
||||
|
||||
set_log() {
|
||||
cat "$SYNC_TEST/remotes/$1/set.log" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Sync is off until it is turned on.
|
||||
reset_remotes
|
||||
set_local_theme lumon
|
||||
run_sync >/dev/null
|
||||
[[ ! -e $SYNC_TEST/ssh-calls ]] || fail "theme sync is off by default"
|
||||
pass "theme sync is off by default"
|
||||
mkdir -p "${local_toggle%/*}"
|
||||
touch "$local_toggle"
|
||||
|
||||
# Syncs every enabled machine except this one, skipping machines already on the theme.
|
||||
reset_remotes
|
||||
set_local_theme lumon
|
||||
echo "lumon" >"$SYNC_TEST/remotes/beta/.local/state/omarchy/current/theme.name"
|
||||
echo "lumon" >"$SYNC_TEST/remotes/local-box/.local/state/omarchy/current/theme.name"
|
||||
output=$(run_sync)
|
||||
[[ $(set_log alpha) == "theme set lumon from=local-box session=wayland-1" ]] || fail "sets the theme inside the newest Hyprland session"
|
||||
pass "sets the theme inside the newest Hyprland session"
|
||||
[[ -z $(set_log beta) && $output == *"beta: already on lumon"* ]] || fail "skips a machine already on the theme"
|
||||
pass "skips a machine already on the theme"
|
||||
! grep -qx retired "$SYNC_TEST/ssh-calls" || fail "skips disabled machines"
|
||||
pass "skips disabled machines"
|
||||
[[ -z $(set_log local-box) && $output == *"local-box: already on lumon"* ]] || fail "leaves this machine alone when it is listed"
|
||||
pass "leaves this machine alone when it is listed"
|
||||
[[ $output == *"down: ssh: connect to host down"* ]] || fail "logs an unreachable machine"
|
||||
pass "logs an unreachable machine"
|
||||
|
||||
# Connects one machine at a time, so an SSH agent asks for approval at most once.
|
||||
[[ $(paste -sd ' ' "$SYNC_TEST/ssh-calls") == "down alpha beta gamma local-box" ]] || fail "connects to machines one at a time in order"
|
||||
pass "connects to machines one at a time in order"
|
||||
|
||||
# A machine with the toggle off refuses themes from other machines.
|
||||
reset_remotes
|
||||
rm "$SYNC_TEST/remotes/gamma/.local/state/omarchy/toggles/herdr-theme-sync"
|
||||
output=$(run_sync)
|
||||
[[ -z $(set_log gamma) && $output == *"gamma: theme sync is off"* ]] || fail "a remote with theme sync off keeps its theme"
|
||||
pass "a remote with theme sync off keeps its theme"
|
||||
|
||||
# A machine with the toggle off sends nothing.
|
||||
reset_remotes
|
||||
rm "$local_toggle"
|
||||
run_sync >/dev/null
|
||||
[[ ! -e $SYNC_TEST/ssh-calls ]] || fail "theme sync off stops sending"
|
||||
pass "theme sync off stops sending"
|
||||
touch "$local_toggle"
|
||||
|
||||
# Turning sync off also stops a run that is still waiting for an earlier one to finish.
|
||||
reset_remotes
|
||||
exec 8>"$SYNC_TEST/run/omarchy-theme-set-herdr-machines.lock"
|
||||
flock 8
|
||||
run_sync >/dev/null &
|
||||
queued=$!
|
||||
sleep 0.5
|
||||
rm "$local_toggle"
|
||||
flock -u 8
|
||||
wait $queued
|
||||
[[ ! -e $SYNC_TEST/ssh-calls ]] || fail "turning sync off stops a queued run"
|
||||
pass "turning sync off stops a queued run"
|
||||
touch "$local_toggle"
|
||||
|
||||
# A theme that arrived from another machine is never sent on.
|
||||
reset_remotes
|
||||
OMARCHY_THEME_SYNC_FROM=elsewhere run_sync >/dev/null
|
||||
[[ ! -e $SYNC_TEST/ssh-calls ]] || fail "a mirrored theme change is not mirrored again"
|
||||
pass "a mirrored theme change is not mirrored again"
|
||||
|
||||
# The theme name reaches the remote as data, never as shell code.
|
||||
reset_remotes
|
||||
set_local_theme 'demo$(touch "$HOME/injected")'
|
||||
run_sync >/dev/null
|
||||
[[ ! -e $SYNC_TEST/remotes/alpha/injected ]] || fail "a theme name cannot run commands on the remote"
|
||||
[[ $(set_log alpha) == 'theme set demo$(touch "$HOME/injected") from=local-box session=wayland-1' ]] || fail "a theme name reaches the remote verbatim"
|
||||
pass "a theme name reaches the remote verbatim without running commands"
|
||||
|
||||
# The menu shows Herdr Theme Sync whenever herdr has an enabled machine, with sync on or off,
|
||||
# so the toggle is there to turn it on.
|
||||
rm "$local_toggle"
|
||||
run_sync --available || fail "an enabled herdr machine makes theme sync available"
|
||||
pass "an enabled herdr machine makes theme sync available while it is off"
|
||||
printf '#!/bin/bash\nprintf "1\\tretired\\tretired\\tdefault\\tdisabled\\n"\n' >"$stub_bin/herdr"
|
||||
! run_sync --available || fail "only disabled herdr machines make theme sync unavailable"
|
||||
pass "only disabled herdr machines make theme sync unavailable"
|
||||
Reference in New Issue
Block a user