update
This commit is contained in:
@@ -2,14 +2,33 @@
|
||||
"""
|
||||
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=...).
|
||||
Drei Betriebsmodi, klar getrennt:
|
||||
|
||||
* ``desktop_shell`` Vom AzA-Hauptprogramm (basis14) per subprocess gestartet
|
||||
mit /empfang/shell/launch?token=... (KEIN target=...).
|
||||
Fenstertitel: "AzA-Empfang \xb7 Desktop"
|
||||
Storage: %APPDATA%\\AzA\\EmpfangWebView
|
||||
AUMID: ch.aza-medwork.empfang.shellwebview
|
||||
|
||||
* ``empfang_chat_shell`` Separat installierbare Huelle (AZA_EmpfangShell.exe via
|
||||
aza_empfang_chat_setup.exe) auf Empfangs-/MPA-Computern.
|
||||
Default-URL beim Doppelklick:
|
||||
https://empfang.aza-medwork.ch/empfang/
|
||||
?empfang_chat_shell=1&shell_source=empfang_chat_shell
|
||||
Fenstertitel: "AzA Empfang Chat"
|
||||
Storage: %APPDATA%\\AzA\\EmpfangChatWebView
|
||||
AUMID: ch.aza-medwork.empfang.chatshell
|
||||
|
||||
* ``minichat`` Kleines Empfang-Popup (?minichat=1). Erbt Storage/AUMID
|
||||
des Parent-Modus, damit die Login-Session erhalten bleibt.
|
||||
Fenstertitel: "AzA MiniChat"
|
||||
|
||||
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.
|
||||
speichert keine Site-Permissions zwischen Sessions. Wir setzen je Modus einen festen
|
||||
``storage_path`` und ``private_mode=False``, damit Mikrofon-Erlaubnis etc. fuer die
|
||||
App-URL persistieren (wie im normalen Edge-Profil) ohne den Systembrowser zu nutzen.
|
||||
Getrennte Storage-Pfade verhindern, dass Haupt- und Empfang-Chat-Huelle sich beim
|
||||
parallelen Coldstart gegenseitig sperren ("Keine Rueckmeldung").
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -107,8 +126,13 @@ def _apply_win32_topmost(value: bool) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _focus_other_empfang_host_window() -> bool:
|
||||
"""Bringt ein anderes AzA-Empfang-Fenster (anderer Prozess) in den Vordergrund."""
|
||||
def _focus_other_empfang_host_window(title_prefixes: tuple[str, ...] = ("AzA-Empfang",)) -> bool:
|
||||
"""Bringt ein anderes AzA-Empfang-Fenster (anderer Prozess) in den Vordergrund.
|
||||
|
||||
``title_prefixes`` schraenkt das Match auf den eigenen Modus ein. Default ist
|
||||
die Desktop-Huelle ("AzA-Empfang"/"AzA-Empfang \xb7 Desktop"), damit Cross-Mode-
|
||||
Fokus (Chat-Huelle <-> Desktop-Huelle) NICHT passiert.
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
return False
|
||||
try:
|
||||
@@ -118,6 +142,7 @@ def _focus_other_empfang_host_window() -> bool:
|
||||
user32 = ctypes.windll.user32
|
||||
my_pid = int(os.getpid())
|
||||
handles: list[int] = []
|
||||
prefixes = tuple(p for p in title_prefixes if p)
|
||||
|
||||
@ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
||||
def _enum(hwnd, _lp):
|
||||
@@ -125,7 +150,8 @@ def _focus_other_empfang_host_window() -> bool:
|
||||
return True
|
||||
buf = ctypes.create_unicode_buffer(260)
|
||||
user32.GetWindowTextW(hwnd, buf, 260)
|
||||
if not (buf.value == "AzA-Empfang" or buf.value.startswith("AzA-Empfang")):
|
||||
t = buf.value or ""
|
||||
if not any(t == p or t.startswith(p) for p in prefixes):
|
||||
return True
|
||||
pid = wintypes.DWORD()
|
||||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
|
||||
@@ -146,25 +172,172 @@ def _focus_other_empfang_host_window() -> bool:
|
||||
|
||||
|
||||
def _empfang_shell_icon_path() -> str | None:
|
||||
"""Windows/pywebview: WinForms laedt Fenstericon aus .ico neben diesem Skript."""
|
||||
p = Path(__file__).resolve().parent / "logo.ico"
|
||||
return str(p) if p.is_file() else None
|
||||
"""Windows/pywebview: WinForms laedt Fenstericon aus .ico neben diesem Skript.
|
||||
|
||||
Im PyInstaller-Bundle liegt logo.ico unter sys._MEIPASS (bzw. neben sys.executable).
|
||||
"""
|
||||
candidates: list[Path] = []
|
||||
try:
|
||||
if getattr(sys, "frozen", False):
|
||||
candidates.append(Path(sys.executable).resolve().parent / "logo.ico")
|
||||
meip = getattr(sys, "_MEIPASS", "")
|
||||
if meip:
|
||||
candidates.append(Path(meip) / "logo.ico")
|
||||
except Exception:
|
||||
pass
|
||||
candidates.append(Path(__file__).resolve().parent / "logo.ico")
|
||||
for c in candidates:
|
||||
try:
|
||||
if c.is_file():
|
||||
return str(c)
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _empfang_webview_storage_dir() -> str:
|
||||
"""Eigener User-Data-Ordner, isoliert vom normalen Browser."""
|
||||
# ---------------------------------------------------------------------------
|
||||
# Modus-Klassifizierung (Desktop-Huelle / separate Empfang-Chat-Huelle /
|
||||
# MiniChat-Popup) - bestimmt Titel, Storage, AppUserModelID.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MODE_DESKTOP_SHELL = "desktop_shell"
|
||||
_MODE_EMPFANG_CHAT_SHELL = "empfang_chat_shell"
|
||||
_MODE_MINICHAT = "minichat"
|
||||
|
||||
|
||||
def _classify_shell_mode(start_url: str, *, parent_hint: str = "") -> str:
|
||||
"""Bestimmt den Betriebsmodus aus der Start-URL.
|
||||
|
||||
Eindeutige Marker (per Reihenfolge):
|
||||
1. ?minichat=1 -> minichat (Parent-Modus folgt unten)
|
||||
2. target=empfang_chat_shell (im /shell/launch) -> empfang_chat_shell
|
||||
3. shell_source=aza_desktop / ?desktop_shell=1 -> desktop_shell
|
||||
4. shell_source=empfang_chat_shell / ?empfang_chat_shell=1 -> empfang_chat_shell
|
||||
5. /empfang/shell/launch?token=... (ohne target) -> desktop_shell (legacy AzA Office)
|
||||
6. Default (kein Argument, Doppelklick) -> empfang_chat_shell
|
||||
|
||||
``parent_hint`` ist nur fuer MiniChat relevant: minichat erbt den Modus
|
||||
seines Parent, damit das WebView2-Profil (und damit die Login-Session)
|
||||
konsistent bleibt.
|
||||
"""
|
||||
u = (start_url or "").strip()
|
||||
low = u.lower()
|
||||
|
||||
if "minichat=1" in low or "mode=minichat" in low:
|
||||
return _MODE_MINICHAT
|
||||
|
||||
if "target=empfang_chat_shell" in low or "target%3dempfang_chat_shell" in low:
|
||||
return _MODE_EMPFANG_CHAT_SHELL
|
||||
|
||||
if (
|
||||
"shell_source=aza_desktop" in low
|
||||
or "shell_source%3daza_desktop" in low
|
||||
or "desktop_shell=1" in low
|
||||
):
|
||||
return _MODE_DESKTOP_SHELL
|
||||
|
||||
if (
|
||||
"shell_source=empfang_chat_shell" in low
|
||||
or "shell_source%3dempfang_chat_shell" in low
|
||||
or "empfang_chat_shell=1" in low
|
||||
):
|
||||
return _MODE_EMPFANG_CHAT_SHELL
|
||||
|
||||
if "/empfang/shell/launch" in low and "token=" in low:
|
||||
# /shell/launch ohne target=... -> historischer AzA-Desktop-Pfad.
|
||||
return _MODE_DESKTOP_SHELL
|
||||
|
||||
_ph = (parent_hint or "").strip().lower()
|
||||
if _ph in (_MODE_DESKTOP_SHELL, _MODE_EMPFANG_CHAT_SHELL):
|
||||
return _ph
|
||||
|
||||
return _MODE_EMPFANG_CHAT_SHELL
|
||||
|
||||
|
||||
def _split_argv_for_shell_mode(argv: list[str]) -> tuple[list[str], str]:
|
||||
"""Filtert ``--shell-mode=<x>`` aus argv heraus.
|
||||
|
||||
Wird vom MiniChat-Subprocess gesetzt, damit der Kind-Prozess das gleiche
|
||||
WebView2-Profil/AUMID wie sein Parent verwendet (Login bleibt erhalten).
|
||||
Andere Modi setzen das Flag nicht; sie werden aus der URL klassifiziert.
|
||||
"""
|
||||
rest: list[str] = []
|
||||
mode = ""
|
||||
for a in argv:
|
||||
s = str(a or "")
|
||||
ls = s.strip()
|
||||
if ls.startswith("--shell-mode="):
|
||||
mode = ls.split("=", 1)[1].strip().lower()
|
||||
continue
|
||||
rest.append(s)
|
||||
return rest, mode
|
||||
|
||||
|
||||
def _window_title_for_mode(mode: str) -> str:
|
||||
if mode == _MODE_DESKTOP_SHELL:
|
||||
return "AzA-Empfang \u00b7 Desktop"
|
||||
if mode == _MODE_MINICHAT:
|
||||
return "AzA MiniChat"
|
||||
return "AzA Empfang Chat"
|
||||
|
||||
|
||||
def _aumid_for_mode(mode: str) -> str:
|
||||
if mode == _MODE_DESKTOP_SHELL:
|
||||
# Kompatibel zur bisherigen Haupt-Huelle (Taskleisten-Icon).
|
||||
return "ch.aza-medwork.empfang.shellwebview"
|
||||
if mode == _MODE_MINICHAT:
|
||||
return "ch.aza-medwork.empfang.minichat"
|
||||
return "ch.aza-medwork.empfang.chatshell"
|
||||
|
||||
|
||||
def _effective_profile_mode(mode: str, parent_mode_hint: str = "") -> str:
|
||||
"""WebView2-Profil-Mode (Cookies/Login-Session) - MiniChat erbt Parent.
|
||||
|
||||
Storage darf MiniChat NICHT entkoppeln, sonst geht die Login-Session verloren.
|
||||
Das Fenster selbst hat trotzdem eigenen Titel/AUMID/Pin (logische Trennung).
|
||||
"""
|
||||
m = (mode or "").strip().lower()
|
||||
if m == _MODE_MINICHAT:
|
||||
ph = (parent_mode_hint or "").strip().lower()
|
||||
if ph in (_MODE_DESKTOP_SHELL, _MODE_EMPFANG_CHAT_SHELL):
|
||||
return ph
|
||||
# Default-Parent fuer MiniChat: Chat-Huelle (Standalone-Empfang-Chat-Shell)
|
||||
return _MODE_EMPFANG_CHAT_SHELL
|
||||
return m or _MODE_DESKTOP_SHELL
|
||||
|
||||
|
||||
def _empfang_webview_storage_dir(mode: str = _MODE_DESKTOP_SHELL, parent_mode_hint: str = "") -> str:
|
||||
"""WebView2-User-Data-Ordner pro Modus, damit Haupt- und Chat-Huelle nicht
|
||||
dasselbe WebView2-Profil sperren.
|
||||
|
||||
MiniChat ERBT den Storage seines Parent-Modus, damit die Login-Session
|
||||
geteilt wird (Cookies, LocalStorage).
|
||||
|
||||
- desktop_shell -> %APPDATA%\\AzA\\EmpfangWebView (kompatibel zur Hauptprogramm-Huelle)
|
||||
- empfang_chat_shell -> %APPDATA%\\AzA\\EmpfangChatWebView
|
||||
- minichat (Parent=Desktop) -> EmpfangWebView
|
||||
- minichat (Parent=ChatShell) -> EmpfangChatWebView
|
||||
"""
|
||||
eff = _effective_profile_mode(mode, parent_mode_hint)
|
||||
appdata = (os.environ.get("APPDATA") or "").strip()
|
||||
if appdata:
|
||||
return str(Path(appdata) / "AzA" / "EmpfangWebView")
|
||||
return str(Path.home() / ".aza_empfang_webview")
|
||||
base = Path(appdata) / "AzA" if appdata else Path.home() / ".aza_empfang_webview"
|
||||
if eff == _MODE_EMPFANG_CHAT_SHELL:
|
||||
return str(base / "EmpfangChatWebView") if appdata else str(base.parent / ".aza_empfang_chat_webview")
|
||||
return str(base / "EmpfangWebView") if appdata else str(base)
|
||||
|
||||
|
||||
def _shell_pin_state_path() -> Path:
|
||||
return Path(_empfang_webview_storage_dir()) / "shell_pin_on_top.json"
|
||||
def _shell_pin_state_path(mode: str = _MODE_DESKTOP_SHELL, parent_mode_hint: str = "") -> Path:
|
||||
"""Pin-State pro Fenster-Modus (MiniChat hat eigenes Pin-Verhalten),
|
||||
liegt aber im Profil-Storage des effektiven Parent-Modus.
|
||||
"""
|
||||
storage = _empfang_webview_storage_dir(mode, parent_mode_hint)
|
||||
if (mode or "").strip().lower() == _MODE_MINICHAT:
|
||||
return Path(storage) / "minichat_pin_on_top.json"
|
||||
return Path(storage) / "shell_pin_on_top.json"
|
||||
|
||||
|
||||
def _load_shell_pin_on_top() -> bool:
|
||||
p = _shell_pin_state_path()
|
||||
def _load_shell_pin_on_top(mode: str = _MODE_DESKTOP_SHELL, parent_mode_hint: str = "") -> bool:
|
||||
p = _shell_pin_state_path(mode, parent_mode_hint)
|
||||
try:
|
||||
if p.is_file():
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
@@ -174,8 +347,8 @@ def _load_shell_pin_on_top() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _save_shell_pin_on_top(value: bool) -> None:
|
||||
p = _shell_pin_state_path()
|
||||
def _save_shell_pin_on_top(value: bool, mode: str = _MODE_DESKTOP_SHELL, parent_mode_hint: str = "") -> None:
|
||||
p = _shell_pin_state_path(mode, parent_mode_hint)
|
||||
try:
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(
|
||||
@@ -189,9 +362,11 @@ def _save_shell_pin_on_top(value: bool) -> None:
|
||||
class EmpfangWebviewApi:
|
||||
"""pywebview JS-API: globaler Patienten-Pinsel (Desktop), Fenster-Pin (always on top)."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, mode: str = _MODE_DESKTOP_SHELL, parent_mode_hint: str = "") -> None:
|
||||
self._window = None
|
||||
self._on_top = _load_shell_pin_on_top()
|
||||
self._mode = (mode or _MODE_DESKTOP_SHELL).strip().lower()
|
||||
self._parent_mode_hint = (parent_mode_hint or "").strip().lower()
|
||||
self._on_top = _load_shell_pin_on_top(self._mode, self._parent_mode_hint)
|
||||
self._pin_lock = threading.Lock()
|
||||
|
||||
def bind_window(self, window) -> None:
|
||||
@@ -213,9 +388,9 @@ class EmpfangWebviewApi:
|
||||
finally:
|
||||
self._pin_lock.release()
|
||||
|
||||
_save_shell_pin_on_top(new_val)
|
||||
_save_shell_pin_on_top(new_val, self._mode, self._parent_mode_hint)
|
||||
print(
|
||||
f"[EMPFANG_TOPMOST] stage=manual_toggle requested={new_val}",
|
||||
f"[EMPFANG_TOPMOST] stage=manual_toggle mode={self._mode} requested={new_val}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
@@ -331,6 +506,10 @@ class EmpfangWebviewApi:
|
||||
|
||||
Nicht-blockierend: Popen in Daemon-Thread. Damit hat das MiniChat-Fenster
|
||||
eine eigene pywebview-Instanz inkl. Pin (on_top), ohne window.open-Popup ohne API.
|
||||
|
||||
Wichtig: Wir reichen den Parent-Modus via ``--shell-mode=<mode>`` durch.
|
||||
Damit teilt sich MiniChat das WebView2-Profil seines Parents (Desktop-Huelle
|
||||
ODER Chat-Huelle), und die Login-Session bleibt erhalten.
|
||||
"""
|
||||
url = str(href or "").strip()
|
||||
if not url:
|
||||
@@ -343,15 +522,18 @@ class EmpfangWebviewApi:
|
||||
except Exception:
|
||||
full = url
|
||||
w, h = 600, 820
|
||||
# MiniChat erbt das effektive Profil seines Parents (nicht "minichat" selbst).
|
||||
parent_mode = _effective_profile_mode(self._mode, self._parent_mode_hint)
|
||||
|
||||
def _spawn() -> None:
|
||||
try:
|
||||
script = Path(__file__).resolve()
|
||||
mode_arg = f"--shell-mode={parent_mode}"
|
||||
if getattr(sys, "frozen", False):
|
||||
cmd = [sys.executable, full, str(w), str(h)]
|
||||
cmd = [sys.executable, mode_arg, full, str(w), str(h)]
|
||||
cwd = str(Path(sys.executable).resolve().parent)
|
||||
else:
|
||||
cmd = [sys.executable, str(script), full, str(w), str(h)]
|
||||
cmd = [sys.executable, str(script), mode_arg, full, str(w), str(h)]
|
||||
cwd = str(script.parent)
|
||||
subprocess.Popen(
|
||||
cmd,
|
||||
@@ -365,22 +547,31 @@ class EmpfangWebviewApi:
|
||||
return {"ok": True}
|
||||
|
||||
def open_browser_chat(self, href: str) -> dict:
|
||||
"""Volle Empfang-Ansicht ohne minichat: erst anderes Fenster fokussieren, sonst neuer Prozess."""
|
||||
"""Volle Empfang-Ansicht ohne minichat: erst anderes Fenster fokussieren, sonst neuer Prozess.
|
||||
|
||||
Cross-Mode-Fokus wird vermieden: nur Fenster mit dem gleichen Titel-Praefix
|
||||
wie der eigene Modus werden in den Vordergrund geholt.
|
||||
"""
|
||||
url = _strip_minichat_query(href)
|
||||
if not url:
|
||||
return {"ok": False, "reason": "empty"}
|
||||
w_main, h_main = 1180, 820
|
||||
# Beim "Browser-Chat oeffnen" aus dem MiniChat das volle Empfang-Fenster
|
||||
# im effektiven Parent-Modus oeffnen, nicht erneut als MiniChat.
|
||||
parent_mode = _effective_profile_mode(self._mode, self._parent_mode_hint)
|
||||
prefixes = (_window_title_for_mode(parent_mode),)
|
||||
|
||||
def _work() -> None:
|
||||
if _focus_other_empfang_host_window():
|
||||
if _focus_other_empfang_host_window(prefixes):
|
||||
return
|
||||
try:
|
||||
script = Path(__file__).resolve()
|
||||
mode_arg = f"--shell-mode={parent_mode}"
|
||||
if getattr(sys, "frozen", False):
|
||||
cmd = [sys.executable, url, str(w_main), str(h_main)]
|
||||
cmd = [sys.executable, mode_arg, url, str(w_main), str(h_main)]
|
||||
cwd = str(Path(sys.executable).resolve().parent)
|
||||
else:
|
||||
cmd = [sys.executable, str(script), url, str(w_main), str(h_main)]
|
||||
cmd = [sys.executable, str(script), mode_arg, url, str(w_main), str(h_main)]
|
||||
cwd = str(script.parent)
|
||||
subprocess.Popen(
|
||||
cmd,
|
||||
@@ -475,6 +666,42 @@ class EmpfangWebviewApi:
|
||||
stop_global_patient_nr_pick()
|
||||
return {"ok": True}
|
||||
|
||||
def start_region_snapshot_capture(self) -> dict:
|
||||
"""Startet nativen Bereichs-Screenshot (Tk-Overlay + PIL) fuer die WebView-Huelle.
|
||||
|
||||
Rueckgabe an JS erfolgt asynchron per evaluate_js -> shellReceiveSnapshotFromDesktop.
|
||||
Keine Bilddaten in Logs.
|
||||
"""
|
||||
from aza_empfang_region_snapshot import start_region_snapshot_capture as _region_snap_start
|
||||
|
||||
win = self._window
|
||||
if win is None:
|
||||
return {"ok": False, "reason": "no_window"}
|
||||
if sys.platform != "win32":
|
||||
return {"ok": False, "reason": "unsupported_os"}
|
||||
|
||||
def on_done(data_url: str | None) -> None:
|
||||
if not data_url:
|
||||
self._pinsel_eval_js(
|
||||
win,
|
||||
"try{if(window.shellReceiveSnapshotFromDesktopCancel)"
|
||||
"window.shellReceiveSnapshotFromDesktopCancel();}catch(_e){}",
|
||||
"snapshot_cancel",
|
||||
)
|
||||
return
|
||||
self._pinsel_eval_js(
|
||||
win,
|
||||
"try{if(window.shellReceiveSnapshotFromDesktop)"
|
||||
"window.shellReceiveSnapshotFromDesktop(%s);}catch(_e){}"
|
||||
% json.dumps(data_url),
|
||||
"shellReceiveSnapshotFromDesktop",
|
||||
)
|
||||
|
||||
rc = _region_snap_start(on_done)
|
||||
if rc == "busy":
|
||||
return {"ok": False, "reason": "busy"}
|
||||
return {"ok": True}
|
||||
|
||||
def play_notification_sound(self, kind: str = "") -> dict:
|
||||
"""Spielt einen kurzen Systemton ab (Windows: winsound.MessageBeep).
|
||||
|
||||
@@ -509,9 +736,13 @@ class EmpfangWebviewApi:
|
||||
werden in einen Daemon-Thread ausgelagert, damit der WebView-Worker
|
||||
nicht haengt ("AzA-Empfang reagiert nicht").
|
||||
|
||||
Sucht nur nach Fenstern, deren Titel zum eigenen Modus passt - keine
|
||||
Cross-Mode-Fokussierung (Desktop-Huelle <-> Chat-Huelle).
|
||||
|
||||
Keine Chat-/Patientendaten werden geloggt oder verarbeitet.
|
||||
"""
|
||||
win = self._window
|
||||
own_title = _window_title_for_mode(self._mode)
|
||||
|
||||
def _do() -> None:
|
||||
try:
|
||||
@@ -538,7 +769,7 @@ class EmpfangWebviewApi:
|
||||
buf = ctypes.create_unicode_buffer(260)
|
||||
user32.GetWindowTextW(hwnd, buf, 260)
|
||||
t = buf.value or ""
|
||||
if not (t == "AzA-Empfang" or t.startswith("AzA-Empfang")):
|
||||
if not (t == own_title or t.startswith(own_title)):
|
||||
return True
|
||||
pid = ctypes.c_ulong()
|
||||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
|
||||
@@ -575,17 +806,37 @@ def _append_query_marker(url: str, key: str, value: str) -> str:
|
||||
return f"{url}{sep}{key}={value}"
|
||||
|
||||
|
||||
_EMPFANG_CHAT_SHELL_DEFAULT_BASE = "https://empfang.aza-medwork.ch/empfang/"
|
||||
|
||||
|
||||
def _build_default_empfang_chat_shell_url() -> str:
|
||||
"""Standard-URL der separaten Empfang-Chat-Huelle (kein Arzt-Desktop):
|
||||
nutzt aza_empfang_app._empfang_url() bzw. den oeffentlichen Fallback und setzt
|
||||
den Marker ?empfang_chat_shell=1 fuer empfang.html (Login-/Handoff-UI).
|
||||
"""Standard-URL der separaten Empfang-Chat-Huelle (kein Arzt-Desktop).
|
||||
|
||||
Setzt BEIDE Marker fuer empfang.html (data-empfang-chat-shell + Login-/Handoff-UI):
|
||||
* empfang_chat_shell=1
|
||||
* shell_source=empfang_chat_shell
|
||||
|
||||
Optional ueberschreibbar per Umgebungsvariable
|
||||
``AZA_EMPFANG_CHAT_SHELL_URL`` (z.B. Staging). Es wird absichtlich NICHT
|
||||
aus aza_empfang_app._empfang_url() / backend_url.txt gelesen, damit die
|
||||
Chat-Huelle unabhaengig von einer optionalen Datei neben der EXE arbeitet
|
||||
und keine alte/abweichende Server-URL erbt.
|
||||
"""
|
||||
base = (os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or "").strip()
|
||||
if not base:
|
||||
base = _EMPFANG_CHAT_SHELL_DEFAULT_BASE
|
||||
# Sicherstellen, dass /empfang/ Pfad enthalten ist
|
||||
try:
|
||||
from aza_empfang_app import _empfang_url # noqa: WPS433 (lazy)
|
||||
base = _empfang_url()
|
||||
p = urlparse(base)
|
||||
if not (p.path or "").rstrip("/").endswith("/empfang"):
|
||||
path = (p.path or "").rstrip("/")
|
||||
new_path = (path + "/empfang/") if not path.endswith("/empfang/") else path
|
||||
base = urlunparse(p._replace(path=new_path or "/empfang/"))
|
||||
except Exception:
|
||||
base = "https://empfang.aza-medwork.ch/empfang/"
|
||||
return _append_query_marker(base, "empfang_chat_shell", "1")
|
||||
pass
|
||||
url = _append_query_marker(base, "empfang_chat_shell", "1")
|
||||
url = _append_query_marker(url, "shell_source", "empfang_chat_shell")
|
||||
return url
|
||||
|
||||
|
||||
def _normalize_handoff_code(raw: str) -> str:
|
||||
@@ -666,11 +917,16 @@ def _split_argv_for_handoff(argv: list[str]) -> tuple[list[str], str, str]:
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
argv = list(argv if argv is not None else sys.argv[1:])
|
||||
argv, handoff_token, handoff_code = _split_argv_for_handoff(argv)
|
||||
argv, parent_mode_hint = _split_argv_for_shell_mode(argv)
|
||||
|
||||
standalone_default = False
|
||||
if not argv or not str(argv[0]).strip():
|
||||
# Standalone / Doppelklick: separate Empfang-Chat-Huelle. Marker fuer empfang.html setzen.
|
||||
# Standalone / Doppelklick: separate Empfang-Chat-Huelle.
|
||||
# Beide Marker (empfang_chat_shell=1 & shell_source=empfang_chat_shell)
|
||||
# werden gesetzt, damit empfang.html die Chat-Shell-UI aktiviert.
|
||||
url = _build_default_empfang_chat_shell_url()
|
||||
argv = [url]
|
||||
standalone_default = True
|
||||
url = str(argv[0]).strip()
|
||||
|
||||
if handoff_token and "/empfang/shell/launch" not in url:
|
||||
@@ -697,10 +953,39 @@ def main(argv: list[str] | None = None) -> int:
|
||||
file=sys.stderr, flush=True,
|
||||
)
|
||||
|
||||
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
|
||||
# Modus zuerst klassifizieren, damit wir mode-abhaengige Default-Groessen
|
||||
# vergeben koennen (Empfang-Chat-Huelle startet kompakter als Desktop-Huelle).
|
||||
mode = _classify_shell_mode(url, parent_hint=parent_mode_hint)
|
||||
if standalone_default and mode == _MODE_DESKTOP_SHELL:
|
||||
# Default-Doppelklick darf nicht versehentlich als Desktop-Huelle starten.
|
||||
mode = _MODE_EMPFANG_CHAT_SHELL
|
||||
|
||||
storage_path = _empfang_webview_storage_dir()
|
||||
# Default-Groessen pro Modus:
|
||||
# desktop_shell -> 1180 x 820 (unveraendert)
|
||||
# empfang_chat_shell -> 900 x 650 (~70% Flaeche; Chat/Eingabe weiter brauchbar)
|
||||
# minichat -> 600 x 820 (wie bisher; nur ueber subprocess gesetzt)
|
||||
if mode == _MODE_EMPFANG_CHAT_SHELL:
|
||||
default_w, default_h = 900, 650
|
||||
elif mode == _MODE_MINICHAT:
|
||||
default_w, default_h = 600, 820
|
||||
else:
|
||||
default_w, default_h = 1180, 820
|
||||
# Explizit per argv uebergebene Groesse (z.B. open_minichat) hat Vorrang.
|
||||
w = int(argv[1]) if len(argv) > 1 and str(argv[1]).isdigit() else default_w
|
||||
h = int(argv[2]) if len(argv) > 2 and str(argv[2]).isdigit() else default_h
|
||||
|
||||
# AppUserModelID je Modus (vor webview.create_window setzen, damit Taskleisten-
|
||||
# Gruppierung sauber bleibt). Idempotent: spaeter im __main__ wieder gesetzt.
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import ctypes # noqa: WPS433
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
||||
_aumid_for_mode(mode),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
storage_path = _empfang_webview_storage_dir(mode, parent_mode_hint)
|
||||
try:
|
||||
Path(storage_path).mkdir(parents=True, exist_ok=True)
|
||||
except OSError as exc:
|
||||
@@ -720,26 +1005,21 @@ def main(argv: list[str] | None = None) -> int:
|
||||
)
|
||||
return 11
|
||||
|
||||
def _window_title_for_start_url(start_url: str) -> str:
|
||||
"""Titel nach Start-URL: Arzt-Desktop-Shell vs. separate Chat-Huelle vs. manueller Test."""
|
||||
u = (start_url or "")
|
||||
low = u.lower()
|
||||
if "/empfang/shell/launch" in u and "token=" in low:
|
||||
if "target=empfang_chat_shell" in low or "target%3Dempfang_chat_shell" in low:
|
||||
return "AzA-Empfang \u00b7 Chat-H\u00fclle"
|
||||
return "AzA-Empfang \u00b7 Desktop"
|
||||
if "shell_source=aza_desktop" in u or "shell_source%3Daza_desktop" in low:
|
||||
return "AzA-Empfang \u00b7 Desktop"
|
||||
return "AzA-Empfang"
|
||||
window_title = _window_title_for_mode(mode)
|
||||
print(
|
||||
f"[EMPFANG_SHELL] start mode={mode} parent_hint={parent_mode_hint or '-'} "
|
||||
f"title={window_title!r} storage_tail={Path(storage_path).name}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
api = EmpfangWebviewApi()
|
||||
api = EmpfangWebviewApi(mode=mode, parent_mode_hint=parent_mode_hint)
|
||||
start_kw = {"storage_path": storage_path, "private_mode": False}
|
||||
_ico = _empfang_shell_icon_path()
|
||||
if _ico:
|
||||
start_kw["icon"] = _ico
|
||||
try:
|
||||
win = webview.create_window(
|
||||
_window_title_for_start_url(url), url, width=w, height=h, js_api=api,
|
||||
window_title, url, width=w, height=h, js_api=api,
|
||||
)
|
||||
api.bind_window(win)
|
||||
webview.start(**start_kw)
|
||||
@@ -750,6 +1030,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# AUMID wird zusaetzlich in main() pro Modus gesetzt. Hier ein konservativer
|
||||
# Default-Wert, damit ein eventueller Crash vor main() korrekt gruppiert wird.
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
Reference in New Issue
Block a user