2022-02-15 04:33:19 +01:00
|
|
|
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;
|
2022-03-10 12:29:41 +01:00
|
|
|
const sudo = require('sudo');
|
2022-02-15 04:33:19 +01:00
|
|
|
|
|
|
|
const inputDevices = '/dev/input/';
|
|
|
|
const inputDevicesById = '/dev/input/by-id/';
|
|
|
|
|
|
|
|
class Watcher {
|
|
|
|
constructor(config, callback) {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (config === undefined || config.device === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-03-10 12:29:41 +01:00
|
|
|
for (let key in config) {
|
2022-02-15 04:33:19 +01:00
|
|
|
this[key] = config[key];
|
|
|
|
}
|
2022-03-10 17:06:44 +01:00
|
|
|
this.keyfilter = new Keyfilter(config.keys);
|
2022-02-24 00:07:57 +01:00
|
|
|
this.restart = config.restart;
|
2022-02-15 04:33:19 +01:00
|
|
|
this.callback = callback;
|
|
|
|
}
|
2022-03-10 12:29:41 +01:00
|
|
|
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 {
|
2022-02-24 00:07:57 +01:00
|
|
|
logger.debug('starting watcher \'' + this.device + '\'...');
|
2022-03-10 12:29:41 +01:00
|
|
|
this.process = spawn('evtest', [this.device]);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
await this.attachListeners();
|
|
|
|
} catch (err) {
|
|
|
|
logger.error(err);
|
|
|
|
}
|
|
|
|
|
2022-02-15 04:33:19 +01:00
|
|
|
}
|
|
|
|
stop() {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.process === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
logger.debug('stopping watcher \'' + this.device + '\'...');
|
|
|
|
this.process.kill();
|
2022-02-16 03:28:17 +01:00
|
|
|
logger.info('watcher \'' + this.device + '\' stopped');
|
2022-02-15 04:33:19 +01:00
|
|
|
}
|
2022-03-10 12:29:41 +01:00
|
|
|
async attachListeners() {
|
|
|
|
if (this.process === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.addStdOutListener();
|
|
|
|
this.addStdErrListener();
|
2022-03-10 12:29:41 +01:00
|
|
|
this.addErrorListener();
|
|
|
|
this.addCloseListener();
|
|
|
|
await this.addSpawnListener();
|
2022-02-15 04:33:19 +01:00
|
|
|
}
|
|
|
|
addStdOutListener() {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.process === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
logger.debug('adding stdout listener to watcher \'' + this.device + '\'...');
|
|
|
|
this.process.stdout.on('data', (data) => {
|
2022-03-29 13:16:04 +02:00
|
|
|
if (this.keyfilter === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-03-29 13:16:04 +02:00
|
|
|
const lines = data.toString().split('\n');
|
|
|
|
for (let index = 0; index < lines.length; index++) {
|
|
|
|
this.handleEvent(this.keyfilter.filter(lines[index]));
|
2022-02-15 04:33:19 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
addStdErrListener() {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.process === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
logger.debug('adding stderr listener to watcher \'' + this.device + '\'...');
|
|
|
|
this.process.stderr.on('data', (data) => {
|
2022-03-10 12:29:41 +01:00
|
|
|
this.error = data.toString().trim();
|
2022-02-15 04:33:19 +01:00
|
|
|
});
|
|
|
|
}
|
2022-03-10 12:29:41 +01:00
|
|
|
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();
|
|
|
|
});
|
2022-02-24 00:07:57 +01:00
|
|
|
});
|
|
|
|
}
|
2022-02-15 04:33:19 +01:00
|
|
|
addCloseListener() {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.process === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
logger.debug('adding close listener to watcher \'' + this.device + '\'...');
|
|
|
|
this.process.on('close', (code) => {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (code === undefined) {
|
2022-02-16 03:28:17 +01:00
|
|
|
code = 0;
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.error !== undefined) {
|
|
|
|
code = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.error !== undefined) {
|
|
|
|
logger.error('watcher \'' + this.device + '\' encountered an error > ' + this.error);
|
|
|
|
this.restart = false;
|
2022-02-16 03:28:17 +01:00
|
|
|
}
|
2022-02-24 00:07:57 +01:00
|
|
|
this.process = undefined;
|
2022-02-16 03:28:17 +01:00
|
|
|
this.code = code;
|
2022-02-15 04:33:19 +01:00
|
|
|
logger.info('watcher \'' + this.device + '\' finished with exit code ' + code);
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.callback !== undefined) {
|
2022-02-24 00:07:57 +01:00
|
|
|
this.callback(this);
|
2022-02-15 04:33:19 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-02-24 00:07:57 +01:00
|
|
|
addErrorListener(reject) {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (this.process === undefined) {
|
2022-02-15 04:33:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
logger.debug('adding error listener to \'' + this.device + '\'...');
|
|
|
|
this.process.on('error', (err) => {
|
2022-02-24 00:07:57 +01:00
|
|
|
reject(logger.error('watcher \'' + this.device + '\' encountered an error >>> ' + err));
|
2022-02-15 04:33:19 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
check() {
|
|
|
|
return new Promise((resolve, reject) => {
|
2022-02-16 02:08:21 +01:00
|
|
|
if (!this.keyfilter.isValid()) {
|
|
|
|
reject('no key(s) defined for watcher \'' + this.device + '\'');
|
|
|
|
}
|
2022-03-10 12:29:41 +01:00
|
|
|
Promise.any([this.device, inputDevices + this.device, inputDevicesById + this.device].map(util.getFileInfo))
|
2022-02-15 04:33:19 +01:00
|
|
|
.then((result) => {
|
2022-03-10 12:29:41 +01:00
|
|
|
if (result.path !== this.device) {
|
2022-02-15 04:33:19 +01:00
|
|
|
logger.info('resolved watcher for device \'' + this.device + '\' to \'' + result.path + '\'')
|
|
|
|
}
|
|
|
|
this.device = result.path;
|
2022-02-16 03:28:17 +01:00
|
|
|
resolve(this);
|
2022-02-15 04:33:19 +01:00
|
|
|
})
|
|
|
|
.catch(reject);
|
|
|
|
});
|
|
|
|
}
|
2022-03-29 13:16:04 +02:00
|
|
|
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);
|
|
|
|
}
|
2022-02-15 04:33:19 +01:00
|
|
|
}
|
|
|
|
module.exports = Watcher;
|