Files
Gabriel Brown 3c359f3f7e 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.
2026-08-21 21:43:14 -04:00

97 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Reminders without an app: transient user timers.
#
# panama-remind add "20m" "stand up" relative: 90s, 20m, 1h, 1h30m
# panama-remind add "9:30" "standup" clock time: today, or tomorrow if past
# panama-remind list one line per pending reminder
# panama-remind pick choose one in the launcher; cancel it
# panama-remind cancel <unit> cancel by unit name
#
# systemd-run carries the whole feature: the timer survives shell exit, fires
# notify-send inside the user session with its activation environment intact,
# and cleans itself up after firing. The message rides the unit Description so
# list can show it without a state file. Everything relative is converted to
# seconds first, so there is exactly one systemd-run form to get right.
set -euo pipefail
usage() {
echo 'usage: panama-remind add "<20m, 1h30m, or 9:30>" "<text>"' >&2
echo ' panama-remind list, pick, or cancel <unit>' >&2
exit 2
}
seconds_until() {
local when="$1" total=0 rest num unit
if [[ "$when" =~ ^([0-9]{1,2}):([0-9]{2})$ ]]; then
local target now
target="$(date -d "$when" +%s 2>/dev/null)" || return 1
now="$(date +%s)"
(( target <= now )) && target=$(( target + 86400 ))
echo $(( target - now ))
return 0
fi
rest="$when"
[[ "$rest" =~ ^([0-9]+[smh])+$ ]] || return 1
while [[ "$rest" =~ ^([0-9]+)([smh])(.*)$ ]]; do
num="${BASH_REMATCH[1]}" unit="${BASH_REMATCH[2]}" rest="${BASH_REMATCH[3]}"
case "$unit" in
s) total=$(( total + num ));;
m) total=$(( total + num * 60 ));;
h) total=$(( total + num * 3600 ));;
esac
done
(( total > 0 )) || return 1
echo "$total"
}
cmd="${1:-}"
case "$cmd" in
add)
when="${2:-}" text="${3:-}"
[[ -n "$when" && -n "$text" ]] || usage
if ! secs="$(seconds_until "$when")"; then
notify-send "Reminder not set" "Could not read \"$when\" — try 20m, 1h30m, or 9:30" 2>/dev/null || true
echo "panama-remind: could not parse \"$when\"" >&2
exit 1
fi
unit="panama-remind-$(date +%s)-$RANDOM"
systemd-run --user --collect --unit="$unit" \
--description="$text" \
--on-active="${secs}s" --timer-property=AccuracySec=1s \
notify-send --urgency=critical --icon=alarm-symbolic "Reminder" "$text" \
>/dev/null
notify-send "Reminder set" "\"$text\" in $when" 2>/dev/null || true
;;
list)
# unit <TAB> when it fires <TAB> message, one per pending reminder.
# `next` is microseconds since the epoch.
systemctl --user list-timers 'panama-remind-*' --output=json 2>/dev/null \
| jq -r '.[] | [.unit, ((.next // 0) / 1000000 | floor)] | @tsv' \
| while IFS=$'\t' read -r unit next; do
desc="$(systemctl --user show "$unit" --property=Description --value 2>/dev/null)"
printf '%s\t%s\t%s\n' "$unit" "$(date -d "@$next" '+%H:%M:%S' 2>/dev/null)" "$desc"
done
;;
pick)
lines="$("$0" list)"
if [[ -z "$lines" ]]; then
notify-send "Reminders" "Nothing pending" 2>/dev/null || true
exit 0
fi
chosen="$(awk -F'\t' '{ printf "%s — %s\n", $3, $2 }' <<<"$lines" \
| vicinae dmenu --navigation-title "Cancel a reminder" \
--section-title 'Pending ({count})' --format index)" || exit 0
[[ -n "$chosen" ]] || exit 0
unit="$(sed -n "$(( chosen + 1 ))p" <<<"$lines" | cut -f1)"
[[ -n "$unit" ]] && "$0" cancel "$unit"
;;
cancel)
unit="${2:-}"; [[ -n "$unit" ]] || usage
systemctl --user stop "${unit%.timer}.timer" 2>/dev/null || true
notify-send "Reminder cancelled" 2>/dev/null || true
;;
*) usage;;
esac