Notice the battery, and the machine it is or is not in
Panama had no idea whether it was running on a laptop. No upower, no battery, no lid, no AC: hypridle.conf says "This is a desktop" in its own header, and that was true of the code as well as the machine. panama-hw answers hardware questions one at a time, exits 0 or 1, and prints nothing, so scripts, services and contracts all ask the same way. The definition the rest of the laptop work hangs on is one line: clamshell is lid-closed AND an external monitor. A machine with no mains supply at all reports as being on wall power, because a desktop cannot run out of it. The battery service follows Vitals: sysfs through FileView, an availability flag, and no subprocess on the timer. Globbing is the one thing QML cannot do -- a battery is BAT0 or BAT1 or CMB0, mains is AC or ADP1 or ACAD -- so panama-battery resolves the names once and the shell reads the files directly after. Nothing falls back to a plausible zero: a desktop shows no indicator, no card, and no charge limit control where the firmware has no ceiling. Also repairs two contracts that were already failing and had not been noticed, because only the full suite runs them. The dependency scanner treated line-initial variable assignments, case labels, comments and heredoc bodies as commands, and `count`, `host`, `cancel` and `import` are all real binaries on Fedora, so `command -v` could not filter them out. It now drops comments and heredoc bodies and requires a command to be followed by whitespace. Verified it still catches a genuinely undeclared dependency rather than passing quietly. The launcher command contract had not been told about the fourteen commands added earlier today.
This commit is contained in:
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# What this machine is, asked one yes-or-no question at a time.
|
||||
#
|
||||
# panama-hw laptop && echo "portable"
|
||||
# panama-hw clamshell && panama-lid close
|
||||
#
|
||||
# Every subcommand exits 0 for yes and 1 for no, prints nothing, and answers
|
||||
# correctly on a machine that has none of the hardware in question. That last
|
||||
# part is the whole point: a desktop must be able to ask "am I in clamshell
|
||||
# mode" and get a calm no rather than an error, because the scripts and
|
||||
# services that ask are shared between machines.
|
||||
#
|
||||
# `--json` answers everything at once, for the health page and for contracts.
|
||||
#
|
||||
# Detection reads sysfs directly rather than shelling out to lspci or upower:
|
||||
# lspci touches PCI config space and wakes a runtime-suspended GPU, which is a
|
||||
# real cost to pay for a question asked at every login.
|
||||
#
|
||||
# Paths are overridable (PANAMA_HW_SYS, PANAMA_HW_ACPI) so the contract can
|
||||
# drive fixture trees. Nothing else should set them.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SYS="${PANAMA_HW_SYS:-/sys}"
|
||||
ACPI="${PANAMA_HW_ACPI:-/proc/acpi}"
|
||||
|
||||
# SMBIOS chassis types that mean "carried around": Portable, Laptop, Notebook,
|
||||
# Hand Held, Sub Notebook, Tablet, Convertible, Detachable. A machine that
|
||||
# reports something else, or reports nothing, is treated as stationary --
|
||||
# guessing "laptop" on an unknown chassis would put battery chrome on a desktop.
|
||||
readonly PORTABLE_CHASSIS=" 8 9 10 11 14 30 31 32 "
|
||||
|
||||
is_laptop() {
|
||||
local type_file="$SYS/class/dmi/id/chassis_type" chassis
|
||||
[[ -r "$type_file" ]] || return 1
|
||||
chassis="$(cat "$type_file" 2>/dev/null)" || return 1
|
||||
[[ "$PORTABLE_CHASSIS" == *" $chassis "* ]]
|
||||
}
|
||||
|
||||
# The first battery, or nothing. Named rather than assumed to be BAT0: the
|
||||
# second battery in a ThinkPad is BAT1, and a machine with only BAT1 exists.
|
||||
battery_path() {
|
||||
local supply type
|
||||
for supply in "$SYS"/class/power_supply/*; do
|
||||
[[ -r "$supply/type" ]] || continue
|
||||
type="$(cat "$supply/type" 2>/dev/null)"
|
||||
if [[ "$type" == "Battery" ]]; then
|
||||
printf '%s\n' "$supply"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
has_battery() { battery_path >/dev/null; }
|
||||
|
||||
# On wall power. A machine with no mains supply at all is a desktop, and a
|
||||
# desktop is always on wall power -- answering "no" there would make every
|
||||
# battery-aware timing apply to a machine that cannot run out of power.
|
||||
on_ac() {
|
||||
local supply type online found=1
|
||||
for supply in "$SYS"/class/power_supply/*; do
|
||||
[[ -r "$supply/type" ]] || continue
|
||||
type="$(cat "$supply/type" 2>/dev/null)"
|
||||
[[ "$type" == "Mains" ]] || continue
|
||||
found=0
|
||||
online="$(cat "$supply/online" 2>/dev/null || echo 0)"
|
||||
[[ "$online" == "1" ]] && return 0
|
||||
done
|
||||
# Mains exists and none of it is online: genuinely on battery.
|
||||
(( found == 0 )) && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
lid_closed() {
|
||||
local state
|
||||
for state in "$ACPI"/button/lid/*/state; do
|
||||
[[ -r "$state" ]] || continue
|
||||
grep -qi closed "$state" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# A connected output that is not the built-in panel. eDP, LVDS and DSI are the
|
||||
# internal ones; everything else arrived through a cable.
|
||||
has_external_monitor() {
|
||||
local status connector
|
||||
for status in "$SYS"/class/drm/card*-*/status; do
|
||||
[[ -r "$status" ]] || continue
|
||||
[[ "$(cat "$status" 2>/dev/null)" == "connected" ]] || continue
|
||||
connector="$(basename "$(dirname "$status")")"
|
||||
case "$connector" in
|
||||
*eDP*|*LVDS*|*DSI*) continue ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# The one definition the rest of the laptop work hangs on: the lid is shut and
|
||||
# there is still a screen to use. Closing the lid on a dock must not suspend;
|
||||
# closing it on a train must.
|
||||
is_clamshell() { lid_closed && has_external_monitor; }
|
||||
|
||||
has_touchpad() {
|
||||
local name
|
||||
for name in "$SYS"/class/input/*/name; do
|
||||
[[ -r "$name" ]] || continue
|
||||
grep -qi touchpad "$name" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Vendor 0x10de on a display-class device. Read from sysfs rather than lspci
|
||||
# so an idle discrete GPU is not woken to answer.
|
||||
has_nvidia() {
|
||||
local device vendor class
|
||||
for device in "$SYS"/bus/pci/devices/*; do
|
||||
[[ -r "$device/vendor" && -r "$device/class" ]] || continue
|
||||
vendor="$(cat "$device/vendor" 2>/dev/null)"
|
||||
[[ "$vendor" == "0x10de" ]] || continue
|
||||
class="$(cat "$device/class" 2>/dev/null)"
|
||||
[[ "$class" == 0x03* ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
answer() { "$1" && printf 'true' || printf 'false'; }
|
||||
|
||||
cmd_json() {
|
||||
printf '{"laptop":%s,"battery":%s,"ac":%s,"lidClosed":%s,"externalMonitor":%s,"clamshell":%s,"touchpad":%s,"nvidia":%s}\n' \
|
||||
"$(answer is_laptop)" "$(answer has_battery)" "$(answer on_ac)" \
|
||||
"$(answer lid_closed)" "$(answer has_external_monitor)" \
|
||||
"$(answer is_clamshell)" "$(answer has_touchpad)" "$(answer has_nvidia)"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
laptop) is_laptop ;;
|
||||
battery) has_battery ;;
|
||||
battery-path) battery_path ;;
|
||||
ac) on_ac ;;
|
||||
lid-closed) lid_closed ;;
|
||||
external-monitor) has_external_monitor ;;
|
||||
clamshell) is_clamshell ;;
|
||||
touchpad) has_touchpad ;;
|
||||
nvidia) has_nvidia ;;
|
||||
--json) cmd_json ;;
|
||||
-h|--help|"")
|
||||
cat <<'USAGE'
|
||||
usage: panama-hw <predicate>
|
||||
|
||||
Exits 0 for yes, 1 for no, and prints nothing.
|
||||
|
||||
laptop a portable chassis
|
||||
battery a battery is present
|
||||
battery-path print the first battery's sysfs path (0 if found)
|
||||
ac on wall power (a machine with no mains is always yes)
|
||||
lid-closed the lid is shut
|
||||
external-monitor a connected output that is not the built-in panel
|
||||
clamshell lid shut AND an external monitor: docked, keep working
|
||||
touchpad a touchpad is present
|
||||
nvidia an NVIDIA display device is present
|
||||
|
||||
--json every answer at once
|
||||
USAGE
|
||||
;;
|
||||
*) printf 'panama-hw: unknown predicate: %s\n' "$1" >&2; exit 2 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user