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:
Executable
+84
@@ -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
|
||||
Reference in New Issue
Block a user