From 514f073a6e873e26aafafac0052056bf0b9ea734 Mon Sep 17 00:00:00 2001 From: Pierre Kisters <1524059+lhns@users.noreply.github.com> Date: Thu, 1 Oct 2026 04:07:00 +0200 Subject: [PATCH] VR keyboard extra keys: the helper holds only Ctrl/Alt, checked in allowlist.mjs down:/up: were checked with MODKEY[arg], which inherited names like constructor or toString pass (xdotool then got a junk key name). The check is now allowedMod() in allowlist.mjs, covered by its test. --- modules/vr-keyboard-extra-keys/allowlist.mjs | 4 +++- modules/vr-keyboard-extra-keys/tests/allowlist.test.mjs | 8 ++++++-- modules/vr-keyboard-extra-keys/xdotool-helper.mjs | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/vr-keyboard-extra-keys/allowlist.mjs b/modules/vr-keyboard-extra-keys/allowlist.mjs index 83c0404..b316a87 100644 --- a/modules/vr-keyboard-extra-keys/allowlist.mjs +++ b/modules/vr-keyboard-extra-keys/allowlist.mjs @@ -7,7 +7,7 @@ // shift) with one key; or shift+Tab // type: one character Steam's key emulation can't produce: non-ASCII // (äöü߀§°´…) or | @ { [ ] } \ ~ ^ ` -// down: / up: hold/release ctrl or alt (checked in the helper) +// down: / up: hold/release ctrl or alt const SPECIAL = new Set(['Escape', 'Delete', 'Home', 'End', 'Prior', 'Next', 'Left', 'Right', 'Up', 'Down', ...Array.from({ length: 12 }, (_, i) => `F${i + 1}`)]); const KEY = /^([a-z0-9]|space|BackSpace|Tab|period|comma|minus|plus|numbersign|less|slash|ssharp|udiaeresis|odiaeresis|adiaeresis)$/; @@ -27,3 +27,5 @@ export function allowedChar(c) { const cp = c.codePointAt(0); return (cp > 0xa0 && !/\s|\p{C}/u.test(c)) || '|@{[]}\\~^`'.includes(c); } + +export const allowedMod = (m) => m === 'ctrl' || m === 'alt'; diff --git a/modules/vr-keyboard-extra-keys/tests/allowlist.test.mjs b/modules/vr-keyboard-extra-keys/tests/allowlist.test.mjs index 47fe98a..8fce5ea 100644 --- a/modules/vr-keyboard-extra-keys/tests/allowlist.test.mjs +++ b/modules/vr-keyboard-extra-keys/tests/allowlist.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import { pathToFileURL } from 'node:url'; -const { allowedCombo, allowedChar } = await import(pathToFileURL(process.argv[2]).href); +const { allowedCombo, allowedChar, allowedMod } = await import(pathToFileURL(process.argv[2]).href); const accept = ['shift+Tab', 'ctrl+Tab', 'ctrl+shift+Tab', 'ctrl+shift+t', 'alt+f', 'shift+Left', 'Escape', 'Delete', 'shift+End', 'ctrl+BackSpace']; // F-keys (keyboard.vr.functionKeys): any of them with any modifiers. @@ -14,8 +14,12 @@ const reject = ['Tab', 'shift+a', 'a', 'Return', 'shift+Return', 'ctrl+Return', 'F0', 'F13', 'F24', 'f5', 'super+F4', 'ctrl+ctrl+F5', 'F5+Return', 'ctrl+alt+shift+Return']; const acceptChar = ['ä', '€', '|', '@', '\\', '`']; const rejectChar = ['a', 'A', '1', ' ', '\n', '\t', 'ab', ' ']; +const acceptMod = ['ctrl', 'alt']; +const rejectMod = ['shift', 'super', 'Control_L', 'constructor', '__proto__', 'toString', '']; for (const c of accept) assert.equal(allowedCombo(c), true, `accepts key:${c}`); for (const c of reject) assert.equal(allowedCombo(c), false, `rejects key:${c}`); for (const c of acceptChar) assert.equal(allowedChar(c), true, `accepts type:${c}`); for (const c of rejectChar) assert.equal(allowedChar(c), false, `rejects type:${JSON.stringify(c)}`); -console.log(`allowlist: ${accept.length + reject.length + acceptChar.length + rejectChar.length} cases passed`); +for (const m of acceptMod) assert.equal(allowedMod(m), true, `accepts down:${m}`); +for (const m of rejectMod) assert.equal(allowedMod(m), false, `rejects down:${m}`); +console.log(`allowlist: ${accept.length + reject.length + acceptChar.length + rejectChar.length + acceptMod.length + rejectMod.length} cases passed`); diff --git a/modules/vr-keyboard-extra-keys/xdotool-helper.mjs b/modules/vr-keyboard-extra-keys/xdotool-helper.mjs index 4be6142..6aa8e0d 100644 --- a/modules/vr-keyboard-extra-keys/xdotool-helper.mjs +++ b/modules/vr-keyboard-extra-keys/xdotool-helper.mjs @@ -8,7 +8,7 @@ // usage: node xdotool-helper.mjs [xdotool] import { readFileSync } from 'node:fs'; import { execFile } from 'node:child_process'; -import { allowedCombo, allowedChar } from './allowlist.mjs'; +import { allowedCombo, allowedChar, allowedMod } from './allowlist.mjs'; const [, , patchPath, unpatchPath, xdotool = 'xdotool'] = process.argv; const PATCH = readFileSync(patchPath, 'utf8'); @@ -37,7 +37,7 @@ function handle(msg) { return xdo(['key', '--', [...parts.filter((m) => !held.has(m)), key].join('+')]); } if (op === 'type' && allowedChar(arg)) return xdo(['type', '--', arg]); - if ((op === 'down' || op === 'up') && MODKEY[arg]) { + if ((op === 'down' || op === 'up') && allowedMod(arg)) { if (op === 'down') held.add(arg); else held.delete(arg); return xdo([op === 'down' ? 'keydown' : 'keyup', MODKEY[arg]]); }