#!/bin/bash # Webcam presence detection daemon. # Checks for a face every 2 minutes; holds a systemd-inhibit idle lock while # the user is detected so hypridle never fires during an active session. # # Camera selection: set PRESENCE_DETECT_CAMERA env var or write # CAMERA= to ~/.config/presence-detect.conf # # Exit codes from python helper: 0=face, 1=no face, 2=camera error # Resolve the script's real directory so the Python helper path stays valid # even when invoked via a symlink or from a different cwd. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py" INHIBIT_PID_FILE="/tmp/presence-inhibit.pid" PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf" INTERVAL=120 # seconds between checks # Resolve camera ID: env var takes highest priority, then config file, then default 0. _camera_id() { if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then echo "$PRESENCE_DETECT_CAMERA" elif [[ -f "$PRESENCE_CFG" ]]; then # -oP 'CAMERA=\K[0-9]+': Perl-style look-behind strips "CAMERA=" prefix. grep -oP 'CAMERA=\K[0-9]+' "$PRESENCE_CFG" 2>/dev/null || echo 0 else echo 0 fi } # Returns true if the inhibitor sentinel process is still alive. _inhibit_running() { [[ -f "$INHIBIT_PID_FILE" ]] && kill -0 "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null } _start_inhibit() { # Guard: don't start a second inhibitor if one is already active. _inhibit_running && return # --what=idle: target the logind idle-inhibit lock that hypridle polls. # "sleep infinity" is the sentinel; its PID is saved so we can kill it later. systemd-inhibit --what=idle --who="presence-detect" \ --why="User presence detected" --mode=block \ sleep infinity & echo $! > "$INHIBIT_PID_FILE" # logger writes to the system journal — visible via `journalctl -t presence-detect`. logger -t presence-detect "Presence detected — idle inhibited" } _stop_inhibit() { _inhibit_running || return # Killing the sleep process releases the systemd-inhibit lock automatically. kill "$(cat "$INHIBIT_PID_FILE")" 2>/dev/null rm -f "$INHIBIT_PID_FILE" logger -t presence-detect "No presence — idle inhibit released" } _cleanup() { # On daemon stop (systemd unit stop, user logout, etc.), release the lock cleanly. _stop_inhibit exit 0 } # Intercept termination signals to ensure the inhibitor PID is never orphaned. trap _cleanup SIGTERM SIGINT SIGHUP while true; do CAMERA="$(_camera_id)" # Run the OpenCV haar-cascade detector; stderr suppressed to keep the journal clean. python3 "$PYTHON_DETECT" "$CAMERA" 2>/dev/null rc=$? case $rc in 0) _start_inhibit ;; 1) _stop_inhibit ;; # rc=2 = camera unavailable — leave current inhibit state unchanged esac sleep "$INTERVAL" done