From 9d52b870d4585c511dc4387b6c0c8d211ca3ec67 Mon Sep 17 00:00:00 2001 From: velvettear Date: Tue, 29 Mar 2022 13:16:04 +0200 Subject: [PATCH] minor fixes to combos, performance optimizations (hopefully) --- example_config.json | 29 +++++++--- libs/keyfilter.js | 126 ++++++++++++++++++++++++++------------------ libs/watcher.js | 47 +++++++++-------- 3 files changed, 124 insertions(+), 78 deletions(-) diff --git a/example_config.json b/example_config.json index f8672e5..a65162a 100644 --- a/example_config.json +++ b/example_config.json @@ -4,7 +4,7 @@ "timestamp": "DD.MM.YYYY HH:mm:ss:SS" }, "combos": { - "delay": 1000 + "timeout": 5000 }, "watchers": [ { @@ -15,8 +15,7 @@ { "key": "key_f1", "combo": [ - "key_f2", - "key_f2" + "key_f5" ], "event": "EV_KEY", "type": "keydown", @@ -25,13 +24,22 @@ { "key": "key_f1", "combo": [ - "key_f2", - "key_f3" + "key_f6" ], "event": "EV_KEY", "type": "keydown", "command": "combo2" }, + { + "key": "key_f2", + "combo": [ + "key_f5", + "key_f6" + ], + "event": "EV_KEY", + "type": "keydown", + "command": "combo3" + }, { "key": "key_enter", "type": "keydown", @@ -71,6 +79,14 @@ ], "sudo": false }, + "combo3": { + "cmd": "notify-send", + "args": [ + "combo #3", + "third combo" + ], + "sudo": false + }, "example": { "cmd": "notify-send", "args": [ @@ -92,7 +108,6 @@ "args": [ "https://velvettear.de" ] - }, - "sudo": false + } } } \ No newline at end of file diff --git a/libs/keyfilter.js b/libs/keyfilter.js index bd13b7f..098eb44 100644 --- a/libs/keyfilter.js +++ b/libs/keyfilter.js @@ -1,3 +1,5 @@ +const logger = require('./logger.js'); + const LINE_START = 'Event: time'; const ACTION_KEYUP = { id: 0, action: 'keyup' }; @@ -10,8 +12,13 @@ class Keyfilter { return; } this.actions = new Map(); + this.registered = { + keys: [], + events: [], + types: [] + } for (let index = 0; index < config.length; index++) { - this.setAction(config[index]); + this.registerAction(config[index]); } this.currentCombo = undefined; } @@ -49,13 +56,17 @@ class Keyfilter { } return result; } - setAction(config) { + registerAction(config) { let key = config.key.toUpperCase(); if (config.combo !== undefined && config.combo.length > 0) { const tmp = JSON.parse(JSON.stringify(config.combo)); tmp.unshift(config.key); 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, { 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) { if (input === undefined || input.length === 0) { return; } - input = input.toString(); - let lines = input.split("\n"); - for (let index = 0; index < lines.length; index++) { - let line = lines[index]; - if (line.length === 0) { + const parsedEvent = this.parseLine(input); + if (this.parsedEventIsUnknown(parsedEvent)) { + return; + } + if (this.isPartOfCombo(parsedEvent)) { + if (parsedEvent.ignore) { + return; + } + return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action)); + } + if (this.isStartOfCombo(parsedEvent)) { + return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action)); + } + for (let [key, event] of this.actions) { + if (!this.parsedEventIsValid(key, event, parsedEvent)) { continue; } - if (!line.startsWith(LINE_START)) { - continue; - } - const parsedEvent = this.parseLine(line); - 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)) { - return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action)); - } - if (this.isPartOfCombo(parsedEvent)) { - if (parsedEvent.ignore) { - 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); - result.delayed = true; - return result; - } - event.captured = new Date().getTime(); - return 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; + return result; } + event.captured = new Date().getTime(); + return result; } } - isParsedEventValid(key, value, parsed) { + parsedEventIsUnknown(parsedEvent) { + return parsedEvent === undefined || !this.registered.keys.includes(parsedEvent.key) || !this.registered.events.includes(parsedEvent.event) || !this.registered.types.includes(parsedEvent.type.id); + } + parsedEventIsValid(key, value, parsed) { if (value.event !== parsed.event || value.type.id !== parsed.type.id) { return false; } @@ -159,6 +172,9 @@ class Keyfilter { }; } parseLine(line) { + if (line === undefined || line.length === 0 || !line.startsWith(LINE_START)) { + return; + } try { const parts = line.split(','); 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; } isStartOfCombo(parsedEvent) { - if (this.currentCombo !== undefined) { - return false; - } let possibilities = []; for (let [actionKey, actionEvent] of this.actions) { if (actionEvent.combo === undefined || actionEvent.combo.length === 0) { continue; } - if (!actionKey.toUpperCase().startsWith(parsedEvent.key)) { + if (!actionKey.toUpperCase().startsWith(parsedEvent.key) || actionEvent.type.id !== parsedEvent.type.id || actionEvent.event !== parsedEvent.event) { continue; } possibilities.push(JSON.parse(JSON.stringify(actionEvent))); @@ -200,12 +213,10 @@ class Keyfilter { done: [parsedEvent.key], possibilities: possibilities }; + this.setComboTimeout(); return true; } isPartOfCombo(parsedEvent) { - if (this.hasComboTimedOut()) { - this.resetCurrentCombo(); - } if (this.currentCombo === undefined || this.currentCombo.type.id !== parsedEvent.type.id || this.currentCombo.event !== parsedEvent.event) { return false; } @@ -213,7 +224,7 @@ class Keyfilter { for (let index = 0; index < this.currentCombo.possibilities.length; index++) { const possibility = this.currentCombo.possibilities[index]; if (possibility.combo.length === 0) { - break; + continue; } if (possibility.combo[0].toUpperCase() !== parsedEvent.key) { continue; @@ -236,14 +247,29 @@ class Keyfilter { tmp.done = this.currentCombo.done; this.currentCombo = tmp; } + this.setComboTimeout(); return true; } - hasComboTimedOut() { - return global.config?.combos?.delay !== undefined && - this.currentCombo?.timestamp !== undefined && - new Date().getTime() - this.currentCombo.timestamp > global.config?.combos?.delay; + setComboTimeout() { + if (this.currentCombo === undefined || global.config?.combos?.timeout === undefined || isNaN(global.config?.combos?.timeout)) { + return; + } + 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() { + this.clearComboTimeout(); + logger.debug('resetting current combo'); this.currentCombo = undefined; } isValid() { diff --git a/libs/watcher.js b/libs/watcher.js index e8f4425..f0a2a30 100644 --- a/libs/watcher.js +++ b/libs/watcher.js @@ -62,30 +62,13 @@ class Watcher { } logger.debug('adding stdout listener to watcher \'' + this.device + '\'...'); this.process.stdout.on('data', (data) => { - if (this.keyfilter == undefined) { + if (this.keyfilter === undefined) { return; } - let filtered = this.keyfilter.filter(data); - if (filtered === undefined) { - return; + const lines = data.toString().split('\n'); + for (let index = 0; index < lines.length; index++) { + 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() { @@ -155,5 +138,27 @@ class Watcher { .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; \ No newline at end of file