Files
alro65 46dc0423a0 fix(display-manager): lazy launch by default + minimize unassigned apps
- 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
2026-05-24 21:53:00 -04:00

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)