fixed combos
This commit is contained in:
parent
1eb5ed1d5a
commit
5dd86716e9
4 changed files with 123 additions and 87 deletions
|
@ -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",
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue