Draw idle as one timeline, and let the power button answer to its owner

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 20:33:44 -04:00
parent 6f0ce639d9
commit e1ff25fc66
23 changed files with 2655 additions and 174 deletions
+102 -3
View File
@@ -15,6 +15,12 @@
# 3. The charge-limit control appears only where the firmware has one.
# 4. The threshold write goes through panama-sudo with a reason, never bare
# sudo, and is read back rather than assumed.
# 5. Health -- design-capacity percentage and charge cycles -- is reported
# where sysfs reports it and is SILENT where it does not. Most of these
# files are optional and plenty of firmware omits them, so the tempting
# failure is a tile reading "100% of design capacity, 0 cycles" on a
# three-year-old battery that simply never said. That is worse than no
# tile: it is a confident wrong answer about whether hardware is dying.
#
# The helper is driven against fixture sysfs trees; the QML side is pinned
# statically, since a battery cannot be simulated into the running shell.
@@ -122,17 +128,110 @@ PANAMA_HW_SYS="$limited/sys" PANAMA_PATH="$repo_dir" "$helper" set-threshold 10
PANAMA_HW_SYS="$limited/sys" PANAMA_PATH="$repo_dir" "$helper" set-threshold abc >/dev/null 2>&1 \
&& note 'a non-numeric threshold was accepted'
# ── 5. The QML side hides itself ─────────────────────────────────────────────
# ── 5. Health, where the firmware reports it ─────────────────────────────────
#
# Two sysfs spellings for the same fact, depending on whether the driver
# reports energy or charge. Both have to work, or half the laptops in the world
# get a blank tile.
with_health() {
local name="$1" prefix="$2" full="$3" design="$4" cycles="${5:-}"
local root
root="$(fixture "$name" 72 1)"
printf '%s\n' "$full" >"$root/sys/class/power_supply/BAT0/${prefix}_full"
printf '%s\n' "$design" >"$root/sys/class/power_supply/BAT0/${prefix}_full_design"
[[ -n "$cycles" ]] && printf '%s\n' "$cycles" >"$root/sys/class/power_supply/BAT0/cycle_count"
printf '%s\n' "$root"
}
# A pack that has lost a tenth of its design capacity, in energy units.
energy="$(with_health energy energy 45000000 50000000 312)"
status="$(ask "$energy" status)"
[[ "$(field "$status" .healthPercent)" == "90" ]] \
|| note "health is not computed from energy_full against energy_full_design (got $(field "$status" .healthPercent))"
[[ "$(field "$status" .cycleCount)" == "312" ]] \
|| note "the charge cycle count is not reported (got $(field "$status" .cycleCount))"
# The same pack, on a driver that reports charge rather than energy.
charge="$(with_health charge charge 45000000 50000000 312)"
status="$(ask "$charge" status)"
[[ "$(field "$status" .healthPercent)" == "90" ]] \
|| note 'health is not read from the charge_full spelling, so a driver that reports charge shows no health at all'
# A pack that reports neither. Absent, null, or zero -- anything but a number
# that looks like an answer.
plain="$(ask "$laptop" status)"
for key in healthPercent cycleCount; do
value="$(field "$plain" ".$key")"
case "$value" in
""|null|0) ;;
*) note "a battery whose firmware reports no $key was given one anyway ($value), which is a confident wrong answer about whether the hardware is dying" ;;
esac
done
# And a machine with no battery at all invents nothing.
desktop_status="$(ask "$desktop" status)"
for key in healthPercent cycleCount; do
value="$(field "$desktop_status" ".$key")"
case "$value" in
""|null|0) ;;
*) note "a machine with no battery reported a $key of $value" ;;
esac
done
# A design capacity of zero would divide by it. Firmware does report this.
zeroed="$(with_health zeroed energy 45000000 0)"
status="$(ask "$zeroed" status)"
value="$(field "$status" .healthPercent)"
case "$value" in
""|null|0) ;;
*) note "a zero design capacity produced a health percentage of $value" ;;
esac
# The status JSON stays parseable in every one of those cases -- an empty
# substitution would have made it valid-looking but wrong above, and invalid
# here.
for candidate in "$energy" "$charge" "$laptop" "$desktop" "$zeroed"; do
jq -e . >/dev/null 2>&1 <<<"$(ask "$candidate" status)" \
|| note 'status stopped emitting valid JSON once the health fields were added'
done
# The service carries them through, and null-safely: `null > 0` is false in
# QML, which is what makes an absent reading hide rather than render as an
# empty tile.
for property in healthPercent cycleCount; do
grep -q "property .*$property" "$service" \
|| note "the battery service does not expose $property, so the tile would read undefined"
done
# ── 6. The QML side hides itself ─────────────────────────────────────────────
grep -q 'property bool available' "$service" \
|| note 'the battery service has no availability flag'
grep -q 'Settings.showBattery && Battery.available' "$cluster" \
|| note 'the bar indicator does not gate on both the preference and the hardware'
grep -q 'visible: Battery.available' "$page" \
# The Power page and the components it is built from. A card lifted into a
# component of its own is a normal thing to do, and every assertion below would
# quietly stop meaning anything if it only ever read PowerPage.qml.
power_surface=("$page")
for candidate in "$(dirname "$page")"/{Power,Battery,Idle}*.qml; do
[[ -r "$candidate" && "$candidate" != "$page" ]] && power_surface+=("$candidate")
done
grep -q 'visible: Battery.available' "${power_surface[@]}" \
|| note 'the Power page battery card does not hide on a machine without one'
grep -q 'visible: Battery.chargeLimitSupported' "$page" \
grep -q 'visible: Battery.chargeLimitSupported' "${power_surface[@]}" \
|| note 'the charge limit control does not hide where the firmware has none'
# The health tiles follow the same rule as everything else here: a reading the
# firmware did not give is a tile that is not drawn.
for property in healthPercent cycleCount; do
grep -q "Battery.$property" "${power_surface[@]}" \
|| note "the Power page never shows $property, so the battery health the helper reads goes nowhere"
grep -qE "visible: .*Battery\.$property" "${power_surface[@]}" \
|| note "the $property tile is drawn unconditionally, so a battery that reports none shows an empty one"
done
# The alias layer has to carry the key, or the binding silently reads undefined
# and the indicator never appears. This exact mistake was made writing it.
for key in showBattery batteryLowPercent batteryCriticalPercent; do