aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--i3blocks/.config/i3blocks/config14
-rwxr-xr-xi3blocks/.local/lib/i3blocks/i3blocks-volume158
2 files changed, 168 insertions, 4 deletions
diff --git a/i3blocks/.config/i3blocks/config b/i3blocks/.config/i3blocks/config
index a3d88b7..b61ea4c 100644
--- a/i3blocks/.config/i3blocks/config
+++ b/i3blocks/.config/i3blocks/config
@@ -6,7 +6,7 @@ yellow="#fabd2f"
red="#fb4934"
[i3blocks-title]
-command=~/.local/bin/i3blocks-title
+command=$SCRIPT_DIR/i3blocks-title
max_length=30
interval=persist
@@ -15,18 +15,24 @@ command=curl -s wttr.in/?m\&format="%c+%t\n" | sed 's/\s\++\(.*\)C/ \1/'
interval=600
[miniflux]
-command=~/.local/bin/i3blocks-miniflux
+command=$SCRIPT_DIR/i3blocks-miniflux
interval=30
format=json
[notmuch]
-command=~/.local/bin/i3blocks-notmuch
+command=$SCRIPT_DIR/i3blocks-notmuch
interval=5
+[volume]
+command=$SCRIPT_DIR/i3blocks-volume
+interval=persist
+format=json
+gui=pavucontrol
+
[time]
command=date +"%a %d/%m %T"
interval=1
[dunst]
-command=~/.local/bin/i3blocks-dunst
+command=$SCRIPT_DIR/i3blocks-dunst
interval=1
diff --git a/i3blocks/.local/lib/i3blocks/i3blocks-volume b/i3blocks/.local/lib/i3blocks/i3blocks-volume
new file mode 100755
index 0000000..cd7c9c4
--- /dev/null
+++ b/i3blocks/.local/lib/i3blocks/i3blocks-volume
@@ -0,0 +1,158 @@
+#!/usr/bin/env python3
+
+import asyncio
+import json
+import os
+import subprocess
+import sys
+
+
+class PulseAudio:
+ icons = {
+ "mute": "\U0000eee8", # nf-fa-volume_mute
+ "low": "\U0000f026", # nf-fa-volume_off
+ "medium": "\U0000f027", # nf-fa-volume_low
+ "high": "\U0000f028", # nf-fa-volume_high
+ }
+
+ def __init__(self):
+ self.print_status()
+
+ @property
+ def volume(self) -> dict:
+ channels = self.sink.get("volume")
+
+ return int(
+ channels.get(next(chan for chan in channels))
+ .get("value_percent")
+ .rstrip("%")
+ )
+
+ @property
+ def icon(self) -> str:
+ if self.sink.get("mute"):
+ return self.icons["mute"]
+
+ match self.volume:
+ case v if v < 33:
+ return self.icons["low"]
+ case v if v < 66:
+ return self.icons["medium"]
+ case _:
+ return self.icons["high"]
+
+ def list_sinks() -> dict[int, dict]:
+ return json.loads(
+ subprocess.run(
+ ["pactl", "--format=json", "list", "sinks"],
+ capture_output=True,
+ encoding="UTF-8",
+ ).stdout
+ )
+
+ def get_default_sink() -> str:
+ return subprocess.run(
+ ["pactl", "get-default-sink"],
+ capture_output=True,
+ encoding="UTF-8",
+ ).stdout
+
+ def print_status(self, update=True, format="json"):
+ if update:
+ self.sinks = PulseAudio.list_sinks()
+ self.default_sink = PulseAudio.get_default_sink()
+ self.sink = next(sink for sink in self.sinks)
+
+ output = {"full_text": f" {self.icon} {self.volume}% "}
+
+ if self.sink.get("mute"):
+ output["color"] = os.environ.get("black")
+ output["background"] = os.environ.get("yellow")
+
+ output.update(self.color)
+
+ match format:
+ case "json":
+ print(json.dumps(output), flush=True)
+ case "raw":
+ for prop in ["full_text", "short_text", "color", "background"]:
+ print(output.get(prop, ""), flush=True)
+ case f:
+ raise ValueError(f"Invalid output format: {f}")
+
+ async def listener(self):
+ 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 {
+ "index": index,
+ "event": "change",
+ "on": "sink",
+ } if index == self.sink.get("index"):
+ self.print_status()
+ case {
+ "index": index,
+ "event": "change",
+ "on": "source-output",
+ }:
+ self.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():
+ pulse_audio = PulseAudio()
+ try:
+ async with asyncio.TaskGroup() as task_group:
+ task_group.create_task(pulse_audio.listener())
+ task_group.create_task(button_handler())
+ except asyncio.CancelledError:
+ return
+
+
+if __name__ == "__main__":
+ asyncio.run(main())