Files
aza/AzA march 2026/aza_empfang_webview.py
2026-05-08 22:35:18 +02:00

169 lines
5.8 KiB
Python

# -*- coding: utf-8 -*-
"""
AzA Empfang Web-Huelle: eigener Desktop-Prozess (pywebview).
Wird vom Desktop per subprocess gestartet, damit keine GUI-Kollision mit Tkinter entsteht.
Argument: erste Start-URL (z.B. GET /empfang/shell/launch?token=...).
WebView2-Profil: pywebview nutzt standardmaessig ``private_mode=True`` (ephemeral) und
speichert keine Site-Permissions zwischen Sessions. Für die Huelle setzen wir einen
festen ``storage_path`` unter %APPDATA%\\AzA\\EmpfangWebView und ``private_mode=False``,
damit z. B. Mikrofon-Erlaubnis für die App-URL persistiert (wie im normalen Edge-Profil),
ohne den Systembrowser zu verwenden.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
def _empfang_webview_storage_dir() -> str:
"""Eigener User-Data-Ordner, isoliert vom normalen Browser."""
appdata = (os.environ.get("APPDATA") or "").strip()
if appdata:
return str(Path(appdata) / "AzA" / "EmpfangWebView")
return str(Path.home() / ".aza_empfang_webview")
class EmpfangWebviewApi:
"""pywebview JS-API: globaler Patienten-Pinsel (Desktop), ohne Server/URL."""
def __init__(self) -> None:
self._window = None
def bind_window(self, window) -> None:
self._window = window
@staticmethod
def _pinsel_eval_js(win, js_code: str, label: str) -> None:
"""Lokal nur stdout/stderr; keine Clipboard-/Patient-Inhalte loggen."""
try:
win.evaluate_js(js_code)
print(f"[pinsel] evaluate_js called ({label})", flush=True)
except Exception as exc:
print(
f"[pinsel] evaluate_js failed ({label}): {type(exc).__name__}: {exc}",
flush=True,
)
try:
win.evaluate_js(
"try{if(window.shellPinselDiagSet)window.shellPinselDiagSet('Pinsel: Fehler: evaluate_js_failed');}catch(_e){}"
)
except Exception:
pass
def start_patient_nr_pick(self) -> dict:
from aza_empfang_smart_pick import (
compact_and_validate_patient_nr_pick_text,
start_global_patient_nr_pick,
)
print("[pinsel] api called", flush=True)
win = self._window
print(f"[pinsel] window bound {win is not None}", flush=True)
if win is None:
return {"ok": False, "reason": "no_window"}
def js_reset() -> None:
self._pinsel_eval_js(
win,
"try{window.shellFinalizeGlobalPickMechanismUi&&window.shellFinalizeGlobalPickMechanismUi();}catch(_e){}",
"shellFinalizeGlobalPickMechanismUi",
)
def on_clipboard(raw: str) -> None:
nn = compact_and_validate_patient_nr_pick_text(raw)
if not nn:
print("[pinsel] validated_rejected", flush=True)
self._pinsel_eval_js(
win,
"try{window.shellReceivePatientNrPickInvalid&&window.shellReceivePatientNrPickInvalid();}catch(_e){}",
"shellReceivePatientNrPickInvalid",
)
return
print("[pinsel] validated_ok", flush=True)
self._pinsel_eval_js(
win,
"try{window.shellReceivePatientNrFromDesktop(%s);}catch(_e){}"
% json.dumps(nn),
"shellReceivePatientNrFromDesktop",
)
print("[pinsel] starting_global_pick", flush=True)
rc = start_global_patient_nr_pick(
on_clipboard_pick=on_clipboard,
reset_pick_ui=js_reset,
owner_tk=None,
schedule_ui=None,
diagnostics=True,
)
print(f"[pinsel] start_global_patient_nr_pick rc={rc}", flush=True)
if rc == "cancelled":
return {"ok": True, "cancelled": True}
if rc == "unavailable":
self._pinsel_eval_js(
win,
"try{window.shellGlobalPinselResetUi&&window.shellGlobalPinselResetUi();}catch(_e){}",
"shellGlobalPinselResetUi(unavailable)",
)
return {"ok": False, "reason": "no_input_hook"}
return {"ok": True}
def cancel_patient_nr_pick(self) -> dict:
from aza_empfang_smart_pick import stop_global_patient_nr_pick
print("[pinsel] cancel_patient_nr_pick called", flush=True)
stop_global_patient_nr_pick()
return {"ok": True}
def main(argv: list[str] | None = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
if not argv or not argv[0].strip():
print(
"Usage:\n"
' python aza_empfang_webview.py "https://host/empfang/shell/launch?token=..."',
file=sys.stderr,
)
return 2
url = argv[0].strip()
w = int(argv[1]) if len(argv) > 1 and str(argv[1]).isdigit() else 1180
h = int(argv[2]) if len(argv) > 2 and str(argv[2]).isdigit() else 820
storage_path = _empfang_webview_storage_dir()
try:
Path(storage_path).mkdir(parents=True, exist_ok=True)
except OSError as exc:
print(
f"WebView-Profil-Ordner konnte nicht angelegt werden ({storage_path}): {exc}",
file=sys.stderr,
)
return 13
try:
import webview # noqa: WPS433 (runtime dependency)
except ImportError:
print(
"pywebview fehlt. Bitte installieren:\n"
" pip install pywebview>=5",
file=sys.stderr,
)
return 11
api = EmpfangWebviewApi()
try:
win = webview.create_window("AzA-Empfang", url, width=w, height=h, js_api=api)
api.bind_window(win)
webview.start(storage_path=storage_path, private_mode=False)
return 0
except Exception as exc:
print(str(exc), file=sys.stderr)
return 12
if __name__ == "__main__":
raise SystemExit(main())