Files
Panama/config/dot/quickshell/scripts/panama-lid
T
Gabriel Brown 9530e32326 Give a laptop its first week: the keys, the lid, the battery's endgame
The audit's fourth tier -- what a Framework owner reaches for in the first
week and found missing.

The power button stops being an instant, unconfirmed poweroff: a shipped
logind drop-in tells the daemon to stand down and the compositor binds the
key to the power menu, the way GNOME turns it into a question. Holding it
still hard-cuts through firmware. change-settings restarts logind so the
change applies without waiting for a boot, and the Power page says what the
button does now.

The function row fills in: F10 (XF86RFKill) toggles airplane mode through a
new panama-osd verb that blocks or unblocks every radio and says which way it
went; F9 (XF86Display) opens the Displays page, the honest action until
mirroring exists. And the lid becomes a switch bind: closing a docked lid
turns the internal panel off so nothing renders inside a closed shell and no
workspace strands on an invisible output, and opening it restores the panel
with the mode and scale chosen in Settings. panama-lid owns both decisions;
undocked machines suspend via logind before any of it matters.

The battery gets an endgame. On battery the screen dims to 30% two-thirds of
the way to blanking -- GNOME's single largest idle battery saver -- and
restores exactly the level it saved. At the urgent threshold the machine
suspends after a fifteen-second grace, cancelled by plugging in, because a
suspend preserves the session for days and a hard cut at 0% preserves
nothing; "Only warn" remains a choice on the Power page. Hibernate joins the
power menu, but only where logind answers CanHibernate with yes -- an entry
that fails silently is worse than none.

And brightness stops being two code paths: the Displays page now embeds the
same control the quick-settings panel uses, so the built-in backlight and
DDC/CI monitors share one surface that withdraws itself where neither
exists. The lid contract narrows to what its principle protects -- a
HandleLidSwitch drop-in -- so deliberate policy for other keys can ship.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
2026-08-23 12:21:08 -04:00

133 lines
5.5 KiB
Bash
Executable File

#!/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
}
# The internal panel's connector name, from the compositor: eDP, LVDS and
# DSI are built in, everything else arrived through a cable -- the same rule
# panama-hw and LidPolicy use.
internal_connector() {
hyprctl -j monitors all 2>/dev/null \
| jq -r '[.[] | select(.name | test("^(eDP|LVDS|DSI)"; "i"))][0].name // empty'
}
# Bound to the lid switch by keybinds.lua. Closing a DOCKED lid turns the
# internal panel off, so nothing renders inside a closed shell and no
# workspace strands on an invisible output; an undocked lid does nothing here
# because logind is already suspending the machine. Failure direction: if the
# disable never runs, the panel stays lit inside the shell -- wasteful, not
# harmful.
cmd_close() {
"$HW" laptop || exit 0
"$HW" external-monitor || exit 0
local internal
internal="$(internal_connector)"
[[ -n "$internal" ]] || exit 0
hyprctl eval "hl.monitor({ output = \"$internal\", disable = true })" >/dev/null
}
# Re-enable on open, preferring what the person chose for this panel in
# Settings (the same displays store monitors.lua reads at startup) and
# falling back to the panel's preferred mode.
cmd_open() {
"$HW" laptop || exit 0
local internal entry mode scale
internal="$(internal_connector)"
[[ -n "$internal" ]] || exit 0
entry="$(jq -c --arg name "$internal" '.displays[$name] // empty' \
"${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json" 2>/dev/null)"
if [[ -n "$entry" ]]; then
mode="$(jq -r '.mode' <<<"$entry")"
scale="$(jq -r '.scale' <<<"$entry")"
hyprctl eval "hl.monitor({ output = \"$internal\", mode = \"$mode\", position = \"auto\", scale = $scale })" >/dev/null
else
hyprctl eval "hl.monitor({ output = \"$internal\", mode = \"preferred\", position = \"auto\", scale = \"auto\" })" >/dev/null
fi
}
case "${1:-status}" in
status) cmd_status ;;
guard) cmd_guard ;;
close) cmd_close ;;
open) cmd_open ;;
-h|--help)
cat <<'USAGE'
usage: panama-lid [status|guard|close|open]
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
close docked lid closed: turn the internal panel off (bound to the lid
switch by keybinds.lua); does nothing undocked
open lid opened: turn the internal panel back on, restoring the mode
and scale chosen in Settings
USAGE
;;
*) printf 'panama-lid: unknown command: %s\n' "$1" >&2; exit 2 ;;
esac