quickshell: keychron mouse battery in bar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 09:32:46 +01:00
parent 54f7c1123b
commit e18ee80a1a
2 changed files with 102 additions and 0 deletions

View file

@ -280,6 +280,45 @@ in
sys.exit(0)
sys.exit(1)
'';
# Keychron mouse battery, straight off the vendor HID interface
# (usage page 0xffc1) of the Ultra-Link receiver or a wired mouse.
# Report 0xb3 with cmd 0x06 asks for base info; the 0xb4 reply has
# battery at data byte 19 (low 7 bits %, bit 7 charging).
# Protocol lifted from the Keychron Launcher's WebHID bundle.
mouseBatteryScript = pkgs.writeScript "mouse-battery" ''
#!${pkgs.python3}/bin/python3
"""mouse-battery — prints "<pct>" or "<pct> charging"."""
import glob, os, select, sys, time
def iface():
for h in glob.glob("/sys/class/hidraw/hidraw*"):
try:
uevent = open(h + "/device/uevent").read()
desc = open(h + "/device/report_descriptor", "rb").read(3)
except OSError:
continue
if ":00003434:" in uevent and desc == b"\x06\xc1\xff":
return "/dev/" + os.path.basename(h)
return None
dev = iface()
if not dev:
sys.exit(1)
fd = os.open(dev, os.O_RDWR)
os.write(fd, bytes([0xb3, 0x06]).ljust(64, b"\0"))
deadline = time.time() + 1.0
while (left := deadline - time.time()) > 0:
if not select.select([fd], [], [], left)[0]:
break
pkt = os.read(fd, 64)
if len(pkt) > 20 and pkt[0] == 0xb4 and pkt[1] == 0x06:
pct = pkt[20] & 0x7f
if pct == 0:
break # mouse off / asleep
print(f"{pct} charging" if pkt[20] & 0x80 else pct)
sys.exit(0)
sys.exit(1)
'';
weatherFetchScript = pkgs.writeShellScript "weather-fetch" ''
loc=$(${pkgs.curl}/bin/curl -sf --max-time 5 https://ipinfo.io/loc 2>/dev/null || true)
case "$loc" in
@ -880,6 +919,7 @@ in
readonly property string weatherFetch: "${weatherFetchScript}"
readonly property string ssh: "${pkgs.openssh}/bin/ssh"
readonly property string novaAnc: "${novaAncScript}"
readonly property string mouseBattery: "${mouseBatteryScript}"
// Unprivileged ping works via ICMP dgram sockets — NixOS sets
// net.ipv4.ping_group_range for everyone (no wrapper exists).
readonly property string ping: "${pkgs.iputils}/bin/ping"
@ -4065,6 +4105,65 @@ in
}
''}
${lib.optionalString (!isMacbook) ''
// Wireless mouse battery — polled off the Keychron
// receiver via the mouse-battery script. Hidden while the
// mouse is off/asleep or the receiver is unplugged.
Item {
id: mouseBatWidget
visible: level > 0
width: Theme.barWidth
height: mouseBatCol.height
property int level: 0
property bool charging: false
property color tint: charging ? Theme.accent
: level <= 15 ? Theme.err
: level <= 30 ? Theme.warn
: Theme.base05
Process {
id: mouseBatProc
command: [Commands.mouseBattery]
stdout: StdioCollector {
onStreamFinished: {
const f = text.trim().split(" ");
mouseBatWidget.level = parseInt(f[0]) || 0;
mouseBatWidget.charging = f[1] === "charging";
}
}
}
Timer {
interval: 60000
repeat: true
running: true
triggeredOnStart: true
onTriggered: mouseBatProc.running = true
}
Column {
id: mouseBatCol
anchors.horizontalCenter: parent.horizontalCenter
spacing: 0
SIcon {
anchors.horizontalCenter: parent.horizontalCenter
text: "mouse"
color: mouseBatWidget.tint
font.pixelSize: 16
}
SText {
anchors.horizontalCenter: parent.horizontalCenter
text: mouseBatWidget.level
color: mouseBatWidget.tint
font.pixelSize: 10
}
}
}
''}
// Quick settings — present on every host, so the shared
// controls sit in the same place on all of them.
Item {