A Windows system tray widget that gives you an actual sense of your Keychron Q6 Max's battery level — something Keychron's own Launcher app and firmware simply don't provide over the 2.4GHz dongle.
Keychron makes genuinely excellent keyboards. Their software is a different story: connect a Q6 Max over its 2.4GHz "Keychron Link" dongle and there is no battery indicator anywhere — not in Launcher, not in Windows, nothing. The only feedback is a blinking red LED under the spacebar once the battery is already critically low, with no way to tell if you're at 80% or 15% before that point.
This project reverse-engineers what's actually possible and ships a practical answer:
- On 2.4G (your daily driver, presumably — lower latency than Bluetooth): a background-estimated battery percentage, modeled from how long the keyboard has been active vs. idle/asleep and Keychron's published battery-life figures.
- On Bluetooth (switch to it for a moment): an exact reading, pulled from the same battery data Windows already uses for its own Bluetooth device battery display.
The full investigation — including why the "obvious" approach (the
documented Keychron mouse battery protocol, adapted for this keyboard) does
not work — is in research/. Short version: this
keyboard's current firmware doesn't answer a battery query over the 2.4G
dongle, confirmed by protocol testing, by reading Keychron Launcher's own
source, and by Launcher's UI showing no battery indicator even wired via
USB-C. Bluetooth's standard GATT Battery Service works immediately.
- 2.4G estimate: polls Windows' system-wide idle timer every 30s and buckets elapsed time into three draw-rate tiers (active/RGB-on, backlight-off-but-awake, fully asleep), based on your keyboard's actual configured timeouts and Keychron's published ~80h (RGB on) / ~180h (RGB off) battery-life figures for a 4000mAh Q6 Max. This is a heuristic, not a real reading — it's clearly labeled as an estimate in the tray tooltip.
- Bluetooth exact reading: rather than fight BLE GATT directly (flaky on
Windows for already-paired devices), this reads the standard Windows PnP
device property (
DEVPKEY_Device_BatteryLevel) that Windows itself populates once your keyboard is paired over Bluetooth — the exact same number Settings → Bluetooth & devices shows you. - Self-recalibration: every time you do a real Bluetooth check, the heuristic estimate resets from that real value, so estimation drift from the 2.4G model doesn't compound indefinitely.
- Tray icon that visually fills/color-codes by battery level (green → amber → red), with a marker distinguishing an estimate from a real reading
- Right-click menu: current estimate, on-demand exact Bluetooth check, "mark as fully charged," Windows autostart toggle
- Persists state across restarts (
%LOCALAPPDATA%\Keychron_Q6_Max_BatteryWidget) - No third-party services, no telemetry, everything local
Grab the latest .exe from Releases and run it. No
installation, no Python required.
Note on antivirus false positives: this is an unsigned, self-contained PyInstaller executable, and Windows Defender's behavioral heuristics will likely flag it — specifically
Behavior:Win32/Persistence.A!ml, triggered by the app writing itself into the Windows startup registry key when you enable "Start with Windows." This is a well-known false-positive pattern for small unsigned tray/autostart utilities and not a sign of anything malicious — see Troubleshooting below. If you'd rather not deal with it at all, build from source (Option B) and you'll see exactly what's running.
git clone <this-repo>
cd Keychron_Q6_Max_BatteryWidget
pip install -r requirements.txt
python main.pyTo build your own standalone exe:
pip install pyinstaller
pyinstaller --noconsole --onefile --windowed --icon=assets\app.ico main.py -n "Keychron Q6 Max Battery Widget"The exe will be in dist/.
Battery-life figures and your keyboard's sleep/backlight timeouts live in
config.py:
BATTERY_CAPACITY_MAH = 4000
HOURS_RGB_ON = 80.0
HOURS_RGB_OFF = 180.0
BACKLIGHT_OFF_TIMEOUT_MINUTES = 10 # match your keyboard's actual setting
AUTO_SLEEP_TIMEOUT_MINUTES = 30 # match your keyboard's actual settingThese are tuned for a stock Q6 Max. If you've changed your keyboard's sleep timers in Launcher (Advance Mode, requires a wired connection), update these to match — the estimate is only as good as these numbers. Other 4000mAh/similar Keychron Max-series boards will likely be close enough to use as-is; check your model's own published battery-life figures if you want a more accurate estimate.
Windows Defender quarantined the exe / flagged it as
Behavior:Win32/Persistence.A!ml. This is a false positive — Defender's
ML heuristics flag any unsigned exe that writes a Windows startup registry
entry, since that's structurally how malware persists too. It's triggered
by the "Start with Windows" feature specifically. To fix:
- Windows Security → Virus & threat protection → Protection history → find the detection → Actions → Restore.
- Add an exclusion for the folder you're running it from: Windows Security → Virus & threat protection → Manage settings → Exclusions → Add an exclusion → Folder.
If you're not comfortable with either, build from source instead (see
above) — nothing about the source is doing anything you can't read for
yourself in autostart.py (it's ~30 lines).
"Check real battery now" says the keyboard wasn't found / no battery level reported. The keyboard needs to be paired over Bluetooth at least once via Windows Settings first (the app doesn't pair devices, only reads already-paired ones), and its physical mode switch needs to be set to BT at the moment you check — 2.4G and Bluetooth are mutually exclusive radio modes on this keyboard, selected by a physical switch software can't override.
The 2.4G estimate seems off. It's a heuristic based on idle time, not a
real reading — see Configuration. Do a real Bluetooth
check occasionally (switch to BT, click "Check real battery now") to
recalibrate it; accuracy between recalibrations depends on how well
config.py's timeouts and hours-per-charge figures match your actual usage
and RGB settings.
Two PowerShell windows flash when checking battery. Should already be
fixed (ble_reader.py uses CREATE_NO_WINDOW) — if you still see this,
you're likely running an older build; pull the latest source and rebuild.
- 2.4G mode does not expose battery. This is a hard limitation of the
keyboard's current firmware / Keychron's dongle protocol, not something
this app can fix in software — see
research/for the full investigation. If Keychron ever ships firmware that answers a battery query over 2.4G, this app could switch to a real live reading; contributions welcome if you get there first. - Heuristic accuracy depends on
config.pymatching your actual keyboard settings and how closely your usage matches Keychron's published best-case battery-life figures. - Windows only (uses
winreg,ctypes.windll, and Windows PnP device properties throughout).
This started as a personal fix for one very specific annoyance, but the underlying problem — Keychron's wireless boards giving you no real battery visibility — likely affects a lot of people.
If you'd like to contribute to this, please do! And if you'd like to chat about this project with me, or other ideas, drop by my twitch when I'm live or send me a message on here if you can!
https://www.twitch.tv/sasirawr
PRs, forks, and issues are welcome, especially if you:
- Have a different Keychron 2.4G board and want to confirm/extend the
protocol findings in
research/ - Want to build proper low-battery notifications, multi-device support, or
a config UI instead of hand-editing
config.py - Have real interest in the bigger goal this points toward: a genuinely open, cross-platform alternative to Keychron Launcher for on-device configuration (remapping, macros, lighting) that also isn't crippled over 2.4G. That's a much bigger project than this widget, but this repo is as good a place as any to start pulling that thread.
Built on protocol facts documented by:
- csutcliff/keychron-battery-dkms — the Keychron mouse battery protocol reference (ultimately not applicable to this keyboard, but the starting point)
- MrAdrianPl/keychron_battery_widget — prior art for a Keychron tray battery widget (M5 mouse, Linux)
- byte-bandit/keychron-m3-linux — passive HID battery listening approach
The actual working protocol details for this keyboard were found by reading Keychron Launcher's own shipped JavaScript, not any of the above.
MIT — see LICENSE.