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.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.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.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. |
+6 -7
View File
@@ -2,7 +2,8 @@
Three patches of Steam's VR keyboard: [extra keys](#extra-keys),
[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
([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
(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
patch) and costs nothing while the keyboard is hidden. Without fresh
frames (option off, relay or SteamVR gone) only laser events count, as
before. A second press during a swipe is Steam's (a tap): one text
field, one word at a time.
patch). Without fresh frames (option off, relay or SteamVR gone) only
laser events count, as before.
- 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
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
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
to the laser only. Depends on Steam and SteamVR UI internals (see
**Caveats:** moving the keyboard pauses touches for 0.3 s. The extra keys'
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)).
**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
// 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
// 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
// last the last frame (null: none / lost)
// demand(ms) full rate for the next ms (0: end), e.g.
// during a laser press; via the relay's
// CDP binding __sfuiCtlIn
// errors { count, last } of throwing subscribers
// 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
// keyboard popup exists. Frames come at up to ~90 Hz while a hand's tip is
// 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).
// keyboard popup exists.
(() => {
const VERSION = 2;
const G = globalThis;
+1 -1
View File
@@ -56,7 +56,7 @@ async function connect(side) {
const closed = new Promise((res) => { ws.onclose = res; });
// No Runtime.enable: bindings work without it, and it would stream the
// 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 });
console.log(`${side}: connected`);
await closed;
+1 -3
View File
@@ -30,7 +30,7 @@
// attribution. No fresh frames at the press: touch events only, as without
// the bridge.
(() => {
const VERSION = 2;
const VERSION = 3;
const STALE = 150, GAP = 60, TOL = 30, AGREE = 40, FAR = 60, WAIT = 250, LATE = 60;
function create({ now = () => performance.now() } = {}) {
@@ -89,7 +89,6 @@
VERSION,
get owner() { return owner; },
get hand() { return hand; },
get mode() { return mode; },
stats,
// { 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
@@ -148,7 +147,6 @@
if (!hand) { waitUntil = touchAt + WAIT; refs = [[c.x, c.y]]; }
return hand;
},
release() { reset(); },
// A bridge frame (null: bridge lost). Returns [x, y] to add to the path, or null.
frame(f) {
const t = now();
+4 -3
View File
@@ -10,9 +10,10 @@
// 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
// (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
// key from Steam's pending touches (m_mapTouched) and cancel its long press
// (Steam's own touch-end cleanup, minus the typing), then decode the path.
// asked for from the press to the release). Once a press leaves its first
// key we drop that key from Steam's pending touches (m_mapTouched) and
// 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
// of Steam's own keys (incl. extraKeys' xdotool fallback for non-ASCII,
// which is async: we pause after such characters).