Skip to content

Commit

Permalink
Surface requirement for key press automation
Browse files Browse the repository at this point in the history
  • Loading branch information
jugglinmike committed Apr 23, 2024
1 parent 967c5d7 commit e05b38e
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
10 changes: 8 additions & 2 deletions lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ const { loadOsModule } = require('../helpers/load-os-module');
module.exports = /** @type {import('yargs').CommandModule} */ ({
command: 'install',
describe: 'Install text to speech extension and other support',
async handler() {
builder(yargs) {
return yargs.option('unattended', {
desc: 'Fail if installation requires human intervention',
boolean: true,
});
},
async handler({ unattended }) {
const installDelegate = loadOsModule('install', {
darwin: () => require('../install/macos'),
win32: () => require('../install/win32'),
});
await installDelegate.install();
await installDelegate.install({ unattended });
},
});
63 changes: 62 additions & 1 deletion lib/install/macos.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { exec: _exec } = require('child_process');
const { resolve } = require('path');
const { promisify } = require('util');
const { 'interaction.pressKeys': pressKeys } = require('../modules/macos/interaction');

const LSREGISTER_EXECUTABLE_PATH =
'/System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/LaunchServices.framework/Versions/Current/Support/lsregister';
Expand All @@ -22,15 +23,60 @@ const SYSTEM_VOICE_IDENTIFIER = 'com.apple.Fred';
const PLUGIN_TRIPLET_IDENTIFIER = 'ausp atdg BOCU';

const exec = promisify(_exec);
const enableKeyAutomationPrompt = `This tool can only be installed on systems which allow automated key pressing.
Please allow the Terminal application to control your computer (the setting is
controlled in System Settings > Privacy & Security > Accessibility).`;

/** @typedef {import('child_process').ExecOptions} ExecOptions */

/**
* Prompt the user to press any key. Resolves when the user presses a key.
*
* @returns {Promise<void>}
*/
exports.install = async function () {
const promptForManualKeyPress = async () => {
process.stdout.write('Press any key to continue... ');
const wasRaw = process.stdin.isRaw;
process.stdin.setRawMode(true);
process.stdin.resume();
const byteArray = await new Promise(resolve => {
process.stdin.once('data', data => resolve(Array.from(data)));
});

process.stdin.pause();
process.stdin.setRawMode(wasRaw);
process.stdout.write('\n');

// Honor "Control + C" motion by exiting.
if (byteArray[0] === 3) {
process.exit(1);
}
};

/**
* @param {object} options
* @param {boolean} options.unattended - Whether installation should fail if
* human intervention is required
*
* @returns {Promise<void>}
*/
exports.install = async function ({ unattended }) {
const options = await getExecOptions();

if (!(await canPressKeys())) {
if (unattended) {
throw new Error('The system cannot automate key pressing.');
} else {
console.error(enableKeyAutomationPrompt);

await promptForManualKeyPress();

if (!(await canPressKeys())) {
throw new Error('The system cannot automate key pressing.');
}
}
}

if (await isInstalled()) {
throw new Error('Already installed');
}
Expand All @@ -55,6 +101,21 @@ exports.uninstall = async function () {
await unregisterExtensions(options);
};

/**
* Experimentally determine whether the current system supports automated key
* pressing by attempting to press an arbitrary key.
*
* @returns {Promise<boolean>}
*/
const canPressKeys = async () => {
try {
await pressKeys(null, { keys: ['shift'] });
} catch ({}) {
return false;
}
return true;
};

const isInstalled = async function () {
const { stdout } = await exec(`auval -v ${PLUGIN_TRIPLET_IDENTIFIER}`);
return /ATDriverGenericMacOSExtension/.test(stdout);
Expand Down

0 comments on commit e05b38e

Please sign in to comment.