mirror of
https://github.com/vincentbernat/i3wm-configuration.git
synced 2025-07-21 11:24:28 +02:00
i3-companion: get battery level from BlueZ
This is made available with PulseAudio 16, exposed to BlueZ with experimental support. Also available in upower, but we are already interacting with BlueZ, so...
This commit is contained in:
parent
4cc25a371c
commit
3f312ed417
1 changed files with 16 additions and 53 deletions
|
@ -739,18 +739,16 @@ async def bluetooth_notifications(i3, event, path, interface, changed, invalid):
|
|||
and "Connected" in args[1]
|
||||
or args[0] == "org.bluez.Adapter1"
|
||||
and "Powered" in args[1]
|
||||
or args[0] == "org.bluez.Battery1"
|
||||
and "Percentage" in args[1]
|
||||
),
|
||||
),
|
||||
)
|
||||
@static(scheduled=None)
|
||||
@retry(2)
|
||||
@debounce(0.2)
|
||||
@polybar("bluetooth")
|
||||
async def bluetooth_status(i3, event, *args):
|
||||
"""Update bluetooth status for Polybar."""
|
||||
if bluetooth_status.scheduled:
|
||||
bluetooth_status.scheduled.cancel()
|
||||
bluetooth_status.scheduled = None
|
||||
if event is StartEvent:
|
||||
# Do we have a bluetooth device?
|
||||
if not os.path.exists("/sys/class/bluetooth"):
|
||||
|
@ -772,17 +770,18 @@ async def bluetooth_status(i3, event, *args):
|
|||
powered = True
|
||||
elif "org.bluez.Device1" in interfaces:
|
||||
# We have a device!
|
||||
device = interfaces["org.bluez.Device1"]
|
||||
if not device["Connected"][1]:
|
||||
device = types.SimpleNamespace(major=0, minor=0, battery=None)
|
||||
interface = interfaces["org.bluez.Device1"]
|
||||
if not interface["Connected"][1]:
|
||||
continue
|
||||
try:
|
||||
device_class = device["Class"][1]
|
||||
major = (device_class & 0x1F00) >> 8
|
||||
minor = (device_class & 0xFC) >> 2
|
||||
mac = device["Address"][1]
|
||||
devices.append((major, minor, mac))
|
||||
device_class = interface["Class"][1]
|
||||
device.major = (device_class & 0x1F00) >> 8
|
||||
device.minor = (device_class & 0xFC) >> 2
|
||||
device.battery = interfaces["org.bluez.Battery1"]["Percentage"][1]
|
||||
except KeyError:
|
||||
devices.append((0, 0, ""))
|
||||
pass
|
||||
devices.append(device)
|
||||
|
||||
# Choose appropriate icons for output
|
||||
# See: https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers
|
||||
|
@ -791,7 +790,7 @@ async def bluetooth_status(i3, event, *args):
|
|||
output = ""
|
||||
else:
|
||||
output = ["bluetooth"]
|
||||
for major, minor, mac in devices:
|
||||
for device in devices:
|
||||
classes = {
|
||||
1: "laptop",
|
||||
2: "phone",
|
||||
|
@ -817,54 +816,18 @@ async def bluetooth_status(i3, event, *args):
|
|||
(lambda x: x & 0x20, "printer"),
|
||||
],
|
||||
}
|
||||
icon = classes.get((major, minor)) or classes.get(major, "unknown")
|
||||
icon = classes.get((device.major, device.minor)) or classes.get(device.major, "unknown")
|
||||
if type(icon) is list:
|
||||
for matcher, name in icon:
|
||||
if matcher(minor):
|
||||
if matcher(device.minor):
|
||||
icon = name
|
||||
break
|
||||
else:
|
||||
icon = "unknown"
|
||||
output.append(icon)
|
||||
if mac.startswith("2C:41:A1"):
|
||||
# Get battery status for BOSE QC 35 II. This is hacky.
|
||||
# For other headsets, the information could be
|
||||
# available through upower DBus. See
|
||||
# https://github.com/Denton-L/based-connect. Starting
|
||||
# from PA 16, it may be exposed by PA, then to BlueZ.
|
||||
# See
|
||||
# https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/482
|
||||
loop = asyncio.get_event_loop()
|
||||
sock = socket.socket(
|
||||
socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM
|
||||
)
|
||||
try:
|
||||
sock.setblocking(False)
|
||||
# Workaround a bug in asyncio: https://bugs.python.org/issue27929
|
||||
fut = loop.create_future()
|
||||
loop._sock_connect(fut, sock, (mac, 8))
|
||||
await fut
|
||||
# Get battery
|
||||
for send, ack, size in (
|
||||
(b"\0\1\1\0", b"\0\1\3\5", 5), # get firmware
|
||||
(b"\2\2\1\0", b"\2\2\3\1", 1), # get battery
|
||||
):
|
||||
await loop.sock_sendall(sock, send)
|
||||
rack = await loop.sock_recv(sock, len(ack))
|
||||
assert rack == ack, "incorrect ack received"
|
||||
result = await loop.sock_recv(sock, size)
|
||||
battery = result[0]
|
||||
finally:
|
||||
sock.close()
|
||||
# Choose an icon
|
||||
icon = f"battery-{(battery+12)//25*25}"
|
||||
if device.battery is not None:
|
||||
icon = f"battery-{(device.battery+12)//25*25}"
|
||||
output[-1] = (output[-1], icon)
|
||||
|
||||
# Schedule a refresh in 5 minutes
|
||||
bluetooth_status.scheduled = loop.call_later(
|
||||
600,
|
||||
lambda: asyncio.create_task(bluetooth_status(i3, StartEvent)),
|
||||
)
|
||||
return "|".join(
|
||||
(" ".join(icons[oo] for oo in o) if type(o) is tuple else icons[o])
|
||||
for o in output
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue