chopstick
All widgets

Battery

Comes with Chopstick

Battery level, charging state and time left; pops up briefly when the battery drops to 20% on battery power and when you plug in the charger.

Already installed? It's in your widget library.

Type it in the notch's search

  • Show battery

What it runs

A widget is a folder of plain files. Here is all of its code.

widget.json
{
  "id": "battery",
  "name": "Battery",
  "icon": "icon.png",
  "description": "Battery level, charging state and time left; pops up briefly when the battery drops to 20% on battery power and when you plug in the charger.",
  "watch": [
    {
      "script": "battery.sh",
      "every": 60,
      "as": "batt"
    }
  ],
  "triggers": [
    {
      "when": {
        "batt.low": true
      },
      "open": "low"
    },
    {
      "when": {
        "batt.pop": true
      },
      "open": "charging"
    }
  ],
  "initial": "glance",
  "anytime": true,
  "states": {
    "glance": {
      "on_enter": [
        {
          "run": "health.sh"
        }
      ],
      "compact": {
        "leading": {
          "symbol": "{{batt.symbol | battery.100percent}}",
          "tint": "{{batt.tint | gray}}"
        },
        "trailing": {
          "text": "{{batt.label}}"
        }
      },
      "expanded": {
        "size": "compact",
        "header": {
          "symbol": "{{batt.symbol | battery.100percent}}",
          "tint": "{{batt.tint | gray}}",
          "title": "Battery"
        },
        "blocks": [
          {
            "progress": {
              "value": "{{batt.pct | 0}}",
              "max": "100",
              "label": "{{batt.status}}",
              "tint": "{{batt.tint | gray}}"
            }
          },
          {
            "note": "{{batt.time}}"
          },
          {
            "note": "{{result.health}}"
          }
        ]
      }
    },
    "low": {
      "open": false,
      "dismiss_after": 4,
      "compact": {
        "leading": {
          "symbol": "{{batt.symbol | battery.25percent}}",
          "tint": "{{batt.tint | orange}}"
        },
        "trailing": {
          "text": "{{batt.label}}"
        }
      },
      "expanded": {
        "size": "compact",
        "header": {
          "symbol": "{{batt.symbol | battery.25percent}}",
          "tint": "{{batt.tint | orange}}",
          "title": "Battery Low"
        },
        "blocks": [
          {
            "progress": {
              "value": "{{batt.pct | 0}}",
              "max": "100",
              "label": "{{batt.time | On battery}}",
              "tint": "{{batt.tint | orange}}"
            }
          }
        ]
      }
    },
    "charging": {
      "open": false,
      "dismiss_after": 4,
      "compact": {
        "leading": {
          "symbol": "battery.100percent.bolt",
          "tint": "green"
        },
        "trailing": {
          "text": "{{batt.pct}}%"
        }
      },
      "expanded": {
        "size": "compact",
        "header": {
          "symbol": "battery.100percent.bolt",
          "tint": "green",
          "title": "{{batt.status | Charging}}"
        },
        "blocks": [
          {
            "progress": {
              "value": "{{batt.pct | 0}}",
              "max": "100",
              "label": "{{batt.full_in | Plugged in}}",
              "tint": "green"
            }
          }
        ]
      }
    }
  },
  "commands": {
    "show": {
      "description": "Show the battery level, charging state, time left and battery health.",
      "phrase": "Show battery", "preview": { "state": "glance", "run": "health.sh" },
      "do": [
        {
          "go": "glance"
        }
      ]
    }
  },
  "preview": {
    "states": [
      "glance",
      "low",
      "charging"
    ],
    "data": {
      "batt": {
        "present": true,
        "low": true,
        "pop": false,
        "ac": false,
        "pct": 14,
        "symbol": "battery.25percent",
        "tint": "orange",
        "status": "On battery",
        "time": "38m left",
        "full_in": "Full in 1h 50m",
        "label": "14%"
      },
      "result": {
        "cycles": "71",
        "capacity": "98",
        "health": "Maximum capacity 98% · 71 cycles"
      }
    }
  }
}
battery.sh
#!/bin/sh
# Battery state from `pmset -g batt`, as one JSON object.
# `pop` is true for one run after the charger is plugged in; the last power
# source is kept in $CHOPSTICK_STORAGE so a restart doesn't announce it again.
OUT=$(pmset -g batt 2>/dev/null)
LINE=$(printf '%s\n' "$OUT" | grep InternalBattery | head -1)
if [ -z "$LINE" ]; then
  echo '{"present":false,"low":false,"pop":false,"ac":true,"pct":0,"label":"No battery","symbol":"powerplug.fill","tint":"gray","status":"No battery","time":"","full_in":""}'
  exit 0
