ui_automata/platform.rs
1use schemars::JsonSchema;
2use serde::Deserialize;
3
4use crate::AutomataError;
5
6// ── Browser / TabInfo ─────────────────────────────────────────────────────────
7
8/// Basic info about a browser tab.
9#[derive(Debug, Clone)]
10pub struct TabInfo {
11 pub title: String,
12 pub url: String,
13}
14
15/// CDP browser: operations for controlling browser sessions and tabs.
16pub trait Browser: Send + Sync + 'static {
17 /// Ensure the browser is running with CDP enabled.
18 fn ensure(&self) -> Result<(), AutomataError>;
19 /// Open a new tab at `url` (or about:blank). Returns the CDP tab ID.
20 fn open_tab(&self, url: Option<&str>) -> Result<String, AutomataError>;
21 /// Close a tab by CDP target ID.
22 fn close_tab(&self, tab_id: &str) -> Result<(), AutomataError>;
23 /// Bring a tab to the foreground (switch to it).
24 fn activate_tab(&self, tab_id: &str) -> Result<(), AutomataError>;
25 /// Navigate a tab to a URL.
26 fn navigate(&self, tab_id: &str, url: &str) -> Result<(), AutomataError>;
27 /// Evaluate a JS expression in a tab. Returns the string result.
28 fn eval(&self, tab_id: &str, expr: &str) -> Result<String, AutomataError>;
29 /// Title + URL of a specific tab.
30 fn tab_info(&self, tab_id: &str) -> Result<TabInfo, AutomataError>;
31 /// All open tabs: (tab_id, TabInfo).
32 fn tabs(&self) -> Result<Vec<(String, TabInfo)>, AutomataError>;
33}
34
35/// Type of mouse click to perform.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
37#[serde(rename_all = "snake_case")]
38pub enum ClickType {
39 Left,
40 Double,
41 Triple,
42 Right,
43 Middle,
44}
45
46/// A UI element handle. Implementations wrap platform-specific COM/UIA objects.
47///
48/// All property methods return `None` / empty on a stale handle; interaction
49/// methods return `Err(AutomataError::Platform(...))` on staleness or failure.
50pub trait Element: Clone + 'static {
51 // ── Properties ───────────────────────────────────────────────────────────
52
53 /// Localized name / label of the element. `None` if empty or unavailable.
54 fn name(&self) -> Option<String>;
55
56 /// Localized role string (e.g. "Button", "Pane", "ToolBar").
57 fn role(&self) -> String;
58
59 /// Value text (ValuePattern) or name fallback.
60 fn text(&self) -> Result<String, AutomataError>;
61
62 /// Direct children's names joined by newlines, excluding the element's own name.
63 fn inner_text(&self) -> Result<String, AutomataError>;
64
65 fn is_enabled(&self) -> Result<bool, AutomataError>;
66 fn is_visible(&self) -> Result<bool, AutomataError>;
67 fn process_id(&self) -> Result<u32, AutomataError>;
68 /// Process name (without .exe) for this element's owning process.
69 /// Returns `None` on non-Windows or if the lookup fails.
70 fn process_name(&self) -> Option<String> {
71 None
72 }
73 /// Native window handle (HWND as u64) for this element's owning window.
74 /// Returns `None` on non-Windows or if the lookup fails.
75 fn hwnd(&self) -> Option<u64> {
76 None
77 }
78
79 /// UIA AutomationId property. `None` if empty or unavailable.
80 fn automation_id(&self) -> Option<String> {
81 None
82 }
83
84 /// Bounding box as `(x, y, width, height)`.
85 fn bounds(&self) -> Result<(i32, i32, i32, i32), AutomataError>;
86
87 /// Direct children. Returns `Err` on a stale handle.
88 fn children(&self) -> Result<Vec<Self>, AutomataError>;
89
90 /// Returns `false` when the element has been detached from the accessibility
91 /// tree (e.g. a dismissed dialog). Root windows have the desktop as parent so
92 /// they return `true`. Default returns `true` for platforms that don't implement it.
93 fn has_parent(&self) -> bool {
94 true
95 }
96
97 /// Navigate to this element's parent. Returns `None` at the root or on error.
98 fn parent(&self) -> Option<Self> {
99 None
100 }
101
102 // ── Interactions ─────────────────────────────────────────────────────────
103
104 fn click(&self) -> Result<(), AutomataError>;
105 fn double_click(&self) -> Result<(), AutomataError>;
106
107 /// Move the mouse cursor to the centre of the element without clicking.
108 fn hover(&self) -> Result<(), AutomataError>;
109
110 /// Click at a position expressed as fractions of the element's bounding box.
111 fn click_at(&self, x_pct: f64, y_pct: f64, kind: ClickType) -> Result<(), AutomataError>;
112
113 fn type_text(&self, text: &str) -> Result<(), AutomataError>;
114 fn press_key(&self, key: &str) -> Result<(), AutomataError>;
115
116 /// Set a field's value directly via IValuePattern (avoids needing to
117 /// select-all + type). Preferred over `type_text` for pre-filled fields.
118 fn set_value(&self, value: &str) -> Result<(), AutomataError>;
119
120 fn focus(&self) -> Result<(), AutomataError>;
121
122 /// Activate this element via UIA's `IInvokePattern::Invoke()`.
123 ///
124 /// Unlike `click()`, `invoke()` does not require a valid bounding rect —
125 /// it works on off-screen elements whose bounds are `(0,0,1,1)` because they
126 /// are scrolled out of view. Prefer this over `Click` + `ScrollIntoView`
127 /// for items in virtualised or scrollable lists (e.g. Settings nav items,
128 /// WinUI ListView rows) where mouse-wheel scrolling causes elastic snap-back.
129 ///
130 /// Falls back to `click()` when the element does not support `InvokePattern`.
131 fn invoke(&self) -> Result<(), AutomataError> {
132 // Default: fall back to click for platforms that don't override this.
133 self.click()
134 }
135
136 /// Scroll ancestor containers until this element is within their visible
137 /// viewport. Uses `ScrollItemPattern` when supported; falls back to a
138 /// geometric ancestor walk with `ScrollPattern`.
139 fn scroll_into_view(&self) -> Result<(), AutomataError>;
140
141 fn activate_window(&self) -> Result<(), AutomataError>;
142 fn minimize_window(&self) -> Result<(), AutomataError>;
143 fn close(&self) -> Result<(), AutomataError>;
144}
145
146/// Platform desktop: discovers windows and provides foreground state.
147pub trait Desktop: Send + 'static {
148 type Elem: Element;
149 type Browser: Browser;
150
151 /// CDP browser handle. Used by the workflow engine for browser automation.
152 fn browser(&self) -> &Self::Browser;
153
154 /// All top-level application windows currently visible.
155 fn application_windows(&self) -> Result<Vec<Self::Elem>, AutomataError>;
156
157 /// Launch an executable by name or full path. Returns the process ID.
158 fn open_application(&self, exe: &str) -> Result<u32, AutomataError>;
159
160 /// The element that currently has keyboard focus (topmost modal if a dialog
161 /// is present). Returns `None` if the foreground window is unknown.
162 fn foreground_window(&self) -> Option<Self::Elem>;
163
164 /// Raw HWND as `u64` for process-ownership checks without a full element
165 /// query. Returns `None` on non-Windows or when nothing is focused.
166 fn foreground_hwnd(&self) -> Option<u64>;
167
168 /// All currently visible tooltip windows on the desktop.
169 /// Returns an empty vec on non-Windows or if none are present.
170 fn tooltip_windows(&self) -> Vec<Self::Elem> {
171 vec![]
172 }
173
174 /// Top-level window handles in Z-order (topmost first).
175 ///
176 /// Used by the anchor resolver to prefer the topmost window of a process
177 /// when multiple windows match the same filter. Returns an empty vec on
178 /// non-Windows or if the enumeration fails.
179 fn hwnd_z_order(&self) -> Vec<u64> {
180 vec![]
181 }
182}