90 lines
2.9 KiB
Python
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)
|