From 1eb5ed1d5af0df4b3c21774cabf52f461b625d65 Mon Sep 17 00:00:00 2001 From: velvettear Date: Thu, 10 Mar 2022 17:06:44 +0100 Subject: [PATCH] added 'sudo' functionality and partially fixed combos --- README.md | 35 +++++++++++++-------- config.json | 41 +++++++++++++++++++++---- libs/keyfilter.js | 77 ++++++++++++++++++++++++++++++----------------- libs/watcher.js | 4 +-- ninwa.service | 7 +++-- 5 files changed, 112 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index e974247..b36441a 100644 --- a/README.md +++ b/README.md @@ -4,35 +4,44 @@ node input watcher ## requirements -- node.js -- yarn - evtest +- node.js +- [nvm](https://github.com/nvm-sh/nvm) -## setup +## setup (as root) -- clone the project -`git clone https://git.velvettear.de/velvettear/ninwa.git` +- install nvm -- enter the project directory -`cd ninwa` +- to load nvm restart your terminal or `source ~/.nvm/nvm.sh` -- installed required modules -`yarn install` +- clone the project (to '/opt/ninwa') +`git clone https://git.velvettear.de/velvettear/ninwa.git /opt/ninwa` -- modify `config.json` according to your needs +- install and switch to a supported node.js version (automatically done via .nvmrc file) +`nvm install` + +- install the required modules +`npm install` + +- switch back to your system's default node.js version +`nvm deactivate` - execute ninwa -`node ninwa.js` +`nvm run ninwa.js` ## systemd **for security reasons it is highly recommended to not run ninwa with root permissions!** - create a new system user -`useradd -r -s /usr/bin/nologin ninwa` +`useradd -U -r -s /usr/bin/nologin node` + +- make your install of nvm available to the new user +`cp -R ~/.nvm /opt/nvm` +`chown -R node /opt/nvm` - symlink the provided systemd-service file and modify it according to your needs -`ln -s /path/to/ninwa/ninwa.service /etc/systemd/system/ninwa.service` +`ln -s /opt/ninwa/ninwa.service /etc/systemd/system/ninwa.service` - reload systemd-services `systemctl daemon-reload` diff --git a/config.json b/config.json index e5e0ced..ef3c3e5 100644 --- a/config.json +++ b/config.json @@ -13,26 +13,39 @@ "key": "key_f1", "combo": [ "key_f2", + "key_f4" + ], + "event": "EV_KEY", + "type": "keydown", + "delay": 1000, + "command": "combo" + }, + { + "key": "key_f1", + "combo": [ "key_f3" ], "event": "EV_KEY", "type": "keydown", - "delay": 3000, - "command": "example" + "delay": 1000, + "command": "combo2" }, { "key": "key_enter", "type": "keydown", - "command": "example2" + "event": "EV_KEY", + "command": "example" }, { "key": "key_esc", "type": "keyup", - "command": "example" + "event": "EV_KEY", + "command": "example2" }, { "key": "key_space", "type": "keyhold", + "event": "EV_KEY", "delay": 1000, "command": "curl" } @@ -40,10 +53,26 @@ } ], "commands": { + "combo": { + "cmd": "notify-send", + "args": [ + "combo #1", + "first combo" + ], + "sudo": false + }, + "combo2": { + "cmd": "notify-send", + "args": [ + "combo #2", + "second combo" + ], + "sudo": false + }, "example": { "cmd": "notify-send", "args": [ - "example", + "example #1", "first example" ], "sudo": false @@ -51,7 +80,7 @@ "example2": { "cmd": "notify-send", "args": [ - "example2", + "example #2", "second example" ], "sudo": false diff --git a/libs/keyfilter.js b/libs/keyfilter.js index be82ed1..6cde331 100644 --- a/libs/keyfilter.js +++ b/libs/keyfilter.js @@ -10,8 +10,8 @@ const VARIABLE_KEY = '{{ key }}'; const VARIABLE_TYPE = '{{ type }}'; class Keyfilter { - constructor(keys, combos) { - if ((keys === undefined || keys.length === 0) && (combos === undefined || combos.length === 0)) { + constructor(keys) { + if ((keys === undefined || keys.length === 0)) { return; } this.actions = new Map(); @@ -30,7 +30,13 @@ class Keyfilter { type = ACTION_KEYHOLD; break; } - this.actions.set(config.key.toUpperCase(), + 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(); + } + this.actions.set(key, { type: type, event: config.event, @@ -67,7 +73,7 @@ class Keyfilter { if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) { continue; } - if (this.isStartOfCombo(key, event)) { + if (this.isStartOfCombo(key, event, parsedEvent)) { return this.getFilterResult(key, event, 'combo', this.currentCombo); } if (this.isPartOfCombo(parsedEvent)) { @@ -75,7 +81,7 @@ class Keyfilter { continue; } const result = this.getFilterResult(key, event, 'combo', this.currentCombo); - if (this.hasComboFinished()) { + if (this.currentCombo.finished) { this.resetCurrentCombo(); } return result; @@ -92,21 +98,22 @@ class Keyfilter { } } isParsedEventValid(key, value, parsed) { - return key === parsed.key && (value.event === undefined || value.event === parsed.event) && value.type.id === parsed.type; + let keyCheck = key === parsed.key; + 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; } getFilterResult(key, event, extraName, extra) { if (key === undefined || event === undefined) { return; } - let result = - // this.replaceVariables( - { - key: key, - type: event.type.action, - command: event.command, - delay: event.delay - } - // ) + let result = { + key: key, + type: event.type.action, + command: event.command, + delay: event.delay + }; if (extraName !== undefined && extra !== undefined) { result[extraName] = extra; } @@ -129,18 +136,27 @@ class Keyfilter { } return new Date().getTime() - event.captured < event.delay; } - isStartOfCombo(key, event) { - if (this.currentCombo !== undefined || event.combo === undefined) { + isStartOfCombo(key, event, parsedEvent) { + if (event.combo === undefined || 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)) { + continue; + } + possibilities.push(actionEvent.combo); + } this.currentCombo = { key: key, type: event.type, delay: event.delay, timestamp: new Date().getTime(), - done: [key], - remaining: JSON.parse(JSON.stringify(event.combo)), - finished: this.hasComboFinished() + done: [parsedEvent.key], + possibilities: [event.combo] }; return true; } @@ -158,22 +174,27 @@ class Keyfilter { if (this.currentCombo.type.id !== parsedEvent.type) { return false; } - if (this.currentCombo.remaining[0].toUpperCase() !== parsedEvent.key) { + let combos = []; + for (let index = 0; index < this.currentCombo.possibilities.length; index++) { + const possibility = this.currentCombo.possibilities[index]; + if (possibility[0].toUpperCase() !== parsedEvent.key) { + continue; + } + combos.push(possibility); + } + if (combos.length === 0) { return false; } this.currentCombo.key = parsedEvent.key; this.currentCombo.timestamp = new Date().getTime(); this.currentCombo.done.push(parsedEvent.key); - this.currentCombo.remaining.shift(); - this.currentCombo.finished = this.hasComboFinished(); + if (this.currentCombo.possibilities.length === 1) { + this.currentCombo.finished = true; + } return true; } - hasComboFinished() { - return this.currentCombo !== undefined && - this.currentCombo.remaining !== undefined && - this.currentCombo.remaining.length === 0; - } hasComboTimedOut() { + return false; const result = this.currentCombo !== undefined && this.currentCombo.delay !== undefined && this.currentCombo.timestamp !== undefined && diff --git a/libs/watcher.js b/libs/watcher.js index 2825478..6bd26dc 100644 --- a/libs/watcher.js +++ b/libs/watcher.js @@ -16,7 +16,7 @@ class Watcher { for (let key in config) { this[key] = config[key]; } - this.keyfilter = new Keyfilter(config.keys, config.combos); + this.keyfilter = new Keyfilter(config.keys); this.restart = config.restart; this.callback = callback; } @@ -75,7 +75,7 @@ class Watcher { } 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 (next key: \'' + filtered.combo.remaining[0] + '\')'); + 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 + '\')'); return; } logger.debug('captured \'' + filtered.combo.type.action + '\' event for \'' + filtered.combo.key + '\' from watcher \'' + this.device + '\' is the last part of a combo'); diff --git a/ninwa.service b/ninwa.service index 7caddf2..4ec934b 100644 --- a/ninwa.service +++ b/ninwa.service @@ -3,9 +3,10 @@ Description=ninwa (node input watcher) [Service] Type=simple -User=ninwa -Group=ninwa -ExecStart=node /opt/ninwa/ninwa.js +User=node +Group=node +WorkingDirectory=/opt/ninwa +ExecStart=/opt/nvm/nvm-exec node ninwa.js [Install] WantedBy=multi-user.target \ No newline at end of file