Files
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

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
}