VR keyboard: cleanup of the controller bridge, gesture input and docs

- gesture-input.js: drop the unused mode getter and release().
- relay.mjs: both sides have a binding; no condition.
- hub.js: the frame cadence is documented once (bridge-patch.js); errors
  listed.
- docs: the bridge's idle cost and "one word at a time" stated once;
  twoHanded's README row points to the docs; rewrapped lines.
This commit is contained in:
Pierre Kisters committed 2026-10-01 04:01:00 +02:00
1 parent 605e42ffa1
commit 2442527514
6 files changed
+16 -19

No files matched your search

+1 -1
View File
@@ -241,7 +241,7 @@ which: [Repository layout](docs/development.md).
| `steamFrame.keyboard.vr.extraKeys.enable` | bool | `false` | VR keyboard with Esc/Ctrl/Alt, arrows, real chords, AltGr/non-ASCII. | | `steamFrame.keyboard.vr.extraKeys.enable` | bool | `false` | VR keyboard with Esc/Ctrl/Alt, arrows, real chords, AltGr/non-ASCII. |
| `steamFrame.keyboard.vr.enable` | bool | `false` | Swipe typing, suggestions and Backspace drag on the VR keyboard; the sub-features below are on by default, see [VR keyboard](docs/keyboard.md#swipe-and-suggestions). | | `steamFrame.keyboard.vr.enable` | bool | `false` | Swipe typing, suggestions and Backspace drag on the VR keyboard; the sub-features below are on by default, see [VR keyboard](docs/keyboard.md#swipe-and-suggestions). |
| `steamFrame.keyboard.vr.swipe.enable` | bool | `true` | Swipe typing. | | `steamFrame.keyboard.vr.swipe.enable` | bool | `true` | Swipe typing. |
| `steamFrame.keyboard.vr.swipe.twoHanded` | bool | `true` | Swipes also with both lasers on the keyboard: the path from the pressing controller's pose (the controller bridge, sampling during a press while the keyboard is shown). | | `steamFrame.keyboard.vr.swipe.twoHanded` | bool | `true` | Swipes also with both lasers on the keyboard: the path from the pressing controller's pose, see [VR keyboard](docs/keyboard.md#swipe-and-suggestions). |
| `steamFrame.keyboard.vr.dictionary.languages` | list of submodules | layout language + English | `{ language; hunspell; words; frequencyOffset; keepFrequentAbove; }`: wordfreq language, `pkgs.hunspellDicts` name (or `null`), most frequent words taken, zipf offset, keep words Hunspell rejects from this zipf on (default `4.0`). Default: the `keyboard.layout` language (de, fr, es, it, nl, pt, sv; 60000) + English (40000, `-0.3`), else English (60000). | | `steamFrame.keyboard.vr.dictionary.languages` | list of submodules | layout language + English | `{ language; hunspell; words; frequencyOffset; keepFrequentAbove; }`: wordfreq language, `pkgs.hunspellDicts` name (or `null`), most frequent words taken, zipf offset, keep words Hunspell rejects from this zipf on (default `4.0`). Default: the `keyboard.layout` language (de, fr, es, it, nl, pt, sv; 60000) + English (40000, `-0.3`), else English (60000). |
| `steamFrame.keyboard.vr.dictionary.contractions` | bool | `true` | Words with apostrophes (`couldn't`, `geht's`), swiped by their letters. | | `steamFrame.keyboard.vr.dictionary.contractions` | bool | `true` | Words with apostrophes (`couldn't`, `geht's`), swiped by their letters. |
| `steamFrame.keyboard.vr.dictionary.extraWords` | list of str | `[ ]` | Words always included, casing as given. | | `steamFrame.keyboard.vr.dictionary.extraWords` | list of str | `[ ]` | Words always included, casing as given. |
+6 -7
View File
@@ -2,7 +2,8 @@
Three patches of Steam's VR keyboard: [extra keys](#extra-keys), Three patches of Steam's VR keyboard: [extra keys](#extra-keys),
[swipe and suggestions](#swipe-and-suggestions) and [swipe and suggestions](#swipe-and-suggestions) and
[touch typing](#touch-typing). They work together or alone. Options: [README, Options](../README.md#options) (`keyboard.vr.*`). [touch typing](#touch-typing). They work together or alone. Options:
[README, Options](../README.md#options) (`keyboard.vr.*`).
The keyboard layout of the Steam session itself is a separate fix The keyboard layout of the Steam session itself is a separate fix
([keyboard layout](session.md#keyboard-layout)). ([keyboard layout](session.md#keyboard-layout)).
@@ -184,10 +185,8 @@ has the letters.
30 px; the trigger as a tiebreak), and while its laser events pause 30 px; the trigger as a tiebreak), and while its laser events pause
(60 ms) the path comes from that laser's hit per frame. The bridge (60 ms) the path comes from that laser's hit per frame. The bridge
samples at full rate from the press to the release (asked for by the samples at full rate from the press to the release (asked for by the
patch) and costs nothing while the keyboard is hidden. Without fresh patch). Without fresh frames (option off, relay or SteamVR gone) only
frames (option off, relay or SteamVR gone) only laser events count, as laser events count, as before.
before. A second press during a swipe is Steam's (a tap): one text
field, one word at a time.
- F-keys: `function-keys.js` decides what the strip shows; the suggestion - F-keys: `function-keys.js` decides what the strip shows; the suggestion
state is kept as it is while F1–F12 are shown. AltGr changes reach the state is kept as it is while F1–F12 are shown. AltGr changes reach the
strip through a `componentDidUpdate` on the keyboard instance (with a strip through a `componentDidUpdate` on the keyboard instance (with a
@@ -231,8 +230,8 @@ the trigger.
- A haptic tick on contact (`haptics`), routed by SteamVR like the - A haptic tick on contact (`haptics`), routed by SteamVR like the
keyboard's other ticks. keyboard's other ticks.
**Caveats:** moving the keyboard pauses touches for 0.3 s. The extra keys' Delete repeat and the swipe patch's gestures react **Caveats:** moving the keyboard pauses touches for 0.3 s. The extra keys'
to the laser only. Depends on Steam and SteamVR UI internals (see Delete repeat and the swipe patch's gestures react to the laser only. Depends on Steam and SteamVR UI internals (see
[after a Steam update](ui-patches.md#after-a-steam-update)). [after a Steam update](ui-patches.md#after-a-steam-update)).
**Remove when** Steam's VR keyboard gets touch input. **Remove when** Steam's VR keyboard gets touch input.
+3 -4
View File
@@ -3,17 +3,16 @@
// extraArgs (like lib/hooks.js): installs window.__sfuiControllers unless an // extraArgs (like lib/hooks.js): installs window.__sfuiControllers unless an
// equal or newer VERSION is there (a newer one takes over the subscribers) // equal or newer VERSION is there (a newer one takes over the subscribers)
// and evaluates to it. relay.mjs calls frame(f) with bridge-patch.js's // and evaluates to it. relay.mjs calls frame(f) with bridge-patch.js's
// frames and lost() when the SteamVR side goes away. // frames (cadence there) and lost() when the SteamVR side goes away.
// subscribe(fn) -> unsubscribe fn(frame) per frame, fn(null) when lost // subscribe(fn) -> unsubscribe fn(frame) per frame, fn(null) when lost
// last the last frame (null: none / lost) // last the last frame (null: none / lost)
// demand(ms) full rate for the next ms (0: end), e.g. // demand(ms) full rate for the next ms (0: end), e.g.
// during a laser press; via the relay's // during a laser press; via the relay's
// CDP binding __sfuiCtlIn // CDP binding __sfuiCtlIn
// errors { count, last } of throwing subscribers
// Each frame's hand points (tip, ray) get the keyboard page's px: x, y // Each frame's hand points (tip, ray) get the keyboard page's px: x, y
// (CSS px of the keyboard popup) and onKeyboard (inside the page), if the // (CSS px of the keyboard popup) and onKeyboard (inside the page), if the
// keyboard popup exists. Frames come at up to ~90 Hz while a hand's tip is // keyboard popup exists.
// within 10 cm of the keyboard or its trigger is pulled, else every 1 s, and
// only while SteamVR shows the keyboard (then one with keyboard null).
(() => { (() => {
const VERSION = 2; const VERSION = 2;
const G = globalThis; const G = globalThis;
+1 -1
View File
@@ -56,7 +56,7 @@ async function connect(side) {
const closed = new Promise((res) => { ws.onclose = res; }); const closed = new Promise((res) => { ws.onclose = res; });
// No Runtime.enable: bindings work without it, and it would stream the // No Runtime.enable: bindings work without it, and it would stream the
// page's console and context events here. // page's console and context events here.
if (cfg.binding) send('Runtime.addBinding', { name: cfg.binding }); send('Runtime.addBinding', { name: cfg.binding });
live[side] = (expression) => send('Runtime.evaluate', { expression }); live[side] = (expression) => send('Runtime.evaluate', { expression });
console.log(`${side}: connected`); console.log(`${side}: connected`);
await closed; await closed;
+1 -3
View File
@@ -30,7 +30,7 @@
// attribution. No fresh frames at the press: touch events only, as without // attribution. No fresh frames at the press: touch events only, as without
// the bridge. // the bridge.
(() => { (() => {
const VERSION = 2; const VERSION = 3;
const STALE = 150, GAP = 60, TOL = 30, AGREE = 40, FAR = 60, WAIT = 250, LATE = 60; const STALE = 150, GAP = 60, TOL = 30, AGREE = 40, FAR = 60, WAIT = 250, LATE = 60;
function create({ now = () => performance.now() } = {}) { function create({ now = () => performance.now() } = {}) {
@@ -89,7 +89,6 @@
VERSION, VERSION,
get owner() { return owner; }, get owner() { return owner; },
get hand() { return hand; }, get hand() { return hand; },
get mode() { return mode; },
stats, stats,
// { phase, id, x, y, target } if the event concerns the gesture (or may // { phase, id, x, y, target } if the event concerns the gesture (or may
// start one: phase 'down'), else null. An 'up' or 'cancel' ends the // start one: phase 'down'), else null. An 'up' or 'cancel' ends the
@@ -148,7 +147,6 @@
if (!hand) { waitUntil = touchAt + WAIT; refs = [[c.x, c.y]]; } if (!hand) { waitUntil = touchAt + WAIT; refs = [[c.x, c.y]]; }
return hand; return hand;
}, },
release() { reset(); },
// A bridge frame (null: bridge lost). Returns [x, y] to add to the path, or null. // A bridge frame (null: bridge lost). Returns [x, y] to add to the path, or null.
frame(f) { frame(f) {
const t = now(); const t = now();
+4 -3
View File
@@ -10,9 +10,10 @@
// started on at release. With both lasers on the keyboard the pressing one // started on at release. With both lasers on the keyboard the pressing one
// may get no touchmoves; its path then comes from the controller bridge // may get no touchmoves; its path then comes from the controller bridge
// (gesture-input.js: the hand whose laser hit the press point; full rate // (gesture-input.js: the hand whose laser hit the press point; full rate
// asked for from the press to the release). Once a press leaves its first key we drop that // asked for from the press to the release). Once a press leaves its first
// key from Steam's pending touches (m_mapTouched) and cancel its long press // key we drop that key from Steam's pending touches (m_mapTouched) and
// (Steam's own touch-end cleanup, minus the typing), then decode the path. // cancel its long press (Steam's own touch-end cleanup, minus the typing),
// then decode the path.
// - Output: one character or "Backspace" per HandleVirtualKeyDown, the path // - Output: one character or "Backspace" per HandleVirtualKeyDown, the path
// of Steam's own keys (incl. extraKeys' xdotool fallback for non-ASCII, // of Steam's own keys (incl. extraKeys' xdotool fallback for non-ASCII,
// which is async: we pause after such characters). // which is async: we pause after such characters).