from __future__ import annotations from dataclasses import dataclass import json from pathlib import Path from threading import Lock PROJECT_ROOT = Path(__file__).resolve().parent.parent CONFIG_PATH = PROJECT_ROOT / "config.json" SUPPORTED_TARGET_PORTS = { "DP1": {"ddm_input": "DP1", "input_codes": {15}}, "DP2": {"ddm_input": "DP2", "input_codes": {19}}, "HDMI": {"ddm_input": "HDMI", "input_codes": {17}}, } @dataclass(slots=True) class AppConfig: device_port: str = "DP1" auxiliary_monitor_id: str | None = None @classmethod def from_dict(cls, data: dict[str, object]) -> "AppConfig": return cls( device_port=_coerce_port_name(data.get("device_port"), "DP1"), auxiliary_monitor_id=_coerce_aux_monitor_id(data.get("auxiliary_monitor_id")), ) def to_dict(self) -> dict[str, str | None]: return { "device_port": self.device_port, "auxiliary_monitor_id": self.auxiliary_monitor_id, } def validate(self) -> list[str]: errors: list[str] = [] supported = ", ".join(SUPPORTED_TARGET_PORTS) if self.device_port not in SUPPORTED_TARGET_PORTS: errors.append(f"Device Port must be one of: {supported}.") return errors class ConfigStore: def __init__(self, path: Path = CONFIG_PATH): self.path = path self._lock = Lock() self._config = self._load_from_disk() def get(self) -> AppConfig: with self._lock: return AppConfig( device_port=self._config.device_port, auxiliary_monitor_id=self._config.auxiliary_monitor_id, ) def save(self, config: AppConfig) -> AppConfig: errors = config.validate() if errors: raise ValueError("; ".join(errors)) with self._lock: self.path.write_text( json.dumps(config.to_dict(), indent=2), encoding="utf-8", ) self._config = AppConfig( device_port=config.device_port, auxiliary_monitor_id=config.auxiliary_monitor_id, ) return AppConfig( device_port=self._config.device_port, auxiliary_monitor_id=self._config.auxiliary_monitor_id, ) def _load_from_disk(self) -> AppConfig: if not self.path.exists(): return AppConfig() try: data = json.loads(self.path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return AppConfig() if not isinstance(data, dict): return AppConfig() return AppConfig.from_dict(data) def _coerce_port_name(value: object, default: str) -> str: if isinstance(value, str): normalized = value.strip().upper() if normalized in SUPPORTED_TARGET_PORTS: return normalized return default def _coerce_aux_monitor_id(value: object) -> str | None: if isinstance(value, str): normalized = value.strip() if normalized: return normalized return None