55 lines
2.2 KiB
Go
55 lines
2.2 KiB
Go
//go:build darwin
|
|
|
|
package main
|
|
|
|
import "errors"
|
|
|
|
// macOS: the AX tree through System Events, walked by a fixed JXA script
|
|
// (osascript -l JavaScript). Every property read is one Apple Event, so the
|
|
// node cap is what keeps a busy window inside the op's timeout. Needs the
|
|
// Accessibility permission; the same two error codes computer:windows knows.
|
|
|
|
const computerTreeKind = "system-events-jxa"
|
|
|
|
const darwinTreeScript = `
|
|
ObjC.import('stdlib');
|
|
const targetPid = parseInt($.getenv('PRIVATAE_TARGET_PID'), 10);
|
|
const maxNodes = parseInt($.getenv('PRIVATAE_MAX_NODES'), 10);
|
|
const se = Application('System Events');
|
|
let count = 0, trunc = false;
|
|
function str(f) { try { const v = f(); return (v === null || v === undefined) ? '' : String(v); } catch (e) { return ''; } }
|
|
function node(el, depth) {
|
|
if (count >= maxNodes || depth > 40) { trunc = true; return null; }
|
|
count++;
|
|
const n = { role: str(() => el.role()).replace(/^AX/, '').toLowerCase() };
|
|
const name = str(() => el.name()); if (name) n.name = name;
|
|
const desc = str(() => el.description()); if (!name && desc) n.name = desc;
|
|
const val = str(() => el.value()); if (val && val.length <= 200) n.value = val;
|
|
try { const p = el.position(), s = el.size(); if (p && s) { n.x = p[0]; n.y = p[1]; n.w = s[0]; n.h = s[1]; } } catch (e) {}
|
|
try { n.enabled = !!el.enabled(); } catch (e) {}
|
|
try { if (el.focused()) n.focused = true; } catch (e) {}
|
|
const kids = [];
|
|
let children = [];
|
|
try { children = el.uiElements(); } catch (e) {}
|
|
for (const c of children) { const k = node(c, depth + 1); if (k) kids.push(k); if (trunc) break; }
|
|
if (kids.length) n.children = kids;
|
|
return n;
|
|
}
|
|
const procs = se.processes.whose({ unixId: targetPid })();
|
|
if (!procs.length) { JSON.stringify({ error: 'no process with pid ' + targetPid }); }
|
|
else {
|
|
const p = procs[0];
|
|
const wins = [];
|
|
for (const w of p.windows()) { const t = node(w, 0); if (t) wins.push(t); }
|
|
JSON.stringify({ app: str(() => p.name()), truncated: trunc, windows: wins });
|
|
}
|
|
`
|
|
|
|
func readTree(pid, maxNodes int) ([]byte, error) {
|
|
out, err := runFixedEnv(helperEnv(pid, maxNodes), "osascript", "-l", "JavaScript", "-e", darwinTreeScript)
|
|
if err != nil {
|
|
return nil, errors.New(osascriptReason(execFailure(err)))
|
|
}
|
|
return out, nil
|
|
}
|