diff --git a/config.json b/config.json index ef3c3e5..e0199d2 100644 --- a/config.json +++ b/config.json @@ -3,6 +3,9 @@ "level": "debug", "timestamp": "DD.MM.YYYY HH:mm:ss:SS" }, + "combos": { + "delay": 5000 + }, "watchers": [ { "device": "/dev/input/by-id/usb-Chicony_HP_Elite_USB_Keyboard-event-kbd", @@ -23,7 +26,8 @@ { "key": "key_f1", "combo": [ - "key_f3" + "key_f1", + "key_f2" ], "event": "EV_KEY", "type": "keydown", diff --git a/libs/keyfilter.js b/libs/keyfilter.js index 6cde331..186d93e 100644 --- a/libs/keyfilter.js +++ b/libs/keyfilter.js @@ -1,4 +1,5 @@ -const config = require('../config.json'); +const comboConfig = require('../config.json').combos; +const commandConfig = require('../config.json').commands; const LINE_START = 'Event: time'; @@ -6,30 +7,52 @@ const ACTION_KEYUP = { id: 0, action: 'keyup' }; const ACTION_KEYDOWN = { id: 1, action: 'keydown' }; const ACTION_KEYHOLD = { id: 2, action: 'keyhold' }; -const VARIABLE_KEY = '{{ key }}'; -const VARIABLE_TYPE = '{{ type }}'; - class Keyfilter { - constructor(keys) { - if ((keys === undefined || keys.length === 0)) { + constructor(config) { + if (config === undefined || config.length === 0) { return; } this.actions = new Map(); - for (let index = 0; index < keys.length; index++) { - this.setAction(keys[index]); + for (let index = 0; index < config.length; index++) { + this.setAction(config[index]); } this.currentCombo = undefined; } - setAction(config) { - let type = ACTION_KEYDOWN; - switch (config.type.toLowerCase()) { - case ACTION_KEYUP.action: - type = ACTION_KEYUP; - break; - case ACTION_KEYHOLD.action: - type = ACTION_KEYHOLD; - break; + getCommand(command) { + if (command === undefined || commandConfig === undefined) { + return; } + const result = commandConfig[command]; + if (result === undefined) { + return; + } + result.name = command; + return result; + } + getActionType(type) { + let result = ACTION_KEYDOWN; + if (isNaN(type)) { + switch (type.toLowerCase()) { + case ACTION_KEYUP.action: + result = ACTION_KEYUP; + break; + case ACTION_KEYHOLD.action: + result = ACTION_KEYHOLD; + break; + } + } else { + switch (type) { + case ACTION_KEYUP.id: + result = ACTION_KEYUP; + break; + case ACTION_KEYHOLD.id: + result = ACTION_KEYHOLD; + break; + } + } + return result; + } + setAction(config) { let key = config.key.toUpperCase(); if (config.combo !== undefined && config.combo.length > 0) { const tmp = JSON.parse(JSON.stringify(config.combo)); @@ -38,9 +61,9 @@ class Keyfilter { } this.actions.set(key, { - type: type, + type: this.getActionType(config.type), event: config.event, - command: getCommand(config.command), + command: this.getCommand(config.command), combo: config.combo, delay: function () { if (config.combo === undefined) { @@ -73,27 +96,25 @@ class Keyfilter { if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) { continue; } - if (this.isStartOfCombo(key, event, parsedEvent)) { - return this.getFilterResult(key, event, 'combo', this.currentCombo); + if (this.isStartOfCombo(parsedEvent)) { + return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action)); } if (this.isPartOfCombo(parsedEvent)) { if (parsedEvent.ignore) { continue; } - const result = this.getFilterResult(key, event, 'combo', this.currentCombo); - if (this.currentCombo.finished) { - this.resetCurrentCombo(); - } - return result; + return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action)); } if (!this.isParsedEventValid(key, event, parsedEvent)) { continue; } if (this.shouldBeDelayed(event)) { - return this.getFilterResult(key, event, 'delayed', true); + const result = this.getFilterResult(parsedEvent.key, event.type.action, event.command); + result.delayed = true; + return result; } event.captured = new Date().getTime(); - return this.getFilterResult(key, event); + return this.getFilterResult(parsedEvent.key, event.type.action, event.command); } } } @@ -102,29 +123,39 @@ class Keyfilter { if (value.combo !== undefined && value.combo.length > 0) { keyCheck = key.includes(parsed.key); } - return keyCheck && (value.event === undefined || value.event === parsed.event) && value.type.id === parsed.type; + return keyCheck && (value.event === undefined || value.event === parsed.event) && value.type.id === parsed.type.id; } - getFilterResult(key, event, extraName, extra) { - if (key === undefined || event === undefined) { + setComboResult(result) { + if (result === undefined || this.currentCombo === undefined) { return; } - let result = { - key: key, - type: event.type.action, - command: event.command, - delay: event.delay - }; - if (extraName !== undefined && extra !== undefined) { - result[extraName] = extra; + if (!this.currentCombo.finished) { + result.combo = { possibilities: this.currentCombo.possibilities.length }; + } else { + result.combo = { + finished: true, + done: this.currentCombo.done + }; + result.command = this.currentCombo.command; } - return result; + return result + } + getFilterResult(key, type, command) { + if (key === undefined || type === undefined) { + return; + } + return { + key: key, + type: type, + command: command + }; } parseLine(line) { try { const parts = line.split(','); const event = parts[1].substring(parts[1].indexOf('(') + 1, parts[1].lastIndexOf(')')); const key = parts[2].substring(parts[2].indexOf('(') + 1, parts[2].indexOf(')')); - const type = parseInt(parts[3].split(' ').pop()); + const type = this.getActionType(parseInt(parts[3].split(' ').pop())); return { event: event, key: key, type: type }; } catch (err) { return; @@ -136,8 +167,8 @@ class Keyfilter { } return new Date().getTime() - event.captured < event.delay; } - isStartOfCombo(key, event, parsedEvent) { - if (event.combo === undefined || this.currentCombo !== undefined) { + isStartOfCombo(parsedEvent) { + if (this.currentCombo !== undefined) { return false; } let possibilities = []; @@ -146,17 +177,20 @@ class Keyfilter { continue; } if (!actionKey.toUpperCase().startsWith(parsedEvent.key)) { - continue; + continue; } - possibilities.push(actionEvent.combo); + possibilities.push(JSON.parse(JSON.stringify(actionEvent))); + } + if (possibilities.length === 0) { + return false; } this.currentCombo = { - key: key, - type: event.type, - delay: event.delay, + key: parsedEvent.key.toUpperCase(), timestamp: new Date().getTime(), + type: parsedEvent.type, + event: parsedEvent.event, done: [parsedEvent.key], - possibilities: [event.combo] + possibilities: possibilities }; return true; } @@ -164,42 +198,49 @@ class Keyfilter { if (this.hasComboTimedOut()) { this.resetCurrentCombo(); } - if (this.currentCombo === undefined) { + if (this.currentCombo === undefined || this.currentCombo.type.id !== parsedEvent.type.id || this.currentCombo.event !== parsedEvent.event) { return false; } - if (this.currentCombo.done.includes(parsedEvent.key)) { - parsedEvent.ignore = true; - return true; - } - if (this.currentCombo.type.id !== parsedEvent.type) { - return false; - } - let combos = []; + // if (this.currentCombo.done.includes(parsedEvent.key)) { + // parsedEvent.ignore = true; + // return true; + // } + let possibilities = []; for (let index = 0; index < this.currentCombo.possibilities.length; index++) { const possibility = this.currentCombo.possibilities[index]; - if (possibility[0].toUpperCase() !== parsedEvent.key) { + if (possibility.combo.length === 0) { + break; + } + if (possibility.combo[0].toUpperCase() !== parsedEvent.key) { continue; } - combos.push(possibility); + possibility.combo.shift(); + possibilities.push(possibility); } - if (combos.length === 0) { + if (possibilities.length === 0) { return false; } - this.currentCombo.key = parsedEvent.key; this.currentCombo.timestamp = new Date().getTime(); this.currentCombo.done.push(parsedEvent.key); - if (this.currentCombo.possibilities.length === 1) { - this.currentCombo.finished = true; + this.currentCombo.possibilities = possibilities; + this.currentCombo.finished = false; + if (this.currentCombo.possibilities.length === 1 && this.currentCombo.possibilities[0].combo.length === 0) { + const tmp = this.currentCombo.possibilities[0]; + delete tmp.combo; + tmp.finished = true; + tmp.timestamp = this.currentCombo.timestamp; + tmp.done = this.currentCombo.done; + this.currentCombo = tmp; } return true; } hasComboTimedOut() { return false; - const result = this.currentCombo !== undefined && - this.currentCombo.delay !== undefined && + return comboConfig !== undefined && + comboConfig.delay !== undefined && + this.currentCombo !== undefined && this.currentCombo.timestamp !== undefined && - new Date().getTime() - this.currentCombo.timestamp > this.currentCombo.delay; - return result; + new Date().getTime() - this.currentCombo.timestamp > comboConfig.delay; } resetCurrentCombo() { this.currentCombo = undefined; @@ -210,17 +251,7 @@ class Keyfilter { } -function getCommand(command) { - if (command === undefined || config.commands === undefined) { - return; - } - const result = config.commands[command]; - if (result === undefined) { - return; - } - result.name = command; - return result; -} + module.exports = Keyfilter; \ No newline at end of file diff --git a/libs/watcher.js b/libs/watcher.js index 6bd26dc..e8f4425 100644 --- a/libs/watcher.js +++ b/libs/watcher.js @@ -69,19 +69,20 @@ class Watcher { if (filtered === undefined) { return; } + logger.debug('handling captured \'' + filtered.type + '\' event for key \'' + filtered.key + '\' from watcher \'' + this.device + '\'...'); if (filtered.delayed) { - logger.debug('delaying captured \'' + filtered.type + '\' event for \'' + filtered.key + '\' from watcher \'' + this.device + '\''); + logger.debug('delaying captured event...'); return; } if (filtered.combo) { if (!filtered.combo.finished) { - logger.debug('captured \'' + filtered.combo.type.action + '\' event for \'' + filtered.combo.key + '\' from watcher \'' + this.device + '\' is part of a combo (possibilities: \'' + filtered.combo.possibilities.length + '\')'); + logger.debug('captured event is part of ' + filtered.combo.possibilities + ' possible combo(s) and not yet finished') return; } - logger.debug('captured \'' + filtered.combo.type.action + '\' event for \'' + filtered.combo.key + '\' from watcher \'' + this.device + '\' is the last part of a combo'); + logger.debug('captured event finished combo \'' + filtered.combo.done.toString().toUpperCase()+ '\''); } this.keyfilter.resetCurrentCombo(); - logger.info('executing command \'' + filtered.command.name + '\' registered for captured \'' + filtered.type + '\' event for \'' + filtered.key + '\' from watcher \'' + this.device + '\''); + 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); diff --git a/libs/watchers.js b/libs/watchers.js index 759583e..f54d044 100644 --- a/libs/watchers.js +++ b/libs/watchers.js @@ -4,11 +4,11 @@ const Watcher = require('./watcher.js'); const watchers = []; async function initialize(config) { - if (config == undefined) { + if (config === undefined) { throw new Error('could not initialize watchers, no config defined'); } - if (config.length == 0) { - throw new Error('no watchers in config \'' + global.config.path + '\' defined'); + if (config.length === 0) { + throw new Error('no watchers defined'); } for (var index = 0; index < config.length; index++) { var watcher = new Watcher(config[index], watcherCallback);