mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
113 lines
3.8 KiB
Python
113 lines
3.8 KiB
Python
"""Read the saved projection layout at connect, then again only after Galaxy saves it.
|
|
|
|
Never read on the drawing thread: a background thread stats the file once a second,
|
|
loads it only when it changed, and the renderer just takes the finished layout.
|
|
"""
|
|
import os
|
|
import threading
|
|
|
|
from openpilot.starpilot.saved_source import read_saved
|
|
from openpilot.starpilot.system.starport.projection_layout import (
|
|
DOCUMENT_KEY, MAX_BYTES, ProjectionLayoutSource, default_layout_for_viewport, decode_layout_for_viewport,
|
|
)
|
|
|
|
|
|
def comma_display_seed():
|
|
"""Display choices for a car screen with no saved layout yet: the comma screen's own."""
|
|
from openpilot.common.params import Params
|
|
from openpilot.starpilot.system.starport.projection_layout import comma_display
|
|
from openpilot.starpilot.ui.appearance_preferences import onroad_appearance
|
|
return comma_display(onroad_appearance(Params()))
|
|
|
|
|
|
def _unsaved_layout(viewport, display_seed):
|
|
try:
|
|
display = display_seed() if display_seed is not None else None
|
|
except Exception:
|
|
display = None # an unreadable comma preference must never block the car screen
|
|
return default_layout_for_viewport(viewport, display)
|
|
|
|
|
|
def load_projection_layout(viewport, source=None, display_seed=None):
|
|
raw, readable = read_saved(source or ProjectionLayoutSource(), DOCUMENT_KEY, MAX_BYTES)
|
|
if not readable:
|
|
return default_layout_for_viewport(viewport)
|
|
if raw is None:
|
|
return _unsaved_layout(viewport, display_seed)
|
|
try:
|
|
return decode_layout_for_viewport(raw, viewport)
|
|
except (ValueError, TypeError, UnicodeError, OverflowError, RecursionError):
|
|
return default_layout_for_viewport(viewport)
|
|
|
|
|
|
CHECK_SECONDS = 1.0
|
|
|
|
|
|
class ProjectionLayoutWatcher:
|
|
"""Loads the layout and, on its own thread, picks up each layout Galaxy saves."""
|
|
|
|
def __init__(self, viewport, source=None, display_seed=None):
|
|
self.viewport = viewport
|
|
self.source = source or ProjectionLayoutSource()
|
|
self.display_seed = display_seed
|
|
self._stamp = None
|
|
self._lock = threading.Lock()
|
|
self._pending = None
|
|
self._stop = threading.Event()
|
|
self._thread = None
|
|
|
|
def start(self):
|
|
self._thread = threading.Thread(target=self._run, name='aa-layout-watch', daemon=True)
|
|
self._thread.start()
|
|
|
|
def close(self):
|
|
self._stop.set()
|
|
if self._thread is not None:
|
|
self._thread.join(timeout=2)
|
|
|
|
def _run(self):
|
|
while not self._stop.wait(CHECK_SECONDS):
|
|
try:
|
|
layout = self.poll()
|
|
except Exception:
|
|
continue # a bad read must never stop live layout updates
|
|
if layout is not None:
|
|
with self._lock:
|
|
self._pending = layout
|
|
|
|
def take(self):
|
|
"""For the drawing thread: the newest saved layout not yet applied, or None. No file access."""
|
|
with self._lock:
|
|
layout, self._pending = self._pending, None
|
|
return layout
|
|
|
|
def _current_stamp(self):
|
|
try:
|
|
info = os.stat(self.source.get_param_path(DOCUMENT_KEY), follow_symlinks=False)
|
|
except FileNotFoundError:
|
|
return None
|
|
return info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns
|
|
|
|
def load(self):
|
|
self._stamp = self._current_stamp()
|
|
return load_projection_layout(self.viewport, self.source, self.display_seed)
|
|
|
|
def poll(self):
|
|
"""Background thread only: the new layout after a save, or None. An unreadable file is retried."""
|
|
try:
|
|
stamp = self._current_stamp()
|
|
except OSError:
|
|
return None
|
|
if stamp == self._stamp:
|
|
return None
|
|
raw, readable = read_saved(self.source, DOCUMENT_KEY, MAX_BYTES)
|
|
if not readable:
|
|
return None
|
|
self._stamp = stamp
|
|
if raw is None:
|
|
return _unsaved_layout(self.viewport, self.display_seed)
|
|
try:
|
|
return decode_layout_for_viewport(raw, self.viewport)
|
|
except (ValueError, TypeError, UnicodeError, OverflowError, RecursionError):
|
|
return default_layout_for_viewport(self.viewport)
|