fixed combos

This commit is contained in:
Daniel Sommer 2022-03-11 15:47:48 +01:00
parent 1eb5ed1d5a
commit 5dd86716e9
4 changed files with 123 additions and 87 deletions

View file

@ -3,6 +3,9 @@
"level": "debug", "level": "debug",
"timestamp": "DD.MM.YYYY HH:mm:ss:SS" "timestamp": "DD.MM.YYYY HH:mm:ss:SS"
}, },
"combos": {
"delay": 5000
},
"watchers": [ "watchers": [
{ {
"device": "/dev/input/by-id/usb-Chicony_HP_Elite_USB_Keyboard-event-kbd", "device": "/dev/input/by-id/usb-Chicony_HP_Elite_USB_Keyboard-event-kbd",
@ -23,7 +26,8 @@
{ {
"key": "key_f1", "key": "key_f1",
"combo": [ "combo": [
"key_f3" "key_f1",
"key_f2"
], ],
"event": "EV_KEY", "event": "EV_KEY",
"type": "keydown", "type": "keydown",

View file

@ -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'; 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_KEYDOWN = { id: 1, action: 'keydown' };
const ACTION_KEYHOLD = { id: 2, action: 'keyhold' }; const ACTION_KEYHOLD = { id: 2, action: 'keyhold' };
const VARIABLE_KEY = '{{ key }}';
const VARIABLE_TYPE = '{{ type }}';
class Keyfilter { class Keyfilter {
constructor(keys) { constructor(config) {
if ((keys === undefined || keys.length === 0)) { if (config === undefined || config.length === 0) {
return; return;
} }
this.actions = new Map(); this.actions = new Map();
for (let index = 0; index < keys.length; index++) { for (let index = 0; index < config.length; index++) {
this.setAction(keys[index]); this.setAction(config[index]);
} }
this.currentCombo = undefined; this.currentCombo = undefined;
} }
setAction(config) { getCommand(command) {
let type = ACTION_KEYDOWN; if (command === undefined || commandConfig === undefined) {
switch (config.type.toLowerCase()) { 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: case ACTION_KEYUP.action:
type = ACTION_KEYUP; result = ACTION_KEYUP;
break; break;
case ACTION_KEYHOLD.action: case ACTION_KEYHOLD.action:
type = ACTION_KEYHOLD; result = ACTION_KEYHOLD;
break; 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(); 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));
@ -38,9 +61,9 @@ class Keyfilter {
} }
this.actions.set(key, this.actions.set(key,
{ {
type: type, type: this.getActionType(config.type),
event: config.event, event: config.event,
command: getCommand(config.command), command: this.getCommand(config.command),
combo: config.combo, combo: config.combo,
delay: function () { delay: function () {
if (config.combo === undefined) { if (config.combo === undefined) {
@ -73,27 +96,25 @@ class Keyfilter {
if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) { if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) {
continue; continue;
} }
if (this.isStartOfCombo(key, event, parsedEvent)) { if (this.isStartOfCombo(parsedEvent)) {
return this.getFilterResult(key, event, 'combo', this.currentCombo); return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action));
} }
if (this.isPartOfCombo(parsedEvent)) { if (this.isPartOfCombo(parsedEvent)) {
if (parsedEvent.ignore) { if (parsedEvent.ignore) {
continue; continue;
} }
const result = this.getFilterResult(key, event, 'combo', this.currentCombo); return this.setComboResult(this.getFilterResult(parsedEvent.key, parsedEvent.type.action));
if (this.currentCombo.finished) {
this.resetCurrentCombo();
}
return result;
} }
if (!this.isParsedEventValid(key, event, parsedEvent)) { if (!this.isParsedEventValid(key, event, parsedEvent)) {
continue; continue;
} }
if (this.shouldBeDelayed(event)) { 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(); 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) { if (value.combo !== undefined && value.combo.length > 0) {
keyCheck = key.includes(parsed.key); 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) { setComboResult(result) {
if (key === undefined || event === undefined) { if (result === undefined || this.currentCombo === undefined) {
return; return;
} }
let result = { if (!this.currentCombo.finished) {
key: key, result.combo = { possibilities: this.currentCombo.possibilities.length };
type: event.type.action, } else {
command: event.command, result.combo = {
delay: event.delay finished: true,
done: this.currentCombo.done
}; };
if (extraName !== undefined && extra !== undefined) { result.command = this.currentCombo.command;
result[extraName] = extra;
} }
return result; return result
}
getFilterResult(key, type, command) {
if (key === undefined || type === undefined) {
return;
}
return {
key: key,
type: type,
command: command
};
} }
parseLine(line) { parseLine(line) {
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(')'));
const key = parts[2].substring(parts[2].indexOf('(') + 1, parts[2].indexOf(')')); 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 }; return { event: event, key: key, type: type };
} catch (err) { } catch (err) {
return; return;
@ -136,8 +167,8 @@ class Keyfilter {
} }
return new Date().getTime() - event.captured < event.delay; return new Date().getTime() - event.captured < event.delay;
} }
isStartOfCombo(key, event, parsedEvent) { isStartOfCombo(parsedEvent) {
if (event.combo === undefined || this.currentCombo !== undefined) { if (this.currentCombo !== undefined) {
return false; return false;
} }
let possibilities = []; let possibilities = [];
@ -148,15 +179,18 @@ class Keyfilter {
if (!actionKey.toUpperCase().startsWith(parsedEvent.key)) { 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 = { this.currentCombo = {
key: key, key: parsedEvent.key.toUpperCase(),
type: event.type,
delay: event.delay,
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
type: parsedEvent.type,
event: parsedEvent.event,
done: [parsedEvent.key], done: [parsedEvent.key],
possibilities: [event.combo] possibilities: possibilities
}; };
return true; return true;
} }
@ -164,42 +198,49 @@ class Keyfilter {
if (this.hasComboTimedOut()) { if (this.hasComboTimedOut()) {
this.resetCurrentCombo(); this.resetCurrentCombo();
} }
if (this.currentCombo === undefined) { if (this.currentCombo === undefined || this.currentCombo.type.id !== parsedEvent.type.id || this.currentCombo.event !== parsedEvent.event) {
return false; return false;
} }
if (this.currentCombo.done.includes(parsedEvent.key)) { // if (this.currentCombo.done.includes(parsedEvent.key)) {
parsedEvent.ignore = true; // parsedEvent.ignore = true;
return true; // return true;
} // }
if (this.currentCombo.type.id !== parsedEvent.type) { let possibilities = [];
return false;
}
let combos = [];
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[0].toUpperCase() !== parsedEvent.key) { if (possibility.combo.length === 0) {
break;
}
if (possibility.combo[0].toUpperCase() !== parsedEvent.key) {
continue; continue;
} }
combos.push(possibility); possibility.combo.shift();
possibilities.push(possibility);
} }
if (combos.length === 0) { if (possibilities.length === 0) {
return false; return false;
} }
this.currentCombo.key = parsedEvent.key;
this.currentCombo.timestamp = new Date().getTime(); this.currentCombo.timestamp = new Date().getTime();
this.currentCombo.done.push(parsedEvent.key); this.currentCombo.done.push(parsedEvent.key);
if (this.currentCombo.possibilities.length === 1) { this.currentCombo.possibilities = possibilities;
this.currentCombo.finished = true; 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; return true;
} }
hasComboTimedOut() { hasComboTimedOut() {
return false; return false;
const result = this.currentCombo !== undefined && return comboConfig !== undefined &&
this.currentCombo.delay !== undefined && comboConfig.delay !== undefined &&
this.currentCombo !== undefined &&
this.currentCombo.timestamp !== undefined && this.currentCombo.timestamp !== undefined &&
new Date().getTime() - this.currentCombo.timestamp > this.currentCombo.delay; new Date().getTime() - this.currentCombo.timestamp > comboConfig.delay;
return result;
} }
resetCurrentCombo() { resetCurrentCombo() {
this.currentCombo = undefined; 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; module.exports = Keyfilter;

View file

@ -69,19 +69,20 @@ class Watcher {
if (filtered === undefined) { if (filtered === undefined) {
return; return;
} }
logger.debug('handling captured \'' + filtered.type + '\' event for key \'' + filtered.key + '\' from watcher \'' + this.device + '\'...');
if (filtered.delayed) { if (filtered.delayed) {
logger.debug('delaying captured \'' + filtered.type + '\' event for \'' + filtered.key + '\' from watcher \'' + this.device + '\''); logger.debug('delaying captured event...');
return; return;
} }
if (filtered.combo) { if (filtered.combo) {
if (!filtered.combo.finished) { 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; 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(); 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) cli.execute(filtered.command.cmd, filtered.command.args, filtered.command.sudo)
.then(logger.info) .then(logger.info)
.catch(logger.error); .catch(logger.error);

View file

@ -4,11 +4,11 @@ const Watcher = require('./watcher.js');
const watchers = []; const watchers = [];
async function initialize(config) { async function initialize(config) {
if (config == undefined) { if (config === undefined) {
throw new Error('could not initialize watchers, no config defined'); throw new Error('could not initialize watchers, no config defined');
} }
if (config.length == 0) { if (config.length === 0) {
throw new Error('no watchers in config \'' + global.config.path + '\' defined'); throw new Error('no watchers defined');
} }
for (var index = 0; index < config.length; index++) { for (var index = 0; index < config.length; index++) {
var watcher = new Watcher(config[index], watcherCallback); var watcher = new Watcher(config[index], watcherCallback);