update
This commit is contained in:
@@ -181,12 +181,59 @@ def _apply_win32_topmost(value: bool) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _focus_other_empfang_host_window(title_prefixes: tuple[str, ...] = ("AzA-Empfang",)) -> bool:
|
||||
def _hwnd_process_alive(hwnd: int) -> bool:
|
||||
"""True wenn das Fenster existiert und der zugehoerige Prozess noch laeuft."""
|
||||
if sys.platform != "win32":
|
||||
return False
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
if not user32.IsWindow(int(hwnd)):
|
||||
return False
|
||||
pid = wintypes.DWORD()
|
||||
user32.GetWindowThreadProcessId(int(hwnd), ctypes.byref(pid))
|
||||
if not int(pid.value):
|
||||
return False
|
||||
hproc = kernel32.OpenProcess(0x1000, False, int(pid.value))
|
||||
if not hproc:
|
||||
return False
|
||||
try:
|
||||
exit_code = wintypes.DWORD()
|
||||
if not kernel32.GetExitCodeProcess(hproc, ctypes.byref(exit_code)):
|
||||
return False
|
||||
return int(exit_code.value) == 259
|
||||
finally:
|
||||
kernel32.CloseHandle(hproc)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _window_title_matches(t: str, *, prefixes: tuple[str, ...] = (), exact_titles: tuple[str, ...] = ()) -> bool:
|
||||
"""Fenstertitel-Match: exakt ODER Prefix (Prefix darf NICHT Desktop-Huelle treffen)."""
|
||||
title = str(t or "")
|
||||
exact = tuple(x for x in exact_titles if x)
|
||||
if exact:
|
||||
return any(title == e for e in exact)
|
||||
pref = tuple(p for p in prefixes if p)
|
||||
return any(title == p or title.startswith(p) for p in pref)
|
||||
|
||||
|
||||
def _focus_other_empfang_host_window(
|
||||
title_prefixes: tuple[str, ...] = ("AzA-Empfang",),
|
||||
*,
|
||||
exact_titles: tuple[str, ...] = (),
|
||||
) -> 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.
|
||||
|
||||
``exact_titles``: nur exakter Titel-Match (z. B. Chat-Huelle "AzA Chat" ohne
|
||||
"AzA Chat \xb7 Desktop").
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
return False
|
||||
@@ -198,6 +245,7 @@ def _focus_other_empfang_host_window(title_prefixes: tuple[str, ...] = ("AzA-Emp
|
||||
my_pid = int(os.getpid())
|
||||
handles: list[int] = []
|
||||
prefixes = tuple(p for p in title_prefixes if p)
|
||||
exact = tuple(t for t in exact_titles if t)
|
||||
|
||||
@ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
||||
def _enum(hwnd, _lp):
|
||||
@@ -206,11 +254,11 @@ def _focus_other_empfang_host_window(title_prefixes: tuple[str, ...] = ("AzA-Emp
|
||||
buf = ctypes.create_unicode_buffer(260)
|
||||
user32.GetWindowTextW(hwnd, buf, 260)
|
||||
t = buf.value or ""
|
||||
if not any(t == p or t.startswith(p) for p in prefixes):
|
||||
if not _window_title_matches(t, prefixes=prefixes, exact_titles=exact):
|
||||
return True
|
||||
pid = wintypes.DWORD()
|
||||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
|
||||
if int(pid.value) != my_pid:
|
||||
if int(pid.value) != my_pid and _hwnd_process_alive(int(hwnd)):
|
||||
handles.append(int(hwnd))
|
||||
return True
|
||||
|
||||
@@ -226,6 +274,51 @@ def _focus_other_empfang_host_window(title_prefixes: tuple[str, ...] = ("AzA-Emp
|
||||
return False
|
||||
|
||||
|
||||
def _resolve_empfang_shell_bundle_path() -> Path | None:
|
||||
"""AZA_EmpfangShell.exe neben der laufenden EXE (wie Office-Singleton-Start)."""
|
||||
if (
|
||||
not getattr(sys, "frozen", False)
|
||||
and str(os.getenv("AZA_EMPFANG_SHELL_USE_PYTHON", "")).strip() == "1"
|
||||
):
|
||||
return None
|
||||
candidates: list[Path] = []
|
||||
try:
|
||||
if getattr(sys, "frozen", False):
|
||||
exe_dir = Path(sys.executable).resolve().parent
|
||||
candidates.append(exe_dir / "AZA_EmpfangShell.exe")
|
||||
candidates.append(exe_dir / "_internal" / "AZA_EmpfangShell.exe")
|
||||
meip = getattr(sys, "_MEIPASS", "")
|
||||
if meip:
|
||||
candidates.append(Path(meip) / "AZA_EmpfangShell.exe")
|
||||
except Exception:
|
||||
pass
|
||||
root = Path(__file__).resolve().parent
|
||||
candidates.append(root / "AZA_EmpfangShell.exe")
|
||||
candidates.append(root / "dist" / "AZA_EmpfangShell.exe")
|
||||
for p in candidates:
|
||||
try:
|
||||
if p.is_file():
|
||||
return p
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _spawn_empfang_chat_shell_process() -> None:
|
||||
"""Startet die dedizierte Chat-Huelle einmal (AZA_EmpfangShell bevorzugt)."""
|
||||
chat_url = _build_default_empfang_chat_shell_url()
|
||||
w_main, h_main = 1180, 820
|
||||
exe_bundle = _resolve_empfang_shell_bundle_path()
|
||||
if exe_bundle is not None:
|
||||
cmd = [str(exe_bundle), chat_url, str(w_main), str(h_main)]
|
||||
cwd = str(exe_bundle.parent)
|
||||
else:
|
||||
script = Path(__file__).resolve()
|
||||
cmd = [sys.executable, str(script), chat_url, str(w_main), str(h_main)]
|
||||
cwd = str(script.parent)
|
||||
subprocess.Popen(cmd, cwd=cwd, **empfang_subprocess_popen_kwargs())
|
||||
|
||||
|
||||
def _focus_office_main_window() -> bool:
|
||||
"""AzA Office (Hauptfenster) in den Vordergrund — fuer Hüllen-Update-Hinweis."""
|
||||
return _focus_other_empfang_host_window(("AzA Office",))
|
||||
@@ -234,11 +327,12 @@ def _focus_office_main_window() -> bool:
|
||||
def _empfang_shell_icon_path(mode: str | None = None) -> str | None:
|
||||
"""Windows/pywebview: WinForms laedt Fenstericon aus .ico neben diesem Skript.
|
||||
|
||||
Im PyInstaller-Bundle liegt logo.ico bzw. aza_kontakt_panel.ico unter sys._MEIPASS
|
||||
(bzw. neben sys.executable). Kontakt-Panel nutzt das eigene Pfoten-Icon.
|
||||
Im PyInstaller-Bundle liegt aza_chat_logo8.ico bzw. aza_kontakt_panel.ico unter
|
||||
sys._MEIPASS (bzw. neben sys.executable). Kontakt-Panel behaelt sein eigenes Icon;
|
||||
Desktop-/Chat-Huelle nutzen Logo8.
|
||||
"""
|
||||
is_kontakt_panel = mode == "kontakt_panel"
|
||||
ico_name = "aza_kontakt_panel.ico" if is_kontakt_panel else "logo.ico"
|
||||
ico_name = "aza_kontakt_panel.ico" if is_kontakt_panel else "aza_chat_logo8.ico"
|
||||
candidates: list[Path] = []
|
||||
try:
|
||||
if getattr(sys, "frozen", False):
|
||||
@@ -251,6 +345,8 @@ def _empfang_shell_icon_path(mode: str | None = None) -> str | None:
|
||||
base = Path(__file__).resolve().parent
|
||||
if is_kontakt_panel:
|
||||
candidates.append(base / "assets" / ico_name)
|
||||
else:
|
||||
candidates.append(base / "assets" / ico_name)
|
||||
candidates.append(base / ico_name)
|
||||
for c in candidates:
|
||||
try:
|
||||
@@ -351,12 +447,12 @@ def _split_argv_for_shell_mode(argv: list[str]) -> tuple[list[str], str]:
|
||||
|
||||
def _window_title_for_mode(mode: str) -> str:
|
||||
if mode == _MODE_DESKTOP_SHELL:
|
||||
return "AzA-Empfang \u00b7 Desktop"
|
||||
return "AzA Chat \u00b7 Desktop"
|
||||
if mode == _MODE_MINICHAT:
|
||||
return "AzA MiniChat"
|
||||
if mode == _MODE_KONTAKT_PANEL:
|
||||
return "AzA Kontakte"
|
||||
return "AzA Empfang Chat"
|
||||
return "AzA Chat"
|
||||
|
||||
|
||||
def _aumid_for_mode(mode: str) -> str:
|
||||
@@ -663,22 +759,31 @@ class EmpfangWebviewApi:
|
||||
def focus_empfang_chat_shell(self) -> dict:
|
||||
"""Kontakt-Panel: bestehende Empfang-Chat-Huelle (anderer Prozess) nach vorne holen.
|
||||
|
||||
Reine Wiederverwendung von ``_focus_other_empfang_host_window`` mit dem
|
||||
Titel-Praefix der Chat-Huelle. Nicht-blockierend (Daemon-Thread), damit
|
||||
der WebView-Worker des Panels nicht haengt. Keine Chat-/Patientendaten.
|
||||
Nur exakt ``AzA Chat`` (empfang_chat_shell) — NICHT ``AzA Chat \xb7 Desktop``.
|
||||
Ist keine lebende Chat-Huelle vorhanden, fordert AzA Office per lokalem IPC
|
||||
den bewaehrten Office-Starter (Shell-Session + Launch-Token) an.
|
||||
|
||||
Der eigentliche Peer-/Thread-Wechsel passiert NICHT hier, sondern in der
|
||||
Huelle selbst: sie liest den per ``POST /empfang/shell/dm-open`` gesetzten
|
||||
Shell-Context beim Fokus erneut (bestehender refreshDesktopShellContext-Pfad).
|
||||
Der Peer-/Thread-Wechsel passiert in der Huelle via Shell-Context
|
||||
(``POST /empfang/shell/dm-open`` + ``refreshDesktopShellContextFromServer``).
|
||||
"""
|
||||
prefixes = (
|
||||
_window_title_for_mode(_MODE_EMPFANG_CHAT_SHELL),
|
||||
_window_title_for_mode(_MODE_DESKTOP_SHELL),
|
||||
)
|
||||
chat_title = _window_title_for_mode(_MODE_EMPFANG_CHAT_SHELL)
|
||||
|
||||
def _work() -> None:
|
||||
try:
|
||||
_focus_other_empfang_host_window(prefixes)
|
||||
from aza_empfang_shell_surface import touch_shell_peer_refresh_signal
|
||||
|
||||
touch_shell_peer_refresh_signal(source="focus_shell")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if _focus_other_empfang_host_window(exact_titles=(chat_title,)):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from aza_empfang_shell_surface import request_office_open_empfang_chat_shell_ipc
|
||||
|
||||
request_office_open_empfang_chat_shell_ipc()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -712,6 +817,89 @@ class EmpfangWebviewApi:
|
||||
threading.Thread(target=_work, daemon=True).start()
|
||||
return {"ok": True}
|
||||
|
||||
def get_chat_update_hint(self) -> dict:
|
||||
"""Chat-only: IPC-Hinweis vom Chat-Host (Kontaktpanel-Badge)."""
|
||||
try:
|
||||
from desktop_update_check import read_chat_update_hint
|
||||
|
||||
hint = read_chat_update_hint()
|
||||
if hint:
|
||||
return hint
|
||||
except Exception:
|
||||
pass
|
||||
return {"available": False}
|
||||
|
||||
def request_chat_update_dialog(self) -> dict:
|
||||
"""Kontaktpanel: Chat-Host oeffnet Update-Dialog (kein Installer in WebView)."""
|
||||
|
||||
def _work() -> None:
|
||||
try:
|
||||
from desktop_update_check import manual_check_for_chat_updates
|
||||
|
||||
manual_check_for_chat_updates(parent=None)
|
||||
except Exception:
|
||||
try:
|
||||
from desktop_update_check import request_chat_update_dialog_ipc
|
||||
|
||||
request_chat_update_dialog_ipc()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=_work, daemon=True).start()
|
||||
return {"ok": True}
|
||||
|
||||
def _inject_kontakt_update_badge(self) -> None:
|
||||
win = self._window
|
||||
if win is None or self._mode != _MODE_KONTAKT_PANEL:
|
||||
return
|
||||
js = (
|
||||
"(function(){try{"
|
||||
"var bar=document.getElementById('aza-chat-update-badge');"
|
||||
"if(!bar){"
|
||||
"bar=document.createElement('div');"
|
||||
"bar.id='aza-chat-update-badge';"
|
||||
"bar.style.cssText='display:none;align-items:center;justify-content:center;gap:10px;"
|
||||
"padding:8px 12px;background:#FFF3E0;border-top:1px solid #E8A54B;color:#7A4A00;"
|
||||
"font-size:13px;position:fixed;left:0;right:0;bottom:0;z-index:9999;';"
|
||||
"var btn=document.createElement('button');"
|
||||
"btn.type='button';"
|
||||
"btn.textContent='Update';"
|
||||
"btn.style.cssText='border:1px solid #E8A54B;background:#FFE8C8;color:#7A4A00;"
|
||||
"border-radius:6px;padding:4px 12px;cursor:pointer;font-size:13px;';"
|
||||
"btn.onclick=function(){try{if(window.pywebview&&window.pywebview.api&&"
|
||||
"window.pywebview.api.request_chat_update_dialog){"
|
||||
"window.pywebview.api.request_chat_update_dialog();}}catch(_e){}};"
|
||||
"var txt=document.createElement('span');txt.id='aza-chat-update-badge-text';"
|
||||
"bar.appendChild(txt);bar.appendChild(btn);document.body.appendChild(bar);"
|
||||
"}"
|
||||
"return bar;}catch(e){return null;}})();"
|
||||
)
|
||||
try:
|
||||
win.evaluate_js(js)
|
||||
except Exception:
|
||||
return
|
||||
try:
|
||||
from desktop_update_check import read_chat_update_hint
|
||||
|
||||
hint = read_chat_update_hint()
|
||||
if hint and hint.get("available"):
|
||||
ver = str(hint.get("latest_version") or "").strip()
|
||||
txt = ("Update " + ver) if ver else "Update"
|
||||
win.evaluate_js(
|
||||
"(function(){var b=document.getElementById('aza-chat-update-badge');"
|
||||
"var t=document.getElementById('aza-chat-update-badge-text');"
|
||||
"if(!b||!t)return;"
|
||||
"t.textContent=" + json.dumps(txt) + ";"
|
||||
"b.style.display='flex';})();"
|
||||
)
|
||||
else:
|
||||
win.evaluate_js(
|
||||
"try{var b=document.getElementById('aza-chat-update-badge');"
|
||||
"if(b)b.style.display='none';}catch(_e){}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _pinsel_eval_js(win, js_code: str, label: str) -> None:
|
||||
"""Lokal nur stdout/stderr; keine Clipboard-/Patient-Inhalte loggen."""
|
||||
@@ -1011,6 +1199,251 @@ class EmpfangWebviewApi:
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
return {"ok": True, "scheduled": True}
|
||||
|
||||
def report_chat_surface_state(self, payload: dict | None = None) -> dict:
|
||||
"""JS-API: aktiver Chat-Peer fuer Desktop-Popup-Unterdrueckung (keine Texte)."""
|
||||
data = payload if isinstance(payload, dict) else {}
|
||||
minimized = False
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
my_pid = int(os.getpid())
|
||||
found_iconic = [False]
|
||||
|
||||
@ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
|
||||
def _cb(hwnd, _lp):
|
||||
if not user32.IsWindowVisible(hwnd) and not user32.IsIconic(hwnd):
|
||||
return True
|
||||
p = wintypes.DWORD()
|
||||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(p))
|
||||
if int(p.value) != my_pid:
|
||||
return True
|
||||
if user32.IsIconic(hwnd):
|
||||
found_iconic[0] = True
|
||||
return False
|
||||
return True
|
||||
|
||||
user32.EnumWindows(_cb, 0)
|
||||
minimized = bool(found_iconic[0])
|
||||
except Exception:
|
||||
minimized = False
|
||||
try:
|
||||
from aza_empfang_shell_surface import write_shell_surface_state
|
||||
|
||||
write_shell_surface_state(
|
||||
{
|
||||
"mode": data.get("mode"),
|
||||
"peer_user_id": data.get("peer_user_id"),
|
||||
"external_peer_user_id": data.get("external_peer_user_id"),
|
||||
"document_visible": data.get("document_visible"),
|
||||
"has_focus": data.get("has_focus"),
|
||||
"shell_minimized": minimized,
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": True}
|
||||
|
||||
def consume_peer_refresh_signal(self) -> dict:
|
||||
"""JS-Polling: Kontakt-Panel hat dm_open gesetzt — Context erneut lesen."""
|
||||
try:
|
||||
last = float(getattr(self, "_peer_refresh_last_ts", 0.0) or 0.0)
|
||||
from aza_empfang_shell_surface import consume_shell_peer_refresh_signal
|
||||
|
||||
ts = consume_shell_peer_refresh_signal(last)
|
||||
if ts > last:
|
||||
self._peer_refresh_last_ts = ts
|
||||
return {"pending": True}
|
||||
except Exception:
|
||||
pass
|
||||
return {"pending": False}
|
||||
|
||||
def shell_reload_current(self) -> dict:
|
||||
"""Begrenzter Reload bei leerer/haengender WebView (keine Endlosschleife)."""
|
||||
win = self._window
|
||||
if win is None:
|
||||
return {"ok": False, "reason": "no_window"}
|
||||
attempts = int(getattr(self, "_shell_reload_attempts", 0) or 0)
|
||||
if attempts >= 2:
|
||||
return {"ok": False, "reason": "limit"}
|
||||
self._shell_reload_attempts = attempts + 1
|
||||
target = str(getattr(self, "_shell_reload_target_url", "") or "").strip()
|
||||
if not target:
|
||||
return {"ok": False, "reason": "no_target"}
|
||||
|
||||
def _do() -> None:
|
||||
try:
|
||||
from aza_empfang_shell_surface import append_empfang_shell_debug_log
|
||||
|
||||
append_empfang_shell_debug_log(
|
||||
f"action=reload attempt={self._shell_reload_attempts} mode={self._mode}"
|
||||
)
|
||||
_webview_navigate(win, target)
|
||||
except Exception as exc:
|
||||
try:
|
||||
from aza_empfang_shell_surface import append_empfang_shell_debug_log
|
||||
|
||||
append_empfang_shell_debug_log(
|
||||
f"action=reload_failed err={type(exc).__name__}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
return {"ok": True, "scheduled": True}
|
||||
|
||||
def _shell_debug(self, msg: str) -> None:
|
||||
try:
|
||||
from aza_empfang_shell_surface import append_empfang_shell_debug_log
|
||||
|
||||
append_empfang_shell_debug_log(msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _schedule_shell_health_checks(self, start_url: str) -> None:
|
||||
"""Nach Start: DOM-Bereitschaft pruefen, bei Blank begrenzt neu laden."""
|
||||
win = self._window
|
||||
if win is None:
|
||||
return
|
||||
self._shell_reload_target_url = _shell_stable_reload_url(start_url, self._mode)
|
||||
self._shell_reload_attempts = 0
|
||||
self._shell_health_checks_done = 0
|
||||
self._shell_debug(
|
||||
f"action=health_watch mode={self._mode} url_path={_shell_redact_url_for_log(start_url)}"
|
||||
)
|
||||
|
||||
def _tick(delay_s: float) -> None:
|
||||
def _run() -> None:
|
||||
if win is None:
|
||||
return
|
||||
done = int(getattr(self, "_shell_health_checks_done", 0) or 0)
|
||||
if done >= 3:
|
||||
return
|
||||
self._shell_health_checks_done = done + 1
|
||||
probe_js = (
|
||||
"(function(){try{"
|
||||
"var a=document.getElementById('app-layout');"
|
||||
"var l=document.getElementById('login-overlay');"
|
||||
"var b=document.getElementById('empfang-shell-boot');"
|
||||
"if(a||l)return 'dom_ok';"
|
||||
"if(b)return 'boot_only';"
|
||||
"return 'blank';"
|
||||
"}catch(e){return 'js_err';}})();"
|
||||
)
|
||||
state = "unknown"
|
||||
try:
|
||||
state = str(win.evaluate_js(probe_js) or "").strip().lower()
|
||||
except Exception as exc:
|
||||
state = f"eval_err:{type(exc).__name__}"
|
||||
bridge = "no"
|
||||
try:
|
||||
bridge = str(
|
||||
win.evaluate_js(
|
||||
"(function(){try{return (window.pywebview&&window.pywebview.api)?'yes':'no';}catch(e){return 'no';}})();"
|
||||
)
|
||||
or "no"
|
||||
).strip().lower()
|
||||
except Exception:
|
||||
bridge = "eval_err"
|
||||
self._shell_debug(
|
||||
f"action=health_probe check={done + 1} dom={state} bridge={bridge}"
|
||||
)
|
||||
if state in ("blank", "boot_only", "js_err") or state.startswith("eval_err"):
|
||||
rel = self.shell_reload_current()
|
||||
if not rel.get("ok"):
|
||||
self._shell_debug(
|
||||
f"action=health_reload_skipped reason={rel.get('reason', '?')}"
|
||||
)
|
||||
try:
|
||||
self._inject_kontakt_update_badge()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Timer(delay_s, _run).start()
|
||||
|
||||
for d in (2.5, 6.0, 12.0):
|
||||
_tick(d)
|
||||
|
||||
|
||||
def _shell_origin_from_env_or_url(start_url: str) -> str:
|
||||
for key in ("AZA_EMPFANG_WEB_BASE", "AZA_EMPFANG_CHAT_SHELL_URL"):
|
||||
val = (os.environ.get(key) or "").strip()
|
||||
if not val:
|
||||
continue
|
||||
try:
|
||||
p = urlparse(val)
|
||||
if p.scheme and p.netloc:
|
||||
return f"{p.scheme}://{p.netloc}"
|
||||
except Exception:
|
||||
pass
|
||||
proxy = _resolve_test_proxy_base()
|
||||
if proxy:
|
||||
return proxy.rstrip("/")
|
||||
u = str(start_url or "").strip()
|
||||
try:
|
||||
p = urlparse(u)
|
||||
if p.scheme and p.netloc:
|
||||
return f"{p.scheme}://{p.netloc}"
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
p2 = urlparse(_EMPFANG_CHAT_SHELL_DEFAULT_BASE)
|
||||
if p2.scheme and p2.netloc:
|
||||
return f"{p2.scheme}://{p2.netloc}"
|
||||
except Exception:
|
||||
pass
|
||||
return "https://empfang.aza-medwork.ch"
|
||||
|
||||
|
||||
def _webview_navigate(win: object, target: str) -> None:
|
||||
url = str(target or "").strip()
|
||||
if not url:
|
||||
return
|
||||
for name in ("load_url", "loadUrl"):
|
||||
fn = getattr(win, name, None)
|
||||
if callable(fn):
|
||||
fn(url)
|
||||
return
|
||||
escaped = json.dumps(url)
|
||||
win.evaluate_js(f"window.location.assign({escaped});")
|
||||
|
||||
|
||||
def _shell_redact_url_for_log(url: str) -> str:
|
||||
try:
|
||||
p = urlparse(str(url or "").strip())
|
||||
if p.scheme and p.netloc:
|
||||
return f"{p.scheme}://{p.netloc}{p.path or '/'}"
|
||||
except Exception:
|
||||
pass
|
||||
return "/empfang/"
|
||||
|
||||
|
||||
def _shell_stable_reload_url(start_url: str, mode: str) -> str:
|
||||
"""Stabile Empfang-URL nach Launch-Redirect (Cookie-Session bleibt erhalten)."""
|
||||
origin = _shell_origin_from_env_or_url(start_url)
|
||||
m = (mode or "").strip().lower()
|
||||
if m == _MODE_EMPFANG_CHAT_SHELL:
|
||||
base = (os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or "").strip()
|
||||
if not base:
|
||||
base = f"{origin.rstrip('/')}/empfang/"
|
||||
url = _append_query_marker(base, "empfang_chat_shell", "1")
|
||||
return _append_query_marker(url, "shell_source", "empfang_chat_shell")
|
||||
if m == _MODE_KONTAKT_PANEL:
|
||||
base = (os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or "").strip()
|
||||
if not base:
|
||||
base = f"{origin.rstrip('/')}/empfang/"
|
||||
url = _append_query_marker(base, "kontakt_panel", "1")
|
||||
return _append_query_marker(url, "shell_source", "kontakt_panel")
|
||||
if m == _MODE_MINICHAT:
|
||||
return f"{origin.rstrip('/')}/empfang/?minichat=1"
|
||||
return _append_query_marker(
|
||||
_append_query_marker(f"{origin.rstrip('/')}/empfang/", "desktop_shell", "1"),
|
||||
"shell_source",
|
||||
"aza_desktop",
|
||||
)
|
||||
|
||||
|
||||
def _append_query_marker(url: str, key: str, value: str) -> str:
|
||||
"""Setzt/ersetzt einen Query-Parameter, ohne andere Parameter zu zerstoeren."""
|
||||
@@ -1027,6 +1460,43 @@ def _append_query_marker(url: str, key: str, value: str) -> str:
|
||||
_EMPFANG_CHAT_SHELL_DEFAULT_BASE = "https://empfang.aza-medwork.ch/empfang/"
|
||||
|
||||
|
||||
def _doku_prompt_test_active() -> bool:
|
||||
return os.environ.get("AZA_DOKU_PROMPT_TEST", "").strip().lower() in ("1", "true", "yes")
|
||||
|
||||
|
||||
def _resolve_test_proxy_base() -> str | None:
|
||||
if not _doku_prompt_test_active():
|
||||
return None
|
||||
try:
|
||||
from aza_empfang_test_html_proxy import test_proxy_base_url
|
||||
|
||||
return test_proxy_base_url()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _empfang_shell_base_for_build() -> str:
|
||||
"""Basis-URL fuer Shell/Kontakt-Panel — im Testbuild lokaler HTML-Proxy."""
|
||||
proxy = _resolve_test_proxy_base()
|
||||
if proxy:
|
||||
return proxy.rstrip("/")
|
||||
base = (os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or "").strip()
|
||||
if base:
|
||||
try:
|
||||
p = urlparse(base)
|
||||
if p.scheme and p.netloc:
|
||||
return f"{p.scheme}://{p.netloc}".rstrip("/")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
p2 = urlparse(_EMPFANG_CHAT_SHELL_DEFAULT_BASE)
|
||||
if p2.scheme and p2.netloc:
|
||||
return f"{p2.scheme}://{p2.netloc}".rstrip("/")
|
||||
except Exception:
|
||||
pass
|
||||
return "https://empfang.aza-medwork.ch"
|
||||
|
||||
|
||||
def _build_default_empfang_chat_shell_url() -> str:
|
||||
"""Standard-URL der separaten Empfang-Chat-Huelle (kein Arzt-Desktop).
|
||||
|
||||
@@ -1042,7 +1512,11 @@ def _build_default_empfang_chat_shell_url() -> str:
|
||||
"""
|
||||
base = (os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or "").strip()
|
||||
if not base:
|
||||
base = _EMPFANG_CHAT_SHELL_DEFAULT_BASE
|
||||
proxy = _resolve_test_proxy_base()
|
||||
if proxy:
|
||||
base = f"{proxy.rstrip('/')}/empfang/"
|
||||
else:
|
||||
base = _EMPFANG_CHAT_SHELL_DEFAULT_BASE
|
||||
# Sicherstellen, dass /empfang/ Pfad enthalten ist
|
||||
try:
|
||||
p = urlparse(base)
|
||||
@@ -1069,7 +1543,11 @@ def _build_default_kontakt_panel_url() -> str:
|
||||
"""
|
||||
base = (os.environ.get("AZA_EMPFANG_CHAT_SHELL_URL") or "").strip()
|
||||
if not base:
|
||||
base = _EMPFANG_CHAT_SHELL_DEFAULT_BASE
|
||||
proxy = _resolve_test_proxy_base()
|
||||
if proxy:
|
||||
base = f"{proxy.rstrip('/')}/empfang/"
|
||||
else:
|
||||
base = _EMPFANG_CHAT_SHELL_DEFAULT_BASE
|
||||
try:
|
||||
p = urlparse(base)
|
||||
if not (p.path or "").rstrip("/").endswith("/empfang"):
|
||||
@@ -1264,13 +1742,23 @@ def main(argv: list[str] | None = None) -> int:
|
||||
_ico = _empfang_shell_icon_path(mode)
|
||||
if _ico:
|
||||
start_kw["icon"] = _ico
|
||||
|
||||
def _on_webview_ready() -> None:
|
||||
try:
|
||||
api._shell_debug(
|
||||
f"action=webview_ready mode={mode} url_path={_shell_redact_url_for_log(url)}"
|
||||
)
|
||||
api._schedule_shell_health_checks(url)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
win = webview.create_window(
|
||||
window_title, url, width=w, height=h, js_api=api,
|
||||
)
|
||||
api.bind_window(win)
|
||||
try:
|
||||
webview.start(**start_kw)
|
||||
webview.start(func=_on_webview_ready, **start_kw)
|
||||
finally:
|
||||
_empfang_quiet_pyi_temp_teardown()
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user