Skip to content

Commit d7af74f

Browse files
committed
feat(cava.py): added reload option, added channel and reverse mode.
chore (hypridle): Added systemctl startup for hypridle.
1 parent 5d16927 commit d7af74f

File tree

6 files changed

+259
-55
lines changed

6 files changed

+259
-55
lines changed

β€ŽConfigs/.local/lib/hyde/cava.pyβ€Ž

Lines changed: 129 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import signal
1919
import atexit
2020
import json
21+
import shlex
2122
from pathlib import Path
2223

2324

@@ -44,9 +45,13 @@ def _load_config(self):
4445
line = line[7:]
4546
if "=" in line:
4647
key, value = line.split("=", 1)
47-
value = value.strip("\"'")
48+
value = value.strip()
4849
if value.startswith("(") and value.endswith(")"):
49-
value = value[1:-1].strip("\"'")
50+
# Remove parentheses and split using shlex to respect quotes
51+
value = value[1:-1].strip()
52+
value = shlex.split(value)
53+
else:
54+
value = value.strip("\"'")
5055
config[key] = value
5156
except Exception as e:
5257
print(f"Warning: Could not load Hyde config: {e}", file=sys.stderr)
@@ -63,7 +68,7 @@ class CavaDataParser:
6368

6469
@staticmethod
6570
def format_data(line, bar_chars="β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ", width=None, standby_mode=""):
66-
"""Format cava data with custom bar characters"""
71+
"""Format cava data with custom bar characters (list or string)"""
6772
line = line.strip()
6873
if not line:
6974
return CavaDataParser._handle_standby_mode(standby_mode, bar_chars, width)
@@ -107,6 +112,7 @@ def format_data(line, bar_chars="β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ", width=None, standby_
107112
char_index = bar_length - 1
108113
else:
109114
char_index = value
115+
# bar_chars can be a list or string
110116
result += bar_chars[char_index]
111117

112118
return result
@@ -257,7 +263,7 @@ def _check_auto_shutdown(self):
257263
while not self.should_shutdown:
258264
time.sleep(1)
259265
with self.clients_lock:
260-
if not self.clients and time.time() - self.last_client_time > 5:
266+
if not self.clients and time.time() - self.last_client_time > 1:
261267
print("No clients connected for 5 seconds, shutting down...")
262268
self.should_shutdown = True
263269
break
@@ -285,19 +291,88 @@ def _broadcast_data(self, data):
285291
self.last_client_time = time.time()
286292

287293
def _handle_client_connections(self):
288-
"""Handle incoming client connections"""
294+
"""Handle incoming client connections and listen for reload command"""
289295
while True:
290296
try:
291297
conn, addr = self.server_socket.accept()
292298
print("New client connected")
299+
threading.Thread(target=self._client_command_listener, args=(conn,), daemon=True).start()
293300
with self.clients_lock:
294301
self.clients.append(conn)
295302
self.last_client_time = time.time()
296303
except OSError:
297304
break
298305

299-
def _create_cava_config(self, bars=16, range_val=15):
300-
"""Create cava configuration file"""
306+
def _client_command_listener(self, conn):
307+
"""Listen for special commands from a client (e.g., reload)"""
308+
try:
309+
conn.settimeout(0.1)
310+
data = b""
311+
while True:
312+
try:
313+
chunk = conn.recv(1024)
314+
if not chunk:
315+
break
316+
data += chunk
317+
if b"\n" in data:
318+
line, data = data.split(b"\n", 1)
319+
if line.strip() == b"CMD:RELOAD":
320+
print("Received reload command from client.")
321+
self._reload_cava_process()
322+
except socket.timeout:
323+
break
324+
except Exception:
325+
pass
326+
327+
def _reload_cava_process(self):
328+
"""Restart cava process and reload config with latest values"""
329+
print("Reloading cava process...")
330+
if self.cava_process and self.cava_process.poll() is None:
331+
self.cava_process.terminate()
332+
try:
333+
self.cava_process.wait(timeout=2)
334+
except subprocess.TimeoutExpired:
335+
self.cava_process.kill()
336+
# Always use latest config values
337+
hyde_config = HydeConfig()
338+
bars = int(hyde_config.get_value("CAVA_BARS", 16))
339+
range_val = int(hyde_config.get_value("CAVA_RANGE", 15))
340+
channels = hyde_config.get_value("CAVA_CHANNELS", "stereo")
341+
reverse = hyde_config.get_value("CAVA_REVERSE", 0)
342+
try:
343+
reverse = int(reverse)
344+
except Exception:
345+
reverse = 1 if str(reverse).lower() in ("true", "yes", "on") else 0
346+
self._create_cava_config(bars, range_val, channels, reverse)
347+
try:
348+
self.cava_process = subprocess.Popen(
349+
["cava", "-p", str(self.config_file)],
350+
stdout=subprocess.PIPE,
351+
stderr=subprocess.PIPE,
352+
text=True,
353+
)
354+
print("Cava process restarted.")
355+
except FileNotFoundError:
356+
print("Error: cava not found. Please install cava.")
357+
358+
def _create_cava_config(self, bars=16, range_val=15, channels="stereo", reverse=0, prefix=""):
359+
"""Create cava configuration file with channels and reverse support, using HydeConfig with or without prefix as appropriate"""
360+
hyde_config = HydeConfig()
361+
362+
if prefix:
363+
config_channels = hyde_config.get_value(f"CAVA_{prefix}_CHANNELS")
364+
config_reverse = hyde_config.get_value(f"CAVA_{prefix}_REVERSE")
365+
else:
366+
config_channels = hyde_config.get_value("CAVA_CHANNELS")
367+
config_reverse = hyde_config.get_value("CAVA_REVERSE")
368+
if config_channels in ("mono", "stereo"):
369+
channels = config_channels
370+
if config_reverse is not None:
371+
try:
372+
reverse = int(config_reverse)
373+
except ValueError:
374+
reverse = 1 if str(config_reverse).lower() in ("true", "yes", "on") else 0
375+
301376
self.temp_dir.mkdir(parents=True, exist_ok=True)
302377

303378
config_content = f"""[general]
@@ -313,12 +388,14 @@ def _create_cava_config(self, bars=16, range_val=15):
313388
raw_target = /dev/stdout
314389
data_format = ascii
315390
ascii_max_range = {range_val}
391+
channels = {channels}
392+
reverse = {reverse}
316393
"""
317394

318395
with open(self.config_file, "w") as f:
319396
f.write(config_content)
320397

321-
def start(self, bars=16, range_val=15):
398+
def start(self, bars=16, range_val=15, channels="stereo", reverse=0):
322399
"""Start the cava server"""
323400
try:
324401
self.temp_dir.mkdir(parents=True, exist_ok=True)
@@ -366,7 +443,7 @@ def start(self, bars=16, range_val=15):
366443

367444
self._write_pid_file()
368445

369-
self._create_cava_config(bars, range_val)
446+
self._create_cava_config(bars, range_val, channels, reverse)
370447

371448
print(f"Starting cava with config: {self.config_file}")
372449
try:
@@ -566,13 +643,24 @@ def parse_command_config(hyde_config, command, args):
566643
"""Parse configuration for a specific command type"""
567644
prefix = f"CAVA_{command.upper()}"
568645

569-
bar_chars = args.bar or hyde_config.get_value(f"{prefix}_BAR", "β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ")
646+
# Prefer --bar-array if present
647+
if hasattr(args, "bar_array") and args.bar_array:
648+
bar_chars = args.bar_array
649+
else:
650+
# Prefer BAR_ARRAY from config if present and is a list
651+
bar_array = hyde_config.get_value(f"{prefix}_BAR_ARRAY")
652+
if bar_array and isinstance(bar_array, list):
653+
bar_chars = bar_array
654+
else:
655+
bar_chars = args.bar or hyde_config.get_value(f"{prefix}_BAR", "β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ")
656+
if isinstance(bar_chars, str):
657+
bar_chars = list(bar_chars)
658+
570659
width = (
571660
args.width
572661
if args.width is not None
573662
else int(hyde_config.get_value(f"{prefix}_WIDTH", "0") or 0)
574663
)
575-
576664
if not width:
577665
width = len(bar_chars) if bar_chars else 8
578666

@@ -584,26 +672,46 @@ def parse_command_config(hyde_config, command, args):
584672
standby_mode = hyde_config.get_value(f"{prefix}_STANDBY", "0")
585673
if standby_mode is None or standby_mode == "":
586674
standby_mode = "\n"
587-
elif standby_mode.isdigit():
675+
elif isinstance(standby_mode, str) and standby_mode.isdigit():
588676
standby_mode = int(standby_mode)
589677

590678
return bar_chars, width, standby_mode
591679

592680

681+
class CavaReloadClient:
682+
"""Minimal client to send reload command to the server"""
683+
def __init__(self):
684+
self.runtime_dir = os.getenv("XDG_RUNTIME_DIR", os.path.join("/run/user", str(os.getuid())))
685+
self.socket_file = os.path.join(self.runtime_dir, "hyde", "cava.sock")
686+
687+
def reload(self):
688+
if not os.path.exists(self.socket_file):
689+
print("Cava manager is not running.")
690+
sys.exit(1)
691+
try:
692+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
693+
s.connect(self.socket_file)
694+
s.sendall(b"CMD:RELOAD\n")
695+
s.close()
696+
print("Reload command sent.")
697+
except Exception as e:
698+
print(f"Failed to send reload command: {e}")
699+
sys.exit(1)
700+
701+
593702
def create_client_parser(subparsers, name, help_text):
594703
"""Create a client parser with common arguments"""
595704
parser = subparsers.add_parser(name, help=help_text)
596705
parser.add_argument("--bar", default=None, help="Bar characters")
706+
parser.add_argument("--bar-array", nargs="+", help="Bar characters as an array (e.g. --bar-array '<span color=red>#</span>' '<span color=green>#</span>')")
597707
parser.add_argument("--width", type=int, help="Bar width")
598708
parser.add_argument(
599709
"--stb",
600710
default=None,
601711
help='Standby mode (0-3 or string): 0=clean (totally hides the module), 1=blank (makes module expand as spaces), 2=full (occupies the module with full bar), 3=low (makes the module display the lowest set bar), ""=displays nothing and compresses the module, string=displays the custom string',
602712
)
603-
604713
if name == "waybar":
605714
parser.add_argument("--json", action="store_true", help="Output JSON format for waybar tooltips")
606-
607715
return parser
608716

609717

@@ -615,12 +723,15 @@ def main():
615723
manager_parser = subparsers.add_parser("manager", help="Start cava manager")
616724
manager_parser.add_argument("--bars", type=int, default=16, help="Number of bars")
617725
manager_parser.add_argument("--range", type=int, default=15, help="ASCII range")
726+
manager_parser.add_argument("--channels", choices=["mono", "stereo"], default="stereo", help="Audio channels: mono or stereo")
727+
manager_parser.add_argument("--reverse", type=int, choices=[0, 1], default=0, help="Reverse frequency order: 0=normal, 1=reverse")
618728

619729
create_client_parser(subparsers, "waybar", "Waybar client")
620730
create_client_parser(subparsers, "stdout", "Stdout client")
621731
create_client_parser(subparsers, "hyprlock", "Hyprlock client")
622732

623733
subparsers.add_parser("status", help="Check manager status")
734+
subparsers.add_parser("reload", help="Reload cava manager (restart cava process)")
624735

625736
args = parser.parse_args()
626737

@@ -630,7 +741,7 @@ def main():
630741
print("Cava manager is already running")
631742
sys.exit(0)
632743

633-
server.start(args.bars, args.range)
744+
server.start(args.bars, args.range, args.channels, args.reverse)
634745

635746
elif args.command in ["waybar", "stdout", "hyprlock"]:
636747
hyde_config = HydeConfig()
@@ -656,6 +767,9 @@ def main():
656767
print("Cava manager is not running")
657768
sys.exit(1)
658769

770+
elif args.command == "reload":
771+
CavaReloadClient().reload()
772+
659773
else:
660774
parser.print_help()
661775

β€ŽConfigs/.local/share/hyde/config.tomlβ€Ž

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ drun_args = [] # Additional arguments for drun mode.
3939
run_args = [] # Additional arguments for run mode.
4040
window_args = [] # Additional arguments for window mode.
4141
filebrowser_args = [] # Additional arguments for filebrowser mode.
42+
drun_style = "style_1" # Style for drun mode.
43+
window_style = "style_1" # Style for window mode.
44+
run_style = "style_1" # Style for run mode.
45+
filebrowser_style = "style_1" # Style for filebrowser mode.
4246

4347
# cliphist.sh configuration.
4448
[rofi.cliphist]
@@ -127,27 +131,27 @@ location = "" # Location/coordinates string for the weather output.
127131

128132
# 'cava.sh stdout' configuration.
129133
[cava.stdout]
130-
max_instances = 1 # Maximum number of cava instances.
131134
bar = "β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ" # Bar characters for cava.
132135
width = 20 # Width of the cava output.
133136
range = 7 # Number of bars minus one.
134137
standby = "🎢" # Standby character for cava.
138+
bar_array = ["β–‘", "β–’", "β–“", "β–ˆ"] # Bar array for stdout preset.
135139

136140
# 'cava.sh hyprlock' configuration.
137141
[cava.hyprlock]
138-
max_instances = 1 # Maximum number of cava instances.
139142
bar = "β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ" # Bar characters for cava.
140143
width = 20 # Width of the cava output.
141144
range = 7 # Number of bars minus one.
142145
standby = "🎢" # Standby character for cava.
146+
bar_array = ["▁", "β–‚", "β–ƒ", "β–„", "β–…", "β–†", "β–‡", "β–ˆ"] # Bar array for hyprlock preset.
143147

144148
# 'cava.sh waybar' configuration.
145149
[cava.waybar]
146-
max_instances = 1 # Maximum number of cava instances.
147150
bar = "β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ" # Bar characters for cava.
148151
width = 20 # Width of the cava output.
149152
range = 7 # Number of bars minus one.
150153
standby = "🎢" # Standby character for cava.
154+
bar_array = ["β—œ", "◝", "β—ž", "β—Ÿ", "β— ", "β—‘", "β—’", "β—£"] # Bar array for waybar preset.
151155

152156
# Hypr configuration.
153157
[hypr.config]

β€ŽConfigs/.local/share/hyde/hyprland.confβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ $default.EDITOR = hyde-launch.sh --fall code-oss text-editor
2525
$default.EXPLORER= hyde-launch.sh --fall dolphin file-manager
2626
$default.TERMINAL = kitty
2727
$default.LOCKSCREEN=hyprlock
28-
$default.IDLE=hypridle
28+
$default.IDLE=systemctl --user start hypridle.service || hypridle
2929

3030
$QUICKAPPS = $default.$QUICKAPPS
3131
$BROWSER = $default.BROWSER

β€ŽConfigs/.local/share/hyde/schema/config.mdβ€Ž

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ brightnesscontrol.sh configuration.
5656
| Key | Description | Default |
5757
| --- | ----------- | ------- |
5858
| bar | Bar characters for cava. | β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ |
59-
| max_instances | Maximum number of cava instances. | 1 |
59+
| bar_array | Bar array for hyprlock preset. | ["▁", "β–‚", "β–ƒ", "β–„", "β–…", "β–†", "β–‡", "β–ˆ"] |
6060
| range | Number of bars minus one. | 7 |
6161
| standby | Standby character for cava. | 🎢 |
6262
| width | Width of the cava output. | 20 |
@@ -68,7 +68,7 @@ brightnesscontrol.sh configuration.
6868
| Key | Description | Default |
6969
| --- | ----------- | ------- |
7070
| bar | Bar characters for cava. | β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ |
71-
| max_instances | Maximum number of cava instances. | 1 |
71+
| bar_array | Bar array for stdout preset. | ["β–‘", "β–’", "β–“", "β–ˆ"] |
7272
| range | Number of bars minus one. | 7 |
7373
| standby | Standby character for cava. | 🎢 |
7474
| width | Width of the cava output. | 20 |
@@ -80,7 +80,7 @@ brightnesscontrol.sh configuration.
8080
| Key | Description | Default |
8181
| --- | ----------- | ------- |
8282
| bar | Bar characters for cava. | β–β–‚β–ƒβ–„β–…β–†β–‡β–ˆ |
83-
| max_instances | Maximum number of cava instances. | 1 |
83+
| bar_array | Bar array for waybar preset. | ["β—œ", "◝", "β—ž", "β—Ÿ", "β— ", "β—‘", "β—’", "β—£"] |
8484
| range | Number of bars minus one. | 7 |
8585
| standby | Standby character for cava. | 🎢 |
8686
| width | Width of the cava output. | 20 |
@@ -220,10 +220,14 @@ rofilaunch.sh configuration.
220220
| Key | Description | Default |
221221
| --- | ----------- | ------- |
222222
| drun_args | Additional arguments for drun mode. | [] |
223+
| drun_style | Style for drun mode. | style_1 |
223224
| filebrowser_args | Additional arguments for filebrowser mode. | [] |
225+
| filebrowser_style | Style for filebrowser mode. | style_1 |
224226
| run_args | Additional arguments for run mode. | [] |
227+
| run_style | Style for run mode. | style_1 |
225228
| scale | Scaling for launch. | 5 |
226229
| window_args | Additional arguments for window mode. | [] |
230+
| window_style | Style for window mode. | style_1 |
227231

228232
### [rofi.theme]
229233

@@ -319,6 +323,7 @@ waybar configuration.
319323
| --- | ----------- | ------- |
320324
| font | Font for waybar. | JetBrainsMono Nerd Font |
321325
| icon_size | Icon size for waybar. | 10 |
326+
| position | A fallback position of the waybar. | top |
322327
| scale | Total scaling for waybar. | 10 |
323328

324329
### [weather]

0 commit comments

Comments
Β (0)