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

@@ -4,7 +4,8 @@
// "above" / "below". CDP bindings: the keyboard page calls
// __sfuiStripOut(state json) -> systemui __sfuiKbdStrip.show(state); the
// panel calls __sfuiStripPick({ seq, index } json) -> keyboard page
// __sfuiSwipe.remote.pick(seq, index). Only these two messages, validated.
// __sfuiSwipe.remote.pick(seq, index). Only these two messages, validated
// (up to 12 items: the F-keys of keyboard.vr.functionKeys).
// On (re)connects and page reloads the keyboard page re-sends its state; if
// the keyboard page goes away, the panel is hidden.
const SIDES = {
@@ -35,7 +36,7 @@ function forward(side, payload) {
try { msg = JSON.parse(payload); } catch { return; }
if (side === 'steam' && Array.isArray(msg?.items) && typeof msg.seq === 'number') {
show({ seq: msg.seq, visible: !!msg.visible, current: Number(msg.current) | 0,
items: msg.items.slice(0, 8).map((s) => String(s).slice(0, 64)), style: cleanStyle(msg.style),
items: msg.items.slice(0, 12).map((s) => String(s).slice(0, 64)), style: cleanStyle(msg.style),
haptic: Math.max(0, Math.min(5, Number(msg.haptic) | 0)), position: msg.position === 'below' ? 'below' : 'above' });
} else if (side === 'vr' && typeof msg?.seq === 'number' && typeof msg.index === 'number') {
live.steam?.(`window.__sfuiSwipe?.remote?.pick(${msg.seq | 0}, ${msg.index | 0})`);