Skip to main content

computer_use/
backend.rs

1use std::ops::Range;
2
3use crate::app::AppInfo;
4use crate::element::RawNode;
5use crate::error::{Error, Result};
6use crate::geometry::Point;
7use crate::keys::KeyChord;
8use crate::screenshot::RawImage;
9use crate::state::WindowInfo;
10
11/// A mouse button.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13#[non_exhaustive]
14pub enum MouseButton {
15    /// Primary button.
16    #[default]
17    Left,
18    /// Secondary button.
19    Right,
20    /// Middle button / wheel click.
21    Middle,
22}
23
24impl std::str::FromStr for MouseButton {
25    type Err = Error;
26
27    fn from_str(s: &str) -> Result<Self> {
28        match s.trim().to_ascii_lowercase().as_str() {
29            "left" | "primary" => Ok(Self::Left),
30            "right" | "secondary" => Ok(Self::Right),
31            "middle" | "center" => Ok(Self::Middle),
32            other => Err(Error::invalid(format!("unknown mouse button {other:?}"))),
33        }
34    }
35}
36
37/// A scroll direction.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39#[non_exhaustive]
40pub enum ScrollDirection {
41    /// Reveal content above.
42    Up,
43    /// Reveal content below.
44    Down,
45    /// Reveal content to the left.
46    Left,
47    /// Reveal content to the right.
48    Right,
49}
50
51impl ScrollDirection {
52    /// Lowercase name.
53    #[must_use]
54    pub const fn as_str(self) -> &'static str {
55        match self {
56            Self::Up => "up",
57            Self::Down => "down",
58            Self::Left => "left",
59            Self::Right => "right",
60        }
61    }
62}
63
64impl std::str::FromStr for ScrollDirection {
65    type Err = Error;
66
67    fn from_str(s: &str) -> Result<Self> {
68        match s.trim().to_ascii_lowercase().as_str() {
69            "up" => Ok(Self::Up),
70            "down" => Ok(Self::Down),
71            "left" => Ok(Self::Left),
72            "right" => Ok(Self::Right),
73            other => Err(Error::invalid(format!("unknown scroll direction {other:?}"))),
74        }
75    }
76}
77
78/// How synthetic input reaches the app.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
80#[non_exhaustive]
81pub enum Delivery {
82    /// Post events directly to the target app without moving the user's pointer or
83    /// changing focus, where the platform allows it. Backends that cannot target an app
84    /// fall back to [`Delivery::Global`].
85    #[default]
86    Background,
87    /// Like [`Delivery::Background`], but the target window is also made active and key
88    /// *inside its own process* for the duration of the input, without changing the real
89    /// frontmost app, raising the window, or moving the pointer.
90    ///
91    /// On macOS this uses private `SkyLight` window-server calls and is what Chromium/Electron
92    /// apps need to accept background clicks and typing. Command-key shortcuts use it automatically.
93    /// Other platforms treat it as [`Delivery::Background`].
94    FocusedBackground,
95    /// Post events through the system input stream. May move the pointer and activate the app.
96    Global,
97}
98
99/// Where synthetic input should be delivered.
100#[derive(Debug, Clone, Copy)]
101pub struct InputTarget<'a> {
102    /// The receiving app (must be running).
103    pub app: &'a AppInfo,
104    /// The receiving window, when known.
105    pub window: Option<&'a WindowInfo>,
106    /// Delivery path.
107    pub delivery: Delivery,
108}
109
110/// Which OS permissions the current process holds.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112#[non_exhaustive]
113pub struct Permissions {
114    /// Accessibility / UI automation access.
115    pub accessibility: bool,
116    /// Screen capture access.
117    pub screen_recording: bool,
118}
119
120impl Permissions {
121    /// Every permission granted.
122    pub const GRANTED: Self = Self { accessibility: true, screen_recording: true };
123
124    /// Creates a permission report.
125    #[must_use]
126    pub const fn new(accessibility: bool, screen_recording: bool) -> Self {
127        Self { accessibility, screen_recording }
128    }
129
130    /// True when nothing is missing.
131    #[must_use]
132    pub const fn all_granted(&self) -> bool {
133        self.accessibility && self.screen_recording
134    }
135}
136
137/// Parameters for [`Backend::capture`].
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139#[non_exhaustive]
140pub struct CaptureRequest {
141    /// Walk the accessibility tree.
142    pub include_tree: bool,
143    /// Capture window pixels.
144    pub include_screenshot: bool,
145    /// Maximum raw nodes to visit.
146    pub max_nodes: usize,
147    /// Maximum depth to visit.
148    pub max_depth: usize,
149}
150
151impl CaptureRequest {
152    /// Creates a request.
153    #[must_use]
154    pub const fn new(include_tree: bool, include_screenshot: bool, max_nodes: usize, max_depth: usize) -> Self {
155        Self { include_tree, include_screenshot, max_nodes, max_depth }
156    }
157}
158
159/// What a backend returns from [`Backend::capture`].
160#[derive(Debug, Clone)]
161#[non_exhaustive]
162pub struct Capture<H> {
163    /// The captured window.
164    pub window: WindowInfo,
165    /// Accessibility nodes in depth-first pre-order, rooted at the window.
166    pub nodes: Vec<RawNode<H>>,
167    /// Whether limits cut the walk short.
168    pub truncated: bool,
169    /// Index into `nodes` of the focused element.
170    pub focused: Option<usize>,
171    /// Selected text in the focused element.
172    pub selected_text: Option<String>,
173    /// Window pixels; the image must cover exactly `window.frame`.
174    pub screenshot: Option<RawImage>,
175    /// Non-fatal problems worth surfacing, e.g. why a screenshot is missing.
176    pub notes: Vec<String>,
177}
178
179impl<H> Capture<H> {
180    /// A capture of `window` with nothing else filled in.
181    #[must_use]
182    pub const fn new(window: WindowInfo) -> Self {
183        Self {
184            window,
185            nodes: Vec::new(),
186            truncated: false,
187            focused: None,
188            selected_text: None,
189            screenshot: None,
190            notes: Vec::new(),
191        }
192    }
193}
194
195/// OS primitives for observing and driving apps.
196///
197/// All coordinates are screen points with a top-left origin. Methods with default
198/// implementations return [`Error::Unsupported`].
199pub trait Backend {
200    /// Handle to a live accessibility element (e.g. `AXUIElement`, `IUIAutomationElement`).
201    type Handle: Clone;
202
203    /// Running apps plus installed apps the user can launch.
204    ///
205    /// # Errors
206    ///
207    /// Fails if the platform's process or app registry cannot be read.
208    fn list_apps(&self) -> Result<Vec<AppInfo>>;
209
210    /// Starts `app` without bringing it to the foreground and returns its running record.
211    ///
212    /// # Errors
213    ///
214    /// Fails if the app cannot be started or does not appear in time.
215    fn launch_app(&self, app: &AppInfo) -> Result<AppInfo> {
216        Err(Error::Unsupported(format!("launching {} is not supported by this backend", app.name)))
217    }
218
219    /// Permissions currently held by this process.
220    fn permissions(&self) -> Permissions {
221        Permissions::GRANTED
222    }
223
224    /// Asks the OS to prompt the user for missing permissions, then reports the current state.
225    fn request_permissions(&self) -> Permissions {
226        self.permissions()
227    }
228
229    /// Captures the app's current window.
230    ///
231    /// # Errors
232    ///
233    /// Fails if the app has no usable window or permissions are missing.
234    fn capture(&self, app: &AppInfo, request: &CaptureRequest) -> Result<Capture<Self::Handle>>;
235
236    /// Invokes a named accessibility action (as listed in [`Action::name`](crate::Action::name)).
237    ///
238    /// # Errors
239    ///
240    /// Fails if the element is gone or rejects the action.
241    fn perform_action(&self, element: &Self::Handle, action: &str) -> Result<()>;
242
243    /// Writes an element's value.
244    ///
245    /// # Errors
246    ///
247    /// Fails if the value is not settable.
248    fn set_value(&self, element: &Self::Handle, value: &str) -> Result<()>;
249
250    /// Reads an element's full, untruncated value.
251    ///
252    /// # Errors
253    ///
254    /// Fails if the element is gone.
255    fn value(&self, element: &Self::Handle) -> Result<Option<String>> {
256        let _ = element;
257        Err(Error::Unsupported("reading element values".into()))
258    }
259
260    /// Selects `range` (byte offsets into `value`, the element's current value).
261    /// An empty range places the caret.
262    ///
263    /// # Errors
264    ///
265    /// Fails if the element does not support text selection.
266    fn select_text(&self, element: &Self::Handle, value: &str, range: Range<usize>) -> Result<()> {
267        let _ = (element, value, range);
268        Err(Error::Unsupported("text selection".into()))
269    }
270
271    /// Inserts text at the caret of the focused element without synthesizing keystrokes.
272    /// Returns `Ok(false)` when the focused element does not support it, so the caller can
273    /// fall back to [`Backend::type_text`].
274    ///
275    /// # Errors
276    ///
277    /// Fails if insertion was attempted and rejected.
278    fn insert_text(&self, app: &AppInfo, text: &str) -> Result<bool> {
279        let _ = (app, text);
280        Ok(false)
281    }
282
283    /// Clicks `count` times at a screen point.
284    ///
285    /// # Errors
286    ///
287    /// Fails if the events cannot be posted.
288    fn click(&self, target: InputTarget<'_>, point: Point, button: MouseButton, count: u32) -> Result<()>;
289
290    /// Scrolls at a screen point by `(dx, dy)` points; positive `dy` reveals content below.
291    ///
292    /// # Errors
293    ///
294    /// Fails if the events cannot be posted.
295    fn scroll(&self, target: InputTarget<'_>, point: Point, dx: f64, dy: f64) -> Result<()>;
296
297    /// Presses the primary button at `from`, moves to `to`, and releases.
298    ///
299    /// # Errors
300    ///
301    /// Fails if the events cannot be posted.
302    fn drag(&self, target: InputTarget<'_>, from: Point, to: Point) -> Result<()>;
303
304    /// Types literal text.
305    ///
306    /// # Errors
307    ///
308    /// Fails if the events cannot be posted.
309    fn type_text(&self, target: InputTarget<'_>, text: &str) -> Result<()>;
310
311    /// Presses and releases a key chord.
312    ///
313    /// # Errors
314    ///
315    /// Fails if the key has no mapping on this platform or events cannot be posted.
316    fn press_key(&self, target: InputTarget<'_>, chord: &KeyChord) -> Result<()>;
317}