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
This commit is contained in:
2026-05-24 21:53:00 -04:00
parent 42b2eec2e1
commit 46dc0423a0
4 changed files with 28 additions and 1 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ 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 = True # launch apps on startup if exe exists
autolaunch: bool = False # launch all apps on startup (False = on-demand only)
# ------------------------------------------------------------------ I/O
@classmethod
+12
View File
@@ -106,6 +106,18 @@ class DisplayManager(QObject):
self._bring_app(app_id, screen_geo)
self._layout.set(screen_serial, app_id)
# Minimize apps not assigned to any screen (free GPU)
self._minimize_unassigned()
def _minimize_unassigned(self) -> None:
"""Minimize every running app that is not currently assigned to any screen."""
assigned = set(
self._layout.get(self._screen_serial(s))
for s in self._app.screens()
) - {None}
for app_id in self._proc_mgr.running_ids():
if app_id not in assigned:
self._proc_mgr.minimize(app_id)
def _bring_app(self, app_id: str, geo: QRect) -> None:
self._proc_mgr.bring_to_screen(app_id, geo.x(), geo.y(), geo.width(), geo.height())
+10
View File
@@ -90,6 +90,16 @@ class ProcessManager(QObject):
win32_utils.move_and_maximize(hwnd, x, y, w, h)
return True
def minimize(self, app_id: str) -> None:
"""Minimize *app_id*'s window to free GPU resources."""
handle = self._handles.get(app_id)
if handle is None or not handle.is_running():
return
title_hint = self._config.apps.get(app_id, AppExeConfig()).title_hint
hwnd = handle.find_hwnd(title_hint)
if hwnd is not None:
win32_utils.minimize_window(hwnd)
def is_running(self, app_id: str) -> bool:
h = self._handles.get(app_id)
return h is not None and h.is_running()
+5
View File
@@ -80,5 +80,10 @@ else:
_user32.ShowWindow(hwnd, _SW_MAXIMIZE)
_user32.SetForegroundWindow(hwnd)
def minimize_window(hwnd: int) -> None:
"""Minimize a window to taskbar to free GPU resources."""
_SW_MINIMIZE = 6
_user32.ShowWindow(hwnd, _SW_MINIMIZE)
def is_window_alive(hwnd: int) -> bool:
return bool(_user32.IsWindow(hwnd))