const logger = require('./logger.js'); const util = require('./util.js'); const Keyfilter = require('./keyfilter.js'); const cli = require('./cli.js'); const spawn = require('child_process').spawn; const sudo = require('sudo'); const inputDevices = '/dev/input/'; const inputDevicesById = '/dev/input/by-id/'; class Watcher { constructor(config, callback) { if (config === undefined || config.device === undefined) { return; } for (let key in config) { this[key] = config[key]; } this.keyfilter = new Keyfilter(config.keys); this.restart = config.restart; this.callback = callback; } async start() { if (this.process !== undefined) { return; } if (this.sudo) { logger.debug('starting sudo watcher \'' + this.device + '\'...'); this.process = sudo(['evtest', this.device], { cachePassword: true, prompt: 'sudo password:' }); } else { logger.debug('starting watcher \'' + this.device + '\'...'); this.process = spawn('evtest', [this.device]); } try { await this.attachListeners(); } catch (err) { logger.error(err); } } stop() { if (this.process === undefined) { return; } logger.debug('stopping watcher \'' + this.device + '\'...'); this.process.kill(); logger.info('watcher \'' + this.device + '\' stopped'); } async attachListeners() { if (this.process === undefined) { return; } this.addStdOutListener(); this.addStdErrListener(); this.addErrorListener(); this.addCloseListener(); await this.addSpawnListener(); } addStdOutListener() { if (this.process === undefined) { return; } logger.debug('adding stdout listener to watcher \'' + this.device + '\'...'); this.process.stdout.on('data', (data) => { if (this.keyfilter === undefined) { return; } const lines = data.toString().split('\n'); for (let index = 0; index < lines.length; index++) { this.handleEvent(this.keyfilter.filter(lines[index])); } }); } addStdErrListener() { if (this.process === undefined) { return; } logger.debug('adding stderr listener to watcher \'' + this.device + '\'...'); this.process.stderr.on('data', (data) => { this.error = data.toString().trim(); }); } addSpawnListener() { return new Promise((resolve, reject) => { logger.debug('adding spawn listener to watcher \'' + this.device + '\'...'); this.process.on('spawn', () => { logger.info('watcher \'' + this.device + '\' initialized and capturing configured events'); resolve(); }); }); } addCloseListener() { if (this.process === undefined) { return; } logger.debug('adding close listener to watcher \'' + this.device + '\'...'); this.process.on('close', (code) => { if (code === undefined) { code = 0; if (this.error !== undefined) { code = 1; } } if (this.error !== undefined) { logger.error('watcher \'' + this.device + '\' encountered an error > ' + this.error); this.restart = false; } this.process = undefined; this.code = code; logger.info('watcher \'' + this.device + '\' finished with exit code ' + code); if (this.callback !== undefined) { this.callback(this); } }); } addErrorListener(reject) { if (this.process === undefined) { return; } logger.debug('adding error listener to \'' + this.device + '\'...'); this.process.on('error', (err) => { reject(logger.error('watcher \'' + this.device + '\' encountered an error >>> ' + err)); }); } check() { return new Promise((resolve, reject) => { if (!this.keyfilter.isValid()) { reject('no key(s) defined for watcher \'' + this.device + '\''); } Promise.any([this.device, inputDevices + this.device, inputDevicesById + this.device].map(util.getFileInfo)) .then((result) => { if (result.path !== this.device) { logger.info('resolved watcher for device \'' + this.device + '\' to \'' + result.path + '\'') } this.device = result.path; resolve(this); }) .catch(reject); }); } handleEvent(event) { if (event === undefined) { return; } logger.debug('handling captured \'' + event.type + '\' event for key \'' + event.key + '\' from watcher \'' + this.device + '\'...'); if (event.delayed) { logger.debug('delaying captured event...'); return; } if (event.combo) { if (!event.combo.finished) { logger.debug('captured event is part of ' + event.combo.possibilities + ' possible combo(s) and not yet finished') return; } logger.debug('captured event finished combo \'' + event.combo.done.toString().toUpperCase() + '\''); } this.keyfilter.resetCurrentCombo(); logger.info('executing command \'' + event.command.name + '\' registered for captured event...'); cli.execute(event.command.cmd, event.command.args, event.command.sudo) .then(logger.info) .catch(logger.error); } } module.exports = Watcher;