mirror of
https://github.com/Andalu30/frame-unboundedMouse-vibed.git
synced 2026-10-04 22:00:03 +02:00
5.1 KiB
5.1 KiB
Steam Frame background: why the mouse is trapped, and why a driver fixes it
These are findings from read-only exploration of a Steam Frame on 2026-10-01 (SteamOS 0.4.2 VR, build 20260928.6175029). Paths and versions may change with updates.
The display chain
The KDE desktop doesn't run on the hardware. It is nested several layers deep:
DSI-1 panel (2× 2160x2160 @ 108/120/144 Hz)
└─ vrcompositor (SteamVR) owns the panel
└─ gamescope --backend openvr draws each app as a VR overlay (--virtual-connector-strategy PerAppId)
│ --output-width 1920 --output-height 1080, --vr-overlay-physical-width 2.67
└─ Xwayland :0
└─ kwin_wayland (X11 *windowed* backend), started by /usr/bin/steamos-nested-desktop with --width 1280 --height 800
└─ Plasma + apps
- Steam's "Desktop" entry is
/usr/share/applications/deckard-nested-desktop.desktop. "Deckard" is the Frame's internal codename. - Gamescope's arguments are hardcoded in
/usr/lib/steamos/gamescope-sessionand run from the user unitgamescope-session.service. That unit isPartOf=steamvr.service, which is why restarting SteamVR kills the desktop. - Gamescope resizes KWin's window to its own 1920×1080 output, whatever size KWin asks for.
The input path
/dev/input/eventX (mouse) → gamescope (libinput) → focused overlay's Xwayland → KWin (as X11 events)
- KWin on the X11 windowed backend never uses libinput, so KDE's mouse and keyboard settings pages are empty.
/org/kde/KWin/InputDevicedoesn't exist. - Gamescope delivers the mouse only to its focused overlay, clamped to that surface. This is the "bounded" mouse the project is named after.
- Back/forward side buttons never reach KDE apps. Gamescope delivers them fine (X button 8/9 on
:0, seen withxinput test-xi2). KWin 6.2.5's X11 windowed backend (X11WindowedBackend::handleButtonPress) then discards every X button above 7: itsdefault:branchreturns. Upstream KWin removed that backend in 2025 without fixing it. KWin's Wayland nested backend passes button codes through unchanged. - SteamVR's laser Back action (a controller's B, or this driver's back button) only reaches SteamVR overlays such as Steam's UI. Gamescope doesn't translate it for the windows it hosts.
The laser pointer
- The laser belongs to vrcompositor. It casts a ray from a pose and sends the overlay it hits
VREvent_MouseMove/ButtonDown/ButtonUp/.... Gamescope turns those into pointer input for its clients. - The actions are in
/opt/steamvr/resources/config/vrcompositor_actions.json. The important one is/actions/lasermouse/in/Pointer(type pose, mandatory), plus LeftClick, RightClick, MiddleClick, scroll and others. - Frame controller bindings (
/opt/steamvr/drivers/frame_controller/resources/input/vrcompositor_bindings_frame_controller.json):- Pointer ←
/user/hand/*/pose/tip - LeftClick ← trigger
- "Quick mouse" (
/actions/quickmouse/in/ActivateQuickMouse) ← squeeze the grip. This just brings the laser up while you're in gamepad mode.
- Pointer ←
- The Frame HMD also has a binding (
drivers/frame_hmd/.../vrcompositor_bindings_frame_hmd.json): Pointer ←/user/head/pose/raw, which is a head-gaze laser. - Key insight: the compositor accepts any pose source for the laser. A virtual device with a mouse-aimed pose is therefore enough, and no compositor hacking is needed. This driver is built on that.
Alternatives that were considered
| idea | why not (yet) |
|---|---|
| Quick mouse | Only an "activate" shortcut. The ray still needs a pose. |
lasermouse mailbox (ws://127.0.0.1:27062, messages dump_laser_overlays, remote_laser_mouse_events, force_activate_laser_mouse) |
The remote-event payload is an undocumented protobuf (VRLink_LaserMouseEvent, from vr_vrlink.proto, built with protobuf-lite so there's no schema in the binary). It seems to be meant for VRLink PC streaming. |
Gamescope flags (--mouse-sensitivity, --force-grab-cursor, overlay size) |
They only change behaviour inside one overlay. |
| Running KWin on DRM/libinput | SteamVR owns the panel. |
Mailbox wire protocol, for reference
This is what the dashboard JS does (/opt/steamvr/resources/webinterface/dashboard/chunk~*.js):
connect ws://127.0.0.1:27062[?secret=...]
send "mailbox_open <name>"
send "mailbox_send <target> <json>" json: {"type": "...", "returnAddress": "<name>", "message_id": N, ...}
recv JSON; replies mirror message_id
Useful locations
| what | where |
|---|---|
| SteamVR runtime | /opt/steamvr (bin/linuxarm64, drivers/, resources/) |
| Driver registration | ~/.config/openvr/openvrpaths.vrpath (vrpathreg show/adddriver/removedriver) |
| User SteamVR settings | ~/.config/openvr/config/steamvr.vrsettings |
| Logs | ~/.local/share/Steam/logs/vrserver.txt, vrcompositor.txt. They're also forwarded to the journal by /usr/share/deckard/steamvr_logs_to_journald.py. |
| OpenVR driver header | /opt/steamvr/tools/hellovr_vulkan_linux/src/openvr/headers/openvr_driver.h (SteamVR 2.1 API: IServerTrackedDeviceProvider_004, ITrackedDeviceServerDriver_005) |