2020-11-12 21:23:37 +00:00
|
|
|
const aperture = require('aperture')()
|
2020-11-12 16:26:08 +00:00
|
|
|
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
|
2020-11-27 14:01:10 +00:00
|
|
|
* @param {*} expectedResult Expected defaults read result
|
2020-11-12 16:26:08 +00:00
|
|
|
*/
|
2021-12-20 19:58:38 +00:00
|
|
|
setDefault(domain, key, params, expectedResult) {
|
2020-11-27 14:01:10 +00:00
|
|
|
return this.register(async () => {
|
2020-11-12 16:26:08 +00:00
|
|
|
const defaultCommand = `defaults write ${domain} ${key} ${params}`
|
2020-11-27 14:01:10 +00:00
|
|
|
|
|
|
|
// 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))
|
2020-11-12 16:26:08 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:24:23 +00:00
|
|
|
/**
|
|
|
|
* Delete MacOS defaults system
|
|
|
|
* @param {*} domain Application domain
|
|
|
|
* @param {*} key Default key
|
|
|
|
*/
|
2021-12-20 19:58:38 +00:00
|
|
|
deleteDefault(domain, key) {
|
2020-11-27 14:01:10 +00:00
|
|
|
return this.register(async () => {
|
2020-11-12 21:24:23 +00:00
|
|
|
const defaultCommand = `defaults delete ${domain} ${key}`
|
2020-11-27 14:01:10 +00:00
|
|
|
await execCommand(defaultCommand)
|
2020-11-12 21:24:23 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-12 16:26:08 +00:00
|
|
|
/**
|
|
|
|
* 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}`))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-11-27 12:45:16 +00:00
|
|
|
* Make active a running application
|
2020-11-12 16:26:08 +00:00
|
|
|
* @param {*} appName Application name
|
|
|
|
*/
|
|
|
|
activateApp(appName) {
|
2020-11-27 12:45:16 +00:00
|
|
|
return this.register(() =>
|
|
|
|
execCommand(`osascript -e 'tell application "${appName}" to activate'`)
|
|
|
|
)
|
2020-11-12 16:26:08 +00:00
|
|
|
}
|
|
|
|
|
2021-12-20 19:58:38 +00:00
|
|
|
/**
|
|
|
|
* Kill an application from it's name
|
|
|
|
* @param {*} appName Application name
|
|
|
|
*/
|
|
|
|
killApp(appName) {
|
|
|
|
return this.register(() => execCommand(`killall ${appName}`))
|
|
|
|
}
|
|
|
|
|
2020-11-12 16:26:08 +00:00
|
|
|
/**
|
|
|
|
* 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 }
|
2020-11-27 12:45:16 +00:00
|
|
|
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}}'`
|
|
|
|
)
|
|
|
|
)
|
2020-11-12 16:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Capture the whole screen into a file
|
|
|
|
* @param {*} output Output file name (png)
|
|
|
|
*/
|
|
|
|
captureScreen(output) {
|
2020-11-27 14:01:10 +00:00
|
|
|
return this.register(async () => {
|
|
|
|
await this.wait(2000)
|
|
|
|
execCommand(`screencapture ${output}`)
|
|
|
|
})
|
2020-11-12 16:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2020-11-27 12:45:16 +00:00
|
|
|
return this.register(() =>
|
|
|
|
execCommand(`screencapture -R${x},${y},${width},${height} ${output}`)
|
|
|
|
)
|
2020-11-12 16:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Capture the app window into a file
|
|
|
|
* @param {*} appName Application name to capture
|
|
|
|
* @param {*} output Output file name (png)
|
|
|
|
*/
|
|
|
|
captureApp(appName, output) {
|
2020-11-27 12:45:16 +00:00
|
|
|
return this.register(() =>
|
|
|
|
execCommand(
|
|
|
|
`screencapture -o -l$(osascript -e 'tell app "${appName}" to id of window 1') ${output}`
|
|
|
|
)
|
|
|
|
)
|
2020-11-12 16:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the video recording using aperturejs
|
|
|
|
* @param {*} options aperturejs options
|
|
|
|
*/
|
|
|
|
startVideo(options) {
|
|
|
|
return this.register(async () => {
|
|
|
|
console.info(' Start video recording...')
|
2020-11-12 21:23:37 +00:00
|
|
|
await aperture.startRecording(options)
|
|
|
|
await aperture.isFileReady
|
2020-11-12 16:26:08 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
}
|
2020-11-12 21:23:37 +00:00
|
|
|
fs.renameSync(fp, output)
|
2020-11-12 16:26:08 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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}]`)
|
2020-11-27 14:01:10 +00:00
|
|
|
const { stderr, stdout } = await exec(command)
|
2020-11-12 16:26:08 +00:00
|
|
|
if (stderr) {
|
|
|
|
throw new Error(stderr)
|
|
|
|
}
|
|
|
|
await wait(delay)
|
2020-11-27 14:01:10 +00:00
|
|
|
return stdout
|
2020-11-12 16:26:08 +00:00
|
|
|
}
|
|
|
|
|
2020-11-27 12:45:16 +00:00
|
|
|
module.exports = MacRunner
|