From 05e75bc41ea6e6d8fee323b463f53dc7e41f3b96 Mon Sep 17 00:00:00 2001 From: rain Date: Tue, 23 Jun 2026 21:51:09 -0400 Subject: [PATCH] sway: extract start-swayidle.sh as single source of truth (fix caffeine) caffeine.sh click was silently breaking idle lock. Two bugs that compounded: 1. caffeine.sh inlined its own swayidle command (with swaylock -f -i , not lock-fancy.sh), drifted out of sync with sway/config which used lock-fancy.sh. Toggle off would restart swayidle with a different lock command than the rest of the session. 2. WAYLAND_DISPLAY isn't set in waybar's on-click context. When caffeine.sh called 'swaymsg output * power off' (part of the restart command) and later called swaylock / grim (via lock-fancy), those failed silently. swayidle itself can run without WAYLAND_DISPLAY but it can't actually monitor input/output, so it exits immediately. Result: user clicks caffeine off, flag clears, icon goes back to 'inactive', but auto-lock is silently dead. The user thinks they turned caffeine off. They didn't. They're just unprotected. Fix: - new start-swayidle.sh holds the canonical swayidle command - sway/config: exec $HOME/.config/sway/start-swayidle.sh - caffeine.sh: killall swayidle; start-swayidle.sh & - start-swayidle.sh probes /proc/*/environ for a Wayland client's env (mako always has it) and exports WAYLAND_DISPLAY / DBUS_SESSION_BUS_ADDRESS / XDG_RUNTIME_DIR / DISPLAY if missing before exec'ing swayidle Single source of truth + env restoration = click works on every box that has the same Wayland-capable process tree (mako, swaybar, etc.), no matter who/what is calling the script. Verified on tadbit: pre-fix toggle off killed swayidle permanently. Post-fix: toggle off restarts swayidle successfully, env inherited from mako's /proc/PID/environ. --- dot_config/sway/caffeine.sh | 22 ++++++++---- dot_config/sway/config | 9 +++-- dot_config/sway/start-swayidle.sh | 57 +++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 12 deletions(-) create mode 100755 dot_config/sway/start-swayidle.sh diff --git a/dot_config/sway/caffeine.sh b/dot_config/sway/caffeine.sh index bf68488..cfec65e 100755 --- a/dot_config/sway/caffeine.sh +++ b/dot_config/sway/caffeine.sh @@ -1,20 +1,28 @@ #!/bin/bash # caffeine.sh — toggle caffeine mode by killing/restarting swayidle # When ON: kill swayidle (no idle lock/screen-off) -# When OFF: restart swayidle with normal timeouts +# When OFF: restart swayidle with the same timeouts as the Sway session +# +# The "restart" path calls start-swayidle.sh, which is the SAME script +# Sway's config uses (`exec $HOME/.config/sway/start-swayidle.sh`). +# Single source of truth: if you change timeouts, change them in +# start-swayidle.sh and both the session start AND the toggle share them. +# (Earlier this script hardcoded the swayidle command itself, and it +# drifted out of sync with sway/config — toggling caffeine off would +# silently change your lock command and timeout values.) +# +# start-swayidle.sh also handles env restoration: when invoked from +# waybar's on-click, WAYLAND_DISPLAY isn't set, and swayidle silently +# exits without it (which means idle lock is silently dead — the +# worst failure mode because the bar still shows "caffeine off"). -WALLPAPER="$HOME/.local/share/wallpapers/teach-invaders-gruvbox.png" FLAG="/tmp/caffeine-inhibit" if [ -f "$FLAG" ]; then # Turn OFF caffeine — restart swayidle rm -f "$FLAG" killall swayidle 2>/dev/null - swayidle -w \ - timeout 300 "swaylock -f -i $WALLPAPER" \ - timeout 600 'swaymsg "output * power off"' \ - resume 'swaymsg "output * power on"' \ - before-sleep "swaylock -f -i $WALLPAPER" & + "$HOME/.config/sway/start-swayidle.sh" & notify-send -t 2000 "☕ Caffeine OFF" "Idle sleep enabled" 2>/dev/null else # Turn ON caffeine — kill swayidle diff --git a/dot_config/sway/config b/dot_config/sway/config index 047b750..52d1176 100644 --- a/dot_config/sway/config +++ b/dot_config/sway/config @@ -55,11 +55,10 @@ exec wlsunset -l 39.96 -L -82.99 exec sworkstyle # === idle / lock === -exec swayidle -w \ - timeout 300 '$HOME/.config/sway/lock-fancy.sh' \ - timeout 600 'swaymsg "output * power off"' \ - resume 'swaymsg "output * power on"' \ - before-sleep '$HOME/.config/sway/lock-fancy.sh' +# Single source of truth: start-swayidle.sh is also called by caffeine.sh +# to restart swayidle after toggling off the inhibit. Don't inline the +# swayidle command here — keep the two in sync via the wrapper. +exec $HOME/.config/sway/start-swayidle.sh # === keybinds === bindsym $mod+Return exec $term diff --git a/dot_config/sway/start-swayidle.sh b/dot_config/sway/start-swayidle.sh new file mode 100755 index 0000000..48d914d --- /dev/null +++ b/dot_config/sway/start-swayidle.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# start-swayidle.sh — canonical swayidle startup for the Sway session. +# +# Single source of truth for the swayidle command. Invoked from: +# 1. ~/.config/sway/config: exec $HOME/.config/sway/start-swayidle.sh +# 2. ~/.config/sway/caffeine.sh: after killall swayidle, to re-arm +# +# Why a wrapper instead of inlining the command in sway/config? Because +# caffeine.sh needs to RESTART swayidle after killing it, and the restart +# must use the same timeouts and lock command as the original. Hardcoding +# it twice drifts (see git log — swayidle line in caffeine.sh got out of +# sync with the one in sway/config, so toggling caffeine off would +# silently change your lock command and timeout values). +# +# Env: this script is run by: +# - sway at session start, with the full Wayland/Sway env (correct) +# - caffeine.sh from waybar's on-click, with NO Wayland env (broken — see below) +# When run from caffeine.sh, swayidle needs WAYLAND_DISPLAY to talk to the +# compositor. We probe /proc for the env of an already-running Wayland client +# (mako, swaybar, foot, etc.) and inherit WAYLAND_DISPLAY / DISPLAY / +# DBUS_SESSION_BUS_ADDRESS / XDG_RUNTIME_DIR from it. This is the same +# trick `dbus-update-activation-environment` uses internally, just per-process. +# +# Returns: never. Runs as the foreground process; sway's exec replaces the +# shell with this script, and swayidle takes over. When started from +# caffeine.sh, the script is backgrounded (&) and this function returns. +set -e + +# --- 1. Restore Wayland env if missing ----------------------------------- +# The bug we're fixing: clicking the caffeine button runs this script +# from waybar's on-click context, which doesn't inherit sway's env. +# swayidle and lock-fancy.sh both call swaymsg/grim which need +# WAYLAND_DISPLAY — without it, swayidle silently exits and the user +# has NO idle lock at all (the worst possible failure mode: looks normal +# in the bar, but auto-lock is silently dead). +if [[ -z "$WAYLAND_DISPLAY" && -z "$SWAYSOCK" ]]; then + # Find any running Wayland client and steal its env. mako (notification + # daemon) is always present and always has the right env. + for pid in /proc/[0-9]*; do + [[ -r "$pid/environ" ]] || continue + candidate=$(tr '\0' '\n' < "$pid/environ" 2>/dev/null | grep -E '^(WAYLAND_DISPLAY|DBUS_SESSION_BUS_ADDRESS|XDG_RUNTIME_DIR|DISPLAY)=' || true) + if [[ -n "$candidate" ]]; then + export $candidate + break + fi + done +fi + +# --- 2. Start swayidle ---------------------------------------------------- +# If invoked from sway's `exec` line (config), swayidle replaces this shell +# and runs forever. If invoked from caffeine.sh (backgrounded with &), it +# runs as a child of the parent shell and caffeine.sh returns immediately. +exec swayidle -w \ + timeout 300 "$HOME/.config/sway/lock-fancy.sh" \ + timeout 600 'swaymsg "output * power off"' \ + resume 'swaymsg "output * power on"' \ + before-sleep "$HOME/.config/sway/lock-fancy.sh"