From 0711f1b4a4045c583c63f494a61262ed1146a944 Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Fri, 29 May 2026 11:18:14 +0100 Subject: fix(suspend): only inhibit for SSH-spawned zellij sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A local zellij session (sway terminal, attended) shouldn't keep the laptop awake — that's the user actively in front of the machine, and normal suspend behaviour should apply. Only zellij sessions that were spawned from an SSH context need the persistent inhibit, so detach + disconnect leaves the host awake until the session ends. Use /proc//environ to detect SSH-spawned zellij: the daemonised zellij server is exec'd by the client and Linux preserves the exec-time environment for the life of the process, so SSH_CONNECTION= survives the SSH session closing. Walk every running `zellij` pid; hold the lock as long as at least one of them has SSH_CONNECTION in its environ. The .path unit still fires on every zellij socket creation, but if no SSH-spawned zellij exists the watcher exits immediately and the service stops with no harm done — a couple of cheap process spawns per local session start, no inhibitor side-effects. --- .../systemd/user/zellij-inhibit-suspend.path | 3 ++ .../systemd/user/zellij-inhibit-suspend.service | 6 ++-- dot_local/bin/executable_zellij-inhibit-watcher | 41 ++++++++++++++++------ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/dot_config/systemd/user/zellij-inhibit-suspend.path b/dot_config/systemd/user/zellij-inhibit-suspend.path index 72c509c..2a4be21 100644 --- a/dot_config/systemd/user/zellij-inhibit-suspend.path +++ b/dot_config/systemd/user/zellij-inhibit-suspend.path @@ -5,6 +5,9 @@ Description=Activate suspend inhibitor whenever zellij has a live session # %t expands to $XDG_RUNTIME_DIR (typically /run/user/$UID); zellij keeps # its per-version session sockets under this directory. Whenever the dir # transitions from empty to non-empty, the service is (re)activated. +# The service's watcher then decides whether to actually hold the lock +# (only if at least one zellij was spawned from an SSH session); if not, +# it exits immediately and the service stops with no harm done. DirectoryNotEmpty=%t/zellij Unit=zellij-inhibit-suspend.service diff --git a/dot_config/systemd/user/zellij-inhibit-suspend.service b/dot_config/systemd/user/zellij-inhibit-suspend.service index 9e9ab40..ed15fff 100644 --- a/dot_config/systemd/user/zellij-inhibit-suspend.service +++ b/dot_config/systemd/user/zellij-inhibit-suspend.service @@ -1,8 +1,10 @@ [Unit] -Description=Hold a systemd-inhibit lock while zellij sessions exist +Description=Hold a systemd-inhibit lock while SSH-spawned zellij sessions exist Documentation=man:systemd-inhibit(1) man:zellij(1) # Independent of any graphical session: this is meant to run on -# headless SSH-attached hosts too. +# headless SSH-attached hosts too. The watcher itself decides whether +# the current zellij activity warrants inhibiting (SSH-spawned only), +# so a local zellij session won't keep the laptop awake. [Service] Type=simple diff --git a/dot_local/bin/executable_zellij-inhibit-watcher b/dot_local/bin/executable_zellij-inhibit-watcher index 0af20dd..6af7032 100755 --- a/dot_local/bin/executable_zellij-inhibit-watcher +++ b/dot_local/bin/executable_zellij-inhibit-watcher @@ -1,20 +1,39 @@ #!/bin/sh -# Block until no zellij sessions remain. +# Block while at least one zellij server process was spawned from an +# SSH context, exit cleanly once none remain. # -# Used as the ExecStart payload of zellij-inhibit-suspend.service: the -# service wraps this script with `systemd-inhibit`, so the inhibit lock -# is held for exactly the lifetime of this process. When the last zellij -# session ends, this script exits 0, the service stops, and the lock is -# released. +# Rationale: a zellij session started locally (e.g. from a sway terminal) +# is the user actively sitting in front of the laptop — that should NOT +# inhibit suspend. Only zellij sessions started while SSH'd in deserve +# the lock, so the host stays awake across detach + disconnect but +# normal local-attended suspend still works. # -# A user-level `.path` unit re-activates the service whenever the zellij -# socket directory becomes non-empty again, so the lock is automatically -# reacquired on the next `zellij` invocation. +# Detection: zellij's daemonised server is exec'd by the client and +# inherits the client's environment. Linux preserves that exec-time +# environment in /proc//environ for the life of the process, even +# after the original SSH session is gone. So an "ssh-spawned" zellij is +# one whose environ contains SSH_CONNECTION=. +# +# This script is the ExecStart payload of zellij-inhibit-suspend.service, +# which wraps it in systemd-inhibit. When this script exits, the lock is +# released. The .path unit re-fires the service on the next zellij socket +# transition. set -eu poll=${ZELLIJ_INHIBIT_POLL:-15} -while sessions=$(zellij list-sessions --short 2>/dev/null) && - [ -n "$sessions" ]; do +has_ssh_zellij() { + pids=$(pgrep -x zellij 2>/dev/null) || return 1 + for pid in $pids; do + [ -r "/proc/$pid/environ" ] || continue + if tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null | + grep -q '^SSH_CONNECTION='; then + return 0 + fi + done + return 1 +} + +while has_ssh_zellij; do sleep "$poll" done -- cgit v1.3.1