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
133 lines
5.5 KiB
Bash
Executable File
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
|