summaryrefslogtreecommitdiff
path: root/users/kassouni/palette/bluetooth.nix
diff options
context:
space:
mode:
Diffstat (limited to 'users/kassouni/palette/bluetooth.nix')
-rw-r--r--users/kassouni/palette/bluetooth.nix80
1 files changed, 80 insertions, 0 deletions
diff --git a/users/kassouni/palette/bluetooth.nix b/users/kassouni/palette/bluetooth.nix
new file mode 100644
index 0000000..58cdf2f
--- /dev/null
+++ b/users/kassouni/palette/bluetooth.nix
@@ -0,0 +1,80 @@
+{ pkgs, config, ...}:
+{
+
+ kassouni.palette.commands = [
+ {
+ name = "toggle bluetooth device";
+ icon = "󰂯";
+ # `jc --bluetoothctl` turns `bluetoothctl devices` into JSON, so the menu is
+ # built from parsed records rather than by splitting "Device <addr> <name>"
+ # 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 html
+ import json
+ import subprocess
+ import sys
+
+ BT = "${pkgs.bluez}/bin/bluetoothctl"
+ JC = "${pkgs.jc}/bin/jc"
+ ROFI = "${pkgs.rofi}/bin/rofi"
+
+
+ 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):
+ """Pango markup row. Names are escaped, they are not our text."""
+ name = html.escape(device["name"])
+ if device["address"] in connected:
+ name += ' <span alpha="45%">connected</span>'
+ 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(
+ [ROFI, "-dmenu", "-i", "-markup-rows", "-format", "i",
+ "-p", "bluetooth"],
+ input="\n".join(rows),
+ capture_output=True,
+ text=True,
+ )
+ if choice.returncode != 0:
+ return 1
+
+ device = known[int(choice.stdout.strip())]
+ action = (
+ "disconnect" if device["address"] in connected else "connect"
+ )
+ return subprocess.run([BT, action, device["address"]]).returncode
+
+
+ if __name__ == "__main__":
+ sys.exit(main())
+ '';
+ }
+ ];
+}