@accesslint/voiceover
Version:
macOS VoiceOver Typescript interface and CLI
191 lines (190 loc) • 6.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.VoiceOver = void 0;
const run_1 = require("@jxa/run");
const child_process_1 = require("child_process");
require("@jxa/global-type");
const util = require("util");
const exec = util.promisify(child_process_1.exec);
const spawn = util.promisify(child_process_1.spawn);
const Commands_js_1 = require("./Commands.js");
var Messages;
(function (Messages) {
Messages["stopped"] = "Session ended";
})(Messages || (Messages = {}));
class VoiceOver {
current = null;
_previous = null;
_timer;
_log = false;
_started;
_stepDelayMs;
constructor({ log, stepDelayMs } = {
log: false,
stepDelayMs: 200,
}) {
this._log = log;
this._stepDelayMs = stepDelayMs;
this._started = false;
}
record({ file }) {
spawn("screencapture", ["-v", "-C", "-k", "-T0", "-g", file], {
detached: true,
});
}
async launch() {
if (this._started) {
return;
}
if (this._log) {
this._timer = setInterval(async () => {
try {
this.current = await this.lastPhrase();
if (!this.current || this.current.trim() === this._previous) {
return;
}
console.log(this.current);
this._previous = this.current.trim();
}
catch (error) {
if (error.message.match(/Application isn't running|Command failed/)) {
return;
}
console.error(error);
}
}, 100);
}
try {
await exec("/System/Library/CoreServices/VoiceOver.app/Contents/MacOS/VoiceOverStarter");
this._started = true;
}
catch (error) {
console.log(error);
}
}
async quit() {
if (!this._started && !this._timer) {
return;
}
try {
clearInterval(this._timer);
await (0, run_1.run)(() => {
const voiceOver = Application("VoiceOver");
voiceOver.quit();
});
this._timer = null;
this._started = false;
console.log(Messages.stopped);
}
catch (error) {
console.error(error);
}
}
async clickNext({ text, role, tries, }) {
const found = await this.seek({ text, role, tries });
if (found) {
await this.execute(Commands_js_1.activate);
}
}
async advance({ target, steps = 1, }) {
const { text, role } = target;
let phrases = [];
let matches = false;
let count = 0;
const textRegex = new RegExp(text, "i");
while (count < steps && !matches) {
const phrase = await this.lastPhrase();
phrases.push(phrase);
if (phrase && phrase.match(textRegex)) {
if (!role) {
matches = true;
}
else if (phrase.startsWith(role) || phrase.endsWith(role)) {
matches = true;
}
}
if (!matches) {
await this.execute(Commands_js_1.moveRight);
count++;
}
else {
return phrases[phrases.length - 1];
}
}
throw new Error(`phrase "${text}" not found in ${phrases}`);
}
async seek({ text, role, tries = 10, }) {
let match = false;
let count = 0;
let phrases = [];
const textRegex = new RegExp(text, "i");
while (count < tries && !match) {
await this.execute(Commands_js_1.moveRight);
const phrase = await this.lastPhrase();
phrases.push(phrase);
if (role) {
if (phrase.endsWith(` ${role}`)) {
if (phrase.match(textRegex)) {
break;
}
}
}
else {
if (phrase.match(textRegex)) {
break;
}
}
count++;
}
return phrases;
}
lastPhrase() {
return (0, run_1.run)(() => {
const voiceOver = Application("VoiceOver");
// @ts-expect-error
return voiceOver.lastPhrase.content();
});
}
execute(command) {
return (0, run_1.run)(({ keyCode, modifiers }, stepDelayMs = 10) => {
const systemEvents = Application("System Events");
delay(stepDelayMs / 1000);
systemEvents.keyCode(keyCode, { using: modifiers });
}, command, this._stepDelayMs);
}
cancel() {
return (0, run_1.run)(() => {
const systemEvents = Application("System Events");
systemEvents.keyCode(53);
});
}
async rotor({ menu, find, } = {}) {
await (0, run_1.run)(({ keyCode, modifiers }) => {
const systemEvents = Application("System Events");
systemEvents.keyCode(keyCode, { using: modifiers });
}, Commands_js_1.rotor);
if (menu) {
const menuFound = await this.seek({ text: `${menu} menu`, tries: 10 });
if (find && menuFound) {
await this.keyStrokes({ text: find, submit: true });
}
}
}
async keyStrokes({ text, submit = false, }) {
const letters = text.split("");
for (const letter of letters) {
await (0, run_1.run)((letter) => {
const systemEvents = Application("System Events");
systemEvents.keystroke(letter);
delay(0.05);
}, letter);
}
if (submit) {
await (0, run_1.run)(() => {
const systemEvents = Application("System Events");
systemEvents.keyCode(36);
});
}
}
}
exports.VoiceOver = VoiceOver;