From 9a47fd47a7536aba3fe7b5801128e1f61798a681 Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Sun, 9 Aug 2026 02:29:36 -0700 Subject: [PATCH] Enforce single-instance in socktop-gestures instead of documenting it The documented restart was `pkill -x lisgd && socktop-gestures &`. pkill exits non-zero when it matches nothing, so `&&` short-circuits and the daemon silently does not start when none was running -- you swipe, get nothing, and reasonably blame the config change you just made. socktop-gestures exec'd lisgd with no guard, so nothing downstream caught it. Rather than only fixing the one-liner, move the invariant into the tool: it now exits non-zero if a daemon is already running, and --replace stops the old one first (waiting for it to actually exit). This is the failure the README itself flags as gotcha 3, so a false success there is expensive. Verified against a live daemon: refusal, --help, bad-option exit codes, and a clean pid handover under --replace with the carousel still working. Co-Authored-By: Claude Opus 5 --- README.md | 13 +++++++++++- socktop-gestures | 43 +++++++++++++++++++++++++++++++++++++++- tools/test-foreground.sh | 19 ++++++------------ 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e08244c..090be96 100644 --- a/README.md +++ b/README.md @@ -80,10 +80,15 @@ Everything lives in **`/usr/local/etc/socktop-swipe.env`**. Edit it there, not i the repo copy. After changing it, restart the daemon and rebuild the session: ```sh -pkill -x lisgd && socktop-gestures & +socktop-gestures --replace & # restarts the daemon, running or not socktop-rack ``` +Use `--replace` rather than `pkill -x lisgd && socktop-gestures`: `pkill` exits +non-zero when it matches nothing, so that one-liner silently starts **nothing** +if no daemon was running — you would swipe, get no response, and reasonably +blame the config change. + | Option | Default | What it does | | --- | --- | --- | | `SOCKTOP_HOSTS` | four Pi profiles | socktop profile names, **in swipe order**. Any count works; layout and carousel adapt. | @@ -113,6 +118,8 @@ systemctl --user enable --now socktop-swipe Either way, **run exactly one gesture daemon**. lisgd does not grab the input device exclusively, so two instances make every swipe fire twice. +`socktop-gestures` enforces this: it exits non-zero if one is already running, +unless given `--replace`. ## Uninstall @@ -160,6 +167,10 @@ Also ensure tmux mouse mode is **off**; `socktop-rack` sets this. Two lisgd instances are running. `pgrep -x lisgd` should show exactly one. +`socktop-gestures` refuses to start if a daemon is already running, so this +should not happen via the normal path; `--replace` restarts cleanly. It can +still occur if lisgd was launched by hand. + ### 4. Swipes do nothing at all In order: is the session running (`tmux ls`)? Is the daemon running diff --git a/socktop-gestures b/socktop-gestures index 207eff4..e174438 100755 --- a/socktop-gestures +++ b/socktop-gestures @@ -25,7 +25,48 @@ SWIPE_CMD=${SWIPE_CMD:-$(dirname "$0")/socktop-swipe} [ -x /usr/local/bin/socktop-swipe ] && SWIPE_CMD=/usr/local/bin/socktop-swipe verbose= -[ "${1:-}" = "-v" ] && verbose=-v +replace=no +for a in "$@"; do + case "$a" in + -v) verbose=-v ;; + --replace) replace=yes ;; + -h | --help) + echo "usage: socktop-gestures [-v] [--replace]" + echo " -v log each detected gesture" + echo " --replace stop an already-running daemon first" + exit 0 + ;; + *) + echo "socktop-gestures: unknown option '$a'" >&2 + exit 2 + ;; + esac +done + +# Single-instance guard. lisgd does not grab the input device exclusively, so a +# second copy makes every swipe fire twice -- the carousel appears to skip panes. +# Enforced here rather than left to the caller: the obvious restart one-liner +# `pkill -x lisgd && socktop-gestures` silently starts NOTHING when no daemon was +# running, because pkill exits non-zero when it matches nothing. +running=$(pgrep -x -u "$(id -u)" lisgd 2>/dev/null || true) +if [ -n "$running" ]; then + if [ "$replace" = yes ]; then + pkill -x -u "$(id -u)" lisgd || true + n=0 + while pgrep -x -u "$(id -u)" lisgd >/dev/null 2>&1 && [ "$n" -lt 30 ]; do + n=$((n + 1)) + sleep 0.1 + done + if pgrep -x -u "$(id -u)" lisgd >/dev/null 2>&1; then + echo "socktop-gestures: existing lisgd would not exit" >&2 + exit 1 + fi + else + echo "socktop-gestures: lisgd is already running (pid: $(echo "$running" | tr '\n' ' '))" >&2 + echo " Two instances make every swipe fire twice. Use --replace to restart it." >&2 + exit 1 + fi +fi if [ ! -e "$TOUCH_DEV" ]; then echo "socktop-gestures: $TOUCH_DEV not present" >&2 diff --git a/tools/test-foreground.sh b/tools/test-foreground.sh index caf2d4c..6558cbb 100755 --- a/tools/test-foreground.sh +++ b/tools/test-foreground.sh @@ -1,20 +1,13 @@ #!/bin/sh -# Run the real gesture daemon in the foreground, with logging, so you can watch -# the display while you swipe. Ctrl-C to stop. +# Run the real gesture daemon in the foreground so you can watch the display +# while you swipe. Ctrl-C to stop. # # Expect: swipe right-to-left -> zooms in one host at a time # swipe left-to-right -> walks back out to the overview # "Execute ..." in the output means a gesture matched and fired. # -# Make sure no other instance is running first -- two copies fire every swipe -# twice, which looks like the carousel skipping. +# Pass --replace to take over from an already-running daemon; without it, +# socktop-gestures refuses to start a second instance (which would make every +# swipe fire twice). set -eu - -if pgrep -x lisgd >/dev/null 2>&1; then - echo "!! lisgd is already running (pid: $(pgrep -x lisgd | tr '\n' ' '))." >&2 - echo "!! Two instances make every swipe fire twice. Stop it first:" >&2 - echo " pkill -x lisgd" >&2 - exit 1 -fi - -exec "$(dirname "$0")/../socktop-gestures" -v +exec "$(dirname "$0")/../socktop-gestures" -v "$@"