A macOS menu bar app that displays battery levels from ZMK split keyboards via BLE.
- Two-line status bar display — Shows Central (C) and Peripheral (P) battery levels with custom-drawn battery icons
- BLE Battery Service — Reads battery levels via standard BLE Battery Service (0x180F) with notification subscription
- Auto Central/Peripheral detection — Uses BLE descriptor (User Description) to identify each half of the split keyboard
- Multiple keyboard support — Register and switch between multiple ZMK keyboards
- Customizable side labels — Override the default
C/Pmenu bar labels withL/Rper keyboard to match your physical left/right layout - Single line layout — Optionally show both battery levels on one line (e.g.
C50% P50%) - Hide battery icon — Optionally hide the battery icons and show only the labels and percentages
- Swap battery positions — Optionally swap the order of the two battery rows in the menu bar
- Auto-reconnect — Automatically reconnects with exponential backoff when the keyboard disconnects
- Launch at Login — Optional auto-start via SMAppService
- macOS 14 (Sonoma) or later
- A ZMK-powered split keyboard with BLE Battery Service enabled
brew install --cask itouuuuuuuuu/tap/zmk-battery-bar- Launch ZMK Battery Bar — it appears in the menu bar with
CandPbattery levels - Click the status bar item to open the popover
- If no keyboard is connected, click Add Keyboard... to scan for BLE devices
- Select your ZMK keyboard from the discovered devices list
- Battery levels update automatically via BLE notifications
The menu bar shows C (Central) and P (Peripheral) by default. To display L / R instead:
- Open the popover and locate the
[ L ][ R ]buttons on the right of each battery row - Tap
LorRon the Central row to assign that letter to the central side (the peripheral row flips automatically) - Tap the same letter again to revert to
C/P
Labels are stored per keyboard, so each registered keyboard keeps its own assignment.
- On launch, the app connects to the previously saved keyboard via
retrievePeripherals(withIdentifiers:) - It discovers the BLE Battery Service (0x180F) and Battery Level Characteristic (0x2A19)
- Descriptor
0x2901(User Description) is read to determine Central vs Peripheral - Battery level notifications are subscribed to for real-time updates, with 60-second polling as fallback
- The status bar icon is rendered as an
NSImageusing SwiftUIImageRenderer
If the Peripheral battery level is not displayed, add the following to your Central side's .conf file:
CONFIG_ZMK_BATTERY_REPORTING=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y# Debug build
swift build
# Build and launch local .app bundle (ad-hoc signing)
./scripts/build-app.sh
open "build/ZMK Battery Bar.app"
# Install to /Applications
cp -r "build/ZMK Battery Bar.app" /Applications/
# Release .app bundle with Developer ID signing
./scripts/build-app.sh "Developer ID Application: Your Name (TEAMID)" "1.0.0"Releases are automated via GitHub Actions. Pushing a version tag triggers the full pipeline:
git tag v1.2.0
git push origin v1.2.0This will automatically:
- Build the app in release mode
- Sign with Developer ID certificate (hardened runtime)
- Notarize with Apple and staple the ticket
- Create a GitHub Release with the signed zip
- Update the Homebrew Cask with the new version and SHA256
| Secret | Description |
|---|---|
DEVELOPER_ID_CERTIFICATE_BASE64 |
Base64-encoded .p12 certificate |
DEVELOPER_ID_CERTIFICATE_PASSWORD |
Password for the .p12 file |
APPLE_ID |
Apple ID email for notarization |
APPLE_ID_PASSWORD |
App-specific password for notarization |
APPLE_TEAM_ID |
Apple Developer Team ID |
HOMEBREW_TAP_TOKEN |
GitHub PAT with write access to homebrew-tap repo |
export APPLE_ID="your@email.com"
export APPLE_ID_PASSWORD="xxxx-xxxx-xxxx-xxxx"
export APPLE_TEAM_ID="XXXXXXXXXX"
./scripts/release.sh 1.2.0 "Developer ID Application: Your Name (TEAMID)"| Directory | Description |
|---|---|
Sources/.../App/ |
Entry point, AppDelegate, NSStatusItem, NSPanel popover |
Sources/.../BLE/ |
CoreBluetooth manager, device discovery, battery reading |
Sources/.../Views/ |
SwiftUI views (status bar, popover, keyboard list) |
Sources/.../Models/ |
BatteryState (@Observable), AppSettings (UserDefaults), KeyboardDevice |
Sources/.../Utilities/ |
Launch at login (SMAppService wrapper) |
ZMK Battery Bar is free and open source. If it's been useful to you, you can support continued development via the Sponsor button at the top of this repository, or directly at https://github.com/sponsors/itouuuuuuuuu.
MIT
