mirror of
https://github.com/Andalu30/frame-unboundedMouse-vibed.git
synced 2026-10-06 06:00:04 +02:00
83 lines
6.3 KiB
Markdown
83 lines
6.3 KiB
Markdown
# How the driver works
|
||
|
||
All code is in [`../src/driver.cpp`](../src/driver.cpp). Everything SteamVR loads lives in [`../driver/mouselaser/`](../driver/mouselaser/).
|
||
|
||
## Pieces
|
||
|
||
```
|
||
vrserver
|
||
└─ loads driver/mouselaser/bin/linuxarm64/driver_mouselaser.so (driver.vrdrivermanifest: alwaysActivate=true)
|
||
HmdDriverFactory() → MouseLaserProvider (IServerTrackedDeviceProvider)
|
||
└─ MouseLaserDevice (ITrackedDeviceServerDriver), serial "mouselaser-0", class Controller
|
||
├─ MouseReader thread ── reads /dev/input/eventX (evdev), EVIOCGRAB when active
|
||
└─ RunFrame() ── pose + input components, every vrserver frame
|
||
vrcompositor
|
||
└─ uses resources/input/vrcompositor_bindings_mouselaser.json (via the profile's default_bindings)
|
||
/actions/lasermouse/in/Pointer ← /user/hand/{left,right}/pose/raw → the laser
|
||
```
|
||
|
||
### Why `alwaysActivate: true`
|
||
`steamvr.vrsettings` has `activateMultipleDrivers: false` by default. That setting only limits which **HMD** driver is picked. Drivers whose manifest sets `alwaysActivate` are loaded in addition. This is the standard route for add-on trackers.
|
||
|
||
### MouseReader (evdev thread)
|
||
- Scans `/dev/input/event*` for the first device with `EV_REL` (REL_X, REL_Y) and `EV_KEY` (BTN_LEFT), optionally filtered by `deviceNameFilter`. A keyboard's "Consumer Control" node doesn't match.
|
||
- `poll()`s with a 200 ms timeout. On `POLLERR`/`POLLHUP` or `ENODEV` (mouse asleep or unplugged) it closes the device and rescans every 2 s.
|
||
- Pressing the toggle key (`toggleButton`, default BTN_EXTRA = 276) flips `active`:
|
||
- **on:** `ioctl(EVIOCGRAB, 1)` gives exclusive access, so gamescope stops receiving the mouse.
|
||
- **off:** `ioctl(EVIOCGRAB, 0)` releases the mouse; button state and deltas are cleared.
|
||
- While active it accumulates `REL_X`/`REL_Y`/`REL_WHEEL` into atomics and tracks BTN_LEFT, BTN_RIGHT and BTN_MIDDLE. `RunFrame` consumes them with `exchange(0)`.
|
||
|
||
### RunFrame (pose)
|
||
- HMD pose: `VRServerDriverHost()->GetRawTrackedDevicePoses(0, &hmd, 1)`. Device 0 is the HMD.
|
||
- On each OFF→ON toggle, yaw and pitch are initialised from the HMD's forward vector (−Z column of `mDeviceToAbsoluteTracking`):
|
||
`yaw = atan2(-fx, -fz)`, `pitch = asin(fy)`.
|
||
- Mouse deltas: `yaw -= dx·k`, `pitch -= dy·k` (sign flipped if `invertY`), where `k = sensitivity·π/180`. Pitch is clamped to ±89°.
|
||
- Orientation is **world-locked**: `q = yaw(Y) · pitch(X)` = `(cy·cp, cy·sp, sy·cp, −sy·sp)` with half-angle sines and cosines. OpenVR controllers point along −Z, so this aims the ray.
|
||
- Position = HMD position + `(0, originOffsetY, 0)` in world space.
|
||
- When inactive: `deviceIsConnected = false` and `poseIsValid = false`, so the compositor falls back to the real controllers.
|
||
|
||
### RunFrame (inputs)
|
||
| component | driven by | bound to (compositor) |
|
||
|---|---|---|
|
||
| `/input/trigger/click`, `/value` | left button | `lasermouse/LeftClick`, `lasermouse_secondary/SwitchLaserHand` |
|
||
| `/input/a/click` | right button | `lasermouse/RightClick` |
|
||
| `/input/b/click` | middle button | `lasermouse/MiddleClick` |
|
||
| `/input/grip/click` | held **true** while active | `quickmouse/ActivateQuickMouse` (keeps the laser up, as squeezing a grip does on Frame controllers) |
|
||
| `/input/thumbstick/y`, `/x` (+ `/touch`) | wheel → Y, tilt wheel → X, via `StickStepper` (see below) | `dualanalog/LeftValue`/`RightValue` (+ touch, mode `joystick`), `scroll_discrete` and `scroll_smooth` (mode `scroll`), the same as the Frame controller's stick |
|
||
|
||
### Wheel → thumbstick (`StickStepper`)
|
||
Steam UI lists are navigated through the compositor's `/actions/dualanalog` stick values, not through scroll events. The Frame controllers' `steam.client` binding only carries haptics. A wheel only produces discrete notches, so `StickStepper` turns them into stick deflection. It is timed with `steady_clock`, independently of the frame rate, and has two modes, chosen with `wheelMode`.
|
||
|
||
**smooth** (default, 0.3.0):
|
||
- On a notch: if the stick is at rest, it jumps to `wheelSmoothMin` (+ `wheelSmoothImpulse` for each extra notch in the same event). Otherwise `wheelSmoothImpulse` is added to its current value. Either way it is capped at `wheelDeflection`.
|
||
- For `wheelSmoothHoldMs` after a notch the value holds. After that it decays exponentially with time constant `wheelSmoothDecayMs`, and snaps to 0 below 0.1.
|
||
- The result:
|
||
- A single notch is about 130 ms above 0.5, which is one list step, followed by a short glide.
|
||
- Spinning at 10 notches/s ramps up to a steady 0.8–1.0 hold, which scrolls continuously without dips.
|
||
- Slow notches (3/s) remain separate steps.
|
||
- These figures come from an offline 120 Hz simulation that drives `StickStepper` directly.
|
||
|
||
**step** (0.2.0 behaviour):
|
||
- The stick is pushed to `±wheelDeflection` for `wheelPressMs`, then held at centre for `wheelReleaseMs`.
|
||
- Further notches queue up, to at most `wheelMaxQueued`.
|
||
- Reversing direction drops the queue and switches immediately.
|
||
- When laser mode turns off, the queue is cleared.
|
||
|
||
### Render model
|
||
`{mouselaser}mouselaser_none` is a single 0.1 mm triangle with a transparent 1×1 texture. Without a model of its own, SteamVR would draw a generic controller at your face.
|
||
|
||
## Files
|
||
| file | purpose |
|
||
|---|---|
|
||
| `driver.vrdrivermanifest` | Driver name and `alwaysActivate`. |
|
||
| `resources/settings/default.vrsettings` | Defaults for the `driver_mouselaser` section. |
|
||
| `resources/input/mouselaser_profile.json` | Input profile. Controller type `mouselaser`, declares components and points to the default bindings. |
|
||
| `resources/input/vrcompositor_bindings_mouselaser.json` | Bindings for app key `openvr.component.vrcompositor`, the laser and dashboard. |
|
||
| `resources/rendermodels/mouselaser_none/` | The invisible model. |
|
||
| `resources/localization/localization.json` | Display names (en_US, es_ES) for the controller type and its inputs, as shown in SteamVR's binding screens: "Left Click", "Mouse Wheel" and so on. |
|
||
|
||
## Changing things safely
|
||
- After editing code: `cmake --build build`, then reboot. The `.so` is only loaded when SteamVR starts.
|
||
- After editing bindings or the profile: reboot. SteamVR may also cache bindings per controller type under `~/.local/share/Steam/config/` or `~/.config/openvr/`.
|
||
- To add a setting: add it to `Settings::Load()`, add the default to `default.vrsettings`, and document it in the README table.
|