VR keyboard F-keys: F1-F12 in the suggestion strip while AltGr is active

keyboard.vr.functionKeys.enable (default off): while AltGr (Fn on layouts
without AltGr) is one-shot, locked or held, the strip shows F1-F12 instead
of the suggestions. A tap types the extra key VKX_F<n>, which the extra
keys' patch presses with xdotool together with the active Ctrl/Alt/Shift
(Alt+F4, Ctrl+F5) and releases the toggles; the text model resets, like
for Esc. The suggestions are kept as they are meanwhile, so when AltGr goes
off the strip shows the same items and selection again.

- vr-keyboard/function-keys.js: what the strip shows, the F-key keys.
- patch.js (VERSION 27): the strip's view, F-key picks, AltGr changes via
  a componentDidUpdate on the keyboard instance (and the poll).
- Allowlist: F1-F12 with any modifiers; Enter still refused.
- relay.mjs: up to 12 strip items.
- Assertions: needs keyboard.vr.enable, extraKeys.enable and a strip
  position "above"/"below" ("inside" would hide the AltGr number row).
- Tests: function-keys.test.mjs (switching and restoring, keys against
  the allowlist, text model), allowlist F-key cases.
This commit is contained in:
Pierre Kisters committed 2026-10-01 03:53:01 +02:00
1 parent af32cf72ce
commit aa8ccc67c9
11 files changed
+238 -32

No files matched your search

+5 -3
View File
@@ -1,13 +1,15 @@
// Allowlist of xdotool-helper.mjs: requests come from Steam's UI JS, so the
// helper must not be able to type ASCII text or press Enter on its behalf.
// Accepted:
// key:<combo> the extra keys (Esc, Del, Home, End, PgUp/PgDn, arrows),
// optionally with modifiers; ctrl and/or alt (+ optional
// key:<combo> the extra keys (Esc, Del, Home, End, PgUp/PgDn, arrows,
// F1-F12 from keyboard.vr.functionKeys), optionally with
// modifiers; ctrl and/or alt (+ optional
// shift) with one key; or shift+Tab
// type:<char> one character Steam's key emulation can't produce: non-ASCII
// (äöü߀§°´…) or | @ { [ ] } \ ~ ^ `
// down:<mod> / up:<mod> hold/release ctrl or alt (checked in the helper)
const SPECIAL = new Set(['Escape', 'Delete', 'Home', 'End', 'Prior', 'Next', 'Left', 'Right', 'Up', 'Down']);
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)$/;
export function allowedCombo(combo) {
@@ -6,8 +6,12 @@ import { pathToFileURL } from 'node:url';
const { allowedCombo, allowedChar } = 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.
const mods = [[], ['ctrl'], ['alt'], ['shift'], ['ctrl', 'alt'], ['ctrl', 'shift'], ['alt', 'shift'], ['ctrl', 'alt', 'shift']];
for (let n = 1; n <= 12; n++) for (const m of mods) accept.push([...m, `F${n}`].join('+'));
const reject = ['Tab', 'shift+a', 'a', 'Return', 'shift+Return', 'ctrl+Return', 'shift+BackSpace',
'shift+shift+Tab', 'super+Tab', 'shift+space', 'ctrl+a+b'];
'shift+shift+Tab', 'super+Tab', 'shift+space', 'ctrl+a+b',
'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', ' '];
for (const c of accept) assert.equal(allowedCombo(c), true, `accepts key:${c}`);