+26
-6
@@ -1,9 +1,29 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
export class AtspiProvider {
|
||||
constructor({ python = 'python3', helper = new URL('./py/atspi_snapshot.py', import.meta.url).pathname, spawnImpl = spawn } = {}) { this.python = python; this.helper = helper; this.spawnImpl = spawnImpl; }
|
||||
async tree({ focusedOnly = true, maxNodes = 400 } = {}) {
|
||||
return new Promise((resolve, reject) => { const child = this.spawnImpl(this.python, [this.helper, focusedOnly ? 'focused' : 'desktop', String(Math.min(400, maxNodes))], { stdio: ['ignore', 'pipe', 'pipe'] }); let out = ''; let err = ''; child.stdout.on('data', (d) => { out += d; }); child.stderr.on('data', (d) => { err += d; }); child.on('error', reject); child.on('close', (code) => { if (code !== 0) return reject(new Error(err || `AT-SPI exited ${code}`)); try { resolve(JSON.parse(out)); } catch (error) { reject(error); } }); });
|
||||
}
|
||||
async action(target, action) { return new Promise((resolve, reject) => { const child = this.spawnImpl(this.python, [this.helper.replace('atspi_snapshot.py', 'atspi_action.py'), JSON.stringify(target), String(action)], { stdio: ['ignore', 'pipe', 'pipe'] }); let out = ''; let err = ''; child.stdout.on('data', (d) => { out += d; }); child.stderr.on('data', (d) => { err += d; }); child.on('error', reject); child.on('close', (code) => { if (code !== 0) return reject(new Error(err || `AT-SPI action exited ${code}`)); try { resolve(JSON.parse(out)); } catch (error) { reject(error); } }); }); }
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user