#!/usr/bin/env bash # hypr-lid-power: delayed lock/suspend for the laptop lid. # # Display/clamshell/hotplug handling is owned by moniqued (see # ~/.config/hypr/monitors.lua and moniqued's clamshell mode). This service only # runs the power timers while the lid is closed with no external display # attached: # - lock after LOCK_SECONDS # - suspend after SUSPEND_SECONDS # # Runs as a systemd user service (hypr-lid-power.service) and polls the lid + # monitor state, so connecting/disconnecting a dock while the lid is closed is # handled too. Lock/suspend use transient systemd timers, which pause during # suspend and are recreated afterwards, so if the machine wakes while the lid # stays closed it will sleep again after the full timeout. set -uo pipefail RUNTIME="/run/user/$(id -u)" LOCK_SECONDS=60 SUSPEND_SECONDS=600 POLL_SECONDS=2 log() { printf '[hypr-lid-power] %s %s\n' "$(date '+%H:%M:%S')" "$*"; } # Run hyprctl against the active Hyprland instance, if any. hypr() { local sig="" for d in "$RUNTIME"/hypr/*/; do if [ -S "$d/.socket.sock" ]; then sig="$(basename "$d")" break fi done [ -n "$sig" ] || return 1 HYPRLAND_INSTANCE_SIGNATURE="$sig" command hyprctl "$@" } lid_closed() { local state state="$(cat /proc/acpi/button/lid/*/state 2>/dev/null)" || return 1 [[ "$state" == *closed* ]] } # True when Hyprland has any output up that is not the built-in panel/headless. has_external() { hypr monitors -j 2>/dev/null | grep '"name": "' | grep -qvE '"(eDP|HEADLESS)-' } stop_timers() { systemctl --user stop hypr-lid-lock.timer hypr-lid-suspend.timer 2>/dev/null || true } # Re-create the named timer unless it is already waiting. Transient timers are # collected after they fire, so this also restarts them after a suspend. ensure_timer() { local name="$1" seconds="$2" shift 2 if ! systemctl --user --quiet is-active "hypr-lid-$name.timer"; then systemd-run --user --quiet --unit="hypr-lid-$name" --on-active="${seconds}s" --collect "$@" fi } powering=no while true; do if ! hypr monitors >/dev/null 2>&1; then # No Hyprland running (e.g. another compositor, or before first launch). powering=no sleep 3 continue fi want_power="no" if lid_closed && ! has_external; then want_power="yes" fi if [ "$want_power" != "$powering" ]; then log "power mode: $powering -> $want_power" if [ "$want_power" = "yes" ]; then ensure_timer lock "$LOCK_SECONDS" loginctl lock-sessions ensure_timer suspend "$SUSPEND_SECONDS" systemctl suspend else stop_timers fi powering="$want_power" fi sleep "$POLL_SECONDS" done