Files
omarchycn/bin/omarchy-restart-shell
T
2c593dbbaa Switch back to the packaged quickshell now that 0.3.1 kills synchronously (#7769)
* Switch back to the packaged quickshell now that 0.3.1 kills synchronously

Omarchy shipped the quickshell-git build for a single fix: 0.3.0's `kill` returned before the instance had exited, so the kill loop in omarchy-restart-shell could race a dying shell. Upstream 0.3.1 ships that fix, which makes extra/quickshell the better package to be on again — signed, versioned, and not rebuilt from a moving branch on every update.

The migration swaps unconditionally instead of first checking which version the mirror offers. A machine left holding quickshell-git while the shipped package list names quickshell has no way to reconcile the two: omarchy-reinstall-pkgs installs that list with --needed, which does not skip a name that is not installed, and the conflict it then walks into has no answer under --noconfirm. A mirror that is briefly behind installs 0.3.0 instead and the next upgrade carries it to 0.3.1, which is much the cheaper way to be wrong.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Codex XHigh <codex@openai.com>

* Drop the quickshell version note from the shell restart loop

The comment qualified the kill loop as needing 0.3.1 or newer, but omarchy-restart-shell ships in the same package upgrade that brings quickshell along, so a machine running this code already has the version the loop depends on. The caveat could never be false where it was read, which left it as version archaeology rather than something the code could not say for itself.

🤖 Generated by Opus 5 in Claude Code.

---------

Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-22 15:02:12 +02:00

94 lines
4.3 KiB
Bash
Executable File

#!/bin/bash
# omarchy:summary=Restart the Omarchy shell
# omarchy:examples=omarchy restart shell
# A caller opened after dev link/unlink may disagree with the still-running
# desktop. The user manager receives Hyprland's environment at session start.
session_omarchy_path=$(systemctl --user show-environment 2>/dev/null | sed -n 's/^OMARCHY_PATH=//p' | tail -n 1)
: "${session_omarchy_path:=$OMARCHY_PATH}"
CONFIG_DIR="$session_omarchy_path/shell"
[[ -f $CONFIG_DIR/shell.qml ]] || { echo "Omarchy shell config not found: $CONFIG_DIR" >&2; exit 1; }
# Allow running from outside the session (e.g. over ssh) by deriving the
# Hyprland instance signature from the newest instance runtime dir.
if [[ -z ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
hypr_dir=$(find "${XDG_RUNTIME_DIR:-/run/user/$UID}/hypr" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -n 1 | cut -d' ' -f2-)
[[ -n $hypr_dir ]] && export HYPRLAND_INSTANCE_SIGNATURE=${hypr_dir##*/}
fi
# Restarting a live lock client would kill the lock screen and strand the
# session behind Hyprland's failsafe. But a LOCK session without an active
# locker — the shell died, or its crash handler re-execed a fresh instance
# that holds no lock — sits in that failsafe with no way to authenticate,
# and a restart plus re-lock is the only way back in without a reboot. So
# ask the lock service rather than merely pinging the shell: only a locker
# that reports the lock secure or in progress is worth preserving.
relock=0
if omarchy-hyprland-session-locked; then
locking=$(OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock status 2>/dev/null |
jq -r '.secure or .requested' 2>/dev/null)
if [[ $locking == "true" ]]; then
echo "Refusing to restart Omarchy shell while the session is locked." >&2
exit 1
fi
relock=1
fi
# The lock plugin loads asynchronously, so a fresh shell answers ping before
# it can lock, and may even refuse early lock requests while its plugins or
# PAM config are still loading. Mirror omarchy-system-sleep-lock: request the
# lock and poll until the session reports secure, re-requesting as needed, so
# recovery never claims success while the failsafe is still up. The deadline
# is generous because slow plugin discovery delays the lock IPC target.
relock_session() {
local state deadline=$((SECONDS + 30))
while (( SECONDS < deadline )); do
state=$(OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock status 2>/dev/null |
jq -r 'if .secure == true then "secure" elif .requested == true then "locking" else "idle" end' 2>/dev/null)
case $state in
secure) return 0 ;;
locking) ;;
*) OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock lock >/dev/null 2>&1 ;;
esac
sleep 0.1
done
return 1
}
# Each kill stops the oldest matching instance and only returns once it has
# fully exited, so the no-duplicate launch below can't race a dying shell.
while timeout 5 quickshell kill -p "$CONFIG_DIR" --any-display >/dev/null 2>&1; do :; done
# Spawn from Hyprland so the shell inherits the canonical session environment,
# not transient variables from a terminal, SSH connection, or development tool.
hyprctl dispatch 'hl.dsp.exec_cmd("omarchy-launch-shell")' >/dev/null
for (( attempt = 0; attempt < 20; attempt++ )); do
if OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell shell ping >/dev/null 2>&1; then
# The session stays compositor-locked after the old lock client died, so
# re-acquire the lock and let the user authenticate out of it.
if (( relock )) && ! relock_session; then
echo "Omarchy shell restarted, but the session lock was not re-secured." >&2
exit 1
fi
# Invitation toasts (like Voxtype/fingerprint setup) die with the old
# shell, and their notify-send waiters hang forever: the dying server
# never emits NotificationClosed. A still-running omarchy-*-invitation
# unit is therefore an unanswered invitation — re-run it so its toast
# reappears on the new shell. Answered invitations have already exited
# and been collected, so the glob no longer matches them.
systemctl --user try-restart 'omarchy-*-invitation.service' 2>/dev/null || true
exit 0
fi
sleep 0.1
done
echo "Omarchy shell did not become ready after restart." >&2
exit 1