"""Resolve capture surface from a Win32 window (windows | browser | mobile stub).""" from __future__ import annotations from typing import Any, Optional # Logical surfaces (align with RPA_ELEMENT_LOCATING.md) SURFACE_WINDOWS = "windows" SURFACE_BROWSER = "browser" SURFACE_ANDROID = "android" SURFACE_IOS = "ios" _BROWSER_PROCESS_NAMES = { "chrome.exe", "msedge.exe", "chromium.exe", "brave.exe", "opera.exe", "vivaldi.exe", } _BROWSER_CLASS_HINTS = { "Chrome_WidgetWin_1", "Chrome_WidgetWin_0", "Chrome_WidgetWin_2", } def normalize_process_name(name: str) -> str: return (name or "").strip().lower() def is_browser_window( *, process_name: str = "", class_name: str = "", title: str = "", ) -> bool: proc = normalize_process_name(process_name) if proc in _BROWSER_PROCESS_NAMES: return True cls = (class_name or "").strip() if cls in _BROWSER_CLASS_HINTS: return True # Edge/Chrome sometimes expose empty process briefly; title heuristics last t = title or "" if " - Google Chrome" in t or " - Microsoft Edge" in t or t.endswith(" - Edge"): return True if t.endswith(" - Chrome"): return True return False def resolve_surface( *, process_name: str = "", class_name: str = "", title: str = "", ) -> str: """Return surface id for this top-level window.""" if is_browser_window( process_name=process_name, class_name=class_name, title=title ): return SURFACE_BROWSER return SURFACE_WINDOWS def surface_from_window_info(info: Any) -> str: return resolve_surface( process_name=getattr(info, "process_name", "") or "", class_name=getattr(info, "class_name", "") or "", title=getattr(info, "title", "") or "", ) def surface_label(surface: str) -> str: return { SURFACE_WINDOWS: "桌面 UIA", SURFACE_BROWSER: "浏览器 Playwright", SURFACE_ANDROID: "Android(未实现)", SURFACE_IOS: "iOS(未实现)", }.get(surface, surface)