1
0
Fork 0

Compare commits

..

No commits in common. "a14921b79e88e1dfeffbaf2d76f38a51259e1e21" and "949d180edd74eda932a268abac34df06cb9dc2c7" have entirely different histories.

4 changed files with 26 additions and 63 deletions

1
svc/.gitignore vendored
View File

@ -4,4 +4,3 @@
.mypy_cache/ .mypy_cache/
__pycache__/ __pycache__/
*.py[co] *.py[co]
/config.json

View File

@ -1,5 +1,7 @@
import io import io
import logging import logging
import os
from pathlib import Path
from typing import Optional from typing import Optional
from PIL import Image from PIL import Image
@ -15,11 +17,16 @@ log = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
HUDCTRL_URLS_FILE = os.environ.get('HUDCTRL_URLS_FILE')
app = fastapi.FastAPI( app = fastapi.FastAPI(
docs_url='/api-doc/', docs_url='/api-doc/',
) )
svc = HUDService() svc = HUDService()
if HUDCTRL_URLS_FILE:
svc.urls_file = Path(HUDCTRL_URLS_FILE)
class PNGImageResponse(fastapi.Response): class PNGImageResponse(fastapi.Response):
@ -54,8 +61,7 @@ async def get_monitor_config():
@app.put('/display/monitors') @app.put('/display/monitors')
async def put_monitor_config( async def put_monitor_config(
bgtasks: fastapi.BackgroundTasks, monitors: str = fastapi.Body(..., media_type='text/plain')
monitors: str = fastapi.Body(..., media_type='text/plain'),
): ):
try: try:
svc.monitor_config = MonitorConfig.from_string(monitors) svc.monitor_config = MonitorConfig.from_string(monitors)
@ -65,7 +71,6 @@ async def put_monitor_config(
fastapi.status.HTTP_400_BAD_REQUEST, fastapi.status.HTTP_400_BAD_REQUEST,
detail=f'Invalid monitor config: {e}', detail=f'Invalid monitor config: {e}',
) )
bgtasks.add_task(svc.save_config)
return {'monitor_config': svc.monitor_config} return {'monitor_config': svc.monitor_config}
@ -137,14 +142,8 @@ async def get_screenshot(
'/screen/{name}/navigate', '/screen/{name}/navigate',
response_class=fastapi.responses.PlainTextResponse, response_class=fastapi.responses.PlainTextResponse,
) )
async def navigate( async def navigate(name: str, url: str = fastapi.Form(...)):
bgtasks: fastapi.BackgroundTasks,
name: str,
url: str = fastapi.Form(...),
):
await svc.navigate(name, url) await svc.navigate(name, url)
svc.urls[name] = url
bgtasks.add_task(svc.save_config)
@app.on_event('shutdown') @app.on_event('shutdown')

View File

@ -1,12 +0,0 @@
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

View File

@ -2,23 +2,18 @@ import asyncio
import base64 import base64
import json import json
import logging import logging
import os
from pathlib import Path from pathlib import Path
from typing import Dict, Optional from typing import Dict, Optional
import pydantic import pydantic
from aiomarionette import Marionette, WindowRect from aiomarionette import Marionette, WindowRect
from .config import Configuration
from .xrandr import MonitorConfig from .xrandr import MonitorConfig
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
CONFIG_PATH = os.environ.get('HUDCTRL_CONFIG_PATH', 'config.json')
class NoMonitorConfig(Exception): class NoMonitorConfig(Exception):
'''Raised when no monitor config has been provided yet''' '''Raised when no monitor config has been provided yet'''
@ -39,7 +34,7 @@ class HUDService:
self.urls: Dict[str, str] = {} self.urls: Dict[str, str] = {}
self.windows: Dict[str, str] = {} self.windows: Dict[str, str] = {}
self.config_file = Path(CONFIG_PATH) self.urls_file = Path('urls.json')
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
async def get_screen(self, name: str) -> HUDScreen: async def get_screen(self, name: str) -> HUDScreen:
@ -109,27 +104,29 @@ class HUDService:
if tasks: if tasks:
await asyncio.wait(tasks) await asyncio.wait(tasks)
def load_config(self) -> None: def load_urls(self) -> None:
try: try:
f = self.config_file.open(encoding='utf-8') f = self.urls_file.open(encoding='utf-8')
except FileNotFoundError: except FileNotFoundError:
return return
except OSError as e: except OSError as e:
log.error('Could not load config: %s', e) log.error('Could not load URL list: %s', e)
return return
log.info('Loading config from %s', f.name) log.info('Loading URL list from %s', self.urls_file)
with f: with f:
try: try:
config = Configuration.parse_obj(json.load(f)) value = json.load(f)
except ValueError as e: except ValueError as e:
log.error('Failed to load config: %s', e) log.error('Failed to load URL list: %s', e)
return return
self.urls = config.urls if isinstance(value, dict):
if config.monitors: self.urls = value
self.monitor_config = MonitorConfig() else:
self.monitor_config.monitors = config.monitors log.error(
self.host = config.host 'Invalid URL list: found %r, expected %r',
self.port = config.port type(value),
dict,
)
async def navigate(self, name: str, url: str) -> None: async def navigate(self, name: str, url: str) -> None:
assert self.marionette assert self.marionette
@ -143,19 +140,6 @@ class HUDService:
await self.marionette.switch_to_window(self.windows[name]) await self.marionette.switch_to_window(self.windows[name])
await self.marionette.refresh() await self.marionette.refresh()
def save_config(self) -> None:
log.info('Saving configuration to %s', self.config_file)
config = Configuration(
monitors=self.monitor_config.monitors
if self.monitor_config
else [],
urls=self.urls,
host=self.host,
port=self.port,
)
f = self.config_file.open('w', encoding='utf-8')
f.write(config.json())
async def set_display(self, host: str, port: int) -> None: async def set_display(self, host: str, port: int) -> None:
if self.marionette: if self.marionette:
log.warning('Closing existing Marionette connection') log.warning('Closing existing Marionette connection')
@ -170,17 +154,10 @@ class HUDService:
if self.marionette is not None: if self.marionette is not None:
await self.marionette.close() await self.marionette.close()
self.marionette = None self.marionette = None
self.save_config()
async def startup(self) -> None: async def startup(self) -> None:
asyncio.create_task(self._startup()) loop = asyncio.get_running_loop()
await loop.run_in_executor(None, self.load_urls)
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: async def take_screenshot(self, screen: str) -> bytes:
assert self.marionette assert self.marionette