minor fixes to combos, performance optimizations (hopefully)

This commit is contained in:
Daniel Sommer 2022-03-29 13:16:04 +02:00
parent bb1cca0433
commit 9d52b870d4
3 changed files with 124 additions and 78 deletions

View file

@ -4,7 +4,7 @@
"timestamp": "DD.MM.YYYY HH:mm:ss:SS" "timestamp": "DD.MM.YYYY HH:mm:ss:SS"
}, },
"combos": { "combos": {
"delay": 1000 "timeout": 5000
}, },
"watchers": [ "watchers": [
{ {
@ -15,8 +15,7 @@
{ {
"key": "key_f1", "key": "key_f1",
"combo": [ "combo": [
"key_f2", "key_f5"
"key_f2"
], ],
"event": "EV_KEY", "event": "EV_KEY",
"type": "keydown", "type": "keydown",
@ -25,13 +24,22 @@
{ {
"key": "key_f1", "key": "key_f1",
"combo": [ "combo": [
"key_f2", "key_f6"
"key_f3"
], ],
"event": "EV_KEY", "event": "EV_KEY",
"type": "keydown", "type": "keydown",
"command": "combo2" "command": "combo2"
}, },
{
"key": "key_f2",
"combo": [
"key_f5",
"key_f6"
],
"event": "EV_KEY",
"type": "keydown",
"command": "combo3"
},
{ {
"key": "key_enter", "key": "key_enter",
"type": "keydown", "type": "keydown",
@ -71,6 +79,14 @@
], ],
"sudo": false "sudo": false
}, },
"combo3": {
"cmd": "notify-send",
"args": [
"combo #3",
"third combo"
],
"sudo": false
},
"example": { "example": {
"cmd": "notify-send", "cmd": "notify-send",
"args": [ "args": [
@ -92,7 +108,6 @@
"args": [ "args": [
"https://velvettear.de" "https://velvettear.de"
] ]
}, }
"sudo": false
} }
} }

View file

@ -1,3 +1,5 @@
const logger = require('./logger.js');
const LINE_START = 'Event: time'; const LINE_START = 'Event: time';
const ACTION_KEYUP = { id: 0, action: 'keyup' }; const ACTION_KEYUP = { id: 0, action: 'keyup' };
@ -10,8 +12,13 @@ class Keyfilter {
return; return;
} }
this.actions = new Map(); this.actions = new Map();
this.registered = {
keys: [],
events: [],
types: []
}
for (let index = 0; index < config.length; index++) { for (let index = 0; index < config.length; index++) {
this.setAction(config[index]); this.registerAction(config[index]);
} }
this.currentCombo = undefined; this.currentCombo = undefined;
} }
@ -49,13 +56,17 @@ class Keyfilter {
} }
return result; return result;
} }
setAction(config) { registerAction(config) {
let key = config.key.toUpperCase(); let key = config.key.toUpperCase();
if (config.combo !== undefined && config.combo.length > 0) { if (config.combo !== undefined && config.combo.length > 0) {
const tmp = JSON.parse(JSON.stringify(config.combo)); const tmp = JSON.parse(JSON.stringify(config.combo));
tmp.unshift(config.key); tmp.unshift(config.key);
key = tmp.toString().toUpperCase(); key = tmp.toString().toUpperCase();
} }
if (Array.from(this.actions.keys()).includes(key)) {
logger.warn('skipping already registered key(s) \'' + key + '\'...');
return;
}
this.actions.set(key, this.actions.set(key,
{ {
type: this.getActionType(config.type), type: this.getActionType(config.type),
@ -70,52 +81,54 @@ class Keyfilter {
}(), }(),
} }
); );
const singleKeys = key.split(',');
for (let index = 0; index < singleKeys.length; index++) {
const singleKey = singleKeys[index];
if (!this.registered.keys.includes(singleKey)) {
this.registered.keys.push(singleKey);
}
}
if (!this.registered.events.includes(this.actions.get(key).event)) {
this.registered.events.push(this.actions.get(key).event);
}
if (!this.registered.types.includes(this.actions.get(key).type.id)) {
this.registered.types.push(this.actions.get(key).type.id);
}
} }
filter(input) { filter(input) {
if (input === undefined || input.length === 0) { if (input === undefined || input.length === 0) {
return; return;
} }
input = input.toString(); const parsedEvent = this.parseLine(input);
let lines = input.split("\n"); if (this.parsedEventIsUnknown(parsedEvent)) {
for (let index = 0; index < lines.length; index++) { return;
let line = lines[index];
if (line.length === 0) {
continue;
} }
if (!line.startsWith(LINE_START)) { if (this.isPartOfCombo(parsedEvent)) {
continue; if (parsedEvent.ignore) {
return;
} }
const parsedEvent = this.parseLine(line); return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action));
if (parsedEvent === undefined) {
continue;
}
for (let [key, event] of this.actions) {
if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) {
continue;
} }
if (this.isStartOfCombo(parsedEvent)) { if (this.isStartOfCombo(parsedEvent)) {
return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action)); return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action));
} }
if (this.isPartOfCombo(parsedEvent)) { for (let [key, event] of this.actions) {
if (parsedEvent.ignore) { if (!this.parsedEventIsValid(key, event, parsedEvent)) {
continue; continue;
} }
return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action));
}
if (!this.isParsedEventValid(key, event, parsedEvent)) {
continue;
}
if (this.shouldBeDelayed(event)) {
const result = this.getFilterResult(parsedEvent.key, event.type.action, event.command); const result = this.getFilterResult(parsedEvent.key, event.type.action, event.command);
if (this.shouldBeDelayed(event)) {
result.delayed = true; result.delayed = true;
return result; return result;
} }
event.captured = new Date().getTime(); event.captured = new Date().getTime();
return this.getFilterResult(parsedEvent.key, event.type.action, event.command); return result;
} }
} }
parsedEventIsUnknown(parsedEvent) {
return parsedEvent === undefined || !this.registered.keys.includes(parsedEvent.key) || !this.registered.events.includes(parsedEvent.event) || !this.registered.types.includes(parsedEvent.type.id);
} }
isParsedEventValid(key, value, parsed) { parsedEventIsValid(key, value, parsed) {
if (value.event !== parsed.event || value.type.id !== parsed.type.id) { if (value.event !== parsed.event || value.type.id !== parsed.type.id) {
return false; return false;
} }
@ -159,6 +172,9 @@ class Keyfilter {
}; };
} }
parseLine(line) { parseLine(line) {
if (line === undefined || line.length === 0 || !line.startsWith(LINE_START)) {
return;
}
try { try {
const parts = line.split(','); const parts = line.split(',');
const event = parts[1].substring(parts[1].indexOf('(') + 1, parts[1].lastIndexOf(')')); const event = parts[1].substring(parts[1].indexOf('(') + 1, parts[1].lastIndexOf(')'));
@ -176,15 +192,12 @@ class Keyfilter {
return new Date().getTime() - event.captured < event.delay; return new Date().getTime() - event.captured < event.delay;
} }
isStartOfCombo(parsedEvent) { isStartOfCombo(parsedEvent) {
if (this.currentCombo !== undefined) {
return false;
}
let possibilities = []; let possibilities = [];
for (let [actionKey, actionEvent] of this.actions) { for (let [actionKey, actionEvent] of this.actions) {
if (actionEvent.combo === undefined || actionEvent.combo.length === 0) { if (actionEvent.combo === undefined || actionEvent.combo.length === 0) {
continue; continue;
} }
if (!actionKey.toUpperCase().startsWith(parsedEvent.key)) { if (!actionKey.toUpperCase().startsWith(parsedEvent.key) || actionEvent.type.id !== parsedEvent.type.id || actionEvent.event !== parsedEvent.event) {
continue; continue;
} }
possibilities.push(JSON.parse(JSON.stringify(actionEvent))); possibilities.push(JSON.parse(JSON.stringify(actionEvent)));
@ -200,12 +213,10 @@ class Keyfilter {
done: [parsedEvent.key], done: [parsedEvent.key],
possibilities: possibilities possibilities: possibilities
}; };
this.setComboTimeout();
return true; return true;
} }
isPartOfCombo(parsedEvent) { isPartOfCombo(parsedEvent) {
if (this.hasComboTimedOut()) {
this.resetCurrentCombo();
}
if (this.currentCombo === undefined || this.currentCombo.type.id !== parsedEvent.type.id || this.currentCombo.event !== parsedEvent.event) { if (this.currentCombo === undefined || this.currentCombo.type.id !== parsedEvent.type.id || this.currentCombo.event !== parsedEvent.event) {
return false; return false;
} }
@ -213,7 +224,7 @@ class Keyfilter {
for (let index = 0; index < this.currentCombo.possibilities.length; index++) { for (let index = 0; index < this.currentCombo.possibilities.length; index++) {
const possibility = this.currentCombo.possibilities[index]; const possibility = this.currentCombo.possibilities[index];
if (possibility.combo.length === 0) { if (possibility.combo.length === 0) {
break; continue;
} }
if (possibility.combo[0].toUpperCase() !== parsedEvent.key) { if (possibility.combo[0].toUpperCase() !== parsedEvent.key) {
continue; continue;
@ -236,14 +247,29 @@ class Keyfilter {
tmp.done = this.currentCombo.done; tmp.done = this.currentCombo.done;
this.currentCombo = tmp; this.currentCombo = tmp;
} }
this.setComboTimeout();
return true; return true;
} }
hasComboTimedOut() { setComboTimeout() {
return global.config?.combos?.delay !== undefined && if (this.currentCombo === undefined || global.config?.combos?.timeout === undefined || isNaN(global.config?.combos?.timeout)) {
this.currentCombo?.timestamp !== undefined && return;
new Date().getTime() - this.currentCombo.timestamp > global.config?.combos?.delay; }
this.clearComboTimeout();
logger.debug('setting timeout for current combo to ' + parseInt(global.config.combos.timeout) + 'ms');
this.comboTimeout = setTimeout(() => {
this.resetCurrentCombo()
}, parseInt(global.config.combos.timeout));
}
clearComboTimeout() {
if (this.comboTimeout === undefined) {
return;
}
logger.debug('clearing timeout for current combo');
clearTimeout(this.comboTimeout);
} }
resetCurrentCombo() { resetCurrentCombo() {
this.clearComboTimeout();
logger.debug('resetting current combo');
this.currentCombo = undefined; this.currentCombo = undefined;
} }
isValid() { isValid() {

View file

@ -62,30 +62,13 @@ class Watcher {
} }
logger.debug('adding stdout listener to watcher \'' + this.device + '\'...'); logger.debug('adding stdout listener to watcher \'' + this.device + '\'...');
this.process.stdout.on('data', (data) => { this.process.stdout.on('data', (data) => {
if (this.keyfilter == undefined) { if (this.keyfilter === undefined) {
return; return;
} }
let filtered = this.keyfilter.filter(data); const lines = data.toString().split('\n');
if (filtered === undefined) { for (let index = 0; index < lines.length; index++) {
return; this.handleEvent(this.keyfilter.filter(lines[index]));
} }
logger.debug('handling captured \'' + filtered.type + '\' event for key \'' + filtered.key + '\' from watcher \'' + this.device + '\'...');
if (filtered.delayed) {
logger.debug('delaying captured event...');
return;
}
if (filtered.combo) {
if (!filtered.combo.finished) {
logger.debug('captured event is part of ' + filtered.combo.possibilities + ' possible combo(s) and not yet finished')
return;
}
logger.debug('captured event finished combo \'' + filtered.combo.done.toString().toUpperCase()+ '\'');
}
this.keyfilter.resetCurrentCombo();
logger.info('executing command \'' + filtered.command.name + '\' registered for captured event...');
cli.execute(filtered.command.cmd, filtered.command.args, filtered.command.sudo)
.then(logger.info)
.catch(logger.error);
}); });
} }
addStdErrListener() { addStdErrListener() {
@ -155,5 +138,27 @@ class Watcher {
.catch(reject); .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; module.exports = Watcher;