#!/bin/bash # omarchy:summary=Keep logind from suspending on lid close while a display it misses is in use # omarchy:group=system # omarchy:hidden=true # systemd-logind treats a closed lid as docked only when an external display is # on a connector type it lists (HDMI, DisplayPort, DVI, VGA...), and USB-C # displays on Apple Silicon arrive on "USB" connectors instead. Left alone, # logind suspends a laptop still in use on one. Holding its handle-lid-switch # inhibitor while such a display is in use leaves the lid to Omarchy's # clamshell handling, as HandleLidSwitchDocked=ignore would. Once released, a # lid that is still shut suspends at once: logind rechecks a closed lid after # every event. UNIT="omarchy-lid-inhibit.service" drm_path="${OMARCHY_DRM_PATH:-/sys/class/drm}" cgroup_path="${OMARCHY_CGROUP_PATH:-/proc/self/cgroup}" # Connected connectors of the external types logind does not count, by the name # Hyprland gives them (card2-USB-2 is USB-2). DVI-A and Composite are on its # list, but a missing comma there fuses them into one entry. uncounted_outputs() { local status connector for status in "$drm_path"/card*-*/status; do [[ -r $status ]] || continue connector=${status%/status} connector=${connector##*/} case $connector in card*-USB-* | card*-DVI-A-* | card*-Composite-*) ;; *) continue ;; esac [[ $(<"$status") == "connected" ]] && printf '%s\n' "${connector#card*-}" done } held() { systemctl --user is-active --quiet "$UNIT" } in_use=0 outputs=$(uncounted_outputs) # In use means enabled in Hyprland: a display switched off in the monitor config # should not keep a closed laptop awake, while one blanked by DPMS still should, # and the connector's own "enabled" flag cannot tell the two apart on every # driver. A compositor that does not answer, as while it reconfigures outputs # around a lid close, changes nothing: releasing then would suspend a closed lid. if [[ -n $outputs ]]; then monitors=$(hyprctl monitors all -j 2>/dev/null) || exit 0 [[ -n $monitors ]] || exit 0 jq -e --arg outputs "$outputs" \ '($outputs | split("\n")) as $names | any(.[]; .disabled == false and (.name | IN($names[])))' \ <<<"$monitors" >/dev/null 2>&1 case $? in 0) in_use=1 ;; 1) ;; *) exit 0 ;; esac fi # Only where logind would ignore a docked lid is there anything to stand in for. # An unanswered query changes nothing, for the same reason as above. wanted=0 if (( in_use )) && omarchy-hw-laptop; then docked_action=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \ org.freedesktop.login1.Manager HandleLidSwitchDocked 2>/dev/null) || exit 0 [[ -n $docked_action ]] || exit 0 [[ $docked_action == 's "ignore"' ]] && wanted=1 fi # Bound to the user unit of the watcher that keeps it in step, so a watcher that # is gone cannot leave the lid inhibited until logout. systemd-run unquotes # property values, so the backslashes in an escaped unit name are doubled. watcher_binding() { local cgroup="" unit read -r cgroup 2>/dev/null <"$cgroup_path" unit=${cgroup##*/} if [[ $cgroup == */user@*.service/* && ( $unit == *.scope || $unit == *.service ) ]]; then unit=${unit//\\/\\\\} printf '%s\n' "--property=BindsTo=$unit" "--property=After=$unit" fi } if (( wanted )); then if ! held; then mapfile -t bind < <(watcher_binding) systemd-run --user --quiet --collect --unit="$UNIT" \ --description="Omarchy clamshell lid inhibitor" \ --property=PartOf=graphical-session.target "${bind[@]}" \ systemd-inhibit --no-ask-password --what=handle-lid-switch --mode=block \ --who=Omarchy --why="External display in use" sleep infinity fi elif held; then systemctl --user stop "$UNIT" fi