A Waybar custom module that shows AirPods battery levels (left, right, case) with click-to-connect/disconnect.
Uses Apple's AAP (Apple Accessory Protocol) over a BlueZ Profile1 L2CAP socket to read battery notifications directly from the AirPods — no polling, no hacks.
| Disconnected | Connecting | Connected |
|---|---|---|
![]() |
![]() |
![]() |
- Real-time battery for left bud, right bud, and case
- Charging indicator (⚡) in tooltip
- Click to connect/disconnect with visual "connecting" feedback
- Duplicate click prevention while connecting
- Auto-reconnects when AirPods come back in range
- Linux with BlueZ 5
- Python 3 with PyGObject (
gi— GLib/Gio bindings) - Waybar
bluetoothctlandrfkill(for click-to-toggle)
Arch:
sudo pacman -S python-gobject bluez bluez-utilsDebian/Ubuntu:
sudo apt install python3-gi gir1.2-glib-2.0 bluezFedora:
sudo dnf install python3-gobject bluezPair your AirPods via your desktop's Bluetooth settings or bluetoothctl, then find the MAC:
bluetoothctl devices | grep -i airpodsOutput looks like:
Device AA:BB:CC:DD:EE:FF XXXX's AirPods
cp airpods.py ~/.config/waybar/airpods.py
chmod +x ~/.config/waybar/airpods.pyAdd "custom/airpods" to your bar's module list in ~/.config/waybar/config.jsonc:
Then add the module config (replace the MAC with yours):
"custom/airpods": {
"exec": "~/.config/waybar/airpods.py AA:BB:CC:DD:EE:FF",
"return-type": "json",
"format": "{icon} ",
"format-icons": {
"connected": "",
"disconnected": "",
"connecting": ""
},
"on-click": "pkill -USR1 -f 'airpods\\.py'",
"tooltip": true
}Alternatively, set the MAC via environment variable and omit it from the exec line:
export AIRPODS_MAC="AA:BB:CC:DD:EE:FF"Add to ~/.config/waybar/style.css:
#custom-airpods {
margin-right: 17px;
}
#custom-airpods.disconnected {
opacity: 0.4;
}
#custom-airpods.connecting {
opacity: 0.6;
}
#custom-airpods.warning {
color: #f9e2af;
}
#custom-airpods.critical {
color: #f38ba8;
}killall waybar && waybar &- The script registers a BlueZ Profile1 for the AAP UUID (
74ec2172-0bad-4d01-8f77-997b2be0722a) - When AirPods connect, BlueZ hands over an L2CAP file descriptor via
NewConnection - The script sends a handshake + notification request over the socket
- AirPods respond with battery packets containing left/right/case levels and charging status
- The script parses these and outputs JSON to stdout for Waybar
- D-Bus
PropertiesChangedsignals detect connect/disconnect events - Clicking the module sends
SIGUSR1to toggle the connection
| Class | Meaning |
|---|---|
disconnected |
AirPods not connected |
connecting |
Connection in progress |
good |
Battery > 40% |
warning |
Battery 21-40% |
critical |
Battery ≤ 20% |
AAP protocol based on Bluetooth-Battery-Meter by maniacx.
This has only been tested with Airpods 2nd Gen, but others should work.
Full transparency: This project was built with heavy AI assistance. I provided the direction and requirements, but AI did most of the heavy lifting on implementation.


