9.8 KiB
frame-unboundedMouse-vibed
An experimental SteamVR driver that lets you control the Steam Frame's laser pointer with a regular mouse, across every VR overlay (Steam UI, the KDE desktop, the SteamVR dashboard, popups), instead of the mouse being trapped inside the desktop window.
Warning
⚠️ This project is vibecoded. Read this first.
This driver was written almost entirely by an AI coding assistant (Claude) during one exploratory session on a Steam Frame, with a human steering, testing and approving each step. It has not been reviewed the way hand-written, production code would be.
- Experimental. It works on one headset, one SteamOS build and one mouse (see Tested on). It may not work on yours.
- It runs inside
vrserver. A bug in the driver can crash SteamVR. On the Frame, SteamVR is your whole session, so the headset can end up showing nothing until you remove the driver (see Recovery).- It grabs your mouse. While the laser mode is on, the driver takes exclusive control of the mouse (
EVIOCGRAB), and nothing else receives its input.- It relies on undocumented behaviour. It depends on SteamVR internals that Valve can change in any update: the compositor's
lasermouseaction set and Frame-specific bindings.- No warranty. Use it at your own risk. Read the code first; it's a single ~400-line file:
src/driver.cpp.
What it does
The Frame's laser pointer is drawn by SteamVR's compositor. The compositor aims it with whatever tracked device supplies a pose, normally a controller. A physical mouse never reaches that system: gamescope consumes it and clamps it to whichever window has focus.
This driver adds a virtual controller to SteamVR:
- Position: follows your head.
- Direction: aimed by mouse movement.
- Buttons: your mouse buttons.
The compositor then treats it like any other laser-pointing hand.
| Mouse | Laser mode OFF (default) | Laser mode ON |
|---|---|---|
| Forward side button (BTN_EXTRA) | Turns laser mode on | Turns laser mode off |
| Movement | Normal desktop cursor | Aims the laser (world-locked: turning your head doesn't move it) |
| Left / right / middle button | Normal | Laser click / right click / middle click |
| Wheel (and tilt wheel) | Normal | Acts like the controller thumbstick. One notch moves one item in Steam UI lists, and spinning the wheel scrolls continuously. It also scrolls the overlay under the laser. |
When laser mode turns on, the ray starts where you're looking. When it's off, the virtual device reports itself as disconnected, so your real controllers keep the laser.
Requirements
- A Steam Frame (aarch64, SteamOS VR variant) with SteamVR at
/opt/steamvr. - A USB or Bluetooth mouse. Your user must be able to read
/dev/input/event*; the defaultsteamosuser is in theinputgroup. gccorclang,cmakeandninja. They are not guaranteed to be on a stock image; installing them may require disabling SteamOS read-only mode.- The OpenVR driver header is not vendored. The build uses the copy SteamVR ships at
/opt/steamvr/tools/hellovr_vulkan_linux/src/openvr/headers. Override it with-DOPENVR_HEADERS=<dir>.
Build and install
cmake -S . -B build -G Ninja && cmake --build build
./install.sh # registers driver/mouselaser with vrpathreg (backs up openvrpaths.vrpath first)
# then REBOOT the headset
install.sh refuses to run if another driver named mouselaser is already registered from a different folder.
Check that it loaded:
grep -a 'mouselaser:' ~/.local/share/Steam/logs/vrserver.txt | tail
# expect: "version 0.3.1-experimental", "activated as device N, role 1", "using /dev/input/eventX (<your mouse>)"
Optional: offline wheel test
cmake -S . -B build -DMOUSELASER_TESTS=ON && cmake --build build && ./build/wheel_sim
This simulates the wheel → thumbstick logic at 120 Hz, without SteamVR, and exits non-zero if a single notch or a smooth spin misbehaves. It is handy when changing the wheel* defaults.
Uninstall
./uninstall.sh # then reboot
Settings
Add any of these to ~/.config/openvr/config/steamvr.vrsettings under a "driver_mouselaser" section. They are read once, when SteamVR starts.
| key | default | meaning |
|---|---|---|
enable |
true |
false loads the driver but adds no device. This is the kill switch. |
sensitivity |
0.05 |
Degrees of ray rotation per mouse count. |
toggleButton |
276 |
evdev key code of the toggle: 276 = BTN_EXTRA (forward), 275 = BTN_SIDE (back). |
role |
1 |
1 = left hand, 2 = right hand. |
deviceNameFilter |
"" |
Substring of the evdev mouse name. Empty means the first device with REL_X/REL_Y and BTN_LEFT. |
originOffsetY |
-0.08 |
Ray origin height relative to the HMD, in metres. |
invertY |
false |
Invert vertical aim. |
wheelMode |
"smooth" |
"smooth": each notch bumps the virtual stick, which then eases back, and spinning holds it. "step": each notch is one fixed flick (the 0.2.0 behaviour). |
wheelDeflection |
1.0 |
Maximum stick deflection (0–1), in both modes. |
wheelSmoothMin |
0.7 |
smooth: deflection after a single notch. Keep it above the UI's step threshold (about 0.5). |
wheelSmoothImpulse |
0.3 |
smooth: extra deflection added by each further notch, so faster spinning pushes harder. |
wheelSmoothHoldMs |
80 |
smooth: how long after a notch the stick holds before easing back. |
wheelSmoothDecayMs |
150 |
smooth: how fast it eases back (exponential time constant). Lower is snappier, higher glides longer. |
wheelPressMs |
90 |
step: how long each notch holds the stick pushed. |
wheelReleaseMs |
60 |
step: gap at centre between queued notches. |
wheelMaxQueued |
10 |
step: maximum notches buffered. |
Tuning the wheel:
- One notch skips two list items: lower
wheelSmoothHoldMs. - Scrolling stops abruptly: raise
wheelSmoothDecayMs. - Fast spins aren't fast enough: raise
wheelSmoothImpulse.
It shows up in SteamVR's "Manage Add-ons"
This is expected. SteamVR lists every driver that isn't resources-only under Settings → Developer → Manage Add-ons (exact menu names may differ). That entry is useful:
- Its toggle is a kill switch. It writes
"driver_mouselaser": {"enable": false}tosteamvr.vrsettings, which is the sameenablesetting the driver honours. Takes effect after a reboot. - Safe mode. SteamVR can block add-ons in "safe mode", apparently when it crashes after they were added. The Unblock button on that page re-enables them, once you've fixed or updated the driver.
Recovery
If SteamVR won't come up properly after installing, there are four options: 0. In VR, open Manage Add-ons, switch mouselaser off, then reboot.
- SSH or RDP in (
xrdpruns on the Frame), run./uninstall.shorvrpathreg removedriver <path>/driver/mouselaser, and reboot. - Set
"driver_mouselaser": { "enable": false }insteamvr.vrsettings, then reboot. - Restore the backup:
cp ~/.config/openvr/openvrpaths.vrpath.bak-mouselaser ~/.config/openvr/openvrpaths.vrpath.
Tested on
| Device | Steam Frame (Snapdragon SM8650) |
| SteamOS | VERSION_ID=0.4.2, VARIANT_ID=vr, build 20260928.6175029 |
| Kernel | 6.18.0-gfbdbca41fd45 |
| SteamVR | as installed at /opt/steamvr on 2026-10-01 |
| Mouse | MCHOSE G3 A (2.4 GHz, has BTN_SIDE/BTN_EXTRA) |
| Result | Loads, toggles, aims and clicks across overlays. Reconnects after the mouse sleeps or replugs. The wheel scrolls Steam UI lists like the thumbstick: 0.2.0 worked but felt a bit choppy. 0.3.0's smooth mode was confirmed working by the owner. |
Known limitations
- The ray starts just below your head (
originOffsetY) and points where you aim, so you see the beam almost end-on. Expect to rely mostly on the cursor dot on overlays. - If a real controller holds the same hand role its own laser pointer seems to stop working.
- Wheel feel is approximate. A wheel isn't a stick: it only sends notches. Smooth mode only simulates a held stick, so it still won't feel exactly like a real thumbstick. Tune it with the
wheel*settings. - Settings are only read when SteamVR starts.
- Two harmless log lines:
steam.client (mouselaser) has no configured binding: only the compositor bindings are provided, and Steam's own binding for the Frame controllers is haptics-only anyway.Driver mouselaser has no suitable devices: logged at load time, presumably because the driver provides no HMD. The device is added right after.
Docs
- docs/how-it-works.md: the driver internals, the pose math and the bindings.
- docs/steam-frame-background.md: what we learned about the Frame's display and input stack that led here.
- docs/development-log.md: where this came from, what was verified, and what is next.
- CLAUDE.md: context and ground rules for AI coding sessions on this repo.
License
MIT © 2026 Andalu30 and contributors.
Third-party notes:
- OpenVR SDK header (
openvr_driver.h, © Valve Corporation, BSD-3-Clause in the public OpenVR SDK). It is not included in this repo. The build uses the copy SteamVR ships on the headset. - SteamVR is proprietary Valve software. This project doesn't contain or redistribute any of it. The input profile and bindings files follow SteamVR's documented JSON formats so the driver can interoperate.
- Not affiliated with or endorsed by Valve. "Steam", "SteamVR" and "Steam Frame" are trademarks of Valve Corporation.
