ucu-mcp 0.3.9 → 0.4.2
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/CHANGELOG.md +67 -3
- package/dist/bin/ucu-mcp.js +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/tools/app-tools.d.ts +2 -0
- package/dist/src/mcp/tools/app-tools.js +220 -0
- package/dist/src/mcp/tools/element-tools.d.ts +23 -0
- package/dist/src/mcp/tools/element-tools.js +59 -0
- package/dist/src/mcp/tools/helpers.d.ts +82 -0
- package/dist/src/mcp/tools/helpers.js +243 -0
- package/dist/src/mcp/tools/index.d.ts +19 -0
- package/dist/src/mcp/tools/index.js +54 -0
- package/dist/src/mcp/tools/input-tools.d.ts +2 -0
- package/dist/src/mcp/tools/input-tools.js +66 -0
- package/dist/src/mcp/tools/keyboard-tools.d.ts +2 -0
- package/dist/src/mcp/tools/keyboard-tools.js +35 -0
- package/dist/src/mcp/tools/screen-tools.d.ts +2 -0
- package/dist/src/mcp/tools/screen-tools.js +69 -0
- package/dist/src/mcp/tools.d.ts +9 -0
- package/dist/src/mcp/tools.js +96 -25
- package/dist/src/platform/base.d.ts +3 -0
- package/dist/src/platform/jxa-helpers.d.ts +11 -0
- package/dist/src/platform/jxa-helpers.js +206 -0
- package/dist/src/platform/macos/ax-tree.d.ts +4 -0
- package/dist/src/platform/macos/ax-tree.js +462 -0
- package/dist/src/platform/macos/base.d.ts +57 -0
- package/dist/src/platform/macos/base.js +92 -0
- package/dist/src/platform/macos/clipboard.d.ts +3 -0
- package/dist/src/platform/macos/clipboard.js +20 -0
- package/dist/src/platform/macos/element.d.ts +4 -0
- package/dist/src/platform/macos/element.js +212 -0
- package/dist/src/platform/macos/focus.d.ts +3 -0
- package/dist/src/platform/macos/focus.js +33 -0
- package/dist/src/platform/macos/helpers.d.ts +35 -0
- package/dist/src/platform/macos/helpers.js +54 -0
- package/dist/src/platform/macos/index.d.ts +2 -0
- package/dist/src/platform/macos/index.js +1 -0
- package/dist/src/platform/macos/input.d.ts +9 -0
- package/dist/src/platform/macos/input.js +62 -0
- package/dist/src/platform/macos/screen.d.ts +7 -0
- package/dist/src/platform/macos/screen.js +197 -0
- package/dist/src/platform/macos/window.d.ts +6 -0
- package/dist/src/platform/macos/window.js +251 -0
- package/dist/src/platform/macos.d.ts +1 -0
- package/dist/src/platform/macos.js +114 -583
- package/dist/src/safety/guard.js +1 -1
- package/dist/src/util/errors.d.ts +7 -2
- package/dist/src/util/errors.js +7 -3
- package/native/cgevent/cgevent-helper +0 -0
- package/native/ocr/ocr-helper +0 -0
- package/native/windowlist/windowlist-helper +0 -0
- package/package.json +1 -1
|
@@ -4,9 +4,11 @@ import { promisify } from "node:util";
|
|
|
4
4
|
import { captureFullScreen, captureRegion } from "../utils/screenshot.js";
|
|
5
5
|
import { click as inputClick, doubleClick as inputDoubleClick, move as inputMove, drag as inputDrag, scroll as inputScroll, typeText, pressShortcut } from "../utils/input.js";
|
|
6
6
|
import { CaptureError, ElementNotFoundError, InputSynthesisError, PermissionError, PlatformError, TargetStaleError, UcuError, WindowNotFoundError } from "../util/errors.js";
|
|
7
|
+
import { logger } from "../util/logger.js";
|
|
7
8
|
import { existsSync } from "node:fs";
|
|
8
9
|
import { join, dirname } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { jxaChildElements, jxaGetBounds, jxaIsVisible, jxaElementActionHelpers } from "./jxa-helpers.js";
|
|
10
12
|
const __macosDirname = dirname(fileURLToPath(import.meta.url));
|
|
11
13
|
const execFileAsync = promisify(execFile);
|
|
12
14
|
function errorMessage(error) {
|
|
@@ -69,6 +71,7 @@ export class MacOSPlatform {
|
|
|
69
71
|
elementCacheMaxSize = 100;
|
|
70
72
|
windowCacheTtlMs = 300;
|
|
71
73
|
windowCache;
|
|
74
|
+
windowCacheInFlight = false;
|
|
72
75
|
activeTarget;
|
|
73
76
|
savedFocus;
|
|
74
77
|
constructor(options) {
|
|
@@ -114,8 +117,12 @@ export class MacOSPlatform {
|
|
|
114
117
|
return;
|
|
115
118
|
this.windowCache = undefined; // Bypass cache — stale detection must use fresh data
|
|
116
119
|
const windows = await this.listWindows(true);
|
|
117
|
-
const
|
|
118
|
-
if (!
|
|
120
|
+
const match = windows.find(w => w.id === this.activeTarget.windowId);
|
|
121
|
+
if (!match) {
|
|
122
|
+
throw new TargetStaleError(this.activeTarget.windowId);
|
|
123
|
+
}
|
|
124
|
+
// Also invalidate if pid changed (app restarted)
|
|
125
|
+
if (match.pid !== this.activeTarget.pid) {
|
|
119
126
|
throw new TargetStaleError(this.activeTarget.windowId);
|
|
120
127
|
}
|
|
121
128
|
}
|
|
@@ -144,8 +151,9 @@ export class MacOSPlatform {
|
|
|
144
151
|
return;
|
|
145
152
|
try {
|
|
146
153
|
const { appName } = this.savedFocus;
|
|
154
|
+
const appNameLiteral = JSON.stringify(appName);
|
|
147
155
|
execFileSync("osascript", [
|
|
148
|
-
"-e", `tell application
|
|
156
|
+
"-e", `tell application ${appNameLiteral} to activate`,
|
|
149
157
|
], { timeout: 5000 });
|
|
150
158
|
}
|
|
151
159
|
catch {
|
|
@@ -190,8 +198,9 @@ export class MacOSPlatform {
|
|
|
190
198
|
], { encoding: "utf-8", timeout: 5000 }).trim();
|
|
191
199
|
return JSON.parse(out);
|
|
192
200
|
}
|
|
193
|
-
catch {
|
|
194
|
-
|
|
201
|
+
catch (error) {
|
|
202
|
+
logger.warn("getScreenSize failed, using fallback", { error: errorMessage(error) });
|
|
203
|
+
return { width: 1920, height: 1080, scaleFactor: 2, estimated: true };
|
|
195
204
|
}
|
|
196
205
|
}
|
|
197
206
|
isScreenLocked() {
|
|
@@ -203,7 +212,8 @@ export class MacOSPlatform {
|
|
|
203
212
|
return /"IOConsoleLocked"\s*=\s*Yes/.test(out);
|
|
204
213
|
}
|
|
205
214
|
catch {
|
|
206
|
-
|
|
215
|
+
// Fail-closed: if we can't determine lock state, assume locked
|
|
216
|
+
return true;
|
|
207
217
|
}
|
|
208
218
|
}
|
|
209
219
|
// ── Window Management ───────────────────────────────────────────────────
|
|
@@ -230,22 +240,28 @@ export class MacOSPlatform {
|
|
|
230
240
|
}
|
|
231
241
|
JSON.stringify(result);
|
|
232
242
|
`;
|
|
233
|
-
const out = execFileSync("osascript", [
|
|
234
|
-
"-l", "JavaScript",
|
|
235
|
-
"-e", jxaScript,
|
|
236
|
-
], { encoding: "utf-8", timeout: 10000 }).trim();
|
|
237
|
-
return JSON.parse(out);
|
|
238
|
-
}
|
|
239
|
-
async focusApp(app) {
|
|
240
|
-
const escapedApp = app.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
241
|
-
this.windowCache = undefined;
|
|
242
243
|
try {
|
|
243
|
-
execFileSync("osascript", [
|
|
244
|
+
const out = execFileSync("osascript", [
|
|
245
|
+
"-l", "JavaScript",
|
|
246
|
+
"-e", jxaScript,
|
|
247
|
+
], { encoding: "utf-8", timeout: 10000 }).trim();
|
|
248
|
+
return JSON.parse(out);
|
|
244
249
|
}
|
|
245
|
-
catch {
|
|
246
|
-
|
|
247
|
-
// Continue with the AX window lookup below so existing callers still work.
|
|
250
|
+
catch (error) {
|
|
251
|
+
rethrowAccessibilityError(error, "list_apps");
|
|
248
252
|
}
|
|
253
|
+
}
|
|
254
|
+
async focusApp(app) {
|
|
255
|
+
const appLiteral = JSON.stringify(app);
|
|
256
|
+
this.windowCache = undefined;
|
|
257
|
+
// NOTE: We intentionally do NOT call AppleScript "activate" here.
|
|
258
|
+
// focus_app sets the internal target context so subsequent operations
|
|
259
|
+
// know which app/window to target. It does NOT bring the app to the
|
|
260
|
+
// foreground — the user should remain in their current app (terminal,
|
|
261
|
+
// Codex, etc.) while the agent works in the background.
|
|
262
|
+
// CGEvent input injection works at the HID level and doesn't require
|
|
263
|
+
// the target app to be frontmost. AX operations target processes by
|
|
264
|
+
// name/PID via System Events, also without needing frontmost status.
|
|
249
265
|
let target;
|
|
250
266
|
const deadline = Date.now() + 3000;
|
|
251
267
|
do {
|
|
@@ -262,16 +278,15 @@ export class MacOSPlatform {
|
|
|
262
278
|
// app name so the tool handler can surface a remediation hint. The
|
|
263
279
|
// bare WindowNotFoundError("CC Switch") was indistinguishable from
|
|
264
280
|
// "the app is not running", which led models to retry forever.
|
|
265
|
-
|
|
266
|
-
err
|
|
267
|
-
"list_windows returned no match for this app. If the app is running, " +
|
|
281
|
+
this.activeTarget = undefined; // Clear stale target on focus failure
|
|
282
|
+
const err = new WindowNotFoundError(app, { hint: "list_windows returned no match for this app. If the app is running, " +
|
|
268
283
|
"the most likely cause is that it is an Electron app whose AX tree is " +
|
|
269
284
|
"not exposed to System Events (System Settings > Privacy & Security > " +
|
|
270
285
|
"Accessibility must be granted to the Electron process itself, not just " +
|
|
271
286
|
"to the host terminal). Pixel-level workaround: call screenshot to " +
|
|
272
287
|
"capture the screen, then ocr to locate UI text and get its bounding " +
|
|
273
288
|
"box coordinates, then click(x, y) at those screen coordinates. " +
|
|
274
|
-
"Alternatively, modify the app's config file or database directly.";
|
|
289
|
+
"Alternatively, modify the app's config file or database directly." });
|
|
275
290
|
throw err;
|
|
276
291
|
}
|
|
277
292
|
this.activeTarget = {
|
|
@@ -301,10 +316,10 @@ export class MacOSPlatform {
|
|
|
301
316
|
].some((name) => normalized.includes(name));
|
|
302
317
|
if (!knownBrowser)
|
|
303
318
|
return undefined;
|
|
304
|
-
const
|
|
319
|
+
const appLiteral = JSON.stringify(appName);
|
|
305
320
|
const jxaScript = `
|
|
306
321
|
function run() {
|
|
307
|
-
var appName =
|
|
322
|
+
var appName = ${appLiteral};
|
|
308
323
|
try {
|
|
309
324
|
var app = Application(appName);
|
|
310
325
|
var url = "";
|
|
@@ -343,6 +358,12 @@ export class MacOSPlatform {
|
|
|
343
358
|
bounds: { ...window.bounds },
|
|
344
359
|
}));
|
|
345
360
|
}
|
|
361
|
+
// P0 #3: Prevent concurrent cache refreshes
|
|
362
|
+
if (this.windowCacheInFlight) {
|
|
363
|
+
// Another call is already refreshing; return stale or empty
|
|
364
|
+
return this.windowCache?.windows.map(w => ({ ...w, bounds: { ...w.bounds } })) ?? [];
|
|
365
|
+
}
|
|
366
|
+
this.windowCacheInFlight = true;
|
|
346
367
|
try {
|
|
347
368
|
// Try native Swift helper first (CGWindowListCopyWindowInfo, ~1ms).
|
|
348
369
|
// Falls back to JXA System Events if the helper is not available.
|
|
@@ -370,6 +391,9 @@ export class MacOSPlatform {
|
|
|
370
391
|
// Fallback: return empty list if both methods fail
|
|
371
392
|
return [];
|
|
372
393
|
}
|
|
394
|
+
finally {
|
|
395
|
+
this.windowCacheInFlight = false;
|
|
396
|
+
}
|
|
373
397
|
}
|
|
374
398
|
listWindowsNative() {
|
|
375
399
|
try {
|
|
@@ -452,11 +476,16 @@ export class MacOSPlatform {
|
|
|
452
476
|
}
|
|
453
477
|
JSON.stringify(result);
|
|
454
478
|
`;
|
|
455
|
-
|
|
456
|
-
"
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
479
|
+
try {
|
|
480
|
+
const jxaOut = execFileSync("osascript", [
|
|
481
|
+
"-l", "JavaScript",
|
|
482
|
+
"-e", jxaScript
|
|
483
|
+
], { encoding: "utf-8", timeout: 15000 });
|
|
484
|
+
return JSON.parse(jxaOut.trim());
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
rethrowAccessibilityError(error, "list_windows_jxa");
|
|
488
|
+
}
|
|
460
489
|
}
|
|
461
490
|
async getWindowState(windowId, depth, includeBounds = true) {
|
|
462
491
|
if (!windowId || windowId === this.activeTarget?.windowId) {
|
|
@@ -468,18 +497,14 @@ export class MacOSPlatform {
|
|
|
468
497
|
}
|
|
469
498
|
const maxDepth = Math.min(depth || 3, 10);
|
|
470
499
|
const maxElements = 50;
|
|
471
|
-
const
|
|
500
|
+
const windowIdLiteral = JSON.stringify(resolvedWindowId);
|
|
472
501
|
const targetWindow = (await this.listWindows(true)).find((w) => w.id === resolvedWindowId);
|
|
473
502
|
const targetJson = JSON.stringify(targetWindow ?? null);
|
|
474
503
|
try {
|
|
475
504
|
const jxaScript = `
|
|
476
505
|
ObjC.import('AppKit');
|
|
477
506
|
var se = Application('System Events');
|
|
478
|
-
|
|
479
|
-
try { return elem.uiElements(); } catch(e1) {
|
|
480
|
-
try { return elem.elements(); } catch(e2) { return []; }
|
|
481
|
-
}
|
|
482
|
-
}
|
|
507
|
+
${jxaChildElements()}
|
|
483
508
|
var result = {window: null, focusedElement: null, tree: null, error: null};
|
|
484
509
|
var target = ${targetJson};
|
|
485
510
|
var includeBounds = ${includeBounds ? "true" : "false"};
|
|
@@ -490,7 +515,7 @@ export class MacOSPlatform {
|
|
|
490
515
|
|
|
491
516
|
function windowMatches(win, proc) {
|
|
492
517
|
if (!target) {
|
|
493
|
-
try { return String(win.id()) === String(
|
|
518
|
+
try { return String(win.id()) === String(${windowIdLiteral}); } catch(e) { return false; }
|
|
494
519
|
}
|
|
495
520
|
try {
|
|
496
521
|
if (target.pid && proc.unixId && proc.unixId() !== target.pid) return false;
|
|
@@ -510,7 +535,7 @@ export class MacOSPlatform {
|
|
|
510
535
|
closeEnough(size[1], b.height, 24);
|
|
511
536
|
} catch(e) {}
|
|
512
537
|
|
|
513
|
-
try { return String(win.id()) === String(
|
|
538
|
+
try { return String(win.id()) === String(${windowIdLiteral}); } catch(e) {}
|
|
514
539
|
return false;
|
|
515
540
|
}
|
|
516
541
|
|
|
@@ -518,7 +543,7 @@ export class MacOSPlatform {
|
|
|
518
543
|
var foundProc = null;
|
|
519
544
|
|
|
520
545
|
// Fast path: resolve "ProcessName/winN" format directly
|
|
521
|
-
var idParts =
|
|
546
|
+
var idParts = ${windowIdLiteral}.split('/');
|
|
522
547
|
if (idParts.length >= 2 && idParts[0]) {
|
|
523
548
|
var procName = idParts[0];
|
|
524
549
|
var winIdx = 0;
|
|
@@ -559,7 +584,7 @@ export class MacOSPlatform {
|
|
|
559
584
|
var winPos = foundWin.position();
|
|
560
585
|
var winSize = foundWin.size();
|
|
561
586
|
result.window = {
|
|
562
|
-
id: String(
|
|
587
|
+
id: String(${windowIdLiteral}),
|
|
563
588
|
title: foundWin.name() || '',
|
|
564
589
|
processName: foundProc.name() || '',
|
|
565
590
|
pid: foundProc.unixId ? foundProc.unixId() : 0,
|
|
@@ -806,7 +831,7 @@ export class MacOSPlatform {
|
|
|
806
831
|
}
|
|
807
832
|
}
|
|
808
833
|
async ocrJxa(tmpPath, screenSize, scaleFactor, region, buf) {
|
|
809
|
-
const
|
|
834
|
+
const pathLiteral = JSON.stringify(tmpPath);
|
|
810
835
|
const jxaScript = `
|
|
811
836
|
function run() {
|
|
812
837
|
ObjC.import('Vision');
|
|
@@ -814,7 +839,7 @@ export class MacOSPlatform {
|
|
|
814
839
|
ObjC.import('Foundation');
|
|
815
840
|
var app = Application.currentApplication();
|
|
816
841
|
app.includeStandardAdditions = true;
|
|
817
|
-
var path =
|
|
842
|
+
var path = ${pathLiteral};
|
|
818
843
|
var url = $.NSURL.fileURLWithPath(path);
|
|
819
844
|
var image = $.NSImage.alloc.initWithContentsOfURL(url);
|
|
820
845
|
if (!image || !image.isValid) {
|
|
@@ -898,10 +923,10 @@ export class MacOSPlatform {
|
|
|
898
923
|
const effectiveApp = app || this.activeTarget?.appName;
|
|
899
924
|
const maxDepth = Math.min(depth || 5, 10);
|
|
900
925
|
const maxResults = Math.min(Math.max(options.maxResults ?? 50, 1), 200);
|
|
901
|
-
const
|
|
902
|
-
const
|
|
903
|
-
const
|
|
904
|
-
const
|
|
926
|
+
const appLiteral = JSON.stringify(effectiveApp || "");
|
|
927
|
+
const textLiteral = text ? JSON.stringify(text) : "null";
|
|
928
|
+
const roleLiteral = role ? JSON.stringify(role) : "null";
|
|
929
|
+
const valueLiteral = value ? JSON.stringify(value) : "null";
|
|
905
930
|
// Pre-compile regex on TS side to validate syntax before passing to JXA
|
|
906
931
|
if (text && textMode === "regex") {
|
|
907
932
|
try {
|
|
@@ -925,11 +950,7 @@ export class MacOSPlatform {
|
|
|
925
950
|
const startTime = Date.now();
|
|
926
951
|
const jxaScript = `
|
|
927
952
|
var se = Application('System Events');
|
|
928
|
-
|
|
929
|
-
try { return elem.uiElements(); } catch(e1) {
|
|
930
|
-
try { return elem.elements(); } catch(e2) { return []; }
|
|
931
|
-
}
|
|
932
|
-
}
|
|
953
|
+
${jxaChildElements()}
|
|
933
954
|
var results = [];
|
|
934
955
|
var scannedCount = 0;
|
|
935
956
|
var matchedCount = 0;
|
|
@@ -937,22 +958,13 @@ export class MacOSPlatform {
|
|
|
937
958
|
var maxResults = ${maxResults};
|
|
938
959
|
var includeBounds = ${includeBounds ? "true" : "false"};
|
|
939
960
|
var visibleOnly = ${visibleOnly ? "true" : "false"};
|
|
940
|
-
var textMode =
|
|
961
|
+
var textMode = ${JSON.stringify(textMode)};
|
|
941
962
|
|
|
942
|
-
var textFilter = ${
|
|
943
|
-
var roleFilter = ${
|
|
944
|
-
var valueFilter = ${
|
|
963
|
+
var textFilter = ${textLiteral};
|
|
964
|
+
var roleFilter = ${roleLiteral};
|
|
965
|
+
var valueFilter = ${valueLiteral};
|
|
945
966
|
|
|
946
|
-
|
|
947
|
-
try {
|
|
948
|
-
var pos = elem.position();
|
|
949
|
-
var sz = elem.size();
|
|
950
|
-
if (!pos || !sz) return false;
|
|
951
|
-
return sz[0] > 0 && sz[1] > 0 && pos[0] > -10000 && pos[1] > -10000;
|
|
952
|
-
} catch(e) {
|
|
953
|
-
return false;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
967
|
+
${jxaIsVisible()}
|
|
956
968
|
|
|
957
969
|
// Shared filter helper. textMatches and valueMatches used to be near
|
|
958
970
|
// copies of the same three-branch dispatch (contains / exact / regex);
|
|
@@ -1019,15 +1031,7 @@ export class MacOSPlatform {
|
|
|
1019
1031
|
return true;
|
|
1020
1032
|
}
|
|
1021
1033
|
|
|
1022
|
-
|
|
1023
|
-
try {
|
|
1024
|
-
var pos = elem.position();
|
|
1025
|
-
var sz = elem.size();
|
|
1026
|
-
return {x: pos[0] || 0, y: pos[1] || 0, width: sz[0] || 0, height: sz[1] || 0};
|
|
1027
|
-
} catch(e) {
|
|
1028
|
-
return {x: 0, y: 0, width: 0, height: 0};
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1034
|
+
${jxaGetBounds()}
|
|
1031
1035
|
|
|
1032
1036
|
function traverse(elem, path, currentDepth) {
|
|
1033
1037
|
if (resultCount[0] >= maxResults) return;
|
|
@@ -1078,11 +1082,11 @@ export class MacOSPlatform {
|
|
|
1078
1082
|
}
|
|
1079
1083
|
|
|
1080
1084
|
try {
|
|
1081
|
-
if (
|
|
1082
|
-
var proc = se.processes[
|
|
1085
|
+
if (${appLiteral}) {
|
|
1086
|
+
var proc = se.processes[${appLiteral}]();
|
|
1083
1087
|
var wins = proc.windows();
|
|
1084
1088
|
for (var w = 0; w < wins.length && resultCount[0] < maxResults; w++) {
|
|
1085
|
-
traverse(wins[w],
|
|
1089
|
+
traverse(wins[w], ${appLiteral} + "/win" + w, 0);
|
|
1086
1090
|
}
|
|
1087
1091
|
} else {
|
|
1088
1092
|
var procs = se.processes();
|
|
@@ -1167,9 +1171,9 @@ export class MacOSPlatform {
|
|
|
1167
1171
|
}
|
|
1168
1172
|
async clickElement(elementId, app) {
|
|
1169
1173
|
this.evictExpiredCacheEntries();
|
|
1170
|
-
const
|
|
1174
|
+
const elementIdLiteral = JSON.stringify(elementId);
|
|
1171
1175
|
const effectiveApp = app || this.activeTarget?.appName;
|
|
1172
|
-
const
|
|
1176
|
+
const appLiteral = JSON.stringify(effectiveApp || "");
|
|
1173
1177
|
const cached = this.elementCache.get(elementId);
|
|
1174
1178
|
if (cached && this.isCacheEntryExpired(cached)) {
|
|
1175
1179
|
this.elementCache.delete(elementId);
|
|
@@ -1177,173 +1181,13 @@ export class MacOSPlatform {
|
|
|
1177
1181
|
const cachedJson = JSON.stringify(this.elementCache.get(elementId) ?? null);
|
|
1178
1182
|
const jxaScript = `
|
|
1179
1183
|
var se = Application('System Events');
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
var elemPath = "${escapedElementId}";
|
|
1186
|
-
var appName = "${escapedApp}";
|
|
1184
|
+
var _result = null;
|
|
1185
|
+
${jxaElementActionHelpers()}
|
|
1186
|
+
var elemPath = ${elementIdLiteral};
|
|
1187
|
+
var appName = ${appLiteral};
|
|
1187
1188
|
var cached = ${cachedJson};
|
|
1188
1189
|
|
|
1189
|
-
|
|
1190
|
-
var parts = path.split('/');
|
|
1191
|
-
if (parts.length < 2) return null;
|
|
1192
|
-
|
|
1193
|
-
var procName = parts[0];
|
|
1194
|
-
var winPart = parts[1];
|
|
1195
|
-
var winIdx = 0;
|
|
1196
|
-
var match = winPart.match(/^win(\\\\d+)$/);
|
|
1197
|
-
if (match) {
|
|
1198
|
-
winIdx = parseInt(match[1]);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
try {
|
|
1202
|
-
var proc = se.processes[procName]();
|
|
1203
|
-
var wins = proc.windows();
|
|
1204
|
-
if (winIdx >= wins.length) return null;
|
|
1205
|
-
var current = wins[winIdx];
|
|
1206
|
-
|
|
1207
|
-
for (var i = 2; i < parts.length; i++) {
|
|
1208
|
-
var idx = parseInt(parts[i]);
|
|
1209
|
-
if (isNaN(idx)) return null;
|
|
1210
|
-
try {
|
|
1211
|
-
var kids = childElements(current);
|
|
1212
|
-
if (idx >= kids.length) return null;
|
|
1213
|
-
current = kids[idx];
|
|
1214
|
-
} catch(e) { return null; }
|
|
1215
|
-
}
|
|
1216
|
-
return current;
|
|
1217
|
-
} catch(e) { return null; }
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function elemString(elem, getter) {
|
|
1221
|
-
try {
|
|
1222
|
-
var value = getter(elem);
|
|
1223
|
-
return value === undefined || value === null ? '' : String(value);
|
|
1224
|
-
} catch(e) {
|
|
1225
|
-
return '';
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
function getBounds(elem) {
|
|
1230
|
-
try {
|
|
1231
|
-
var pos = elem.position();
|
|
1232
|
-
var sz = elem.size();
|
|
1233
|
-
return {x: pos[0] || 0, y: pos[1] || 0, width: sz[0] || 0, height: sz[1] || 0};
|
|
1234
|
-
} catch(e) {
|
|
1235
|
-
return {x: 0, y: 0, width: 0, height: 0};
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
function descriptorMatches(elem) {
|
|
1240
|
-
if (!cached) return true;
|
|
1241
|
-
var role = elemString(elem, function(e) { return e.role(); });
|
|
1242
|
-
var name = elemString(elem, function(e) { return e.name(); });
|
|
1243
|
-
var desc = elemString(elem, function(e) { return e.description(); });
|
|
1244
|
-
var value = elemString(elem, function(e) { return e.value(); });
|
|
1245
|
-
if (cached.role && role && role !== cached.role) return false;
|
|
1246
|
-
if (cached.name && name && name !== cached.name) return false;
|
|
1247
|
-
if (cached.value && value && value !== cached.value) return false;
|
|
1248
|
-
if (cached.description && desc && desc !== cached.description) return false;
|
|
1249
|
-
return true;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
function scoreEquivalent(elem) {
|
|
1253
|
-
if (!cached) return -1;
|
|
1254
|
-
var score = 0;
|
|
1255
|
-
var role = elemString(elem, function(e) { return e.role(); });
|
|
1256
|
-
var name = elemString(elem, function(e) { return e.name(); });
|
|
1257
|
-
var desc = elemString(elem, function(e) { return e.description(); });
|
|
1258
|
-
var value = elemString(elem, function(e) { return e.value(); });
|
|
1259
|
-
var subrole = elemString(elem, function(e) { return e.subrole(); });
|
|
1260
|
-
var identifier = elemString(elem, function(e) { return e.identifier(); });
|
|
1261
|
-
if (cached.role && role === cached.role) score += 4;
|
|
1262
|
-
if (cached.name && name === cached.name) score += 4;
|
|
1263
|
-
if (cached.value && value === cached.value) score += 3;
|
|
1264
|
-
if (cached.description && desc === cached.description) score += 2;
|
|
1265
|
-
if (cached.subrole && subrole === cached.subrole) score += 2;
|
|
1266
|
-
if (cached.identifier && identifier === cached.identifier) score += 3;
|
|
1267
|
-
var b = getBounds(elem);
|
|
1268
|
-
if (cached.bounds) {
|
|
1269
|
-
var cx = b.x + b.width / 2;
|
|
1270
|
-
var cy = b.y + b.height / 2;
|
|
1271
|
-
var ocx = cached.bounds.x + cached.bounds.width / 2;
|
|
1272
|
-
var ocy = cached.bounds.y + cached.bounds.height / 2;
|
|
1273
|
-
var distance = Math.sqrt(Math.pow(cx - ocx, 2) + Math.pow(cy - ocy, 2));
|
|
1274
|
-
if (distance < 8) score += 4;
|
|
1275
|
-
else if (distance < 40) score += 2;
|
|
1276
|
-
else if (distance < 120) score += 1;
|
|
1277
|
-
}
|
|
1278
|
-
return score;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
function refetchEquivalent() {
|
|
1282
|
-
if (!cached) return null;
|
|
1283
|
-
var targetApp = appName || cached.appName || '';
|
|
1284
|
-
var best = null;
|
|
1285
|
-
var bestScore = 0;
|
|
1286
|
-
var visited = [0];
|
|
1287
|
-
function visit(elem, depth) {
|
|
1288
|
-
if (visited[0] > 350 || depth > 10) return;
|
|
1289
|
-
visited[0]++;
|
|
1290
|
-
var score = scoreEquivalent(elem);
|
|
1291
|
-
if (score > bestScore) {
|
|
1292
|
-
best = elem;
|
|
1293
|
-
bestScore = score;
|
|
1294
|
-
}
|
|
1295
|
-
try {
|
|
1296
|
-
var kids = childElements(elem);
|
|
1297
|
-
for (var i = 0; i < kids.length; i++) visit(kids[i], depth + 1);
|
|
1298
|
-
} catch(e) {}
|
|
1299
|
-
}
|
|
1300
|
-
try {
|
|
1301
|
-
if (targetApp) {
|
|
1302
|
-
var proc = se.processes[targetApp]();
|
|
1303
|
-
var wins = proc.windows();
|
|
1304
|
-
for (var w = 0; w < wins.length; w++) visit(wins[w], 0);
|
|
1305
|
-
} else {
|
|
1306
|
-
var procs = se.processes();
|
|
1307
|
-
for (var p = 0; p < procs.length; p++) {
|
|
1308
|
-
try {
|
|
1309
|
-
var wins2 = procs[p].windows();
|
|
1310
|
-
for (var w2 = 0; w2 < wins2.length; w2++) visit(wins2[w2], 0);
|
|
1311
|
-
} catch(e2) {}
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
} catch(e) {}
|
|
1315
|
-
return bestScore >= 6 ? best : null;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
var elem = null;
|
|
1319
|
-
|
|
1320
|
-
if (appName) {
|
|
1321
|
-
try {
|
|
1322
|
-
var proc = se.processes[appName]();
|
|
1323
|
-
var wins = proc.windows();
|
|
1324
|
-
var parts = elemPath.split('/');
|
|
1325
|
-
var winIdx = 0;
|
|
1326
|
-
var match = parts[0].match(/^win(\\\\d+)$/);
|
|
1327
|
-
if (match) winIdx = parseInt(match[1]);
|
|
1328
|
-
if (winIdx < wins.length) {
|
|
1329
|
-
var current = wins[winIdx];
|
|
1330
|
-
for (var i = 1; i < parts.length; i++) {
|
|
1331
|
-
var idx = parseInt(parts[i]);
|
|
1332
|
-
if (isNaN(idx)) break;
|
|
1333
|
-
try {
|
|
1334
|
-
var kids = childElements(current);
|
|
1335
|
-
if (idx >= kids.length) break;
|
|
1336
|
-
current = kids[idx];
|
|
1337
|
-
} catch(e) { break; }
|
|
1338
|
-
}
|
|
1339
|
-
elem = current;
|
|
1340
|
-
}
|
|
1341
|
-
} catch(e) {}
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
if (!elem) {
|
|
1345
|
-
elem = resolveElementByFullPath(elemPath);
|
|
1346
|
-
}
|
|
1190
|
+
var elem = resolveElementInApp(elemPath, appName) || resolveElementByFullPath(elemPath);
|
|
1347
1191
|
|
|
1348
1192
|
if (elem && !descriptorMatches(elem)) {
|
|
1349
1193
|
elem = refetchEquivalent() || elem;
|
|
@@ -1354,11 +1198,11 @@ export class MacOSPlatform {
|
|
|
1354
1198
|
}
|
|
1355
1199
|
|
|
1356
1200
|
if (!elem) {
|
|
1357
|
-
|
|
1201
|
+
_result = {success: false, error: "Element not found: " + elemPath};
|
|
1358
1202
|
} else {
|
|
1359
1203
|
try {
|
|
1360
1204
|
elem.actions.AXPress.perform();
|
|
1361
|
-
|
|
1205
|
+
_result = {success: true};
|
|
1362
1206
|
} catch(e) {
|
|
1363
1207
|
try {
|
|
1364
1208
|
var pos = elem.position();
|
|
@@ -1372,12 +1216,13 @@ export class MacOSPlatform {
|
|
|
1372
1216
|
$.CGEventPost($.kCGHIDEventTap, down);
|
|
1373
1217
|
var up = $.CGEventCreateMouseEvent(src, $.kCGEventLeftMouseUp, pt, $.kCGMouseButtonLeft);
|
|
1374
1218
|
$.CGEventPost($.kCGHIDEventTap, up);
|
|
1375
|
-
|
|
1219
|
+
_result = {success: true};
|
|
1376
1220
|
} catch(e2) {
|
|
1377
|
-
|
|
1221
|
+
_result = {success: false, error: "Could not click element: " + String(e2.message || e2)};
|
|
1378
1222
|
}
|
|
1379
1223
|
}
|
|
1380
1224
|
}
|
|
1225
|
+
JSON.stringify(_result);
|
|
1381
1226
|
`;
|
|
1382
1227
|
try {
|
|
1383
1228
|
const out = execFileSync("osascript", [
|
|
@@ -1397,10 +1242,10 @@ export class MacOSPlatform {
|
|
|
1397
1242
|
}
|
|
1398
1243
|
async typeInElement(elementId, text, app, clearFirst) {
|
|
1399
1244
|
this.evictExpiredCacheEntries();
|
|
1400
|
-
const
|
|
1245
|
+
const textLiteral = JSON.stringify(text);
|
|
1401
1246
|
const effectiveApp = app || this.activeTarget?.appName;
|
|
1402
|
-
const
|
|
1403
|
-
const
|
|
1247
|
+
const appLiteral = JSON.stringify(effectiveApp || "");
|
|
1248
|
+
const elementIdLiteral = JSON.stringify(elementId);
|
|
1404
1249
|
const cached = this.elementCache.get(elementId);
|
|
1405
1250
|
if (cached && this.isCacheEntryExpired(cached)) {
|
|
1406
1251
|
this.elementCache.delete(elementId);
|
|
@@ -1408,175 +1253,15 @@ export class MacOSPlatform {
|
|
|
1408
1253
|
const cachedJson = JSON.stringify(this.elementCache.get(elementId) ?? null);
|
|
1409
1254
|
const jxaScript = `
|
|
1410
1255
|
var se = Application('System Events');
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
}
|
|
1416
|
-
var elemPath = "${escapedElementId}";
|
|
1417
|
-
var appName = "${escapedApp}";
|
|
1418
|
-
var textToType = "${escapedText}";
|
|
1256
|
+
var _result = null;
|
|
1257
|
+
${jxaElementActionHelpers()}
|
|
1258
|
+
var elemPath = ${elementIdLiteral};
|
|
1259
|
+
var appName = ${appLiteral};
|
|
1260
|
+
var textToType = ${textLiteral};
|
|
1419
1261
|
var shouldClear = ${clearFirst ? "true" : "false"};
|
|
1420
1262
|
var cached = ${cachedJson};
|
|
1421
1263
|
|
|
1422
|
-
|
|
1423
|
-
var parts = path.split('/');
|
|
1424
|
-
if (parts.length < 2) return null;
|
|
1425
|
-
|
|
1426
|
-
var procName = parts[0];
|
|
1427
|
-
var winPart = parts[1];
|
|
1428
|
-
var winIdx = 0;
|
|
1429
|
-
var match = winPart.match(/^win(\\\\d+)$/);
|
|
1430
|
-
if (match) {
|
|
1431
|
-
winIdx = parseInt(match[1]);
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
try {
|
|
1435
|
-
var proc = se.processes[procName]();
|
|
1436
|
-
var wins = proc.windows();
|
|
1437
|
-
if (winIdx >= wins.length) return null;
|
|
1438
|
-
var current = wins[winIdx];
|
|
1439
|
-
|
|
1440
|
-
for (var i = 2; i < parts.length; i++) {
|
|
1441
|
-
var idx = parseInt(parts[i]);
|
|
1442
|
-
if (isNaN(idx)) return null;
|
|
1443
|
-
try {
|
|
1444
|
-
var kids = childElements(current);
|
|
1445
|
-
if (idx >= kids.length) return null;
|
|
1446
|
-
current = kids[idx];
|
|
1447
|
-
} catch(e) { return null; }
|
|
1448
|
-
}
|
|
1449
|
-
return current;
|
|
1450
|
-
} catch(e) { return null; }
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
function elemString(elem, getter) {
|
|
1454
|
-
try {
|
|
1455
|
-
var value = getter(elem);
|
|
1456
|
-
return value === undefined || value === null ? '' : String(value);
|
|
1457
|
-
} catch(e) {
|
|
1458
|
-
return '';
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
function getBounds(elem) {
|
|
1463
|
-
try {
|
|
1464
|
-
var pos = elem.position();
|
|
1465
|
-
var sz = elem.size();
|
|
1466
|
-
return {x: pos[0] || 0, y: pos[1] || 0, width: sz[0] || 0, height: sz[1] || 0};
|
|
1467
|
-
} catch(e) {
|
|
1468
|
-
return {x: 0, y: 0, width: 0, height: 0};
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
function descriptorMatches(elem) {
|
|
1473
|
-
if (!cached) return true;
|
|
1474
|
-
var role = elemString(elem, function(e) { return e.role(); });
|
|
1475
|
-
var name = elemString(elem, function(e) { return e.name(); });
|
|
1476
|
-
var desc = elemString(elem, function(e) { return e.description(); });
|
|
1477
|
-
var value = elemString(elem, function(e) { return e.value(); });
|
|
1478
|
-
if (cached.role && role && role !== cached.role) return false;
|
|
1479
|
-
if (cached.name && name && name !== cached.name) return false;
|
|
1480
|
-
if (cached.value && value && value !== cached.value) return false;
|
|
1481
|
-
if (cached.description && desc && desc !== cached.description) return false;
|
|
1482
|
-
return true;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
function scoreEquivalent(elem) {
|
|
1486
|
-
if (!cached) return -1;
|
|
1487
|
-
var score = 0;
|
|
1488
|
-
var role = elemString(elem, function(e) { return e.role(); });
|
|
1489
|
-
var name = elemString(elem, function(e) { return e.name(); });
|
|
1490
|
-
var desc = elemString(elem, function(e) { return e.description(); });
|
|
1491
|
-
var value = elemString(elem, function(e) { return e.value(); });
|
|
1492
|
-
var subrole = elemString(elem, function(e) { return e.subrole(); });
|
|
1493
|
-
var identifier = elemString(elem, function(e) { return e.identifier(); });
|
|
1494
|
-
if (cached.role && role === cached.role) score += 4;
|
|
1495
|
-
if (cached.name && name === cached.name) score += 4;
|
|
1496
|
-
if (cached.value && value === cached.value) score += 3;
|
|
1497
|
-
if (cached.description && desc === cached.description) score += 2;
|
|
1498
|
-
if (cached.subrole && subrole === cached.subrole) score += 2;
|
|
1499
|
-
if (cached.identifier && identifier === cached.identifier) score += 3;
|
|
1500
|
-
var b = getBounds(elem);
|
|
1501
|
-
if (cached.bounds) {
|
|
1502
|
-
var cx = b.x + b.width / 2;
|
|
1503
|
-
var cy = b.y + b.height / 2;
|
|
1504
|
-
var ocx = cached.bounds.x + cached.bounds.width / 2;
|
|
1505
|
-
var ocy = cached.bounds.y + cached.bounds.height / 2;
|
|
1506
|
-
var distance = Math.sqrt(Math.pow(cx - ocx, 2) + Math.pow(cy - ocy, 2));
|
|
1507
|
-
if (distance < 8) score += 4;
|
|
1508
|
-
else if (distance < 40) score += 2;
|
|
1509
|
-
else if (distance < 120) score += 1;
|
|
1510
|
-
}
|
|
1511
|
-
return score;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
function refetchEquivalent() {
|
|
1515
|
-
if (!cached) return null;
|
|
1516
|
-
var targetApp = appName || cached.appName || '';
|
|
1517
|
-
var best = null;
|
|
1518
|
-
var bestScore = 0;
|
|
1519
|
-
var visited = [0];
|
|
1520
|
-
function visit(elem, depth) {
|
|
1521
|
-
if (visited[0] > 350 || depth > 10) return;
|
|
1522
|
-
visited[0]++;
|
|
1523
|
-
var score = scoreEquivalent(elem);
|
|
1524
|
-
if (score > bestScore) {
|
|
1525
|
-
best = elem;
|
|
1526
|
-
bestScore = score;
|
|
1527
|
-
}
|
|
1528
|
-
try {
|
|
1529
|
-
var kids = childElements(elem);
|
|
1530
|
-
for (var i = 0; i < kids.length; i++) visit(kids[i], depth + 1);
|
|
1531
|
-
} catch(e) {}
|
|
1532
|
-
}
|
|
1533
|
-
try {
|
|
1534
|
-
if (targetApp) {
|
|
1535
|
-
var proc = se.processes[targetApp]();
|
|
1536
|
-
var wins = proc.windows();
|
|
1537
|
-
for (var w = 0; w < wins.length; w++) visit(wins[w], 0);
|
|
1538
|
-
} else {
|
|
1539
|
-
var procs = se.processes();
|
|
1540
|
-
for (var p = 0; p < procs.length; p++) {
|
|
1541
|
-
try {
|
|
1542
|
-
var wins2 = procs[p].windows();
|
|
1543
|
-
for (var w2 = 0; w2 < wins2.length; w2++) visit(wins2[w2], 0);
|
|
1544
|
-
} catch(e2) {}
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
} catch(e) {}
|
|
1548
|
-
return bestScore >= 6 ? best : null;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
var elem = null;
|
|
1552
|
-
|
|
1553
|
-
if (appName) {
|
|
1554
|
-
try {
|
|
1555
|
-
var proc = se.processes[appName]();
|
|
1556
|
-
var wins = proc.windows();
|
|
1557
|
-
var parts = elemPath.split('/');
|
|
1558
|
-
var winIdx = 0;
|
|
1559
|
-
var match = parts[0].match(/^win(\\\\d+)$/);
|
|
1560
|
-
if (match) winIdx = parseInt(match[1]);
|
|
1561
|
-
if (winIdx < wins.length) {
|
|
1562
|
-
var current = wins[winIdx];
|
|
1563
|
-
for (var i = 1; i < parts.length; i++) {
|
|
1564
|
-
var idx = parseInt(parts[i]);
|
|
1565
|
-
if (isNaN(idx)) break;
|
|
1566
|
-
try {
|
|
1567
|
-
var kids = childElements(current);
|
|
1568
|
-
if (idx >= kids.length) break;
|
|
1569
|
-
current = kids[idx];
|
|
1570
|
-
} catch(e) { break; }
|
|
1571
|
-
}
|
|
1572
|
-
elem = current;
|
|
1573
|
-
}
|
|
1574
|
-
} catch(e) {}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
if (!elem) {
|
|
1578
|
-
elem = resolveElementByFullPath(elemPath);
|
|
1579
|
-
}
|
|
1264
|
+
var elem = resolveElementInApp(elemPath, appName) || resolveElementByFullPath(elemPath);
|
|
1580
1265
|
|
|
1581
1266
|
if (elem && !descriptorMatches(elem)) {
|
|
1582
1267
|
elem = refetchEquivalent() || elem;
|
|
@@ -1587,7 +1272,7 @@ export class MacOSPlatform {
|
|
|
1587
1272
|
}
|
|
1588
1273
|
|
|
1589
1274
|
if (!elem) {
|
|
1590
|
-
|
|
1275
|
+
_result = {success: false, error: "Element not found: " + elemPath};
|
|
1591
1276
|
} else {
|
|
1592
1277
|
try {
|
|
1593
1278
|
elem.focused = true;
|
|
@@ -1614,13 +1299,15 @@ export class MacOSPlatform {
|
|
|
1614
1299
|
if (!didSet) {
|
|
1615
1300
|
try {
|
|
1616
1301
|
se.keystroke(textToType);
|
|
1302
|
+
_result = {success: true};
|
|
1617
1303
|
} catch(e) {
|
|
1618
|
-
|
|
1304
|
+
_result = {success: false, error: "Could not type into element: " + String(e.message || e)};
|
|
1619
1305
|
}
|
|
1306
|
+
} else {
|
|
1307
|
+
_result = {success: true};
|
|
1620
1308
|
}
|
|
1621
|
-
|
|
1622
|
-
JSON.stringify({success: true});
|
|
1623
1309
|
}
|
|
1310
|
+
JSON.stringify(_result);
|
|
1624
1311
|
`;
|
|
1625
1312
|
try {
|
|
1626
1313
|
const out = execFileSync("osascript", [
|
|
@@ -1669,170 +1356,13 @@ export class MacOSPlatform {
|
|
|
1669
1356
|
const cachedJson = JSON.stringify(this.elementCache.get(elementId) ?? null);
|
|
1670
1357
|
const jxaScript = `
|
|
1671
1358
|
var se = Application('System Events');
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
try { return elem.elements(); } catch(e2) { return []; }
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1359
|
+
var _result = null;
|
|
1360
|
+
${jxaElementActionHelpers()}
|
|
1677
1361
|
var elemPath = ${elementIdLiteral};
|
|
1678
1362
|
var appName = ${appLiteral};
|
|
1679
1363
|
var valueToSet = ${valueLiteral};
|
|
1680
1364
|
var cached = ${cachedJson};
|
|
1681
1365
|
|
|
1682
|
-
function resolveElementByFullPath(path) {
|
|
1683
|
-
var parts = path.split('/');
|
|
1684
|
-
if (parts.length < 2) return null;
|
|
1685
|
-
|
|
1686
|
-
var procName = parts[0];
|
|
1687
|
-
var winPart = parts[1];
|
|
1688
|
-
var winIdx = 0;
|
|
1689
|
-
var match = winPart.match(/^win(\\\\d+)$/);
|
|
1690
|
-
if (match) winIdx = parseInt(match[1]);
|
|
1691
|
-
|
|
1692
|
-
try {
|
|
1693
|
-
var proc = se.processes[procName]();
|
|
1694
|
-
var wins = proc.windows();
|
|
1695
|
-
if (winIdx >= wins.length) return null;
|
|
1696
|
-
var current = wins[winIdx];
|
|
1697
|
-
|
|
1698
|
-
for (var i = 2; i < parts.length; i++) {
|
|
1699
|
-
var idx = parseInt(parts[i]);
|
|
1700
|
-
if (isNaN(idx)) return null;
|
|
1701
|
-
try {
|
|
1702
|
-
var kids = childElements(current);
|
|
1703
|
-
if (idx >= kids.length) return null;
|
|
1704
|
-
current = kids[idx];
|
|
1705
|
-
} catch(e) { return null; }
|
|
1706
|
-
}
|
|
1707
|
-
return current;
|
|
1708
|
-
} catch(e) { return null; }
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
function resolveElementInApp(path, targetApp) {
|
|
1712
|
-
if (!targetApp) return null;
|
|
1713
|
-
var parts = path.split('/');
|
|
1714
|
-
var start = parts[0] === targetApp ? 1 : 0;
|
|
1715
|
-
var winPart = parts[start] || 'win0';
|
|
1716
|
-
var winIdx = 0;
|
|
1717
|
-
var match = winPart.match(/^win(\\\\d+)$/);
|
|
1718
|
-
if (match) winIdx = parseInt(match[1]);
|
|
1719
|
-
|
|
1720
|
-
try {
|
|
1721
|
-
var proc = se.processes[targetApp]();
|
|
1722
|
-
var wins = proc.windows();
|
|
1723
|
-
if (winIdx >= wins.length) return null;
|
|
1724
|
-
var current = wins[winIdx];
|
|
1725
|
-
for (var i = start + 1; i < parts.length; i++) {
|
|
1726
|
-
var idx = parseInt(parts[i]);
|
|
1727
|
-
if (isNaN(idx)) return null;
|
|
1728
|
-
try {
|
|
1729
|
-
var kids = childElements(current);
|
|
1730
|
-
if (idx >= kids.length) return null;
|
|
1731
|
-
current = kids[idx];
|
|
1732
|
-
} catch(e) { return null; }
|
|
1733
|
-
}
|
|
1734
|
-
return current;
|
|
1735
|
-
} catch(e) { return null; }
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
function elemString(elem, getter) {
|
|
1739
|
-
try {
|
|
1740
|
-
var value = getter(elem);
|
|
1741
|
-
return value === undefined || value === null ? '' : String(value);
|
|
1742
|
-
} catch(e) {
|
|
1743
|
-
return '';
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
function getBounds(elem) {
|
|
1748
|
-
try {
|
|
1749
|
-
var pos = elem.position();
|
|
1750
|
-
var sz = elem.size();
|
|
1751
|
-
return {x: pos[0] || 0, y: pos[1] || 0, width: sz[0] || 0, height: sz[1] || 0};
|
|
1752
|
-
} catch(e) {
|
|
1753
|
-
return {x: 0, y: 0, width: 0, height: 0};
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function descriptorMatches(elem) {
|
|
1758
|
-
if (!cached) return true;
|
|
1759
|
-
var role = elemString(elem, function(e) { return e.role(); });
|
|
1760
|
-
var name = elemString(elem, function(e) { return e.name(); });
|
|
1761
|
-
var desc = elemString(elem, function(e) { return e.description(); });
|
|
1762
|
-
var value = elemString(elem, function(e) { return e.value(); });
|
|
1763
|
-
if (cached.role && role && role !== cached.role) return false;
|
|
1764
|
-
if (cached.name && name && name !== cached.name) return false;
|
|
1765
|
-
if (cached.value && value && value !== cached.value) return false;
|
|
1766
|
-
if (cached.description && desc && desc !== cached.description) return false;
|
|
1767
|
-
return true;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
function scoreEquivalent(elem) {
|
|
1771
|
-
if (!cached) return -1;
|
|
1772
|
-
var score = 0;
|
|
1773
|
-
var role = elemString(elem, function(e) { return e.role(); });
|
|
1774
|
-
var name = elemString(elem, function(e) { return e.name(); });
|
|
1775
|
-
var desc = elemString(elem, function(e) { return e.description(); });
|
|
1776
|
-
var value = elemString(elem, function(e) { return e.value(); });
|
|
1777
|
-
var subrole = elemString(elem, function(e) { return e.subrole(); });
|
|
1778
|
-
var identifier = elemString(elem, function(e) { return e.identifier(); });
|
|
1779
|
-
if (cached.role && role === cached.role) score += 4;
|
|
1780
|
-
if (cached.name && name === cached.name) score += 4;
|
|
1781
|
-
if (cached.value && value === cached.value) score += 3;
|
|
1782
|
-
if (cached.description && desc === cached.description) score += 2;
|
|
1783
|
-
if (cached.subrole && subrole === cached.subrole) score += 2;
|
|
1784
|
-
if (cached.identifier && identifier === cached.identifier) score += 3;
|
|
1785
|
-
var b = getBounds(elem);
|
|
1786
|
-
if (cached.bounds) {
|
|
1787
|
-
var cx = b.x + b.width / 2;
|
|
1788
|
-
var cy = b.y + b.height / 2;
|
|
1789
|
-
var ocx = cached.bounds.x + cached.bounds.width / 2;
|
|
1790
|
-
var ocy = cached.bounds.y + cached.bounds.height / 2;
|
|
1791
|
-
var distance = Math.sqrt(Math.pow(cx - ocx, 2) + Math.pow(cy - ocy, 2));
|
|
1792
|
-
if (distance < 8) score += 4;
|
|
1793
|
-
else if (distance < 40) score += 2;
|
|
1794
|
-
else if (distance < 120) score += 1;
|
|
1795
|
-
}
|
|
1796
|
-
return score;
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
function refetchEquivalent() {
|
|
1800
|
-
if (!cached) return null;
|
|
1801
|
-
var targetApp = appName || cached.appName || '';
|
|
1802
|
-
var best = null;
|
|
1803
|
-
var bestScore = 0;
|
|
1804
|
-
var visited = [0];
|
|
1805
|
-
function visit(elem, depth) {
|
|
1806
|
-
if (visited[0] > 350 || depth > 10) return;
|
|
1807
|
-
visited[0]++;
|
|
1808
|
-
var score = scoreEquivalent(elem);
|
|
1809
|
-
if (score > bestScore) {
|
|
1810
|
-
best = elem;
|
|
1811
|
-
bestScore = score;
|
|
1812
|
-
}
|
|
1813
|
-
try {
|
|
1814
|
-
var kids = childElements(elem);
|
|
1815
|
-
for (var i = 0; i < kids.length; i++) visit(kids[i], depth + 1);
|
|
1816
|
-
} catch(e) {}
|
|
1817
|
-
}
|
|
1818
|
-
try {
|
|
1819
|
-
if (targetApp) {
|
|
1820
|
-
var proc = se.processes[targetApp]();
|
|
1821
|
-
var wins = proc.windows();
|
|
1822
|
-
for (var w = 0; w < wins.length; w++) visit(wins[w], 0);
|
|
1823
|
-
} else {
|
|
1824
|
-
var procs = se.processes();
|
|
1825
|
-
for (var p = 0; p < procs.length; p++) {
|
|
1826
|
-
try {
|
|
1827
|
-
var wins2 = procs[p].windows();
|
|
1828
|
-
for (var w2 = 0; w2 < wins2.length; w2++) visit(wins2[w2], 0);
|
|
1829
|
-
} catch(e2) {}
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
} catch(e) {}
|
|
1833
|
-
return bestScore >= 6 ? best : null;
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
1366
|
var elem = resolveElementInApp(elemPath, appName) || resolveElementByFullPath(elemPath);
|
|
1837
1367
|
if (elem && !descriptorMatches(elem)) {
|
|
1838
1368
|
elem = refetchEquivalent() || elem;
|
|
@@ -1842,15 +1372,16 @@ export class MacOSPlatform {
|
|
|
1842
1372
|
}
|
|
1843
1373
|
|
|
1844
1374
|
if (!elem) {
|
|
1845
|
-
|
|
1375
|
+
_result = {success: false, error: "Element not found: " + elemPath};
|
|
1846
1376
|
} else {
|
|
1847
1377
|
try {
|
|
1848
1378
|
elem.value = valueToSet;
|
|
1849
|
-
|
|
1379
|
+
_result = {success: true};
|
|
1850
1380
|
} catch(e) {
|
|
1851
|
-
|
|
1381
|
+
_result = {success: false, error: "Could not set AX value: " + String(e.message || e)};
|
|
1852
1382
|
}
|
|
1853
1383
|
}
|
|
1384
|
+
JSON.stringify(_result);
|
|
1854
1385
|
`;
|
|
1855
1386
|
try {
|
|
1856
1387
|
const out = execFileSync("osascript", [
|