42b2eec2e1
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
111 lines
3.9 KiB
Python
111 lines
3.9 KiB
Python
"""Settings dialog for AR Display Manager — configure exe paths and button position."""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from PySide6.QtWidgets import (
|
|
QCheckBox,
|
|
QComboBox,
|
|
QDialog,
|
|
QDialogButtonBox,
|
|
QFileDialog,
|
|
QFormLayout,
|
|
QGroupBox,
|
|
QHBoxLayout,
|
|
QLineEdit,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from display_manager.app_registry import APPS
|
|
from display_manager.autostart import disable as autostart_disable
|
|
from display_manager.autostart import enable as autostart_enable
|
|
from display_manager.autostart import is_enabled as autostart_is_enabled
|
|
from display_manager.config import AppExeConfig, DisplayManagerConfig
|
|
|
|
|
|
class SettingsDialog(QDialog):
|
|
"""Modal settings editor for the Display Manager."""
|
|
|
|
def __init__(self, config: DisplayManagerConfig, parent: QWidget | None = None) -> None:
|
|
super().__init__(parent)
|
|
self.setWindowTitle("AR Display Manager — Settings")
|
|
self.setMinimumWidth(520)
|
|
self._config = config
|
|
self._fields: dict[str, QLineEdit] = {}
|
|
self._build_ui()
|
|
|
|
def _build_ui(self) -> None:
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Button position
|
|
pos_group = QGroupBox("Floating button")
|
|
pos_form = QFormLayout(pos_group)
|
|
self._pos_combo = QComboBox()
|
|
for label, val in [
|
|
("Top-right (default)", "top-right"),
|
|
("Top-left", "top-left"),
|
|
("Bottom-right", "bottom-right"),
|
|
("Bottom-left", "bottom-left"),
|
|
]:
|
|
self._pos_combo.addItem(label, userData=val)
|
|
idx = self._pos_combo.findData(self._config.button_position)
|
|
if idx >= 0:
|
|
self._pos_combo.setCurrentIndex(idx)
|
|
pos_form.addRow("Position on each monitor:", self._pos_combo)
|
|
layout.addWidget(pos_group)
|
|
|
|
# App executables
|
|
apps_group = QGroupBox("Application executables")
|
|
apps_form = QFormLayout(apps_group)
|
|
for app_meta in APPS:
|
|
exe_cfg = self._config.apps.get(app_meta.id, AppExeConfig())
|
|
row = QHBoxLayout()
|
|
field = QLineEdit(exe_cfg.exe)
|
|
field.setPlaceholderText("(not configured — click Browse)")
|
|
self._fields[app_meta.id] = field
|
|
row.addWidget(field, stretch=1)
|
|
browse = QPushButton("Browse…")
|
|
browse.clicked.connect(lambda checked=False, f=field: self._browse(f))
|
|
row.addWidget(browse)
|
|
apps_form.addRow(f"{app_meta.icon} {app_meta.name}:", row)
|
|
layout.addWidget(apps_group)
|
|
|
|
# Autostart
|
|
sys_group = QGroupBox("System")
|
|
sys_form = QFormLayout(sys_group)
|
|
self._autostart_chk = QCheckBox("Start AR Display Manager with Windows")
|
|
self._autostart_chk.setChecked(autostart_is_enabled())
|
|
sys_form.addRow(self._autostart_chk)
|
|
layout.addWidget(sys_group)
|
|
|
|
# Buttons
|
|
btns = QDialogButtonBox(
|
|
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
|
)
|
|
btns.accepted.connect(self._accept)
|
|
btns.rejected.connect(self.reject)
|
|
layout.addWidget(btns)
|
|
|
|
def _browse(self, field: QLineEdit) -> None:
|
|
path, _ = QFileDialog.getOpenFileName(
|
|
self, "Select executable", str(Path.home()), "Executables (*.exe);;All files (*)"
|
|
)
|
|
if path:
|
|
field.setText(path)
|
|
|
|
def _accept(self) -> None:
|
|
self._config.button_position = self._pos_combo.currentData()
|
|
for app_meta in APPS:
|
|
if app_meta.id not in self._config.apps:
|
|
self._config.apps[app_meta.id] = AppExeConfig()
|
|
self._config.apps[app_meta.id].exe = self._fields[app_meta.id].text()
|
|
self._config.save()
|
|
# Autostart
|
|
if self._autostart_chk.isChecked():
|
|
autostart_enable()
|
|
else:
|
|
autostart_disable()
|
|
self.accept()
|