diff --git a/svc/.gitignore b/svc/.gitignore index 1468cf9..6a5629d 100644 --- a/svc/.gitignore +++ b/svc/.gitignore @@ -4,3 +4,4 @@ .mypy_cache/ __pycache__/ *.py[co] +/config.json diff --git a/svc/src/hudctrl/api.py b/svc/src/hudctrl/api.py index 7275af0..0ca59b9 100644 --- a/svc/src/hudctrl/api.py +++ b/svc/src/hudctrl/api.py @@ -1,7 +1,5 @@ import io import logging -import os -from pathlib import Path from typing import Optional from PIL import Image @@ -17,16 +15,11 @@ log = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) -HUDCTRL_URLS_FILE = os.environ.get('HUDCTRL_URLS_FILE') - - app = fastapi.FastAPI( docs_url='/api-doc/', ) svc = HUDService() -if HUDCTRL_URLS_FILE: - svc.urls_file = Path(HUDCTRL_URLS_FILE) class PNGImageResponse(fastapi.Response): diff --git a/svc/src/hudctrl/config.py b/svc/src/hudctrl/config.py new file mode 100644 index 0000000..d06b39b --- /dev/null +++ b/svc/src/hudctrl/config.py @@ -0,0 +1,12 @@ +from typing import Optional + +import pydantic + +from .xrandr import Monitor + + +class Configuration(pydantic.BaseModel): + monitors: list[Monitor] = pydantic.Field(default_factory=list) + urls: dict[str, str] = pydantic.Field(default_factory=dict) + host: Optional[str] = None + port: Optional[int] = None diff --git a/svc/src/hudctrl/hud.py b/svc/src/hudctrl/hud.py index 4f5f293..fa8adba 100644 --- a/svc/src/hudctrl/hud.py +++ b/svc/src/hudctrl/hud.py @@ -2,18 +2,23 @@ import asyncio import base64 import json import logging +import os from pathlib import Path from typing import Dict, Optional import pydantic from aiomarionette import Marionette, WindowRect +from .config import Configuration from .xrandr import MonitorConfig log = logging.getLogger(__name__) +CONFIG_PATH = os.environ.get('HUDCTRL_CONFIG_PATH', 'config.json') + + class NoMonitorConfig(Exception): '''Raised when no monitor config has been provided yet''' @@ -34,7 +39,7 @@ class HUDService: self.urls: Dict[str, str] = {} self.windows: Dict[str, str] = {} - self.urls_file = Path('urls.json') + self.config_file = Path(CONFIG_PATH) self.lock = asyncio.Lock() async def get_screen(self, name: str) -> HUDScreen: @@ -104,29 +109,27 @@ class HUDService: if tasks: await asyncio.wait(tasks) - def load_urls(self) -> None: + def load_config(self) -> None: try: - f = self.urls_file.open(encoding='utf-8') + f = self.config_file.open(encoding='utf-8') except FileNotFoundError: return except OSError as e: - log.error('Could not load URL list: %s', e) + log.error('Could not load config: %s', e) return - log.info('Loading URL list from %s', self.urls_file) + log.info('Loading config from %s', f.name) with f: try: - value = json.load(f) + config = Configuration.parse_obj(json.load(f)) except ValueError as e: - log.error('Failed to load URL list: %s', e) + log.error('Failed to load config: %s', e) return - if isinstance(value, dict): - self.urls = value - else: - log.error( - 'Invalid URL list: found %r, expected %r', - type(value), - dict, - ) + self.urls = config.urls + if config.monitors: + self.monitor_config = MonitorConfig() + self.monitor_config.monitors = config.monitors + self.host = config.host + self.port = config.port async def navigate(self, name: str, url: str) -> None: assert self.marionette @@ -154,10 +157,17 @@ class HUDService: if self.marionette is not None: await self.marionette.close() self.marionette = None + self.save_config() async def startup(self) -> None: - loop = asyncio.get_running_loop() - await loop.run_in_executor(None, self.load_urls) + asyncio.create_task(self._startup()) + + async def _startup(self) -> None: + await asyncio.to_thread(self.load_config) + if self.host and self.port: + await self.set_display(self.host, self.port) + if self.monitor_config: + await self.initialize() async def take_screenshot(self, screen: str) -> bytes: assert self.marionette