30 lines
1.9 KiB
JavaScript
30 lines
1.9 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
|
|
function runHelper({ spawnImpl, python, helper, args, timeoutMs, label }) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawnImpl(python, [helper, ...args], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
let out = ''; let err = ''; let settled = false;
|
|
const timer = timeoutMs > 0 ? setTimeout(() => { if (settled) return; settled = true; child.kill('SIGTERM'); setTimeout(() => child.kill('SIGKILL'), 500); reject(new Error(`${label} timed out`)); }, timeoutMs) : null;
|
|
const done = (fn) => (value) => { if (settled) return; settled = true; if (timer) clearTimeout(timer); fn(value); };
|
|
child.stdout.on('data', (d) => { out += d; });
|
|
child.stderr.on('data', (d) => { err += d; });
|
|
child.on('error', done(reject));
|
|
child.on('close', (code) => done(code === 0 ? resolve : reject)(code === 0 ? out : new Error(err.trim() || `${label} exited ${code}`)));
|
|
});
|
|
}
|
|
|
|
export class AtspiProvider {
|
|
constructor({ python = 'python3', helper = new URL('./py/atspi_snapshot.py', import.meta.url).pathname, spawnImpl = spawn, timeoutMs = 5000 } = {}) {
|
|
this.python = python; this.helper = helper; this.spawnImpl = spawnImpl; this.timeoutMs = timeoutMs;
|
|
}
|
|
async tree({ focusedOnly = true, maxNodes = 400 } = {}) {
|
|
const out = await runHelper({ spawnImpl: this.spawnImpl, python: this.python, helper: this.helper, args: [focusedOnly ? 'focused' : 'desktop', String(Math.min(400, maxNodes))], timeoutMs: this.timeoutMs, label: 'AT-SPI' });
|
|
return JSON.parse(String(out));
|
|
}
|
|
async action(target, action) {
|
|
const helper = this.helper.replace('atspi_snapshot.py', 'atspi_action.py');
|
|
const out = await runHelper({ spawnImpl: this.spawnImpl, python: this.python, helper, args: [JSON.stringify(target), String(action)], timeoutMs: this.timeoutMs, label: 'AT-SPI action' });
|
|
return JSON.parse(String(out));
|
|
}
|
|
}
|