File size: 3,045 Bytes
e0265b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from __future__ import annotations

import argparse
import ctypes
import os
import shutil
import sys
from pathlib import Path

from PySide6.QtCore import QTimer
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication

from adam.app import build_application


WINDOWS_APP_ID = "ADAM.Desktop.Orchestrator"


def application_paths() -> tuple[Path, Path]:
    """Return the writable app folder and bundled read-only resource folder."""
    source_root = Path(__file__).resolve().parent
    if getattr(sys, "frozen", False):
        return Path(sys.executable).resolve().parent, Path(
            getattr(sys, "_MEIPASS", source_root)
        ).resolve()
    return source_root, source_root


def prepare_portable_config(app_root: Path, resource_root: Path) -> None:
    """Seed a packaged copy with its default registry without overwriting user edits."""
    if app_root == resource_root:
        return
    config_dir = app_root / "config"
    config_dir.mkdir(parents=True, exist_ok=True)
    bundled_config = resource_root / "config"
    for name in ("tools.json", "settings.json", "external_tools.json"):
        source = bundled_config / name
        destination = config_dir / name
        if source.is_file() and not destination.exists():
            shutil.copy2(source, destination)


def set_windows_app_identity() -> None:
    """Give Windows a stable identity instead of grouping ADAM under Python."""
    if sys.platform != "win32":
        return
    try:
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(WINDOWS_APP_ID)
    except (AttributeError, OSError):
        pass


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="ADAM desktop orchestrator")
    parser.add_argument(
        "--screenshot",
        type=Path,
        help="Save a launch screenshot and exit (used for visual smoke tests).",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    project_root, resource_root = application_paths()
    prepare_portable_config(project_root, resource_root)
    set_windows_app_identity()

    if args.screenshot and not os.environ.get("QT_QPA_PLATFORM"):
        os.environ["QT_QPA_PLATFORM"] = "offscreen"

    qt_app = QApplication(sys.argv)
    qt_app.setApplicationName("ADAM")
    qt_app.setOrganizationName("ADAM")
    qt_app.setStyle("Fusion")

    icon_path = resource_root / "assets" / "adam_atom.png"
    if icon_path.exists():
        qt_app.setWindowIcon(QIcon(str(icon_path)))

    window, services = build_application(project_root)
    window.show()

    if args.screenshot:
        destination = args.screenshot.resolve()

        def capture() -> None:
            destination.parent.mkdir(parents=True, exist_ok=True)
            window.grab().save(str(destination))
            services.shutdown()
            qt_app.quit()

        QTimer.singleShot(1200, capture)

    exit_code = qt_app.exec()
    services.shutdown()
    return exit_code


if __name__ == "__main__":
    raise SystemExit(main())