fi

# " -InternalBattery-0 (id=…)	82%; charging; 1:05 remaining present: true"
PCT=$(printf '%s' "$LINE" | sed -n 's/.*[^0-9]\([0-9][0-9]*\)%;.*/\1/p')
STATE=$(printf '%s' "$LINE" | sed -n 's/.*%; *\([^;]*\);.*/\1/p')
HM=$(printf '%s' "$LINE" | sed -n 's/.*; *\([0-9][0-9]*:[0-9][0-9]\) remaining.*/\1/p')
[ -n "$PCT" ] || PCT=0
case "$OUT" in *"'AC Power'"*) AC=true ;; *) AC=false ;; esac

# "1:05" -> "1h 5m"; nothing for "0:00" or no estimate.
DUR=""
if [ -n "$HM" ] && [ "$HM" != "0:00" ]; then
  H=${HM%%:*}; M=$(expr "${HM##*:}" + 0)
  if [ "$H" -gt 0 ]; then DUR="${H}h ${M}m"; else DUR="${M}m"; fi
fi

TIME=""; FULL=""
case "$STATE" in
  charging)          STATUS="Charging"; [ -n "$DUR" ] && FULL="Full in $DUR" ;;
  "finishing charge") STATUS="Finishing charge"; [ -n "$DUR" ] && FULL="Full in $DUR" ;;
  charged)           STATUS="Charged" ;;
  "AC attached")     STATUS="Plugged in, not charging" ;;
  *)                 STATUS="On battery"; [ -n "$DUR" ] && TIME="$DUR left" ;;
esac
[ -n "$FULL" ] && TIME="$FULL"

if [ "$AC" = true ]; then
  SYMBOL="battery.100percent.bolt"; TINT="green"; LOW=false
else
  if   [ "$PCT" -le 10 ]; then SYMBOL="battery.0percent"
  elif [ "$PCT" -le 35 ]; then SYMBOL="battery.25percent"
  elif [ "$PCT" -le 60 ]; then SYMBOL="battery.50percent"
  elif [ "$PCT" -le 85 ]; then SYMBOL="battery.75percent"
  else SYMBOL="battery.100percent"; fi
  if   [ "$PCT" -le 10 ]; then TINT="red"; LOW=true
  elif [ "$PCT" -le 20 ]; then TINT="orange"; LOW=true
  else TINT="#FFFFFF"; LOW=false; fi
fi

# Pop once per plug-in: only on a change from battery to AC we saw ourselves.
POP=false
F="$CHOPSTICK_STORAGE/power"
NOW=battery; [ "$AC" = true ] && NOW=ac
if [ -n "$CHOPSTICK_STORAGE" ]; then
  PREV=$(cat "$F" 2>/dev/null)
  [ "$NOW" = ac ] && [ "$PREV" = battery ] && POP=true
  if [ "$NOW" != "$PREV" ]; then mkdir -p "$CHOPSTICK_STORAGE" && echo "$NOW" > "$F"; fi
fi

printf '{"present":true,"low":%s,"pop":%s,"ac":%s,"pct":%d,"label":"%d%%","symbol":"%s","tint":"%s","status":"%s","time":"%s","full_in":"%s"}\n' \
  "$LOW" "$POP" "$AC" "$PCT" "$PCT" "$SYMBOL" "$TINT" "$STATUS" "$TIME" "$FULL"
health.sh
#!/bin/sh
# Cycle count and maximum capacity from ioreg (run when the card opens).
IO=$(ioreg -rc AppleSmartBattery 2>/dev/null)
val() { printf '%s\n' "$IO" | sed -n "s/^[ |]*\"$1\" = \([0-9][0-9]*\)\$/\1/p" | head -1; }
CYCLES=$(val CycleCount); MAX=$(val MaxCapacity); DESIGN=$(val DesignCapacity)
# Apple silicon reports MaxCapacity as a percent; Intel Macs in mAh.
CAP=""
if [ -n "$MAX" ]; then
  if [ "$MAX" -le 100 ]; then CAP=$MAX
  elif [ -n "$DESIGN" ] && [ "$DESIGN" -gt 0 ]; then CAP=$((MAX * 100 / DESIGN)); fi
fi
LINE=""
[ -n "$CAP" ] && LINE="Maximum capacity $CAP%"
[ -n "$CYCLES" ] && LINE="${LINE:+$LINE · }$CYCLES cycles"
printf '{"cycles":"%s","capacity":"%s","health":"%s"}\n' "$CYCLES" "$CAP" "$LINE"