Reactive UIA Event Wait — Focus Change

The accessibility recorder polls the focused element every ~250 ms, so it can miss a fast focus transition and reacts a quarter-second late. UIA exposes real events: wait_for_focus_change blocks on the native AddFocusChangedEventHandler and returns the moment focus moves — the zero-latency, miss-free “wait until focus lands on the dialog” primitive, the accessibility-tree analogue of wait_for_window / wait_for_image.

It is a thin dispatch onto the injectable accessibility.backends.get_backend() seam — headless-testable on any platform by injecting a fake backend; the real event subscription (registered / unregistered under a lock, on the calling thread) lives in the Windows backend. Imports no PySide6.

Headless API

from je_auto_control import wait_for_focus_change, click_text

click_text("Settings")
focused = wait_for_focus_change(timeout=3)
# {"name": "Search", "role": "ControlType_50004", "app_name": "app.exe", ...}
if focused is not None:
    ...   # focus has moved — the dialog / next field is ready

Returns the newly-focused element as {name, role, app_name, bounds, …}, or None if no focus change occurs within timeout seconds (default 5).

Executor commands

AC_wait_for_focus_change (timeout) returns {changed, element}. It is exposed as the read-only ac_wait_for_focus_change MCP tool and as a Script Builder command under Native UI.