macos-defaults/record/mac-runner.js
2021-12-20 20:58:38 +01:00

207 lines
5.4 KiB
JavaScript

const aperture = require('aperture')()
const wait = require('delay')
const fs = require('fs')
const util = require('util')
const exec = util.promisify(require('child_process').exec)
class MacRunner {
constructor(commands = []) {
this.commands = commands
}
/**
* Set MacOS defaults system
* @param {*} domain Application domain
* @param {*} key Default key
* @param {*} params Values for the default
* @param {*} expectedResult Expected defaults read result
*/
setDefault(domain, key, params, expectedResult) {
return this.register(async () => {
const defaultCommand = `defaults write ${domain} ${key} ${params}`
// Retry command until it works (sometimes it doesn't...)
for (let i = 10; i--; i > 0) {
try {
await execCommand(defaultCommand, 100)
const result = await execCommand(`defaults read ${domain} ${key}`, 0)
if (expectedResult === result.trim()) {
break
}
} catch (error) {
console.error(error)
}
if (i === 1) {
throw new Error(`[${defaultCommand}] failed (too much trials)`)
}
}
})
}
/**
* Read MacOS defaults system
* @param {*} domain Application domain
* @param {*} key Default key
*/
readDefault(domain, key) {
return this.register(async () => {
const defaultCommand = `defaults read ${domain} | grep ${key}`
console.log(await execCommand(defaultCommand, 0))
})
}
/**
* Delete MacOS defaults system
* @param {*} domain Application domain
* @param {*} key Default key
*/
deleteDefault(domain, key) {
return this.register(async () => {
const defaultCommand = `defaults delete ${domain} ${key}`
await execCommand(defaultCommand)
})
}
/**
* Open an application from it's name
* @param {*} appName Application name (ex: Finder)
* @param {*} params Application parameters
*/
openApp(appName, params = '') {
return this.register(() => execCommand(`open -a "${appName}" ${params}`))
}
/**
* Make active a running application
* @param {*} appName Application name
*/
activateApp(appName) {
return this.register(() =>
execCommand(`osascript -e 'tell application "${appName}" to activate'`)
)
}
/**
* Kill an application from it's name
* @param {*} appName Application name
*/
killApp(appName) {
return this.register(() => execCommand(`killall ${appName}`))
}
/**
* Move and resize an application window
* @param {*} appName Application name
* @param {*} x X coordinate (from the left of the screen)
* @param {*} y Y coordinate (from the top of the screen)
* @param {*} width Width of the app window
* @param {*} height Height of the app window
*/
moveAndResizeApp(appName, x, y, width, height) {
const h = { start: x, end: x + width }
const v = { start: y, end: y + height }
return this.register(() =>
execCommand(
`osascript -e 'tell application "${appName}" to set the bounds of the first window to {${h.start}, ${v.start}, ${h.end}, ${v.end}}'`
)
)
}
/**
* Capture the whole screen into a file
* @param {*} output Output file name (png)
*/
captureScreen(output) {
return this.register(async () => {
await this.wait(2000)
execCommand(`screencapture ${output}`)
})
}
/**
* Capture a screen rect into a file
* @param {*} x X coordinate (from the left of the screen)
* @param {*} y Y coordinate (from the top of the screen)
* @param {*} width Width of the capture
* @param {*} height Height of the capture
* @param {*} output Output file name (png)
*/
captureScreenRect(x, y, width, height, output) {
return this.register(() =>
execCommand(`screencapture -R${x},${y},${width},${height} ${output}`)
)
}
/**
* Capture the app window into a file
* @param {*} appName Application name to capture
* @param {*} output Output file name (png)
*/
captureApp(appName, output) {
return this.register(() =>
execCommand(
`screencapture -o -l$(osascript -e 'tell app "${appName}" to id of window 1') ${output}`
)
)
}
/**
* Start the video recording using aperturejs
* @param {*} options aperturejs options
*/
startVideo(options) {
return this.register(async () => {
console.info(' Start video recording...')
await aperture.startRecording(options)
await aperture.isFileReady
})
}
/**
* Stop the video recording
* @param {*} output video file output
*/
stopVideo(output) {
return this.register(async () => {
console.info(' Stop video recording...')
const fp = await aperture.stopRecording()
if (fs.existsSync(output)) {
fs.unlinkSync(output)
}
fs.renameSync(fp, output)
})
}
/**
* Wait for a given delay
* @param {*} delay Delay in ms
*/
wait(delay) {
return this.register(() => wait(delay))
}
/**
* Execute the runner with all given commands
*/
async run() {
await this.commands.reduce((p, fn) => p.then(fn), Promise.resolve())
}
register(command) {
this.commands.push(command)
return new MacRunner(this.commands)
}
}
async function execCommand(command, delay = 1000) {
console.info(` Command: [${command}]`)
const { stderr, stdout } = await exec(command)
if (stderr) {
throw new Error(stderr)
}
await wait(delay)
return stdout
}
module.exports = MacRunner