Source code for exlab_wizard.window.pywebview_app
"""pywebview-driven native window. Backend Spec §15.3.2.
Opens a single pywebview window pointed at
``http://127.0.0.1:<port>`` from the handshake. Window title, size, and
icon are hard-coded here per §15.3.2; devtools are gated by the
``EXLAB_DEBUG`` env var so release artifacts never enable them.
The actual ``webview`` import is deferred and isolated behind helper
functions so unit tests on a headless host can exercise URL building,
debug flag detection, and the assembly path without booting a real
webview backend.
"""
from __future__ import annotations
import os
from collections.abc import Callable
from typing import TYPE_CHECKING, Any
from exlab_wizard.logging import get_logger
if TYPE_CHECKING:
from exlab_wizard.window.main import ServerHandshake
__all__ = [
"DEFAULT_HEIGHT",
"DEFAULT_WIDTH",
"WINDOW_TITLE",
"build_window_url",
"is_debug_enabled",
"run_window",
]
_log = get_logger(__name__)
WINDOW_TITLE = "ExLab-Wizard"
DEFAULT_WIDTH = 1280
DEFAULT_HEIGHT = 800
DEBUG_ENV_VAR = "EXLAB_DEBUG"
[docs]
def build_window_url(handshake: ServerHandshake) -> str:
"""Return the URL the window points at.
Always loopback (Backend §4.1: "binds to ``127.0.0.1`` only"); the
handshake's port is mandatory.
"""
return f"http://127.0.0.1:{int(handshake.port)}"
[docs]
def is_debug_enabled() -> bool:
"""True when ``EXLAB_DEBUG`` is set to a truthy string. Backend §15.3.2."""
raw = os.environ.get(DEBUG_ENV_VAR, "")
if not raw:
return False
return raw.strip().lower() not in {"0", "false", "no", "off"}
[docs]
def run_window(
handshake: ServerHandshake,
*,
create_window: Callable[..., Any] | None = None,
start: Callable[..., Any] | None = None,
) -> int:
"""Open a single pywebview window pointed at the handshake's port.
``create_window`` and ``start`` are dependency-injection hooks for
tests; production code defers to ``webview.create_window`` and
``webview.start``. Returns 0 on clean exit.
"""
url = build_window_url(handshake)
debug = is_debug_enabled()
_log.info("opening window at %s (debug=%s)", url, debug)
if create_window is None or start is None:
webview = _import_webview()
if create_window is None:
create_window = webview.create_window
if start is None:
start = webview.start
create_window(
title=WINDOW_TITLE,
url=url,
width=DEFAULT_WIDTH,
height=DEFAULT_HEIGHT,
resizable=True,
)
start(debug=debug)
return 0
def _import_webview() -> Any:
"""Lazily import pywebview to keep the module import cheap."""
import webview
return webview