cosh_tools/computer/types.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4/// What `computer_apps` should report.
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
6#[serde(rename_all = "lowercase")]
7pub enum AppsTarget {
8 /// Every running application (default).
9 #[default]
10 All,
11 /// The application that currently holds the system foreground, plus the
12 /// element inside it holding keyboard focus — the cheap "where do my
13 /// keystrokes go?" check before `computer_keyboard`.
14 Focused,
15}
16
17/// Input for `computer_apps`.
18///
19/// `target: "all"` (default) lists every running application;
20/// `target: "focused"` resolves the foreground application and its
21/// keyboard-focused element instead.
22#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
23pub struct ComputerApps {
24 /// What to report: `all` (default) = every running app with PIDs and the
25 /// foreground flag; `focused` = the foreground app plus the element
26 /// holding keyboard focus inside it.
27 pub target: Option<AppsTarget>,
28 /// How long to wait for a foreground application to exist, in
29 /// milliseconds (`target: "focused"` only). Default 3000.
30 pub timeout_ms: Option<u64>,
31}
32
33/// Output format for `computer_snapshot`.
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
35#[serde(rename_all = "lowercase")]
36pub enum SnapshotFormat {
37 /// Compact indented outline (one line per element). Default.
38 #[default]
39 Tree,
40 /// Structured JSON (`role`/`name`/`value`/`children` per node).
41 Json,
42}
43
44/// Input for `computer_snapshot`.
45#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
46pub struct ComputerSnapshot {
47 /// Application name (exact match, e.g. `"Safari"`). Provide `name` or
48 /// `pid` — exactly one.
49 pub name: Option<String>,
50 /// Application process ID. Provide `name` or `pid` — exactly one.
51 pub pid: Option<u32>,
52 /// Optional CSS-like selector to snapshot only the matching subtree
53 /// instead of the whole application, e.g. `"window[name='Main'] > group"`.
54 pub selector: Option<String>,
55 /// 1-based match index when `selector` matches multiple elements
56 /// (default 1).
57 pub nth: Option<usize>,
58 /// Maximum snapshot depth. Default 12; hard cap 40.
59 pub max_depth: Option<u32>,
60 /// Output format. Default `tree`.
61 pub format: Option<SnapshotFormat>,
62 /// How long to wait for the application to surface in the accessibility
63 /// tree, in milliseconds. Default 3000.
64 pub timeout_ms: Option<u64>,
65 /// Target an OS SHELL SURFACE instead of an application (mutually
66 /// exclusive with `name`/`pid`): the menu bar, Dock/taskbar/panel, tray
67 /// status items, the desktop, or a flyout open right now. `selector`
68 /// then narrows within that surface's tree.
69 pub surface: Option<SurfaceKind>,
70}
71
72/// One running application, as reported by `computer_apps`.
73#[derive(Debug, Clone, Serialize, JsonSchema)]
74pub struct AppInfo {
75 /// Application name.
76 pub name: String,
77 /// Process ID, when the platform reports one.
78 pub pid: Option<u32>,
79 /// Whether this app currently holds the system foreground.
80 pub foreground: bool,
81}
82
83/// Output of `computer_apps`.
84#[derive(Debug, Clone, Serialize, JsonSchema)]
85pub struct AppsOutput {
86 /// Running applications (unsorted, platform enumeration order).
87 pub apps: Vec<AppInfo>,
88 /// Convenience count of [`AppsOutput::apps`].
89 pub count: usize,
90}
91
92/// The element holding keyboard focus inside the foreground application,
93/// reported by `computer_apps` with `target: "focused"`.
94#[derive(Debug, Clone, Serialize, JsonSchema)]
95pub struct FocusedElement {
96 /// Element role, snake_case (same vocabulary as selectors).
97 pub role: String,
98 /// Accessible name, when the element has one.
99 pub name: Option<String>,
100 /// Current value, when the element has one (e.g. the text in a focused
101 /// field).
102 pub value: Option<String>,
103 /// Path of roles from the application root down to the focused element
104 /// (inclusive), e.g. `["window", "group", "text_field"]` — context for
105 /// locating the field without another snapshot.
106 pub path: Vec<String>,
107}
108
109/// Output of `computer_apps` with `target: "focused"`.
110#[derive(Debug, Clone, Serialize, JsonSchema)]
111pub struct FocusedOutput {
112 /// Name of the foreground application.
113 pub app: String,
114 /// Process ID of the foreground application.
115 pub pid: Option<u32>,
116 /// The element inside the app holding keyboard focus, when one does.
117 /// `None` when the app has no focused element (e.g. focus is on the
118 /// window itself or the platform does not report element focus).
119 pub focused_element: Option<FocusedElement>,
120}
121
122/// Input for `computer_screenshot`.
123#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
124pub struct ComputerScreenshot {
125 /// Capture only this region of the display, as `[x, y, width, height]`
126 /// in display pixels (origin top-left). Mutually exclusive with element
127 /// capture (`app`/`pid` + `selector`).
128 pub region: Option<Vec<i32>>,
129 /// Application name (exact match) for element capture: shoot only the
130 /// pixels under the element matched by `selector`. Requires `selector`.
131 pub app: Option<String>,
132 /// Application process ID for element capture. Requires `selector`.
133 pub pid: Option<u32>,
134 /// Optional CSS-like selector for element capture, e.g.
135 /// `"window[name='Main'] > group"`. Requires `app` or `pid`.
136 ///
137 /// With `annotate: true` this changes meaning: it selects which elements
138 /// get annotated boxes (default `"*"` — every element of the app), and
139 /// the legend maps each tag to a round-trippable selector. Comma
140 /// alternations (`"button, link"`) are rejected in both modes.
141 pub selector: Option<String>,
142 /// 1-based match index when `selector` matches multiple elements
143 /// (default 1). Not applicable to annotated captures — the legend
144 /// covers every match; narrow `selector` instead.
145 pub nth: Option<usize>,
146 /// Draw a labeled box on every matching element and return a legend
147 /// mapping each tag (`B7`) back to a selector (`*:nth(7)`) that
148 /// `computer_act` accepts directly — visual grounding without pixel
149 /// coordinates. Requires `app` or `pid` (annotation groups must be
150 /// application-scoped) and is mutually exclusive with `region`.
151 /// Default `false`.
152 #[serde(default)]
153 pub annotate: bool,
154 /// Maximum delivered image width in pixels (default 1568). Larger
155 /// captures are downscaled preserving aspect ratio; coordinates always
156 /// refer to the delivered image.
157 pub max_width: Option<u32>,
158 /// How long to wait for the app to appear (element capture only), in
159 /// milliseconds. Default 3000.
160 pub timeout_ms: Option<u64>,
161 /// Target an OS SHELL SURFACE instead of an application (mutually
162 /// exclusive with `app`/`pid`; requires `selector`): shoot the pixels
163 /// under the matched element of the menu bar, Dock/taskbar/panel, tray
164 /// status items, the desktop, or a flyout open right now. With
165 /// `annotate: true` the legend covers the surface's elements the same
166 /// way it covers an app's.
167 pub surface: Option<SurfaceKind>,
168}
169
170/// Output of `computer_screenshot`.
171///
172/// The PNG travels inline via [`ScreenshotOutput::images`] (base64
173/// `ImageBlock`s) — no file references.
174#[derive(Debug, Clone, Serialize, JsonSchema)]
175pub struct ScreenshotOutput {
176 /// Width of the DELIVERED image, in pixels (after any downscale).
177 /// Coordinates for touch tools are expressed in this space.
178 pub width: u32,
179 /// Height of the DELIVERED image, in pixels.
180 pub height: u32,
181 /// Desktop-coordinate origin of the image: the desktop point under the
182 /// delivered image's pixel (0, 0) — `(x, y)`.
183 pub desktop_origin: (i32, i32),
184 /// Desktop pixels per delivered image pixel — `(dx, dy)`. Map an image
185 /// coordinate back to the screen as
186 /// `desktop = desktop_origin + image_coord * desktop_scale`.
187 pub desktop_scale: (f64, f64),
188 /// Size of the encoded PNG in bytes.
189 pub bytes: usize,
190 /// Legend of the annotated capture: one entry per drawn box, mapping the
191 /// tag drawn on the image (`B7`) to a selector that resolves the
192 /// element — pass it to `computer_act` as-is. Empty for plain
193 /// captures.
194 pub legend: Vec<LegendEntryOutput>,
195 /// Elements that matched the annotation selector but could not be
196 /// drawn, each with the reason — the picture and the legend never
197 /// disagree silently. Empty for plain captures.
198 pub omitted: Vec<OmissionOutput>,
199 /// How many matched elements were not described at all because the
200 /// annotation cap (100) was reached. `0` when the cap did not bite;
201 /// narrow the `selector` when it does.
202 pub truncated: usize,
203 /// Inline image payload (base64 PNG) for the multimodal channel.
204 #[serde(skip)]
205 pub images: Vec<cosh_sdk::connector::ImageBlock>,
206}
207
208/// One legend entry of an annotated capture: a box on the image plus the
209/// selector that reaches the element it labels.
210#[derive(Debug, Clone, Serialize, JsonSchema)]
211pub struct LegendEntryOutput {
212 /// What is drawn in the box — `"B7"`.
213 pub tag: String,
214 /// Selector usable as-is against the annotation scope —
215 /// `"button[name='Export']:nth(7)"`. This round-trip is the point: read
216 /// the tag off the image, act on the selector.
217 pub selector: String,
218 /// The element's role, snake_case as everywhere else.
219 pub role: String,
220 /// The element's accessible name, when it has one.
221 pub name: Option<String>,
222 /// The box colour, RGB, for correlating a box with its entry by eye.
223 pub color: [u8; 3],
224}
225
226/// An element the annotation selector matched but that could not be drawn.
227#[derive(Debug, Clone, Serialize, JsonSchema)]
228pub struct OmissionOutput {
229 /// The selector that would reach this element.
230 pub selector: String,
231 /// The element's role, snake_case.
232 pub role: String,
233 /// The element's accessible name, when it has one.
234 pub name: Option<String>,
235 /// Why it could not be drawn: `no_bounds`, `zero_area` or
236 /// `outside_capture`.
237 pub reason: String,
238}
239
240/// Output of `computer_snapshot`.
241#[derive(Debug, Clone, Serialize, JsonSchema)]
242pub struct SnapshotOutput {
243 /// Name of the application the snapshot was taken from.
244 pub app: String,
245 /// Process ID of the application, when known.
246 pub pid: Option<u32>,
247 /// Rendered snapshot: an indented outline (`tree`) or pretty-printed
248 /// JSON (`json`), one element per node with role, name, value and states.
249 pub snapshot: String,
250 /// Number of elements in the snapshot.
251 pub elements: usize,
252}
253
254/// Tri-state toggle value for a checkable control (`checked` in
255/// [`ElementStates`]).
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
257#[serde(rename_all = "lowercase")]
258pub enum ToggleState {
259 /// Checkable and currently off.
260 Off,
261 /// Checkable and currently on.
262 On,
263 /// Tri-state checkbox in the indeterminate state.
264 Mixed,
265}
266
267impl From<xa11y::Toggled> for ToggleState {
268 fn from(t: xa11y::Toggled) -> Self {
269 match t {
270 xa11y::Toggled::Off => Self::Off,
271 xa11y::Toggled::On => Self::On,
272 xa11y::Toggled::Mixed => Self::Mixed,
273 }
274 }
275}
276
277/// The element states a snapshot reports, normalized from the platform's
278/// [`xa11y::StateSet`].
279///
280/// The subset is chosen for action planning: these are the flags that change
281/// whether an interaction will succeed (a `disabled` button fails
282/// `computer_act`'s press; a `hidden` row needs `scroll_into_view` first).
283/// Static attributes (focusable, modal, required) and window-level `active`
284/// are left out to keep per-node cost low.
285///
286/// Default (`enabled` + `visible`, everything else off/absent) matches
287/// [`xa11y::StateSet`]'s default, so "all defaults" renders as nothing in the
288/// outline and as a predictable flat object in JSON.
289#[derive(Debug, Clone, PartialEq, Serialize)]
290pub struct ElementStates {
291 /// Whether the element accepts interaction (`false` = aria `disabled`).
292 pub enabled: bool,
293 /// Whether the element is on-screen (`false` = aria `hidden`).
294 pub visible: bool,
295 /// Whether the element currently holds keyboard focus.
296 pub focused: bool,
297 /// `None` = not checkable. `Some(Off)` is meaningful (an unchecked
298 /// checkbox), hence `Option` rather than a plain bool.
299 pub checked: Option<ToggleState>,
300 /// Whether the element is the selected item of its container.
301 pub selected: bool,
302 /// `None` = not expandable. `Some(false)` = a collapsed disclosure.
303 pub expanded: Option<bool>,
304 /// Whether the element accepts text input.
305 pub editable: bool,
306 /// Whether an async operation is in progress on the element.
307 pub busy: bool,
308}
309
310impl Default for ElementStates {
311 fn default() -> Self {
312 Self {
313 enabled: true,
314 visible: true,
315 focused: false,
316 checked: None,
317 selected: false,
318 expanded: None,
319 editable: false,
320 busy: false,
321 }
322 }
323}
324
325impl ElementStates {
326 /// Normalize a platform [`xa11y::StateSet`] into the reported subset.
327 #[must_use]
328 pub fn from_state_set(states: &xa11y::StateSet) -> Self {
329 Self {
330 enabled: states.enabled,
331 visible: states.visible,
332 focused: states.focused,
333 checked: states.checked.map(ToggleState::from),
334 selected: states.selected,
335 expanded: states.expanded,
336 editable: states.editable,
337 busy: states.busy,
338 }
339 }
340
341 /// Whether every flag matches the default (enabled + visible, nothing
342 /// else). The outline renders nothing for such elements.
343 #[must_use]
344 pub fn is_default(&self) -> bool {
345 *self == Self::default()
346 }
347
348 /// aria-vocabulary tokens for the non-default flags, in fixed order:
349 /// `disabled`, `hidden`, `focused`, `checked`/`unchecked`/`mixed`,
350 /// `selected`, `expanded`/`collapsed`, `editable`, `busy`. Fixed order
351 /// keeps the outline predictable to parse.
352 #[must_use]
353 pub fn outline_tokens(&self) -> Vec<&'static str> {
354 let mut tokens = Vec::new();
355 if !self.enabled {
356 tokens.push("disabled");
357 }
358 if !self.visible {
359 tokens.push("hidden");
360 }
361 if self.focused {
362 tokens.push("focused");
363 }
364 match self.checked {
365 Some(ToggleState::On) => tokens.push("checked"),
366 Some(ToggleState::Off) => tokens.push("unchecked"),
367 Some(ToggleState::Mixed) => tokens.push("mixed"),
368 None => {}
369 }
370 if self.selected {
371 tokens.push("selected");
372 }
373 match self.expanded {
374 Some(true) => tokens.push("expanded"),
375 Some(false) => tokens.push("collapsed"),
376 None => {}
377 }
378 if self.editable {
379 tokens.push("editable");
380 }
381 if self.busy {
382 tokens.push("busy");
383 }
384 tokens
385 }
386}
387
388/// One element of the rendered snapshot: role, name, value, current states
389/// and children.
390///
391/// This is the tool's own tree — xa11y's `TreeNode` carries no states, and
392/// the builder below reads them from the same `ElementData` the node's
393/// identity comes from, so building here adds zero extra provider round
394/// trips per node.
395#[derive(Debug, Clone, Serialize)]
396pub struct StateNode {
397 /// Element role, snake_case (same vocabulary as selectors).
398 pub role: String,
399 /// Accessible name, when the element has one.
400 pub name: Option<String>,
401 /// Current value, when the element has one.
402 pub value: Option<String>,
403 /// Current element states.
404 pub states: ElementStates,
405 /// Child elements.
406 pub children: Vec<StateNode>,
407}
408/// Action to perform on the element matched by `selector`.
409#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
410#[serde(rename_all = "snake_case")]
411pub enum ActAction {
412 /// Click / invoke the element via the accessibility action layer
413 /// (buttons, links, menu items).
414 #[default]
415 Press,
416 /// Set keyboard focus.
417 Focus,
418 /// Remove keyboard focus.
419 Blur,
420 /// Toggle a two- or three-state control (checkbox, switch).
421 Toggle,
422 /// Select the element (list item, tab, row).
423 Select,
424 /// Expand a disclosure, menu, combo box or tree item.
425 Expand,
426 /// Collapse an expanded element.
427 Collapse,
428 /// Open the element's context menu or dropdown.
429 ShowMenu,
430 /// Increment a stepper/slider.
431 Increment,
432 /// Decrement a stepper/slider.
433 Decrement,
434 /// Scroll the element into view.
435 ScrollIntoView,
436 /// Replace the element's text value entirely (text fields). Requires
437 /// `value`.
438 SetValue,
439 /// Set a numeric value (slider, spinner). Requires `numeric_value`.
440 SetNumericValue,
441 /// Select a text range `start..end` inside the element. Requires `range`.
442 SelectText,
443 /// Type `value` at the element's caret (focuses it first, inserts rather
444 /// than replacing). Requires `value`.
445 TypeText,
446 /// Platform-specific action by name (e.g. `"raise"`). Requires `value`.
447 PerformAction,
448}
449
450/// Input for `computer_act` — ONE step of a semantic pipeline: either an
451/// accessibility action on the element matched by `selector`, a keyboard
452/// action (`key`/`text`), or a `wait` pause — chained via `then` with
453/// shell `&&` semantics.
454#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
455pub struct ComputerAct {
456 /// Application name (exact match, e.g. 'Safari'). Provide `name` or `pid`.
457 /// Semantic-action steps only.
458 pub name: Option<String>,
459 /// Application process ID. Provide `name` or `pid`. Semantic-action
460 /// steps only.
461 pub pid: Option<u32>,
462 /// CSS-like selector for the target element, e.g. `button[name='OK']`.
463 /// Required on a semantic-action step; absent on keyboard/wait steps.
464 pub selector: Option<String>,
465 /// 1-based match index when `selector` matches multiple elements
466 /// (default 1).
467 pub nth: Option<usize>,
468 /// Action to perform (default `press`). Semantic-action steps only.
469 pub action: Option<ActAction>,
470 /// Text for `set_value`, `type_text` and `perform_action`.
471 pub value: Option<String>,
472 /// Number for `set_numeric_value`.
473 pub numeric_value: Option<f64>,
474 /// `[start, end]` (0-based; `end` EXCLUSIVE — `[0, 3]` selects three
475 /// characters, matching the AT-SPI convention) for `select_text`.
476 pub range: Option<Vec<u32>>,
477 /// How long to wait for the app AND a visible+enabled element match, in
478 /// milliseconds (default 3000). Unlike computer_snapshot/computer_screenshot,
479 /// actions auto-wait for actionability.
480 pub timeout_ms: Option<u64>,
481 /// Target an OS SHELL SURFACE instead of an application (mutually
482 /// exclusive with `name`/`pid`): the menu bar, Dock/taskbar/panel, tray
483 /// status items, the desktop, or a flyout open right now. Semantic
484 /// steps only — a surface has no process to type into; keyboard steps
485 /// in the same chain still need an element step to have set focus.
486 pub surface: Option<SurfaceKind>,
487 // ── Keyboard fields (shared engine with computer_control) ──
488 /// Key to tap, by name: single characters (lowercase — for uppercase
489 /// hold `shift`) or named keys (`enter`, `escape`, `tab`, `space`,
490 /// `backspace`, `delete`, `insert`, `up`, `down`, `left`, `right`,
491 /// `home`, `end`, `pageup`, `pagedown`, `f1`..`f12`). Types into
492 /// whatever holds keyboard focus NOW.
493 pub key: Option<String>,
494 /// Literal text to type into the focused element. Mutually exclusive
495 /// with `key`.
496 pub text: Option<String>,
497 /// Modifier keys held while tapping `key` (`shift`, `ctrl`, `alt`,
498 /// `meta`) — e.g. key `a` + `held: ["ctrl"]` = select all. `text`
499 /// rejects `held`.
500 pub held: Option<Vec<String>>,
501 /// Milliseconds to WAIT as a STANDALONE step (no other fields): gives
502 /// the app time to settle before the next step acts. Capped at 10 s.
503 pub wait: Option<u64>,
504 /// Optional NEXT step of the pipeline, executed only if this step
505 /// succeeded. Same shape as this step, recursively.
506 pub then: Option<Box<ComputerAct>>,
507}
508
509/// Output of `computer_act`.
510#[derive(Debug, Clone, Serialize, JsonSchema)]
511pub struct ActOutput {
512 /// What was executed, per step: semantic actions as
513 /// `<action> \`<selector>\` (app)`, keyboard steps as the tapped key +
514 /// held modifiers or the typed text, waits as `waited N ms`.
515 /// Pipeline steps join with ` → `.
516 pub sent: String,
517}
518
519/// The condition `computer_wait` watches, mirroring xa11y's
520/// [`xa11y::ElementState`] (the same vocabulary `computer_snapshot` reports
521/// as state tokens).
522#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
523#[serde(rename_all = "lowercase")]
524pub enum WaitState {
525 /// A matching element exists in the tree.
526 Attached,
527 /// No element matches the selector (a dialog closed, a spinner went
528 /// away). Tolerates the element never having existed.
529 Detached,
530 /// A matching element exists and is visible (default).
531 #[default]
532 Visible,
533 /// A matching element is hidden or no longer exists.
534 Hidden,
535 /// A matching element exists and is enabled.
536 Enabled,
537 /// A matching element exists but is disabled.
538 Disabled,
539 /// A matching element holds keyboard focus.
540 Focused,
541 /// No matching element holds keyboard focus.
542 Unfocused,
543}
544
545/// Input for `computer_wait` — block until a selector in a target app
546/// reaches a state, or time out with a diagnosis of the last observed
547/// state.
548#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
549pub struct ComputerWait {
550 /// Application name (exact match, e.g. `"Safari"`). Provide `name` or
551 /// `pid` — exactly one.
552 pub name: Option<String>,
553 /// Application process ID. Provide `name` or `pid` — exactly one.
554 pub pid: Option<u32>,
555 /// CSS-like selector for the element to watch, e.g.
556 /// `progress_bar[name='Exporting…']`.
557 pub selector: Option<String>,
558 /// 1-based match index when `selector` matches multiple elements
559 /// (default 1).
560 pub nth: Option<usize>,
561 /// The condition to wait for (default `visible`).
562 pub state: Option<WaitState>,
563 /// How long to wait for the condition, in milliseconds. Default 10 000;
564 /// capped at 60 000 so a stuck call cannot pin the session for minutes.
565 pub timeout_ms: Option<u64>,
566}
567
568/// Output of `computer_wait` when the condition was met in time.
569#[derive(Debug, Clone, Serialize, JsonSchema)]
570pub struct WaitOutput {
571 /// Whether the condition was met before the timeout (always `true` in
572 /// the success response — a timeout is an error carrying the diagnosis).
573 pub met: bool,
574 /// How long the call actually waited, in milliseconds.
575 pub elapsed_ms: u64,
576 /// The state the element was in when the condition was met — a
577 /// convenience sanity check for the model (e.g. a `detached` wait's
578 /// element is gone; an `enabled` wait's element reports enabled).
579 pub observed: WaitObservation,
580}
581
582/// What the watched element looked like when the wait resolved.
583#[derive(Debug, Clone, Serialize, JsonSchema)]
584pub struct WaitObservation {
585 /// Whether an element matched the selector at resolution time.
586 pub attached: bool,
587 /// Whether the matched element was visible (absent when detached).
588 pub visible: Option<bool>,
589 /// Whether the matched element was enabled (absent when detached).
590 pub enabled: Option<bool>,
591 /// Whether the matched element held keyboard focus (absent when
592 /// detached).
593 pub focused: Option<bool>,
594}
595
596/// Which OS shell surface to target instead of an application — the parts
597/// of the desktop OUTSIDE any app: menu bar, Dock/taskbar/panel, tray
598/// status items, the desktop itself, transient flyouts (Notification
599/// Center, Quick Settings, shell context menus). Mirrors xa11y's
600/// [`xa11y::ShellSurfaceKind`] exactly (same snake_case wire spelling).
601#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
602#[serde(rename_all = "snake_case")]
603pub enum SurfaceKind {
604 /// The frontmost application's menu bar (macOS: File → Save As, the
605 /// Apple menu). Windows/Linux: per-window menu bars live in the app's
606 /// own tree, not here.
607 #[default]
608 MenuBar,
609 /// System status items / tray icons (macOS menu-bar extras, Windows
610 /// tray overflow needs a press first — content only exists while the
611 /// overflow is open).
612 StatusItems,
613 /// The Windows taskbar.
614 Taskbar,
615 /// Linux desktop panels (GNOME top bar, KDE panel, …).
616 Panel,
617 /// The macOS Dock.
618 Dock,
619 /// The desktop itself (icons, wallpaper-level widgets).
620 Desktop,
621 /// Transient shell flyouts open RIGHT NOW: Notification Center, Quick
622 /// Settings, shell context menus. They exist only while open — the
623 /// caller performs the press that opens them on a real element first,
624 /// then re-enumerates.
625 Flyout,
626 /// A shell surface the platform reported without a known kind.
627 Unknown,
628}
629
630/// Pointer action to perform at desktop coordinates.
631#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
632#[serde(rename_all = "snake_case")]
633pub enum PointerAction {
634 /// Left-click at (`x`, `y`).
635 #[default]
636 Click,
637 /// Double left-click at (`x`, `y`).
638 DoubleClick,
639 /// Right-click at (`x`, `y`).
640 RightClick,
641 /// Move the cursor to (`x`, `y`) without clicking.
642 Move,
643 /// Press a mouse button down at the current position (`button`, default
644 /// left) — pair with `up` for custom drags.
645 Down,
646 /// Release a previously pressed mouse button.
647 Up,
648 /// Scroll by (`dx`, `dy`) wheel steps at (`x`, `y`). Requires `dx`/`dy`.
649 Scroll,
650 /// Press at the start point, interpolate movement to the end point
651 /// (~60 Hz), release. Requires both endpoints — coordinates (`x`/`y` +
652 /// `x2`/`y2`) or elements (`selector` + `to_selector`), not a mix.
653 /// `duration_ms` paces the movement (default 150, min 50).
654 Drag,
655}
656
657/// Where a pointer action lands inside the element's bounds.
658///
659/// The element form is tree-grounded and therefore allowed in Build mode;
660/// the coordinate form is position-dependent and stays Yolo/Command only.
661#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, JsonSchema)]
662#[serde(rename_all = "snake_case")]
663pub enum PointerAnchor {
664 /// Center of the bounds (default).
665 #[default]
666 Center,
667 /// Top-left corner (half-open bounds: the last point inside, not the
668 /// exclusive edge).
669 TopLeft,
670 /// Top-right corner.
671 TopRight,
672 /// Bottom-left corner.
673 BottomLeft,
674 /// Bottom-right corner.
675 BottomRight,
676}
677
678/// Input for `computer_control` — one step of a desktop-control pipeline:
679/// EITHER a pointer action (click/scroll/move/drag — `key`/`text` absent)
680/// OR a keyboard action (`key` or `text`), at raw coordinates or on an
681/// accessibility-tree element.
682///
683/// A step may chain the NEXT step via `then`: the steps run sequentially in
684/// a single call, each only if the previous succeeded (shell `&&` semantics)
685/// — the first failing step aborts the chain with the point of failure.
686/// Targeting is PER STEP: the canonical pattern is `click` an element (the
687/// real click moves OS keyboard focus — the one mechanism that works on
688/// every toolkit) and `then` type into the focused field. `up` needs no
689/// target.
690#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
691pub struct ComputerControl {
692 // ── Pointer fields ──
693 /// X coordinate in desktop pixels (coordinate form; also the drag START).
694 pub x: Option<i32>,
695 /// Y coordinate in desktop pixels (coordinate form; also the drag START).
696 pub y: Option<i32>,
697 /// Pointer action (default `click`). Ignored — and rejected together
698 /// with any other pointer field — when the step is a keyboard step
699 /// (`key`/`text`).
700 pub action: Option<PointerAction>,
701 /// Button for `down`/`up` (`left` default, `right`, `middle`).
702 pub button: Option<String>,
703 /// Horizontal scroll delta (wheel steps) for `scroll`.
704 pub dx: Option<i32>,
705 /// Vertical scroll delta (wheel steps) for `scroll`; positive scrolls down.
706 pub dy: Option<i32>,
707 /// Targeting PER STEP: application name (exact match) scoping
708 /// `selector`. Provide `app` or `pid` — exactly one, only together with
709 /// `selector`.
710 pub app: Option<String>,
711 /// Application process ID for the element form. Per step.
712 pub pid: Option<u32>,
713 /// Target an OS SHELL SURFACE instead of an application (mutually
714 /// exclusive with `app`/`pid`; only together with `selector`): the
715 /// menu bar, Dock/taskbar/panel, tray status items, the desktop, or a
716 /// flyout open right now. Pointer steps act on the element matched
717 /// under the surface's root the same way they do under an app-scoped
718 /// step.
719 pub surface: Option<SurfaceKind>,
720 /// CSS-like selector of the element this step acts on, e.g.
721 /// `"button[name='OK']"` — the point resolves from the element's CURRENT
722 /// bounds when the step runs, never stale. A `click` on an element moves
723 /// OS keyboard focus to it, which is how the next step's typing finds
724 /// the right field. Per step.
725 pub selector: Option<String>,
726 /// 1-based match index when `selector` matches multiple elements
727 /// (default 1). Requires `selector`. Per step.
728 pub nth: Option<usize>,
729 /// Where inside the element's bounds the point lands (default `center`).
730 pub anchor: Option<PointerAnchor>,
731 /// Number of consecutive clicks for click actions (1 default, 2 =
732 /// double-click, 3 = triple). Independent of `action`: `click` +
733 /// `count: 2` is a double-click.
734 pub count: Option<u32>,
735 /// Modifier keys for THIS step: held during a click or drag, or held
736 /// while tapping `key` (`shift`, `ctrl`, `alt`, `meta`) — e.g. `click` +
737 /// `held: ["ctrl"]` = ctrl+click; key `a` + `held: ["ctrl"]` = select
738 /// all. `text` rejects `held`.
739 pub held: Option<Vec<String>>,
740 /// End X for `drag` (with `y2`). Mutually exclusive with `to_selector`.
741 pub x2: Option<i32>,
742 /// End Y for `drag`.
743 pub y2: Option<i32>,
744 /// End element selector for `drag` (CSS-like selector on the same app) —
745 /// the element form for the drag's end point. Mutually exclusive with
746 /// `x2`/`y2`.
747 pub to_selector: Option<String>,
748 /// Total duration of a `drag`'s movement, in milliseconds (default 150,
749 /// min 50). Backends interpolate the pointer path across this time.
750 pub duration_ms: Option<u64>,
751 // ── Keyboard fields ──
752 /// Key to tap, by name: single characters (`a`, `5`, `.` — lowercase;
753 /// for uppercase hold `shift`), or `enter`, `escape`, `tab`, `space`,
754 /// `backspace`, `delete`, `insert`, `up`, `down`, `left`, `right`,
755 /// `home`, `end`, `pageup`, `pagedown`, `f1`..`f12`. Typing goes into
756 /// the element focused by an earlier step's element click (or whatever
757 /// holds focus).
758 pub key: Option<String>,
759 /// Literal text to type into the focused element (any characters,
760 /// including uppercase). Mutually exclusive with `key`.
761 pub text: Option<String>,
762 /// Milliseconds to WAIT as a STANDALONE step (no other fields): gives
763 /// the app time to open a dialog or create an ephemeral field before
764 /// the next step acts — e.g. click the menu item that opens a rename
765 /// box, then `{"wait": 600}`, then type into it. Capped at 10 s.
766 pub wait: Option<u64>,
767 /// Optional NEXT step of the pipeline, executed only if this step
768 /// succeeded (e.g. click `text_field` → then text `"olá"` → then key
769 /// `enter`). Same shape as this step, recursively.
770 pub then: Option<Box<ComputerControl>>,
771}
772
773/// Output of `computer_control`.
774#[derive(Debug, Clone, Serialize, JsonSchema)]
775pub struct ControlOutput {
776 /// What was executed, per step: pointer actions as
777 /// `click <what>@x,y`, keyboard steps as the tapped key + held modifiers
778 /// or the typed text. Pipeline steps join with ` → `.
779 pub sent: String,
780}