Commit Graph
141 Commits
Author SHA1 Message Date
Pierre Kisters 269321ea92 Uninstall: detach /nix lazily instead of waiting for its users
Programs started from the Nix store (the Steam session itself, the
desktop portal) keep /nix busy until logout, so nix-installer's
`systemctl stop nix.mount` failed and the wait for them could never end.
A runtime drop-in (LazyUnmount=yes on nix.mount, in /run, removed again
afterwards) makes that stop detach /nix; the programs keep their open
files until they exit. They are listed for information only, without a
prompt. The re-exec of a bash from the Nix store is no longer needed.
2026-10-01 23:14:35 +02:00
Pierre Kisters b852a1eb07 Installer: cleanup of restart-check, --clone and the uninstall changes
- uninstall: nix-installer's copy outside /nix is made and run as root
  (a user-writable copy run by sudo could be swapped); README uninstall
  paragraph shortened to one account of what happens when Nix stays.
- session.nix: the restart-check activation after steamFrameUserServices,
  so the latter's comment is above it again.
- install.sh: a duplicate `local` in port_listening; comments.
- docs/cleanup.md: the installer internals of --clone (how an existing
  clone is recognised, which git) and restart-check (what it compares,
  test overrides); README keeps the usage.
- README install paragraph: shorter list of the dashboard patches.
2026-10-01 18:18:54 +02:00
Pierre Kisters 59afc2f616 Launchers: neutral examples and made-up fixtures
Examples showed specific apps as if the repository installed or
configured them.

