feat: AR Display Manager — multi-monitor floating app switcher

Adds the AR Display Manager daemon (Task #9):

- display_manager/ package with 8 modules:
  - app_registry.py   : static metadata for the 4 bridge apps
  - config.py         : JSON-persisted config + per-screen layout store
  - win32_utils.py    : ctypes wrappers (EnumWindows, SetWindowPos, ShowWindow)
  - process_manager.py: launch + track app processes, HWND lookup
  - floating_button.py: always-on-top 52×52 px AR button per monitor
                        with draggable placement + custom-painted popup
  - display_manager.py: orchestrator (QScreens → buttons → app placement
                        + system tray with Settings/Launch/Quit)
  - settings_dialog.py: modal dialog for exe paths, button position,
                        and Windows autostart toggle
  - autostart.py      : HKCU Run registry read/write helpers
- display_manager_main.py at repo root: launcher script
- Studio Overview tab: "Lanzar Display Manager" button
- pyproject.toml: display-manager optional dependency group (PySide6)

Layout persistence: ~/.ar-autopilot/display_manager/layout.json
Config:            ~/.ar-autopilot/display_manager/config.json
Supports up to 4 monitors (2 HDMI native + 2 USB DisplayLink).
Double-click tray icon to toggle button visibility.

AR_electronics — AR-Autopilot Project
This commit is contained in:
2026-05-24 21:48:27 -04:00
parent b68cd64cf1
commit 42b2eec2e1
13 changed files with 1120 additions and 0 deletions
+51
View File
@@ -18,9 +18,12 @@ from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPixmap
from PySide6.QtWidgets import (
QFrame,
QHBoxLayout,
QLabel,
QListWidget,
QMainWindow,
QMessageBox,
QPushButton,
QSplitter,
QStatusBar,
QTabWidget,
@@ -193,6 +196,30 @@ class StudioMainWindow(QMainWindow):
guide.setTextFormat(Qt.TextFormat.RichText)
guide.setWordWrap(True)
layout.addWidget(guide)
# AR Display Manager quick launch
sep2 = QFrame()
sep2.setFrameShape(QFrame.Shape.HLine)
sep2.setStyleSheet(f"color:{ACCENT_MID}; margin: 10px 0;")
layout.addWidget(sep2)
dm_row = QHBoxLayout()
dm_label = QLabel(
f"<b style='color:{ACCENT_MID}'>🖥 AR Display Manager</b><br/>"
f"<span style='color:{TEXT_MUTED};font-size:11px;'>"
"Gestiona cuál app aparece en cada monitor del J6412 "
"(hasta 4 pantallas simultáneas).</span>"
)
dm_label.setTextFormat(Qt.TextFormat.RichText)
dm_label.setWordWrap(True)
dm_row.addWidget(dm_label, stretch=1)
dm_btn = QPushButton("Lanzar Display Manager")
dm_btn.setMinimumWidth(200)
dm_btn.clicked.connect(self._launch_display_manager)
dm_row.addWidget(dm_btn)
layout.addLayout(dm_row)
layout.addStretch(1)
footer = QLabel(
@@ -204,3 +231,27 @@ class StudioMainWindow(QMainWindow):
layout.addWidget(footer)
return w
# ── Display Manager launcher ──────────────────────────────────────────────
def _launch_display_manager(self) -> None:
import subprocess
import sys as _sys
launcher = REPO_ROOT / "display_manager_main.py"
if not launcher.exists():
QMessageBox.warning(
self,
"Not found",
f"display_manager_main.py not found at:\n{launcher}",
)
return
try:
subprocess.Popen(
[_sys.executable, str(launcher)],
creationflags=(
subprocess.CREATE_NEW_PROCESS_GROUP
if _sys.platform == "win32" else 0
),
)
except Exception as exc:
QMessageBox.critical(self, "Launch failed", str(exc))