"""Allow running as ``python -m src.gui``. Launches Streamlit with headless=true and opens Chrome in --app mode so the window has no address bar, tabs, or bookmarks — looks like a native desktop app. """ import os import shutil import subprocess import sys import threading import time from pathlib import Path _URL = "http://localhost:8501" def _find_app_browser() -> str | None: """Return the path to a Chromium-based browser that supports --app mode. Chrome is preferred. Edge (Chromium-based, ships on every Windows 10+ install) is the fallback that lets ``window.close()`` actually close the tab on the Close page — without it Windows users who don't have Chrome end up in a regular browser tab where the close button is silently blocked by browser security. """ # PATH first — covers Linux package installs and devs who put their # preferred browser on PATH explicitly. for name in ( "chrome", "google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "msedge", "microsoft-edge", ): found = shutil.which(name) if found: return found # Windows common install locations. if sys.platform == "win32": candidates = [ # Chrome — preferred when present. Path(os.environ.get("PROGRAMFILES", r"C:\Program Files")) / "Google" / "Chrome" / "Application" / "chrome.exe", Path(os.environ.get("PROGRAMFILES(X86)", r"C:\Program Files (x86)")) / "Google" / "Chrome" / "Application" / "chrome.exe", Path(os.environ.get("LOCALAPPDATA", "")) / "Google" / "Chrome" / "Application" / "chrome.exe", # Edge — ships with Windows 10+, Chromium-based, also # supports --app and the window.close() it enables. Path(os.environ.get("PROGRAMFILES(X86)", r"C:\Program Files (x86)")) / "Microsoft" / "Edge" / "Application" / "msedge.exe", Path(os.environ.get("PROGRAMFILES", r"C:\Program Files")) / "Microsoft" / "Edge" / "Application" / "msedge.exe", ] for c in candidates: if c.exists(): return str(c) # macOS if sys.platform == "darwin": candidates = [ Path("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"), Path("/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"), Path("/Applications/Chromium.app/Contents/MacOS/Chromium"), ] for c in candidates: if c.exists(): return str(c) return None def _open_app_window() -> None: """Wait for the server, then open the URL in an --app-mode window. --app mode makes the window look like a desktop app (no address bar, no tabs) AND — crucially — makes ``window.close()`` work, because the browser treats --app windows as programmatically closable. The regular-browser fallback prints a warning so the user knows why the Close button on the closing screen will require a manual Ctrl+W. """ time.sleep(2) browser = _find_app_browser() if browser: subprocess.Popen([browser, f"--app={_URL}"]) return print( "DataTools: Chrome and Edge were not found in their standard " "locations. Falling back to the default browser. The Close " "button on the shutdown screen will likely require a manual " "Ctrl+W to close the tab (a browser security restriction " "prevents JavaScript from closing user-opened tabs).", file=sys.stderr, ) import webbrowser webbrowser.open(_URL) app_path = Path(__file__).parent / "app.py" # Open the browser in a background thread so it doesn't block Streamlit threading.Thread(target=_open_app_window, daemon=True).start() subprocess.run([ sys.executable, "-m", "streamlit", "run", str(app_path), "--server.headless", "true", ])