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.
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.
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.
- 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>.
cmake -S . -B build -G Ninja && cmake --build build
./install.sh # registers driver/mouselaser with vrpathreg (backs up openvrpaths.vrpath first)
# then REBOOT the headsetinstall.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>)"cmake -S . -B build -DMOUSELASER_TESTS=ON && cmake --build build && ./build/wheel_simThis 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.sh # then rebootAdd 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.
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.
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.
| 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. |
- 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/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.
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.

0 comments
log in to comment.