mirror of
https://github.com/Andalu30/frame-unboundedMouse-vibed.git
synced 2026-10-06 00:00:04 +02:00
Side button now works as Back.
This commit is contained in:
1 parent
da48c37235
commit
12416e005d
6 files changed
+39
-12
No files matched your search
@@ -33,6 +33,7 @@ The compositor then treats it like any other laser-pointing hand.
|
||||
| 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 |
|
||||
| Back side button (BTN_SIDE) | Normal | SteamVR's laser **Back**, the same action as the right Frame controller's B button. Works in Steam UI menus; it does not reach the KDE desktop (see Known limitations). |
|
||||
| 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.
|
||||
@@ -55,7 +56,7 @@ cmake -S . -B build -G Ninja && cmake --build build
|
||||
Check that it loaded:
|
||||
```sh
|
||||
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>)"
|
||||
# expect: "version 0.4.0-experimental", "activated as device N, role 1", "using /dev/input/eventX (<your mouse>)"
|
||||
```
|
||||
|
||||
### Optional: offline wheel test
|
||||
@@ -77,6 +78,7 @@ Add any of these to `~/.config/openvr/config/steamvr.vrsettings` under a `"drive
|
||||
| `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). |
|
||||
| `backButton` | `275` | evdev key code that sends the laser Back action while laser mode is on (275 = BTN_SIDE). `-1` disables it. |
|
||||
| `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. |
|
||||
@@ -96,11 +98,6 @@ Add any of these to `~/.config/openvr/config/steamvr.vrsettings` under a `"drive
|
||||
- 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}` to `steamvr.vrsettings`, which is the same `enable` setting 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.
|
||||
@@ -123,6 +120,7 @@ If SteamVR won't come up properly after installing, there are four options:
|
||||
- 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.
|
||||
- **Back doesn't reach the KDE desktop.** SteamVR's laser Back goes to SteamVR overlays. Steam's UI handles it, but gamescope doesn't pass it on to the windows it hosts (a real controller's B behaves the same). With laser mode off, the side buttons don't work in KDE either, because the nested KWin's X11 backend drops X buttons 8 and up. See [docs/steam-frame-background.md](docs/steam-frame-background.md).
|
||||
- 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.
|
||||
|
||||
@@ -56,7 +56,9 @@ The broader notes from that session (hardware, display pipeline, KDE scale, ultr
|
||||
|
||||
13. **Demo media** (2026-10-01). The owner recorded `~/Videos/mousedemo.mp4` (44.2 s, 1920×1080, 20 fps). It was trimmed (first 3 s and last 2 s removed, leaving 39.2 s) and converted to `docs/media/demo.webp`, which the README shows as the hero image.
|
||||
|
||||
14. **Back button, 0.4.0** (2026-10-04). The mouse's back side button (BTN_SIDE, setting `backButton`) is now bound to the compositor's `/actions/lasermouse/in/back`. That is the same action as the right Frame controller's B; right A is `home`. The owner reports Back works in the Steam UI and not on the KDE overlay. Investigating KDE separately (see `steam-frame-background.md`, input path) showed two causes: gamescope doesn't forward SteamVR Back to its windows, and KWin's X11 nested backend drops X buttons 8 and up even with laser mode off. The driver can't fix either one, and nothing was added for it.
|
||||
|
||||
## State at hand-off
|
||||
- The driver is registered from this repo (`<repo>/driver/mouselaser`). The old prototype registration has been removed.
|
||||
- Nothing is vendored. The build depends on SteamVR's bundled header.
|
||||
- Licensed MIT (see `LICENSE`). SPDX tags are on the sources.
|
||||
- Licensed MIT (see `LICENSE`). SPDX tags are on the sources.
|
||||
@@ -23,6 +23,8 @@ DSI-1 panel (2× 2160x2160 @ 108/120/144 Hz)
|
||||
```
|
||||
- KWin on the X11 windowed backend never uses libinput, so KDE's mouse and keyboard settings pages are empty. `/org/kde/KWin/InputDevice` doesn'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 with `xinput test-xi2`). KWin 6.2.5's X11 windowed backend (`X11WindowedBackend::handleButtonPress`) then discards every X button above 7: its `default:` branch `return`s. 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.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"/input/b" : { "type" : "button", "click" : true, "binding_image_point" : [ 50, 50 ], "order" : 3 },
|
||||
"/input/grip" : { "type" : "button", "click" : true, "binding_image_point" : [ 50, 50 ], "order" : 4 },
|
||||
"/input/thumbstick" : { "type" : "joystick", "touch" : true, "binding_image_point" : [ 50, 50 ], "order" : 5 },
|
||||
"/input/back" : { "type" : "button", "click" : true, "binding_image_point" : [ 50, 50 ], "order" : 6 },
|
||||
"/pose/raw" : { "type" : "pose", "binding_image_point" : [ 50, 50 ] },
|
||||
"/pose/tip" : { "type" : "pose", "binding_image_point" : [ 50, 50 ] }
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"app_key": "openvr.component.vrcompositor",
|
||||
"category": "steamvr_input",
|
||||
"controller_type": "mouselaser",
|
||||
"description": "Mouse aims the SteamVR laser. Buttons click, and the wheel acts as the thumbstick (lists and scrolling).",
|
||||
"description": "Mouse aims the SteamVR laser. Buttons click, the back side button goes back, and the wheel acts as the thumbstick (lists and scrolling).",
|
||||
"name": "Mouse Laser: dashboard and overlays",
|
||||
"options": {},
|
||||
"simulated_actions": [],
|
||||
@@ -74,6 +74,24 @@
|
||||
"output": "/actions/lasermouse/in/middleclick"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/user/hand/left/input/back",
|
||||
"mode": "button",
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/lasermouse/in/back"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/user/hand/right/input/back",
|
||||
"mode": "button",
|
||||
"inputs": {
|
||||
"click": {
|
||||
"output": "/actions/lasermouse/in/back"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+10
-4
@@ -31,7 +31,7 @@
|
||||
using namespace vr;
|
||||
|
||||
static const char *k_section = "driver_mouselaser";
|
||||
static const char *k_version = "0.3.1-experimental";
|
||||
static const char *k_version = "0.4.0-experimental";
|
||||
|
||||
static void Log(const char *fmt, ...)
|
||||
{
|
||||
@@ -54,6 +54,7 @@ struct Settings
|
||||
{
|
||||
float sensitivityDeg = 0.05f; // degrees of ray rotation per mouse count
|
||||
int toggleButton = BTN_EXTRA;
|
||||
int backButton = BTN_SIDE; // sends the compositor's laser "back"; -1 = none
|
||||
int role = TrackedControllerRole_LeftHand;
|
||||
std::string nameFilter; // substring of the evdev name; empty = first mouse found
|
||||
float originOffsetY = -0.08f; // metres, world space, relative to the HMD
|
||||
@@ -79,6 +80,8 @@ struct Settings
|
||||
if (err == VRSettingsError_None) sensitivityDeg = f;
|
||||
int32_t i = s->GetInt32(k_section, "toggleButton", &err);
|
||||
if (err == VRSettingsError_None) toggleButton = i;
|
||||
i = s->GetInt32(k_section, "backButton", &err);
|
||||
if (err == VRSettingsError_None) backButton = i;
|
||||
i = s->GetInt32(k_section, "role", &err);
|
||||
if (err == VRSettingsError_None) role = i;
|
||||
char buf[256] = {};
|
||||
@@ -194,7 +197,7 @@ public:
|
||||
std::atomic<bool> toggled{false}; // set on every toggle; RunFrame clears it
|
||||
std::atomic<int> dx{0}, dy{0};
|
||||
std::atomic<int> wheel{0}, hwheel{0};
|
||||
std::atomic<bool> left{false}, right{false}, middle{false};
|
||||
std::atomic<bool> left{false}, right{false}, middle{false}, back{false};
|
||||
|
||||
// Destroying a joinable std::thread calls std::terminate (taking vrserver down),
|
||||
// so make sure the thread is stopped even if Deactivate() was never called.
|
||||
@@ -281,7 +284,7 @@ private:
|
||||
Log("EVIOCGRAB failed (%s), staying inactive\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
left = right = middle = false;
|
||||
left = right = middle = back = false;
|
||||
dx = dy = wheel = hwheel = 0;
|
||||
active = on;
|
||||
toggled = true;
|
||||
@@ -338,6 +341,7 @@ private:
|
||||
if (ev.code == BTN_LEFT) left = ev.value != 0;
|
||||
else if (ev.code == BTN_RIGHT) right = ev.value != 0;
|
||||
else if (ev.code == BTN_MIDDLE) middle = ev.value != 0;
|
||||
else if (ev.code == m_settings.backButton) back = ev.value != 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -364,6 +368,7 @@ public:
|
||||
VRScalarType_Absolute, VRScalarUnits_NormalizedOneSided);
|
||||
VRDriverInput()->CreateBooleanComponent(c, "/input/a/click", &m_a);
|
||||
VRDriverInput()->CreateBooleanComponent(c, "/input/b/click", &m_b);
|
||||
VRDriverInput()->CreateBooleanComponent(c, "/input/back/click", &m_back);
|
||||
VRDriverInput()->CreateBooleanComponent(c, "/input/grip/click", &m_grip);
|
||||
VRDriverInput()->CreateBooleanComponent(c, "/input/thumbstick/touch", &m_stickTouch);
|
||||
VRDriverInput()->CreateScalarComponent(c, "/input/thumbstick/x", &m_stickX,
|
||||
@@ -450,6 +455,7 @@ public:
|
||||
VRDriverInput()->UpdateScalarComponent(m_triggerValue, l ? 1.f : 0.f, 0);
|
||||
VRDriverInput()->UpdateBooleanComponent(m_a, active && m_mouse.right, 0);
|
||||
VRDriverInput()->UpdateBooleanComponent(m_b, active && m_mouse.middle, 0);
|
||||
VRDriverInput()->UpdateBooleanComponent(m_back, active && m_mouse.back, 0);
|
||||
VRDriverInput()->UpdateBooleanComponent(m_grip, active, 0); // holds "quick mouse" on
|
||||
VRDriverInput()->UpdateBooleanComponent(m_stickTouch, stickX != 0.f || stickY != 0.f, 0);
|
||||
VRDriverInput()->UpdateScalarComponent(m_stickX, stickX, 0);
|
||||
@@ -464,7 +470,7 @@ private:
|
||||
DriverPose_t m_pose = {};
|
||||
float m_yaw = 0, m_pitch = 0;
|
||||
StickStepper m_stepX, m_stepY;
|
||||
VRInputComponentHandle_t m_trigger = 0, m_triggerValue = 0, m_a = 0, m_b = 0, m_grip = 0;
|
||||
VRInputComponentHandle_t m_trigger = 0, m_triggerValue = 0, m_a = 0, m_b = 0, m_back = 0, m_grip = 0;
|
||||
VRInputComponentHandle_t m_stickTouch = 0, m_stickX = 0, m_stickY = 0;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user