main dotfiles / dot_config / sway / status.py
Eric Bower  ·  2026-09-02
  1#!/usr/bin/env python3
  2import json
  3import os
  4import shutil
  5import subprocess
  6import sys
  7import threading
  8import time
  9
 10def get_sound():
 11    try:
 12        muted = subprocess.check_output(["pamixer", "--get-mute"], text=True, stderr=subprocess.DEVNULL).strip() == "true"
 13        vol = subprocess.check_output(["pamixer", "--get-volume"], text=True, stderr=subprocess.DEVNULL).strip()
 14        label = "MUTED" if muted else "VOL"
 15        return f"{label} {vol}%"
 16    except Exception:
 17        return "VOL ?"
 18
 19def get_battery():
 20    if not os.path.exists("/sys/class/power_supply"):
 21        return None
 22
 23    try:
 24        for bat in os.listdir("/sys/class/power_supply"):
 25            if not bat.startswith("BAT"):
 26                continue
 27
 28            path = f"/sys/class/power_supply/{bat}"
 29            cap_file = f"{path}/capacity"
 30            if not os.path.exists(cap_file):
 31                continue
 32
 33            with open(cap_file) as f:
 34                cap = f.read().strip()
 35
 36            status = "BAT"
 37            stat_file = f"{path}/status"
 38            if os.path.exists(stat_file):
 39                with open(stat_file) as f:
 40                    if f.read().strip() == "Charging":
 41                        status = "CHR"
 42
 43            return f"{status} {cap}%"
 44    except Exception:
 45        pass
 46
 47    return None
 48
 49def get_brightness():
 50    try:
 51        act = subprocess.check_output(["brightnessctl", "-c", "backlight", "get"], text=True, stderr=subprocess.DEVNULL).strip()
 52        max_b = subprocess.check_output(["brightnessctl", "-c", "backlight", "max"], text=True, stderr=subprocess.DEVNULL).strip()
 53        return f"BRT {int(int(act) * 100 / int(max_b))}%"
 54    except Exception:
 55        return None
 56
 57def get_wifi_ssid(iface):
 58    try:
 59        out = subprocess.check_output(["iw", "dev", iface, "link"], text=True, stderr=subprocess.DEVNULL)
 60        for line in out.splitlines():
 61            if "SSID:" in line:
 62                return line.split("SSID:", 1)[1].strip()
 63    except Exception:
 64        pass
 65    return None
 66
 67def get_net():
 68    try:
 69        route = subprocess.check_output(["ip", "route", "show", "default"], text=True, stderr=subprocess.DEVNULL)
 70    except Exception:
 71        return "NET Disconnected"
 72
 73    if "dev" not in route:
 74        return "NET Disconnected"
 75
 76    iface = route.split("dev")[1].split()[0]
 77    if not iface.startswith(("wl", "wlan")):
 78        return f"ETH {iface}"
 79
 80    ssid = get_wifi_ssid(iface)
 81    return f"NET {ssid}" if ssid else f"NET {iface}"
 82
 83def get_time():
 84    return time.strftime("%I:%M %m/%d").lstrip("0")
 85
 86def generate_blocks():
 87    blocks = [
 88        {"name": "sound", "full_text": get_sound(), "color": "#a6e22e"},
 89    ]
 90
 91    bat = get_battery()
 92    if bat:
 93        blocks.append({"name": "battery", "full_text": bat, "color": "#e6db74"})
 94
 95    bright = get_brightness()
 96    if bright:
 97        blocks.append({"name": "brightness", "full_text": bright, "color": "#66d9ef"})
 98
 99    blocks.extend([
100        {"name": "wifi", "full_text": get_net(), "color": "#ae81ff"},
101        {"name": "clock", "full_text": get_time(), "color": "#f8f8f2"},
102    ])
103
104    return blocks
105
106def handle_click(block_name, button):
107    if button != 1:
108        return
109
110    if block_name == "wifi":
111        cmd = ["monstar", "-e", "nmtui"] if shutil.which("monstar") else ["foot", "-e", "nmtui"]
112        subprocess.Popen(cmd)
113    elif block_name == "sound":
114        cmd = ["pavucontrol"] if shutil.which("pavucontrol") else ["pamixer", "-t"]
115        subprocess.Popen(cmd)
116
117def listen_clicks():
118    while True:
119        try:
120            line = sys.stdin.readline()
121            if not line:
122                break
123            clean_line = line.strip().lstrip("[,").rstrip(",")
124            if not clean_line:
125                continue
126
127            event = json.loads(clean_line)
128            handle_click(event.get("name"), event.get("button", 1))
129        except Exception:
130            pass
131
132def main():
133    try:
134        print(json.dumps({"version": 1, "click_events": True}))
135        print("[")
136        sys.stdout.flush()
137
138        threading.Thread(target=listen_clicks, daemon=True).start()
139
140        while True:
141            print(json.dumps(generate_blocks()) + ",")
142            sys.stdout.flush()
143            time.sleep(1)
144    except (BrokenPipeError, KeyboardInterrupt):
145        try:
146            sys.stdout.close()
147        except Exception:
148            pass
149        sys.exit(0)
150
151if __name__ == "__main__":
152    main()