macos-defaults/record/mac-runner.js
2022-08-19 15:38:29 +02:00

233 lines
6 KiB
JavaScript

const aperture = require('aperture')()
const robot = require('robotjs')
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
}
/**
* @return {{screenWidth: number, screenHeight: number}} The main screen size
*/
static getScreenSize() {
const { width, height } = robot.getScreenSize()
return { screenWidth: width, screenHeight: height }
}
/**
* @return {number} The Dock height
*/
static getDockHeight() {
return 76
}
/**
* @return {number} The Menu Bar height
*/
static getMenuBarHeight() {
return 22
}
/**
* 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)
* @param {boolean} disableShadow Do not capture the App shadow
*/
captureApp(appName, output, disableShadow = true) {
return this.register(() =>
execCommand(
`screencapture ${
disableShadow ? '-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