1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Portable intent, platform-specific mechanism.
//!
//! Some things every backend can do, but by completely different means.
//! "Wait until the application stops being busy" is one operation to the person
//! writing automation; underneath, Windows polls the mouse cursor shape and
//! macOS probes the Accessibility messaging timeout.
//!
//! Putting those behind one name is what keeps `#[cfg]` out of business logic
//! — which matters, because this crate deliberately makes platform-specific
//! calls a *compile* error. Without recipes, that choice would push the
//! conditionals into every caller.
use crate;
use crate::;
use Duration;
/// Clicks at an offset inside a window, wherever the window happens to be.
///
/// The alternative — an absolute screen coordinate — is what production AutoIt
/// automation usually does, and it is why that automation also has to pin the
/// screen resolution and refuse to start if it changes. Anchoring to the window
/// removes that constraint.
///
/// ```no_run
/// # use autoitx::{AutoIt, Selector, recipes};
/// # let ai = AutoIt::new()?;
/// let dialog = Selector::from("[TITLE:Acme ERP;CLASS:ui60Modal_W32]");
/// // "OK", 600 across and 420 down from the dialog's top-left corner.
/// recipes::click_in_window(&ai, &dialog, 600, 420)?;
/// # Ok::<(), autoitx::Error>(())
/// ```
///
/// # Errors
///
/// [`Error::WindowNotFound`] if nothing matches, or whatever the click returns.
/// The same, for a point already computed.
///
/// # Errors
///
/// As [`click_in_window`].
/// Waits until the target application is ready for input.
///
/// The mechanism is where the two platforms part company:
///
/// | | how |
/// |---|---|
/// | Windows | polls the system cursor until it is an arrow or an I-beam — the idiom automation writes by hand as `cursor == 2 \|\| cursor == 5`, here with the timeout that version invariably lacks |
/// | macOS | asks the frontmost application for an accessibility attribute with a short messaging timeout; a busy application does not answer in time |
///
/// The macOS version measures responsiveness directly. Windows has to infer it
/// from what the pointer looks like, because it has no way to ask.
///
/// # Errors
///
/// [`Error::Timeout`] if the application never settles.
/// Reads the focused field or selection by copying it, without the usual race.
///
/// The clipboard is how AutoIt automation reads a screen it cannot query: select,
/// copy, read. The hard part is knowing *when* the copy landed. The idiom in the
/// wild is to put a sentinel on the clipboard first and poll until it changes:
///
/// ```csharp
/// AutoItX.ClipPut("NO-VALUE"); // a fixed sentinel, in production code
/// // ... select and copy ...
/// if (AutoItX.ClipGet() == "NO-VALUE") { /* assume nothing was copied */ }
/// ```
///
/// That has three failure modes, and all three have been observed:
///
/// - the cell genuinely contains the sentinel, and a real value reads as empty;
/// - the copy re-writes the value that was already there, so nothing appears to
/// change and the read times out;
/// - the copy never happens at all — the keystroke went to the wrong window —
/// and the *stale* clipboard is returned as if it were this field's value.
///
/// This waits on the OS clipboard sequence number instead, which Windows bumps
/// on every write by any process. It cannot collide, it notices identical
/// rewrites, and a copy that never happened is detected rather than papered
/// over.
///
/// ```no_run
/// # use autoitx::{AutoIt, recipes, keys};
/// # use std::time::Duration;
/// # let ai = AutoIt::new()?;
/// // Select the current field and read it.
/// let value = recipes::read_screen_text(
/// &ai,
/// keys!("{END}{SHIFTDOWN}{HOME}{SHIFTUP}"),
/// Duration::from_secs(5),
/// )?;
/// # Ok::<(), autoitx::Error>(())
/// ```
///
/// # Errors
///
/// [`Error::Timeout`] if nothing reached the clipboard — which means the copy
/// did not happen, not that the field was empty. An empty field still bumps the
/// sequence number and yields `Ok("")`.
/// Waits for a window to appear, then activates it and waits for focus.
///
/// The opening move of nearly every automation flow.
///
/// # Errors
///
/// [`Error::Timeout`] if the window never appears or never takes focus.