diff options
Diffstat (limited to 'i3blocks/.local/bin')
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-cpu | 42 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-disk | 28 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-dunst | 146 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-gpu | 30 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-memory | 30 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-miniflux | 39 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-net | 54 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-notmuch | 33 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-title | 17 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-volume | 128 | ||||
-rwxr-xr-x | i3blocks/.local/bin/i3blocks-wttr | 152 |
11 files changed, 699 insertions, 0 deletions
diff --git a/i3blocks/.local/bin/i3blocks-cpu b/i3blocks/.local/bin/i3blocks-cpu new file mode 100755 index 0000000..ddb1d68 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-cpu @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +import os +import json + +ICON = "\U000f0ee0" # +BLACK = f"#{os.environ.get("BASE16_COLOR_00_HEX")}" +RED = f"#{os.environ.get("BASE16_COLOR_08_HEX")}" +YELLOW = f"#{os.environ.get("BASE16_COLOR_0A_HEX")}" +AQUA = f"#{os.environ.get("BASE16_COLOR_0C_HEX")}" + + +def main(): + off = json.loads(os.environ.get("values", json.dumps([0 for i in range(10)]))) + + with open("/proc/stat", "r") as f: + val = [int(x) for x in f.readline().strip().split(" ")[2:]] + + if elapsed := sum(val + off): + usage = sum(val[0:3] + val[5:] + off[0:3] + off[5:]) / elapsed + else: + usage = 0 + + output = { + "full_text": " {} {:.0%} ".format(ICON, usage), + "values": [-x for x in val], + } + if usage > 0.9: + output["color"] = BLACK + output["background"] = RED + elif usage > 0.6: + output["color"] = BLACK + output["background"] = YELLOW + elif usage > 0.3: + output["color"] = BLACK + output["background"] = AQUA + + print(json.dumps(output, ensure_ascii=False), flush=True) + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-disk b/i3blocks/.local/bin/i3blocks-disk new file mode 100755 index 0000000..a98a40c --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-disk @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import os + +ICON = "\U000f02ca" # +GB = 1073741824 + + +def main(): + statvfs = os.statvfs("/") + avail = bytes = statvfs.f_bavail * statvfs.f_frsize + + for unit in ["B", "KB", "MB", "GB", "TB", "PB"]: + if avail < 1024.0 or unit == "PB": + break + avail /= 1024.0 + + print(f" {ICON} {round(avail)}{unit} \n") + if bytes < (20 * GB): + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}") + elif bytes < (10 * GB): + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}") + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-dunst b/i3blocks/.local/bin/i3blocks-dunst new file mode 100755 index 0000000..df3d9d4 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-dunst @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +import asyncio +import os +import sys +import json +import pprint + +from dbus_next.aio import MessageBus +import i3ipc +from i3ipc.aio import Connection + + +BUS_NAME = "org.freedesktop.Notifications" +OBJECT_PATH = "/org/freedesktop/Notifications" +DUNST_INTERFACE = "org.dunstproject.cmd0" +PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties" +I3_EVENTS = [ + i3ipc.Event.WINDOW_FOCUS, + i3ipc.Event.WINDOW_FULLSCREEN_MODE, + i3ipc.Event.WINDOW_CLOSE, + i3ipc.Event.WORKSPACE_FOCUS, +] + + +class Dunst: + @classmethod + async def connect(cls): + self = cls() + self.bus = await MessageBus().connect() + + self.obj = self.bus.get_proxy_object( + BUS_NAME, + OBJECT_PATH, + await self.bus.introspect(BUS_NAME, OBJECT_PATH), + ) + + self.dunst = self.obj.get_interface(DUNST_INTERFACE) + self.paused = await self.dunst.get_paused() + self.displayed = await self.dunst.get_displayed_length() + self.waiting = await self.dunst.get_waiting_length() + + self.i3 = await Connection().connect() + + for event in I3_EVENTS: + self.i3.on(event, self.handle_i3_event) + + return self + + async def handle_i3_event(self, conn, e): + if e.change == "close": + enable = 0 + elif isinstance(e, i3ipc.WorkspaceEvent): + enable = e.current.fullscreen_mode + else: + enable = e.container.fullscreen_mode + + await self.dunst.call_rule_enable("transient_skip", enable) + + + def print_status(self): + if self.paused: + icon = "\U000f009b" # + else: + icon = "\U000f009a" # + + if self.waiting > 0: + count = f" {self.waiting}" + else: + count = "" + + output = {"full_text": f" {icon}{count} "} + + if self.displayed + self.waiting > 0: + output["color"] = f"#{os.environ.get("BASE16_COLOR_00_HEX")}" + output["background"] = f"#{os.environ.get("BASE16_COLOR_0C_HEX")}" + elif self.paused: + output["color"] = f"#{os.environ.get("BASE16_COLOR_00_HEX")}" + output["background"] = f"#{os.environ.get("BASE16_COLOR_0A_HEX")}" + + print(json.dumps(output, ensure_ascii=False), flush=True) + + def on_properties_changed(self, _name, property, invalidated): + update = False + for property, variant in property.items(): + match property: + case "paused": + self.paused = variant.value + case "displayedLength": + self.displayed = variant.value + case "waitingLength": + self.waiting = variant.value + case _: + continue + update = True + + if update: + self.print_status() + + async def listener(self): + properties = self.obj.get_interface(PROPERTIES_INTERFACE) + properties.on_properties_changed(self.on_properties_changed) + + await self.bus.wait_for_disconnect() + + async def button_handler(self): + loop = asyncio.get_event_loop() + reader = asyncio.StreamReader() + protocol = asyncio.StreamReaderProtocol(reader) + await loop.connect_read_pipe(lambda: protocol, sys.stdin) + + while True: + line = await reader.readline() + + if not line: + await asyncio.sleep(1) + continue + + match json.loads(line): + case {"button": 1}: + await self.dunst.call_notification_show() + case {"button": 2}: + pass + case {"button": 3}: + await self.dunst.set_paused(not self.paused) + case {"button": 4}: + pass + case {"button": 5}: + pass + + +async def main(): + dunst = await Dunst.connect() + dunst.print_status() + + try: + async with asyncio.TaskGroup() as task_group: + task_group.create_task(dunst.i3.main()) + task_group.create_task(dunst.listener()) + task_group.create_task(dunst.button_handler()) + except asyncio.CancelledError: + return + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/i3blocks/.local/bin/i3blocks-gpu b/i3blocks/.local/bin/i3blocks-gpu new file mode 100755 index 0000000..df96d6c --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-gpu @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import os +import glob + +ICON = "\U000f0379" # + + +def main(): + files = glob.glob("/sys/class/drm/card*/device/gpu_busy_percent") + if not files: + return + + with open(files[0]) as f: + load = int(f.readline()) + + print(f" {ICON} {load}% \n") + if load > 90: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}") + elif load > 60: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}") + elif load > 30: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_0C_HEX")}") + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-memory b/i3blocks/.local/bin/i3blocks-memory new file mode 100755 index 0000000..f62d5c7 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-memory @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import os +import linecache + +ICON = "\U000f035b" # + + +def main(): + linecache.getline("/proc/meminfo", 0) + linecache.getline("/proc/meminfo", 2) + + with open("/proc/meminfo", "r") as f: + meminfo = dict( + (i.split()[0].rstrip(":"), int(i.split()[1])) for i in f.readlines() + ) + + used = 100 - round(100 * meminfo["MemAvailable"] / meminfo["MemTotal"]) + + print(f" {ICON} {used}% \n") + if used > 90: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}") + elif used > 75: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}") + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-miniflux b/i3blocks/.local/bin/i3blocks-miniflux new file mode 100755 index 0000000..f87f1d3 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-miniflux @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +import requests + +ICON = "\U000f046b" # + + +def get_count(): + url = "https://miniflux.tobyvin.dev/v1/feeds/counters" + headers = {"X-Auth-Token": os.environ.get("MINIFLUX_TOKEN")} + resp = requests.get(url, headers=headers) + return sum(resp.json().get("unreads").values()) + + +def main(): + match int(os.environ.get("BLOCK_BUTTON", "0")): + case 1: + subprocess.run(["xdg-open", "https://miniflux.tobyvin.dev"]) + + try: + count = get_count() + except requests.exceptions.ConnectionError: + count = "x" + + print(f" {ICON} {count} \n") + match count: + case str(c) | int(c) if c > 10: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}") + case int(c) if c > 0: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}") + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-net b/i3blocks/.local/bin/i3blocks-net new file mode 100755 index 0000000..2faa071 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-net @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import glob +import os +from enum import Enum + + +class IFaceType(Enum): + ETHERNET = "\U000f0200" + WIRELESS = "\U000f05a9" + TUNNEL = "\U000f0582" + UNKNOWN = "" + + @staticmethod + def from_path(path: str): + try: + with open(os.path.join(path, "type"), "r") as f: + match int(f.readline()): + case 1 if os.path.isdir( + os.path.join(path, "wireless") + ) or os.path.islink(os.path.join(path, "phy80211")): + return IFaceType.WIRELESS + case 1: + return IFaceType.ETHERNET + case 65534: + return IFaceType.TUNNEL + case _: + raise ValueError("Invalid interface type") + except Exception: + return IFaceType.UNKNOWN + + +class IFace: + def __init__(self, path: str): + self.path = path + self.name = os.path.basename(path) + self._type = IFaceType.from_path(path) + + def __str__(self): + return self._type.value + + +def main(): + ifaces = [] + for net in glob.glob("/sys/class/net/*"): + iface = IFace(net) + if iface.name != "lo" and iface._type != IFaceType.UNKNOWN: + ifaces.append(str(iface)) + + print(f" {" ".join(ifaces)} \n") + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-notmuch b/i3blocks/.local/bin/i3blocks-notmuch new file mode 100755 index 0000000..a81fae6 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-notmuch @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import notmuch +import os + +ICON = "\U000f01ee" # + + +def main(): + # Workaround for bug in the notmuch module's default config resolution + if os.environ.get("NOTMUCH_CONFIG") is None: + os.environ["NOTMUCH_CONFIG"] = os.path.join( + os.environ.get( + "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config") + ), + "notmuch", + os.environ.get("NOTMUCH_PROFILE", "default"), + "config", + ) + + count = notmuch.Database().create_query("tag:unread").count_messages() + + print(f" {ICON} {count} \n") + if count > 10: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}") + elif count > 0: + print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}") + print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}") + + +if __name__ == "__main__": + main() diff --git a/i3blocks/.local/bin/i3blocks-title b/i3blocks/.local/bin/i3blocks-title new file mode 100755 index 0000000..c014adc --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-title @@ -0,0 +1,17 @@ +#!/bin/sh + +into_block='{ + "full_text": (.name[0:50] // ""), + "short_text": ([.app_id, .window_title, .name] | map(select(. != null and . != "")) | (first // "") | .[0:25]), +}' + +swaymsg -t get_tree | jq -c '.. | select(.focused? == true and .type == "con") | '"$into_block" + +swaymsg --monitor -t subscribe '["window", "workspace"]' | jq -c --unbuffered ' + select((.change | test("close|focus|title")) and (.current.type == "workspace" or .container.focused)) | + if .current.type == "workspace" or .change == "close" then + { "full_text": "" } + else + .container | '"$into_block"' + end +' diff --git a/i3blocks/.local/bin/i3blocks-volume b/i3blocks/.local/bin/i3blocks-volume new file mode 100755 index 0000000..40854e7 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-volume @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 + +import asyncio +import json +import os +import subprocess +import sys + + +ICONS = { + "mute": "\U000f075f", # + "low": "\U000f057f", # + "medium": "\U000f0580", # + "high": "\U000f057e", # +} + + +def is_muted(): + return ( + subprocess.run( + ["pactl", "get-sink-mute", "@DEFAULT_SINK@"], + capture_output=True, + encoding="UTF-8", + ) + .stdout.removeprefix("Mute: ") + .strip() + == "yes" + ) + + +def get_volume(): + stdout = subprocess.run( + ["pactl", "get-sink-volume", "@DEFAULT_SINK@"], + capture_output=True, + encoding="UTF-8", + ).stdout.strip() + + for s in stdout.removeprefix("Volume: ").split(): + if s.endswith("%"): + return int(s.rstrip("%")) + + +def print_status(): + match get_volume(): + case None: + output = {} + case v if is_muted(): + output = { + "full_text": f" {ICONS["mute"]} {v}% ", + "color": f"#{os.environ.get("BASE16_COLOR_00_HEX")}", + "background": f"#{os.environ.get("BASE16_COLOR_0A_HEX")}", + } + case v if v > 66: + output = {"full_text": f" {ICONS["high"]} {v}% "} + case v if v > 33: + output = {"full_text": f" {ICONS["medium"]} {v}% "} + case v: + output = {"full_text": f" {ICONS["low"]} {v}% "} + + print(json.dumps(output, ensure_ascii=False), flush=True) + + +async def listener(): + process = await asyncio.create_subprocess_exec( + "pactl", + "--format=json", + "subscribe", + stdout=asyncio.subprocess.PIPE, + ) + + while True: + line = await process.stdout.readline() + + if not line: + await asyncio.sleep(1) + continue + + match json.loads(line.decode("UTF-8")): + case {"on": "sink"} | {"on": "source-output"}: + print_status() + + +async def button_handler(): + loop = asyncio.get_event_loop() + reader = asyncio.StreamReader() + protocol = asyncio.StreamReaderProtocol(reader) + await loop.connect_read_pipe(lambda: protocol, sys.stdin) + proc: subprocess.Popen = None + + while True: + line = await reader.readline() + + if not line: + await asyncio.sleep(1) + continue + + match json.loads(line): + case {"button": 1} if proc: + proc.terminate() + proc = None + case {"button": 1, "gui": cmd}: + proc = subprocess.Popen(cmd, shell=True) + case {"button": 2}: + pass + case {"button": 3}: + subprocess.run(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]) + case {"button": 4}: + subprocess.run( + ["wpctl", "set-volume", "-l", "1.5", "@DEFAULT_AUDIO_SINK@", "5%+"] + ) + case {"button": 5}: + subprocess.run( + ["wpctl", "set-volume", "-l", "1.5", "@DEFAULT_AUDIO_SINK@", "5%-"] + ) + + +async def main(): + print_status() + try: + async with asyncio.TaskGroup() as task_group: + task_group.create_task(listener()) + task_group.create_task(button_handler()) + except asyncio.CancelledError: + return + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/i3blocks/.local/bin/i3blocks-wttr b/i3blocks/.local/bin/i3blocks-wttr new file mode 100755 index 0000000..8ae4857 --- /dev/null +++ b/i3blocks/.local/bin/i3blocks-wttr @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +import time + +import requests + +ICON = "\U000f046b" # + +WWO_CODE = { + "113": "Sunny", + "116": "PartlyCloudy", + "119": "Cloudy", + "122": "VeryCloudy", + "143": "Fog", + "176": "LightShowers", + "179": "LightSleetShowers", + "182": "LightSleet", + "185": "LightSleet", + "200": "ThunderyShowers", + "227": "LightSnow", + "230": "HeavySnow", + "248": "Fog", + "260": "Fog", + "263": "LightShowers", + "266": "LightRain", + "281": "LightSleet", + "284": "LightSleet", + "293": "LightRain", + "296": "LightRain", + "299": "HeavyShowers", + "302": "HeavyRain", + "305": "HeavyShowers", + "308": "HeavyRain", + "311": "LightSleet", + "314": "LightSleet", + "317": "LightSleet", + "320": "LightSnow", + "323": "LightSnowShowers", + "326": "LightSnowShowers", + "329": "HeavySnow", + "332": "HeavySnow", + "335": "HeavySnowShowers", + "338": "HeavySnow", + "350": "LightSleet", + "353": "LightShowers", + "356": "HeavyShowers", + "359": "HeavyRain", + "362": "LightSleetShowers", + "365": "LightSleetShowers", + "368": "LightSnowShowers", + "371": "HeavySnowShowers", + "374": "LightSleetShowers", + "377": "LightSleet", + "386": "ThunderyShowers", + "389": "ThunderyHeavyRain", + "392": "ThunderySnowShowers", + "395": "HeavySnowShowers", +} + +WEATHER_SYMBOL_WI_DAY = { + "Unknown": "", + "Cloudy": "", + "Fog": "", + "HeavyRain": "", + "HeavyShowers": "", + "HeavySnow": "", + "HeavySnowShowers": "", + "LightRain": "", + "LightShowers": "", + "LightSleet": "", + "LightSleetShowers": "", + "LightSnow": "", + "LightSnowShowers": "", + "PartlyCloudy": "", + "Sunny": "", + "ThunderyHeavyRain": "", + "ThunderyShowers": "", + "ThunderySnowShowers": "", + "VeryCloudy": "", +} + +WEATHER_SYMBOL_WI_NIGHT = { + "Unknown": "", + "Cloudy": "", + "Fog": "", + "HeavyRain": "", + "HeavyShowers": "", + "HeavySnow": "", + "HeavySnowShowers": "", + "LightRain": "", + "LightShowers": "", + "LightSleet": "", + "LightSleetShowers": "", + "LightSnow": "", + "LightSnowShowers": "", + "PartlyCloudy": "", + "Sunny": "", + "ThunderyHeavyRain": "", + "ThunderyShowers": "", + "ThunderySnowShowers": "", + "VeryCloudy": "", +} + + +def parse_data() -> str: + data = requests.get("https://wttr.in/?format=j1").json() + + current = next(iter(data.get("current_condition", [])), None) + + if current is None: + return "" + + code = current.get("weatherCode", "0") + key = WWO_CODE.get(code, "Unknown") + + local_obs_date, local_obs_time = current.get("localObsDateTime").split(maxsplit=1) + astronomy = next( + iter( + next( + w.get("astronomy") + for w in data.get("weather", []) + if w.get("date", None) == local_obs_date and w.get("astronomy", None) + ) + ), + None, + ) + + if astronomy is None: + return None + + local = time.strptime(local_obs_time, "%I:%M %p") + sunrise = time.strptime(astronomy.get("sunrise"), "%I:%M %p") + sunset = time.strptime(astronomy.get("sunset"), "%I:%M %p") + + if local > sunrise and local < sunset: + icon = WEATHER_SYMBOL_WI_DAY.get(key) + else: + icon = WEATHER_SYMBOL_WI_NIGHT.get(key) + + if temp := current.get("temp_C"): + return " {} {}°C ".format(icon, temp) + + +def main(): + try: + print(parse_data()) + except Exception: + print() + + +if __name__ == "__main__": + main() |