turn-up-arch/config.py

90 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""
config.py — Load and validate turnup configuration from config.json
"""
import json
import logging
import os
import sys
log = logging.getLogger("turnupd")
DEFAULT_CONFIG: dict = {
"port": "/dev/ttyACM0",
"baud": 115200,
"knobs": {
"0": {"action": "sink_volume", "target": "default"},
"1": {"action": "sink_volume", "target": "default"},
"2": {"action": "source_volume", "target": "default"},
"3": {"action": "sink_volume", "target": "default"},
"4": {"action": "sink_volume", "target": "default"},
},
"buttons": {
"0": {"action": "mute_sink", "target": "default"},
"1": {"action": "mute_source", "target": "default"},
"2": {"action": "command", "target": ""},
"3": {"action": "command", "target": ""},
"4": {"action": "command", "target": ""},
},
}
VALID_KNOB_ACTIONS: frozenset = frozenset(
{"sink_volume", "source_volume", "app_volume", "group_volume"}
)
VALID_BUTTON_ACTIONS: frozenset = frozenset(
{"mute_sink", "mute_source", "command"}
)
# Default config search path: ~/.config/turnup/config.json
_XDG_CONFIG_DIR = os.path.join(
os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
"turnup",
)
DEFAULT_CONFIG_PATH = os.path.join(_XDG_CONFIG_DIR, "config.json")
def load_config(path: str | None = None) -> dict:
"""Load configuration from *path*.
If *path* is ``None`` the XDG-compliant location
``~/.config/turnup/config.json`` is used, falling back to the directory
that contains this module for backwards compatibility.
Returns the parsed configuration dictionary. Exits with status 1 on
malformed JSON.
"""
if path is None:
# Prefer XDG location; fall back to the directory next to this module.
if os.path.exists(DEFAULT_CONFIG_PATH):
path = DEFAULT_CONFIG_PATH
else:
script_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(script_dir, "config.json")
if not os.path.exists(path):
log.warning("No config.json found at %s — writing defaults", path)
_write_default(path)
return dict(DEFAULT_CONFIG)
try:
with open(path) as f:
cfg: dict = json.load(f)
log.info("Loaded config from %s", path)
return cfg
except json.JSONDecodeError as exc:
log.error("Invalid JSON in %s: %s", path, exc)
sys.exit(1)
def _write_default(path: str) -> None:
"""Write the default configuration to *path*, creating directories as needed."""
try:
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
json.dump(DEFAULT_CONFIG, f, indent=2)
f.write("\n")
log.info("Created default config at %s", path)
except OSError as exc:
log.warning("Could not write default config: %s", exc)