{ pkgs, config, ...}: { kassouni.palette.commands = [ { name = "bluetooth"; icon = "󰂯"; # `jc --bluetoothctl` turns `bluetoothctl devices` into JSON, so the menu is # built from parsed records rather than by splitting "Device " # lines. That subcommand leaves every status field empty, so connectedness # comes from a second `devices Connected` call intersected on address. exe = pkgs.writers.writePython3 "bluetooth-toggle" { # The interpolated store paths below are longer than 79 columns. flakeIgnore = [ "E501" ]; } '' import json import subprocess import sys BT = "${pkgs.bluez}/bin/bluetoothctl" JC = "${pkgs.jc}/bin/jc" FUZZEL = "${pkgs.fuzzel}/bin/fuzzel" def devices(*args): """Parse `bluetoothctl devices [args]` into a list of records.""" listed = subprocess.run( [BT, "devices", *args], capture_output=True, text=True ) parsed = subprocess.run( [JC, "--bluetoothctl"], input=listed.stdout, capture_output=True, text=True, ) return json.loads(parsed.stdout or "[]") def label(device, connected): name = device["name"] if device["address"] in connected: name += " (connected)" return name def main(): known = devices() if not known: return 0 # Older bluez has no `devices Connected`; that call just comes back # empty there, which degrades to "nothing is connected". connected = {d["address"] for d in devices("Connected")} rows = [label(d, connected) for d in known] choice = subprocess.run( [FUZZEL, "--dmenu", "--index", "--prompt", "bluetooth "], input="\n".join(rows), capture_output=True, text=True, ) if choice.returncode != 0: return 1 index = int(choice.stdout.strip() or -1) if not 0 <= index < len(known): return 1 device = known[index] action = ( "disconnect" if device["address"] in connected else "connect" ) return subprocess.run([BT, action, device["address"]]).returncode if __name__ == "__main__": sys.exit(main()) ''; } ]; }