Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions bin/omarchy-restart-shell
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,16 @@ while timeout 5 quickshell kill -p "$CONFIG_DIR" --any-display >/dev/null 2>&1;

# 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
launch_shell() {
hyprctl dispatch 'hl.dsp.exec_cmd("omarchy-launch-shell")' >/dev/null
}

launch_shell

for (( attempt = 0; attempt < 20; attempt++ )); do
# Plugin discovery and first-load QML compilation can take several seconds.
ready_deadline=$((SECONDS + 30))
next_launch=$((SECONDS + 2))
while (( SECONDS < ready_deadline )); 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.
Expand All @@ -86,6 +93,14 @@ for (( attempt = 0; attempt < 20; attempt++ )); do
systemctl --user try-restart 'omarchy-*-invitation.service' 2>/dev/null || true
exit 0
fi

# Quickshell can release its IPC target before its singleton lock. A launch
# in that gap exits immediately, so retry until one replacement stays up.
if (( SECONDS >= next_launch )); then
launch_shell
next_launch=$((SECONDS + 2))
fi

sleep 0.1
done

Expand Down
70 changes: 67 additions & 3 deletions test/shell.d/restart-shell-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ restart_state="$test_tmp/restart-pids"
restart_log="$test_tmp/restart.log"
restart_env_log="$test_tmp/restart-env.log"
dispatch_log="$test_tmp/dispatch.log"
dispatch_count="$test_tmp/dispatch-count"
ipc_log="$test_tmp/ipc.log"
runtime_dir="$test_tmp/runtime"
mkdir -p "$restart_root/shell" "$restart_bin" "$runtime_dir"
Expand All @@ -89,9 +90,19 @@ printf '%s\n' "$*" >>"$OMARCHY_TEST_IPC_LOG"

case "$*" in
*'shell ping')
[[ $* == *"-p $OMARCHY_TEST_SESSION_PATH/shell"* ]] &&
if [[ -n ${OMARCHY_TEST_QS_READY_AFTER:-} ]]; then
attempts=$(<"$OMARCHY_TEST_QS_READY_COUNT")
attempts=$((attempts + 1))
printf '%s\n' "$attempts" >"$OMARCHY_TEST_QS_READY_COUNT"
fi

if [[ $* == *"-p $OMARCHY_TEST_SESSION_PATH/shell"* ]] &&
grep -Fx '303' "$OMARCHY_TEST_QS_STATE" >/dev/null &&
(( ${attempts:-1} >= ${OMARCHY_TEST_QS_READY_AFTER:-1} )); then
printf 'ok\n'
else
exit 1
fi
;;
*'lock lock')
touch "$OMARCHY_TEST_QS_STATE.locked"
Expand Down Expand Up @@ -141,8 +152,15 @@ if [[ ${1:-} == "-j" && ${2:-} == "monitors" ]]; then
fi
elif [[ ${1:-} == "dispatch" && ${2:-} == hl.dsp.exec_cmd* ]]; then
printf '%s\n' "${2:-}" >>"$OMARCHY_TEST_DISPATCH_LOG"
OMARCHY_PATH="$OMARCHY_TEST_SESSION_PATH" \
env -u OMARCHY_TEST_TRANSIENT_ENV omarchy-launch-shell
if [[ -n ${OMARCHY_TEST_DISPATCH_COUNT:-} ]]; then
attempts=$(<"$OMARCHY_TEST_DISPATCH_COUNT")
attempts=$((attempts + 1))
printf '%s\n' "$attempts" >"$OMARCHY_TEST_DISPATCH_COUNT"
fi
if (( ${attempts:-1} > ${OMARCHY_TEST_DISPATCH_FAILS:-0} )); then
OMARCHY_PATH="$OMARCHY_TEST_SESSION_PATH" \
env -u OMARCHY_TEST_TRANSIENT_ENV omarchy-launch-shell
fi
printf 'ok\n'
elif [[ ${1:-} == "dispatch" ]]; then
exit 1
Expand Down Expand Up @@ -214,6 +232,52 @@ grep -F 'hl.dsp.exec_cmd("omarchy-launch-shell")' "$dispatch_log" >/dev/null ||
grep -F "ipc -n -p $restart_root/shell call -- shell ping" "$ipc_log" >/dev/null || fail "restart checks readiness in the session checkout"
pass "restart replaces duplicate shell instances from the session checkout"

# Plugin discovery and first-load QML compilation can take longer than the old
# 20-attempt readiness budget. Keep polling long enough for a slow shell that
# eventually becomes IPC-responsive.
ready_count="$test_tmp/ready-count"
printf '0\n' >"$ready_count"
printf '303\n' >"$restart_state"

PATH="$restart_bin:$PATH" \
OMARCHY_PATH="$restart_root" \
XDG_RUNTIME_DIR="$runtime_dir" \
OMARCHY_TEST_QS_STATE="$restart_state" \
OMARCHY_TEST_QS_LOG="$restart_log" \
OMARCHY_TEST_QS_ENV_LOG="$restart_env_log" \
OMARCHY_TEST_DISPATCH_LOG="$dispatch_log" \
OMARCHY_TEST_IPC_LOG="$ipc_log" \
OMARCHY_TEST_SESSION_PATH="$restart_root" \
OMARCHY_TEST_QS_READY_AFTER=25 \
OMARCHY_TEST_QS_READY_COUNT="$ready_count" \
timeout 8 "$ROOT/bin/omarchy-restart-shell" || fail "restart waits for a slow shell to become ready"

(( $(<"$ready_count") >= 25 )) || fail "restart keeps polling beyond the old readiness budget"
pass "restart waits for a slow shell to become ready"

# Quickshell can release its IPC target before releasing its singleton lock.
# When that happens the first replacement exits with "instance already
# running", so waiting for readiness alone can never recover the bar.
printf '0\n' >"$dispatch_count"
: >"$restart_state"

PATH="$restart_bin:$PATH" \
OMARCHY_PATH="$restart_root" \
XDG_RUNTIME_DIR="$runtime_dir" \
OMARCHY_TEST_QS_STATE="$restart_state" \
OMARCHY_TEST_QS_LOG="$restart_log" \
OMARCHY_TEST_QS_ENV_LOG="$restart_env_log" \
OMARCHY_TEST_DISPATCH_LOG="$dispatch_log" \
OMARCHY_TEST_DISPATCH_COUNT="$dispatch_count" \
OMARCHY_TEST_DISPATCH_FAILS=1 \
OMARCHY_TEST_IPC_LOG="$ipc_log" \
OMARCHY_TEST_SESSION_PATH="$restart_root" \
timeout 8 "$ROOT/bin/omarchy-restart-shell" || fail "restart retries a replacement that loses the singleton-lock race"

(( $(<"$dispatch_count") >= 2 )) || fail "restart retries shell launch after the first replacement exits"
[[ $(<"$restart_state") == 303 ]] || fail "retried launch leaves one fresh shell instance"
pass "restart retries a replacement that loses the singleton-lock race"

: >"$restart_log"
printf '303\n' >"$restart_state"
touch "$restart_state.locked"
Expand Down