@elizaos/computeruse 0.24.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +34 -0
- package/build.rs +10 -0
- package/computeruse.darwin-arm64.node +0 -0
- package/index.d.ts +0 -0
- package/index.js +327 -0
- package/package.json +74 -0
- package/scripts/sync-version.js +60 -0
- package/src/desktop.rs +2763 -0
- package/src/element.rs +1341 -0
- package/src/exceptions.rs +65 -0
- package/src/lib.rs +26 -0
- package/src/locator.rs +172 -0
- package/src/selector.rs +158 -0
- package/src/types.rs +963 -0
- package/src/window_manager.rs +342 -0
- package/tests/comprehensive-ui-elements.test.js +524 -0
- package/tests/cross-app-verification.test.js +243 -0
- package/tests/desktop-verify.test.js +169 -0
- package/tests/element-chaining.test.js +158 -0
- package/tests/element-range.test.js +207 -0
- package/tests/element-scroll-into-view.test.js +256 -0
- package/tests/element-value.test.js +264 -0
- package/tests/execute-browser-script-wrapper.test.js +135 -0
- package/tests/fixtures/sample-browser-script.js +7 -0
- package/tests/fixtures/script-with-env.js +16 -0
- package/tests/locator-validate.test.js +260 -0
- package/tests/locator-waitfor.test.js +286 -0
- package/wrapper.d.ts +84 -0
- package/wrapper.js +344 -0
- package/wrapper.ts +394 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
//! Window manager bindings for Node.js
|
|
2
|
+
|
|
3
|
+
use napi_derive::napi;
|
|
4
|
+
#[cfg(target_os = "windows")]
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
#[cfg(target_os = "windows")]
|
|
8
|
+
use computeruse::WindowManager as RustWindowManager;
|
|
9
|
+
|
|
10
|
+
/// Information about a window
|
|
11
|
+
#[napi(object)]
|
|
12
|
+
pub struct WindowInfo {
|
|
13
|
+
/// Window handle
|
|
14
|
+
pub hwnd: i64,
|
|
15
|
+
/// Process name (e.g., "notepad.exe")
|
|
16
|
+
pub process_name: String,
|
|
17
|
+
/// Process ID
|
|
18
|
+
pub process_id: u32,
|
|
19
|
+
/// Z-order position (0 = topmost)
|
|
20
|
+
pub z_order: u32,
|
|
21
|
+
/// Whether the window is minimized
|
|
22
|
+
pub is_minimized: bool,
|
|
23
|
+
/// Whether the window is maximized
|
|
24
|
+
pub is_maximized: bool,
|
|
25
|
+
/// Whether the window has WS_EX_TOPMOST style
|
|
26
|
+
pub is_always_on_top: bool,
|
|
27
|
+
/// Window title
|
|
28
|
+
pub title: String,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[cfg(target_os = "windows")]
|
|
32
|
+
impl From<computeruse::WindowInfo> for WindowInfo {
|
|
33
|
+
fn from(info: computeruse::WindowInfo) -> Self {
|
|
34
|
+
Self {
|
|
35
|
+
hwnd: info.hwnd as i64,
|
|
36
|
+
process_name: info.process_name,
|
|
37
|
+
process_id: info.process_id,
|
|
38
|
+
z_order: info.z_order,
|
|
39
|
+
is_minimized: info.is_minimized,
|
|
40
|
+
is_maximized: info.is_maximized,
|
|
41
|
+
is_always_on_top: info.is_always_on_top,
|
|
42
|
+
title: info.title,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Window manager for controlling window states
|
|
48
|
+
///
|
|
49
|
+
/// Provides functionality for:
|
|
50
|
+
/// - Enumerating windows with Z-order tracking
|
|
51
|
+
/// - Bringing windows to front (bypassing Windows focus-stealing prevention)
|
|
52
|
+
/// - Minimizing/maximizing windows
|
|
53
|
+
/// - Capturing and restoring window states for workflows
|
|
54
|
+
#[napi]
|
|
55
|
+
pub struct WindowManager {
|
|
56
|
+
#[cfg(target_os = "windows")]
|
|
57
|
+
inner: Arc<RustWindowManager>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[napi]
|
|
61
|
+
impl Default for WindowManager {
|
|
62
|
+
fn default() -> Self {
|
|
63
|
+
Self::new()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[napi]
|
|
68
|
+
impl WindowManager {
|
|
69
|
+
/// Create a new WindowManager instance
|
|
70
|
+
#[napi(constructor)]
|
|
71
|
+
pub fn new() -> Self {
|
|
72
|
+
Self {
|
|
73
|
+
#[cfg(target_os = "windows")]
|
|
74
|
+
inner: Arc::new(RustWindowManager::new()),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Update window cache with current window information
|
|
79
|
+
#[napi]
|
|
80
|
+
pub async fn update_window_cache(&self) -> napi::Result<()> {
|
|
81
|
+
#[cfg(target_os = "windows")]
|
|
82
|
+
{
|
|
83
|
+
self.inner.update_window_cache().await.map_err(|e| {
|
|
84
|
+
napi::Error::from_reason(format!("Failed to update window cache: {}", e))
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
#[cfg(not(target_os = "windows"))]
|
|
88
|
+
{
|
|
89
|
+
Err(napi::Error::from_reason(
|
|
90
|
+
"WindowManager is only supported on Windows",
|
|
91
|
+
))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Get topmost window for a process by name
|
|
96
|
+
#[napi]
|
|
97
|
+
pub async fn get_topmost_window_for_process(
|
|
98
|
+
&self,
|
|
99
|
+
#[allow(unused_variables)] process: String,
|
|
100
|
+
) -> napi::Result<Option<WindowInfo>> {
|
|
101
|
+
#[cfg(target_os = "windows")]
|
|
102
|
+
{
|
|
103
|
+
Ok(self
|
|
104
|
+
.inner
|
|
105
|
+
.get_topmost_window_for_process(&process)
|
|
106
|
+
.await
|
|
107
|
+
.map(WindowInfo::from))
|
|
108
|
+
}
|
|
109
|
+
#[cfg(not(target_os = "windows"))]
|
|
110
|
+
{
|
|
111
|
+
Err(napi::Error::from_reason(
|
|
112
|
+
"WindowManager is only supported on Windows",
|
|
113
|
+
))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Get topmost window for a specific PID
|
|
118
|
+
#[napi]
|
|
119
|
+
pub async fn get_topmost_window_for_pid(&self, #[allow(unused_variables)] pid: u32) -> napi::Result<Option<WindowInfo>> {
|
|
120
|
+
#[cfg(target_os = "windows")]
|
|
121
|
+
{
|
|
122
|
+
Ok(self
|
|
123
|
+
.inner
|
|
124
|
+
.get_topmost_window_for_pid(pid)
|
|
125
|
+
.await
|
|
126
|
+
.map(WindowInfo::from))
|
|
127
|
+
}
|
|
128
|
+
#[cfg(not(target_os = "windows"))]
|
|
129
|
+
{
|
|
130
|
+
Err(napi::Error::from_reason(
|
|
131
|
+
"WindowManager is only supported on Windows",
|
|
132
|
+
))
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Get all visible always-on-top windows
|
|
137
|
+
#[napi]
|
|
138
|
+
pub async fn get_always_on_top_windows(&self) -> napi::Result<Vec<WindowInfo>> {
|
|
139
|
+
#[cfg(target_os = "windows")]
|
|
140
|
+
{
|
|
141
|
+
Ok(self
|
|
142
|
+
.inner
|
|
143
|
+
.get_always_on_top_windows()
|
|
144
|
+
.await
|
|
145
|
+
.into_iter()
|
|
146
|
+
.map(WindowInfo::from)
|
|
147
|
+
.collect())
|
|
148
|
+
}
|
|
149
|
+
#[cfg(not(target_os = "windows"))]
|
|
150
|
+
{
|
|
151
|
+
Err(napi::Error::from_reason(
|
|
152
|
+
"WindowManager is only supported on Windows",
|
|
153
|
+
))
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Minimize only always-on-top windows (excluding target)
|
|
158
|
+
/// Returns the number of windows minimized
|
|
159
|
+
#[napi]
|
|
160
|
+
pub async fn minimize_always_on_top_windows(&self, #[allow(unused_variables)] target_hwnd: i64) -> napi::Result<u32> {
|
|
161
|
+
#[cfg(target_os = "windows")]
|
|
162
|
+
{
|
|
163
|
+
self.inner
|
|
164
|
+
.minimize_always_on_top_windows(target_hwnd as isize)
|
|
165
|
+
.await
|
|
166
|
+
.map_err(|e| {
|
|
167
|
+
napi::Error::from_reason(format!(
|
|
168
|
+
"Failed to minimize always-on-top windows: {}",
|
|
169
|
+
e
|
|
170
|
+
))
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
#[cfg(not(target_os = "windows"))]
|
|
174
|
+
{
|
|
175
|
+
Err(napi::Error::from_reason(
|
|
176
|
+
"WindowManager is only supported on Windows",
|
|
177
|
+
))
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Minimize all visible windows except the target
|
|
182
|
+
#[napi]
|
|
183
|
+
pub async fn minimize_all_except(&self, #[allow(unused_variables)] target_hwnd: i64) -> napi::Result<u32> {
|
|
184
|
+
#[cfg(target_os = "windows")]
|
|
185
|
+
{
|
|
186
|
+
self.inner
|
|
187
|
+
.minimize_all_except(target_hwnd as isize)
|
|
188
|
+
.await
|
|
189
|
+
.map_err(|e| napi::Error::from_reason(format!("Failed to minimize windows: {}", e)))
|
|
190
|
+
}
|
|
191
|
+
#[cfg(not(target_os = "windows"))]
|
|
192
|
+
{
|
|
193
|
+
Err(napi::Error::from_reason(
|
|
194
|
+
"WindowManager is only supported on Windows",
|
|
195
|
+
))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Maximize window if not already maximized
|
|
200
|
+
/// Returns true if the window was maximized (wasn't already maximized)
|
|
201
|
+
#[napi]
|
|
202
|
+
pub async fn maximize_if_needed(&self, #[allow(unused_variables)] hwnd: i64) -> napi::Result<bool> {
|
|
203
|
+
#[cfg(target_os = "windows")]
|
|
204
|
+
{
|
|
205
|
+
self.inner
|
|
206
|
+
.maximize_if_needed(hwnd as isize)
|
|
207
|
+
.await
|
|
208
|
+
.map_err(|e| napi::Error::from_reason(format!("Failed to maximize window: {}", e)))
|
|
209
|
+
}
|
|
210
|
+
#[cfg(not(target_os = "windows"))]
|
|
211
|
+
{
|
|
212
|
+
Err(napi::Error::from_reason(
|
|
213
|
+
"WindowManager is only supported on Windows",
|
|
214
|
+
))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Bring window to front using AttachThreadInput trick
|
|
219
|
+
///
|
|
220
|
+
/// This uses AttachThreadInput to bypass Windows' focus-stealing prevention.
|
|
221
|
+
/// Returns true if the window is now in the foreground.
|
|
222
|
+
#[napi]
|
|
223
|
+
pub async fn bring_window_to_front(&self, #[allow(unused_variables)] hwnd: i64) -> napi::Result<bool> {
|
|
224
|
+
#[cfg(target_os = "windows")]
|
|
225
|
+
{
|
|
226
|
+
self.inner
|
|
227
|
+
.bring_window_to_front(hwnd as isize)
|
|
228
|
+
.await
|
|
229
|
+
.map_err(|e| {
|
|
230
|
+
napi::Error::from_reason(format!("Failed to bring window to front: {}", e))
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
#[cfg(not(target_os = "windows"))]
|
|
234
|
+
{
|
|
235
|
+
Err(napi::Error::from_reason(
|
|
236
|
+
"WindowManager is only supported on Windows",
|
|
237
|
+
))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Minimize window if not already minimized
|
|
242
|
+
/// Returns true if the window was minimized (wasn't already minimized)
|
|
243
|
+
#[napi]
|
|
244
|
+
pub async fn minimize_if_needed(&self, #[allow(unused_variables)] hwnd: i64) -> napi::Result<bool> {
|
|
245
|
+
#[cfg(target_os = "windows")]
|
|
246
|
+
{
|
|
247
|
+
self.inner
|
|
248
|
+
.minimize_if_needed(hwnd as isize)
|
|
249
|
+
.await
|
|
250
|
+
.map_err(|e| napi::Error::from_reason(format!("Failed to minimize window: {}", e)))
|
|
251
|
+
}
|
|
252
|
+
#[cfg(not(target_os = "windows"))]
|
|
253
|
+
{
|
|
254
|
+
Err(napi::Error::from_reason(
|
|
255
|
+
"WindowManager is only supported on Windows",
|
|
256
|
+
))
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Capture current state before workflow
|
|
261
|
+
#[napi]
|
|
262
|
+
pub async fn capture_initial_state(&self) -> napi::Result<()> {
|
|
263
|
+
#[cfg(target_os = "windows")]
|
|
264
|
+
{
|
|
265
|
+
self.inner.capture_initial_state().await.map_err(|e| {
|
|
266
|
+
napi::Error::from_reason(format!("Failed to capture initial state: {}", e))
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
#[cfg(not(target_os = "windows"))]
|
|
270
|
+
{
|
|
271
|
+
Err(napi::Error::from_reason(
|
|
272
|
+
"WindowManager is only supported on Windows",
|
|
273
|
+
))
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// Restore windows that were minimized and target window to their original state
|
|
278
|
+
/// Returns the number of windows restored
|
|
279
|
+
#[napi]
|
|
280
|
+
pub async fn restore_all_windows(&self) -> napi::Result<u32> {
|
|
281
|
+
#[cfg(target_os = "windows")]
|
|
282
|
+
{
|
|
283
|
+
self.inner
|
|
284
|
+
.restore_all_windows()
|
|
285
|
+
.await
|
|
286
|
+
.map_err(|e| napi::Error::from_reason(format!("Failed to restore windows: {}", e)))
|
|
287
|
+
}
|
|
288
|
+
#[cfg(not(target_os = "windows"))]
|
|
289
|
+
{
|
|
290
|
+
Err(napi::Error::from_reason(
|
|
291
|
+
"WindowManager is only supported on Windows",
|
|
292
|
+
))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Clear captured state
|
|
297
|
+
#[napi]
|
|
298
|
+
pub async fn clear_captured_state(&self) -> napi::Result<()> {
|
|
299
|
+
#[cfg(target_os = "windows")]
|
|
300
|
+
{
|
|
301
|
+
self.inner.clear_captured_state().await;
|
|
302
|
+
Ok(())
|
|
303
|
+
}
|
|
304
|
+
#[cfg(not(target_os = "windows"))]
|
|
305
|
+
{
|
|
306
|
+
Err(napi::Error::from_reason(
|
|
307
|
+
"WindowManager is only supported on Windows",
|
|
308
|
+
))
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// Check if a process is a UWP/Modern app
|
|
313
|
+
#[napi]
|
|
314
|
+
pub async fn is_uwp_app(&self, #[allow(unused_variables)] pid: u32) -> napi::Result<bool> {
|
|
315
|
+
#[cfg(target_os = "windows")]
|
|
316
|
+
{
|
|
317
|
+
Ok(self.inner.is_uwp_app(pid).await)
|
|
318
|
+
}
|
|
319
|
+
#[cfg(not(target_os = "windows"))]
|
|
320
|
+
{
|
|
321
|
+
Err(napi::Error::from_reason(
|
|
322
|
+
"WindowManager is only supported on Windows",
|
|
323
|
+
))
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// Track a window as the target for restoration
|
|
328
|
+
#[napi]
|
|
329
|
+
pub async fn set_target_window(&self, #[allow(unused_variables)] hwnd: i64) -> napi::Result<()> {
|
|
330
|
+
#[cfg(target_os = "windows")]
|
|
331
|
+
{
|
|
332
|
+
self.inner.set_target_window(hwnd as isize).await;
|
|
333
|
+
Ok(())
|
|
334
|
+
}
|
|
335
|
+
#[cfg(not(target_os = "windows"))]
|
|
336
|
+
{
|
|
337
|
+
Err(napi::Error::from_reason(
|
|
338
|
+
"WindowManager is only supported on Windows",
|
|
339
|
+
))
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|