Closing the lid at a desk is not closing it in a bag

logind handles the lid correctly except for the one case it cannot
see: an external display means a closed lid is a docked machine, not
one being put away. Its own docked test looks for an ACPI docking
station that modern hardware does not have.

Panama does not take the lid over to fix that. It holds a logind
handle-lid-switch inhibitor while an external display is connected and
releases it when the last one goes, which needs no lid watcher, no
polling, and no drop-in. The direction it fails in is the point: if the
guard dies, logind's default comes back and a docked laptop suspends,
which is annoying. A drop-in setting HandleLidSwitch=ignore plus a
watcher of our own fails the other way, leaving a lid that does nothing
at all on a machine being carried out of a building.

Locking on the way down needed no work: hypridle's before_sleep_cmd
already runs loginctl lock-session, so a lid-close suspend is a locked
suspend. The contract fails anything that duplicates it.

Not yet verified against a real lid, which is stated in the helper's
header rather than implied by silence. The decision logic, the
inhibitor's shape, and every machine that should hold none of it are
covered.
This commit is contained in:
Gabriel Brown
2026-08-21 22:36:36 -04:00
parent bc6d63b70f
commit 50a99a5ad0
5 changed files with 315 additions and 1 deletions
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# What closing the lid should do.
#
# panama-lid status what this machine would do right now, as JSON
# panama-lid guard hold the inhibitor while clamshell use is possible
#
# The rule is one sentence: closing the lid should suspend, UNLESS there is an
# external monitor, in which case the machine is docked and should keep working.
#
# The implementation is deliberately not a lid watcher. logind already handles
# the lid perfectly well; what it cannot do is notice that an external display
# makes a closed lid mean something different, because its own "docked" test
# looks for an ACPI docking station that modern hardware does not have.
#
# So Panama does not take the lid over. It holds a `handle-lid-switch`
# inhibitor while an external monitor is connected, and releases it when the
# last one goes away. logind does everything else, which has three properties
# worth having:
#
# * Nothing has to watch the lid, and no polling loop exists.
# * If this ever fails or is killed, the inhibitor is released and the
# machine goes back to logind's default. The failure mode is "a docked
# laptop suspends", not "the lid does nothing at all", which is the failure
# mode a logind drop-in would have.
# * Locking before sleep is already handled: hypridle's before_sleep_cmd
# runs `loginctl lock-session`, so a suspend from a closed lid is a locked
# suspend without anything here being involved.
#
# Started and stopped by services/LidPolicy.qml on display topology changes.
#
# NOT YET VERIFIED ON A LAPTOP. The logic and the inhibitor are testable on any
# machine and are covered by tests/setup/lid-contract, but the end-to-end
# behavior of closing a real lid on a docked machine needs a machine with a
# lid. Everything here fails toward logind's default, so the untested path is
# "keeps working while docked" rather than anything that could strand a session.
set -uo pipefail
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
HW="$PANAMA_PATH/bin/panama-hw"
answer() { "$@" && printf 'true' || printf 'false'; }
cmd_status() {
local inhibited=false
systemd-inhibit --list 2>/dev/null | grep -q 'panama-lid' && inhibited=true
printf '{"laptop":%s,"lidClosed":%s,"externalMonitor":%s,"clamshell":%s,"inhibited":%s}\n' \
"$(answer "$HW" laptop)" \
"$(answer "$HW" lid-closed)" \
"$(answer "$HW" external-monitor)" \
"$(answer "$HW" clamshell)" \
"$inhibited"
}
# Holds the inhibitor for as long as it runs. Exits immediately, holding
# nothing, when this machine has no reason to want one -- a desktop has no lid
# to inhibit and an undocked laptop should suspend normally.
cmd_guard() {
"$HW" laptop || exit 0
"$HW" external-monitor || exit 0
exec systemd-inhibit \
--what=handle-lid-switch \
--who=panama-lid \
--why="An external display is connected, so a closed lid is a docked machine rather than one being put away" \
--mode=block \
sleep infinity
}
case "${1:-status}" in
status) cmd_status ;;
guard) cmd_guard ;;
-h|--help)
cat <<'USAGE'
usage: panama-lid [status|guard]
status what closing the lid would do right now, as JSON
guard hold a handle-lid-switch inhibitor while an external display is
connected; exits immediately on a machine that needs none
USAGE
;;
*) printf 'panama-lid: unknown command: %s\n' "$1" >&2; exit 2 ;;
esac