- Docs, README, template and option examples: the Signal Flatpak
  (org.signal.Signal, sgnl:// and signalcaptcha://) and a made-up
  org.example.App instead of the previous chat and remote-desktop apps;
  no install lines (the app is installed by the user, as noted).
- Portal fix: the file-dialog symptom phrased generically.
- Launcher check: made-up Flatpak entries org.example.Chat (Electron,
  --file-forwarding @@u %U @@, an action, an SSO scheme) and
  org.example.Remote (Qt, -qwindowtitle %c @@u %u @@, localized keys)
  replace the copied ones; Firefox, gedit and Jellyfin stay.
2026-10-01 18:16:37 +02:00
Pierre Kisters 3acacc6388 Uninstall: wait until nothing uses /nix before removing Nix
nix-installer uninstall failed at `systemctl stop nix.mount` ("Job
failed") while programs started from the Nix store still ran: a terminal
session's tools, or apps that mapped Home Manager's mime.cache.

- Before nix-installer runs, after Home Manager is gone, uninstall scans
  /proc for the user's processes whose exe, cwd, root, an fd or a mapped
  file is in /nix, and lists them by name and PID (the shells it was
  started from included; itself, its subshells and its curl | bash
  pipeline skipped). Close them and press Enter to re-check, or abort;
  with --yes or without a terminal it stops: "close these or reboot, then
  run uninstall again". Nothing is killed (replaces the old kill prompt).
- nix-installer runs from a copy outside /nix.
- cleanup check H: a fake /proc (STEAM_FRAME_NIX_PROC) with programs from
  the store stops before Nix; without them Nix is removed.
2026-10-01 18:14:19 +02:00
Pierre Kisters da01b7e5ac install.sh: install --clone <git-url> [--dir] [--ref]
Clones your own config repository (git@host:owner/repo, https://...,
github:owner/repo) into ~/nix-config or --dir and installs it like
--flake <dir>. An existing clone of the same repository (any URL form) is
reused and, after asking, pulled with --ff-only; another repository, a
non-clone or a different branch is refused. A failed clone hints at SSH
URLs and gh auth login. Uses SteamOS' git, else Nix's.

Checked against local bare repositories through a logging git.
2026-10-01 18:09:01 +02:00
Pierre Kisters 6b12d5329f Install: name what waits for a restart; uninstall survives a failed Nix uninstall
A fresh install into a running session left the keyboard layout and the
SteamVR dashboard patches (VR pet, the VR keyboard's suggestion strip, ...)
silently off until a reboot: gamescope reads XKB_DEFAULT_* only at its
start, and SteamVR opens its DevTools port (VRWebHelper.DebuggerEnabled)
only at its start. Neither can be applied at runtime.

- install.sh restart-check (run on every switch by session.nix, and by
  install at its end): compares the keyboard-layout drop-in with the
  running gamescope session's environment and checks whether SteamVR runs
  without port 8087; install ends with "Restart once" listing them.
- uninstall: a failing nix-installer uninstall (e.g. /nix busy) no longer
  aborts the script before the ~/.config/home-manager link and Home
  Manager state are removed; it says to reboot and run uninstall again.
- cleanup check: restart-check on fake cgroup/proc dirs.
2026-10-01 18:06:50 +02:00
Pierre Kisters 92e9d17e09 Launchers: a neutral example app in docs and tests
The Nix-package launcher example in docs and option examples is now
Signal (pkgs.signal-desktop, desktop ID "signal", sgnl:// and
signalcaptcha:// handlers). The check uses a made-up org.example.App
from a fake package (fake store path), covering the same Exec, action,
MimeType, source and missing-entry cases.

Screenshots: placeholder Steam account IDs in the docs, option example
and check.
2026-10-01 18:06:50 +02:00
Pierre Kisters a01a49da45 README: all modules, removed options, two-handed swipe and F-keys, exact option types
The module list gains vr-keyboard-controllers and vr-keyboard-touch; the
removed-options table gains the list form of launcherMenu.iconFallbacks,
windowCurvature.snap and barDragRoom; the intro counts the icon fallbacks
among the defaults; the swipe feature line names two-handed swipe and
functionKeys; the VR keyboard rows give the ranges the types enforce.
2026-10-01 04:52:53 +02:00
Pierre Kisters 4819ea26a7 VR pet: model sources in the repo
The Toon Cat (sources/toon-cat/), the Tuxedo Cat glTF (sources/tuxedo-cat/)
and the Quaternius Shiba Inu and Fox (models/{shiba,fox}/model.glb), each
byte-identical to the previously fetched file and with a LICENSE.md
(author, original URL, licence, changes). The build no longer downloads
them; three.js (preview only) still comes from npm. The baked frames,
model dirs and icons are unchanged.
2026-10-01 04:46:53 +02:00
Pierre Kisters 77085bad8a Docs: VR pet, its models, credits and changes outside Nix
docs/pet.md (usage, configuration, how it works), docs/pet-models.md
(the model spec), the README's feature line, options, changes outside
Nix and Credits (Toon Cat FREE and Tuxedo Cat, CC-BY 4.0; Quaternius
Shiba Inu and Fox, CC0), the cleanup artifact, the layout and runtime
names, and the template.
2026-10-01 04:33:39 +02:00
Pierre Kisters 4034fd4ded VR pet: a cat or dog in SteamVR's scene (steamFrame.pet)
A SteamVR dashboard patch (vr-pet) drawing a baked 3D pet next to the
windows: the Toon Cat in five coats, a Shiba Inu, a Fox and a Dachshund,
plus models from steamFrame.pet.extraModels. It walks around, follows,
sits, lies and sleeps, can be picked up by its grip bar, petted and
switched in its menu; its spot, pose and model are saved state.

Also the vr-pet command, "Pet" in the "+" menu with the current model's
icon (steam-frame-nix-pet-icon), the flake outputs pet-models,
pet-icons and pet-preview, and the check pet. A built-in model with
"private": true fails evaluation; private models belong in extraModels.
2026-10-01 04:33:39 +02:00
Pierre Kisters fbf9519be0 Cleanup: the VR pet's tmpfs icon link (artifact pet)
<runtimeDir>/steam-frame-nix/vr-pet: icon.png when it links to a
*-vr-pet-icons store icon, its empty .lock and stray .icon.tmp.* links,
then the directory; kept by --orphans --keep pet. The empty runtime
directory now also goes with --orphans.
2026-10-01 04:33:39 +02:00
Pierre Kisters 57ed00e29c VR keyboard bridge: adaptive sampling, on demand only without touch typing
The controller bridge reads the poses with setTimeout instead of a fixed
11 ms interval: the next read after (distance - 10 cm) / 2.5 m/s, 11-250 ms,
the distance being the nearest tip's to the keyboard's rect plus a margin,
so a hand up to 2.5 m/s is read within 10 cm before it reaches the surface
(the tracker's crossing and 8 cm jump guard see 11 ms steps as before).
Full rate during a demand or with a pulled trigger. Without touch typing
(controllers.continuous, set by vr-keyboard-touch) no reads while the
keyboard is shown until a swipe demands them; a demand starting reads at
once. Bridge VERSION 6. Tests with a fake clock: bridge.test.mjs.
2026-10-01 04:28:15 +02:00
Pierre Kisters 1612c162fb Docs: the VR keyboard relays under "Only while running" 2026-10-01 04:10:14 +02:00
Pierre Kisters e99a9e4a96 Cleanup: the artifact list lives in docs/cleanup.md only
install.sh's header comment repeated docs/cleanup.md's table; it points
there now. The script's tool list names grep, sed and awk too.
2026-10-01 04:10:04 +02:00
Pierre Kisters b48f644b1b Cleanup: the debugger restore reports a key changed to false as false, not absent
jq's // treats false like null. The runtime restore script changes with
it, so the next SteamVR start rewrites it once (one daemon-reload).
2026-10-01 04:10:04 +02:00
Pierre Kisters 1dd5df3d8b Install: a long list of untracked files doesn't abort the install
With pipefail, head closing the pipe early made git's SIGPIPE fail the
assignment under set -e.
2026-10-01 04:10:04 +02:00
Pierre Kisters 04023a72d9 Cleanup: drop an unused return value, a no-op case branch and a repeated mode test
debugger_ensure_hook's "changed" status was never read; clean_icons'
case for Home Manager's links did nothing; cmd_cleanup computes --all
once. --quiet in the usage synopsis.
2026-10-01 04:10:04 +02:00
Pierre Kisters eb8206f4ee Cleanup: a prefs.js that can't be read is left as is, not emptied
grep's read error (status 2) was swallowed with || true, so the profile's
prefs.js could be rewritten with nothing. Now it warns and keeps both
prefs.js and user.js for the next run.
2026-10-01 04:10:04 +02:00
Pierre Kisters 32bd719284 Cleanup: rewrites count as actions and keep the --quiet header to one
c_write was fed through a pipe, so it ran in a subshell: its action
count and the printed header were lost, which printed "nothing to clean
up" after a rewrite, and a second header with --quiet. It takes the
content as an argument now.
2026-10-01 04:10:04 +02:00
Pierre Kisters daf7c70ed3 Cleanup: a Firefox profile in use is detected again with other users' processes running
ff_in_use piped find into grep -q; with pipefail, find's error on any
unreadable /proc/<pid>/fd (root's processes) made every profile look
unused, so cleanup could remove user.js and rewrite prefs.js under a
running Firefox. The Nix check's sandbox has no such processes.
2026-10-01 04:10:04 +02:00
Pierre Kisters c810df7f5a VR keyboard: the keyboard popup lookup in the finder library
find.keyboardPopup() (finders.js VERSION 3) replaces the same
g_PopupManager lookup in the swipe, extra keys and touch typing patches
(VERSIONs 29, 24, 4). hub.js keeps its own: it gets no finder library.
2026-10-01 04:09:59 +02:00
Pierre Kisters 0908713667 VR keyboard: the strip relay resyncs once per burst of new page contexts
Every executionContextCreated (Runtime.enable alone reports each existing
context) started its own resync timer; one debounced timer now, as the
injectors do.
2026-10-01 04:09:13 +02:00
Pierre Kisters 378265389d VR keyboard: unused exports and fields removed
textmodel's lastReset (the patch logs the reason itself), __sfuiSwipe.model,
corrector's distance/fold and the decoder's N/W/cost/resample exports were
read by nothing (patch, tests, replay script).
2026-10-01 04:09:13 +02:00
Pierre Kisters 70e9a1aeb1 VR keyboard: a closed keyboard popup's instance detaches itself
Its 0.5 s poll (a SharedJSContext timer) kept running until the next
injection and could publish a hidden strip with its own sequence numbers
over the new popup's, so a pick on the panel was dropped as stale. The
poll now detaches it once the popup's document is gone.
2026-10-01 04:09:13 +02:00
Pierre Kisters 76de7734c1 VR keyboard extra keys: docs: the helper's debug variables, one place for the other-keymaps note
VRKBD_VERBOSE and VRKBD_DISPLAY were undocumented; the extra keys section
gets a Debugging line like the others. That the routing is harmless on
other keymaps was said twice; now once, under Layouts.
2026-10-01 04:09:13 +02:00
Pierre Kisters c99b0b699f VR keyboard extra keys: the helper reconnects when Steam's page closes while connecting
A CDP call sent after the socket closed (e.g. Steam restarting between
Runtime.enable and the first inject) waited forever, so the helper hung
instead of reconnecting. Such calls now resolve empty at once.
2026-10-01 04:09:13 +02:00
Pierre Kisters 514f073a6e 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.
2026-10-01 04:09:13 +02:00
Pierre Kisters ad7e4f7678 UI patches: docs line wrapping, mkPatch link text 2026-10-01 04:09:13 +02:00
Pierre Kisters 521092d66b UI patches: no re-injection timer for a session that closed while attaching
The attach handler set the 15 s interval after its awaits even when the
socket had closed meanwhile (onclose already ran), leaking a timer per
such session. Also documents the optional stateDir config key.
2026-10-01 04:09:13 +02:00
Pierre Kisters d6ddad9cfe Window curvature docs: the control bar's factor 3 only in its own docs 2026-10-01 04:09:13 +02:00
Pierre Kisters df501a169b Window curvature: the press contract documented once
- docs/window-curvature.md holds the full contract (now also: the next
  press starts at 1x, return values, a drag keeps its value, neither side
  restores the other's state); the patch headers of window-curvature and
  frame-controls point to it and keep only their own side.
- frame-controls header: the localStorage history points to
  docs/ui-patches.md#persistent-state.
Comments only, no VERSION bumps.
2026-10-01 04:09:13 +02:00
Pierre Kisters c2427b4e20 Window control bar: signature expects without minified names
The Float action's import alias (l.yWq) and the frame controls' local
names (p, v) change with every SteamVR build and would warn after each
update; match the stable parts only.
2026-10-01 04:09:13 +02:00
Pierre Kisters 503a9e742b Jellyfin docs: "approximately" for command (as for the launchers' command), rewrapped lines 2026-10-01 04:09:13 +02:00
Pierre Kisters 11e41874c1 Firefox docs: one parenthesis for the desktop profile, rewrapped lines 2026-10-01 04:09:13 +02:00
Pierre Kisters b08600f9c3 Jellyfin: the shim check runs with set -euo pipefail, like the Firefox one 2026-10-01 04:09:13 +02:00
Pierre Kisters 2e259c49fe Firefox: the desktop profile wrapper keeps prefs.js when rewriting it fails
A failed write of prefs.js.sfn (e.g. a full disk) emptied prefs.js; now
prefs.js and the user.js link stay for the next launch to retry.
2026-10-01 04:09:13 +02:00
Pierre Kisters 4538ecea06 VR "+" menu grid: a popup whose window throws no longer stops the scan
p.window was read outside the guard, so an exception there ended the
1 s scan for the remaining popups (pinned-desktop already guards it).
2026-10-01 04:09:13 +02:00
Pierre Kisters 9cd22f047d Launchers, VR "+" menu: module header comments point to the docs
The patch list, keyring summary, triggers and icon fallback history are
in docs/launchers.md, docs/launcher-menu.md and next to the code they
describe.
2026-10-01 04:09:13 +02:00
Pierre Kisters b0ca57d4bd VR "+" menu icon fallbacks: hints no longer stop at an unreadable entry
A launcher whose Flatpak is not installed leaves a dangling link in
~/.local/share/applications; awk failed on it and set -e ended the
hints there.
2026-10-01 04:09:13 +02:00
Pierre Kisters 667422cb50 Docker: its user service in the runtime names table 2026-10-01 04:09:13 +02:00
Pierre Kisters 1b581a40e2 Session modules: header comments point to the docs instead of repeating them
SteamVR debugger and Docker keep only the internals the docs lack; the
screenshots script names package.nix as what prepends its variables.
2026-10-01 04:09:13 +02:00
Pierre Kisters 464099c1cd Clipboard sync: a process gone since pgrep no longer aborts the switch
ps fails for it, and with pipefail the assignment ended the activation;
it is skipped now.
2026-10-01 04:09:13 +02:00
Pierre Kisters ae8686e284 Session: the outer bus and runtime dir only for the user-service step
steamFrameUserServices exported XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS
into the rest of the activation script; a subshell keeps them to its
systemctl calls.
2026-10-01 04:09:13 +02:00
Pierre Kisters 2442527514 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.
2026-10-01 04:01:00 +02:00
Pierre Kisters 605e42ffa1 VR keyboard controller bridge: no 11 ms timer while the keyboard is hidden
The TICK_MS loop ran (and returned at once) at ~90 Hz all the time; it now
runs only while SteamVR shows the keyboard, started and stopped by the
250 ms poll. The pose query's 1 s timeout timer is cleared once the query
settles. Frames are unchanged.
2026-10-01 04:01:00 +02:00
Pierre Kisters aa8ccc67c9 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.
2026-10-01 03:53:01 +02:00
Pierre Kisters af32cf72ce VR keyboard swipe with two controllers: the path from the controller bridge
With both lasers on the keyboard SteamVR forwards per poll only one laser's
movement, so the pressing one may get no touchmoves and its swipe became a
tap. keyboard.vr.swipe.twoHanded (default on; turns on the controller
bridge): a press is attributed to the hand whose laser hit is nearest to it
(within 30 px, the trigger as a tiebreak; without recent frames the first
frame after the press decides, within 60 px). The path comes from runs of
one source: the touch's own moves while they flow, that hand's laser hit
per bridge frame once they pause for 60 ms, touchmoves again if frames
pause. A touchend disagreeing with the laser during a bridge run ends at
the laser. Touchmoves on the other hand's laser move the attribution.
Without fresh frames: touch events only, as before. A second press during
a swipe stays Steam's tap (one text field, one word at a time).

The bridge sends full rate only on demand now: hub.js demand(ms) goes
back through the relay (CDP binding __sfuiCtlIn) to systemui's
__sfuiCtl.demand; the swipe patch asks from the press to the release.

Tests: gesture-input with the two-controller recording and synthetic bridge
frames (a pressing hand without touchmoves, attribution with both lasers on
the keyboard, a stale bridge, frames stopping mid-swipe, re-attribution,
interleaved hover and presses); hub demand.
2026-10-01 03:45:47 +02:00
Pierre Kisters 0699d0f532 VR keyboard touch typing: the touched key highlights (and long-presses)
Steam's UpdateTouchState counts the event's `touches` per key: that count
is the pressed highlight (rgLayoutTouchCount -> Touched) and where the long
press (Backspace repeat, accents) starts. The touch-start event left the
new touch out of `touches`, like a touchend; now it is in, as in a DOM
touchstart (tracker.js touchEvent, with a test against a model of Steam's
handlers).
2026-10-01 03:36:13 +02:00
Pierre Kisters 63d589c261 VR keyboard controller bridge: no traffic while idle
The relay no longer enables the Runtime domain on systemui (bindings work
without it; it streamed the page's console and context events). Full-rate
frames only while a tip is within 10 cm of the keyboard or a trigger is
pulled (not for a laser resting on it); idle frames every 1 s instead of
250 ms. Nothing while the keyboard is hidden.
2026-10-01 03:33:07 +02:00