46dc0423a0
- autolaunch default: True → False (on-demand only, saves GPU on startup) - add ProcessManager.minimize() to minimize a window to taskbar - add win32_utils.minimize_window() (SW_MINIMIZE via user32) - DisplayManager._minimize_unassigned(): after each app switch, minimize every running app not currently assigned to any screen, freeing iGPU resources (critical on J6412 UHD 600 with limited EUs) Background: J6412 Intel UHD 600 has only 16 EUs @ 800 MHz. Running AR-ECDIS (MapLibre GL) and GPS (OpenLayers) simultaneously consumes ~60% iGPU. By minimizing inactive apps Windows suspends their GPU presentation chain, dropping idle GPU load near zero. AR_electronics — AR-Autopilot Project
111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
"""Configuration and layout persistence for AR Display Manager."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
CONFIG_DIR = Path.home() / ".ar-autopilot" / "display_manager"
|
|
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
LAYOUT_FILE = CONFIG_DIR / "layout.json"
|
|
|
|
|
|
@dataclass
|
|
class AppExeConfig:
|
|
"""Path + launch args for one application."""
|
|
exe: str = ""
|
|
args: list[str] = field(default_factory=list)
|
|
title_hint: str = "" # substring to match window title when searching
|
|
|
|
|
|
_DEFAULT_EXES: dict[str, AppExeConfig] = {
|
|
"autopilot": AppExeConfig(
|
|
exe=str(
|
|
REPO_ROOT / "display" / "build" / "windows" / "x64" / "runner"
|
|
/ "Release" / "display.exe"
|
|
),
|
|
title_hint="AR Autopilot",
|
|
),
|
|
"ecdis": AppExeConfig(exe="", title_hint="AR-ECDIS"),
|
|
"compass": AppExeConfig(exe="", title_hint="Compass"),
|
|
"gps": AppExeConfig(exe="", title_hint="GPS Navigator"),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class DisplayManagerConfig:
|
|
apps: dict[str, AppExeConfig] = field(default_factory=dict)
|
|
button_position: str = "top-right" # top-right | top-left | bottom-right | bottom-left
|
|
button_margin: int = 12 # px from screen edge
|
|
autolaunch: bool = False # launch all apps on startup (False = on-demand only)
|
|
|
|
# ------------------------------------------------------------------ I/O
|
|
@classmethod
|
|
def load(cls) -> "DisplayManagerConfig":
|
|
cfg = cls(apps=dict(_DEFAULT_EXES))
|
|
if not CONFIG_FILE.exists():
|
|
return cfg
|
|
try:
|
|
with open(CONFIG_FILE, encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
cfg.button_position = data.get("button_position", cfg.button_position)
|
|
cfg.button_margin = int(data.get("button_margin", cfg.button_margin))
|
|
cfg.autolaunch = bool(data.get("autolaunch", cfg.autolaunch))
|
|
for app_id, raw in data.get("apps", {}).items():
|
|
cfg.apps[app_id] = AppExeConfig(
|
|
exe=raw.get("exe", ""),
|
|
args=raw.get("args", []),
|
|
title_hint=raw.get("title_hint", ""),
|
|
)
|
|
except Exception:
|
|
pass
|
|
return cfg
|
|
|
|
def save(self) -> None:
|
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(
|
|
{
|
|
"button_position": self.button_position,
|
|
"button_margin": self.button_margin,
|
|
"autolaunch": self.autolaunch,
|
|
"apps": {
|
|
k: {"exe": v.exe, "args": v.args, "title_hint": v.title_hint}
|
|
for k, v in self.apps.items()
|
|
},
|
|
},
|
|
f,
|
|
indent=2,
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------- Layout
|
|
|
|
class LayoutStore:
|
|
"""Persists {screen_serial → app_id} so layout survives restarts."""
|
|
|
|
def __init__(self) -> None:
|
|
self._data: dict[str, str] = {}
|
|
self._load()
|
|
|
|
def _load(self) -> None:
|
|
if LAYOUT_FILE.exists():
|
|
try:
|
|
with open(LAYOUT_FILE, encoding="utf-8") as f:
|
|
self._data = json.load(f)
|
|
except Exception:
|
|
self._data = {}
|
|
|
|
def get(self, screen_serial: str) -> str | None:
|
|
return self._data.get(screen_serial)
|
|
|
|
def set(self, screen_serial: str, app_id: str) -> None:
|
|
self._data[screen_serial] = app_id
|
|
self._save()
|
|
|
|
def _save(self) -> None:
|
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
with open(LAYOUT_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(self._data, f, indent=2)
|