"""作用域内 UIA 唯一定位、真实点击和逐字输入。""" from __future__ import annotations import random from typing import Any, Iterator from service.desktop_session import DesktopRpaError, WindowCandidate, activate_window from util.selectors import ElementSpec def require_uiautomation(): try: import uiautomation as auto except ImportError as exc: raise DesktopRpaError("UIA_UNAVAILABLE", "缺少 uiautomation 依赖") from exc return auto def root_from_window(window: WindowCandidate): auto = require_uiautomation() return auto.ControlFromHandle(window.hwnd) def _walk(root: Any, *, depth: int = 0, max_depth: int = 25) -> Iterator[Any]: if depth > max_depth: return yield root if depth == max_depth: return try: children = root.GetChildren() except Exception: children = [] for child in children: yield from _walk(child, depth=depth + 1, max_depth=max_depth) def _matches(control: Any, spec: ElementSpec) -> bool: if spec.name is not None and (getattr(control, "Name", None) or "") != spec.name: return False if spec.automation_id is not None and ( getattr(control, "AutomationId", None) or "" ) != spec.automation_id: return False if spec.control_type is not None and ( (getattr(control, "ControlTypeName", None) or "").removesuffix("Control") != spec.control_type.removesuffix("Control") ): return False if spec.class_name is not None and ( getattr(control, "ClassName", None) or "" ) != spec.class_name: return False return True def locate_unique(root: Any, spec: ElementSpec, *, max_depth: int = 25): search_root = root if spec.ancestor_name: ancestor_spec = ElementSpec( logical_name=f"{spec.logical_name}.ancestor", name=spec.ancestor_name, ) search_root = locate_unique(root, ancestor_spec, max_depth=max_depth) matched = [control for control in _walk(search_root, max_depth=max_depth) if _matches(control, spec)] if not matched: raise DesktopRpaError("ELEMENT_NOT_FOUND", f"{spec.logical_name} 未找到") if len(matched) != spec.expected_count: raise DesktopRpaError( "ELEMENT_AMBIGUOUS", f"{spec.logical_name} 期望 {spec.expected_count} 个,实际 {len(matched)} 个", ) return matched[0] def click_control(control: Any, *, window: WindowCandidate) -> None: activate_window(window) rect = control.BoundingRectangle if rect.width() <= 0 or rect.height() <= 0 or rect.left < -10000: raise DesktopRpaError("ELEMENT_NOT_READY", "目标元素不可见或边界矩形无效") control.Click( x=random.randint(-2, 2), y=random.randint(-2, 2), simulateMove=True, ) def type_text(control: Any, text: str, *, window: WindowCandidate) -> None: auto = require_uiautomation() click_control(control, window=window) auto.SendKeys("{Ctrl}a", waitTime=0.05) auto.SendKeys("{Delete}", waitTime=0.05) for char in text: auto.SendKeys(char, waitTime=random.uniform(0.04, 0.12))