mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-15 04:05:35 +02:00
pointer: the 3D mouse is the right hand again, not a treadmill
As a treadmill (the default since 564bb4b), the 3D mouse's laser clicks and
resizes SteamVR's windows but can't move them by their grab bars: the
compositor's window grab follows only /user/hand/left or /user/hand/right,
and logs nothing. With the right hand's role the same laser moves them
(headset test 2026-10-13, SteamVR 2.18.2). The driver's default role is 2
again and POINTER_ROLE defaults to right; treadmill stays a setting.
update-check warns when the device is at /user/treadmill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8f79a90044
commit
797e5c7b12
7 files changed
+24
-22
No files matched your search
@@ -123,6 +123,8 @@ The driver starts disconnected, because holding the right-hand role while SteamV
|
||||
|
||||
With a hand role, a Frame controller held in that hand takes the role back (its touch sensors count as use), and the virtual controller is left with no role and no clicks. So mixing a controller in one hand with the 3D mouse or a bare hand's ray in the other fails whenever that controller is merely held. The treadmill role (4) is no hand: the real controllers keep both hand roles, and the virtual controller never fights them. It held the dashboard laser in the gaze-first tests (2026-09-30, branch `gaze-first`), with two conditions. SteamVR gives a device the `/user/treadmill` path only when the device is added, so the driver must hint the role from `Activate`, not on connect: the `role` in its `default.vrsettings` (or `driver_ft_pointer/role` in SteamVR's settings), and switching it needs a SteamVR restart. And the compositor binding needs `/user/treadmill` entries next to the hand ones (`ft_pointer_vrcompositor.json`). A treadmill reports no controller role (`GetControllerRoleForTrackedDeviceIndex` is invalid), so the helper's "no hand role" release skips it. The laser claim (`/input/a`, bound to `switchlaserhand`) works the same; SteamVR took the laser back to our device 11-15 ms after a controller took it.
|
||||
|
||||
It was the default from 2026-10-09 until 2026-10-13, and then went back to the right hand. As a treadmill, the laser clicks and resizes SteamVR's windows, but pressing a window's grab bar doesn't move it: the compositor's window grab follows only a hand (it hard-codes `/user/hand/left` and `/user/hand/right`), and logs nothing. With the right hand's role, the same laser moves them (headset test, 2026-10-13, SteamVR 2.18.2). So the default is the right hand, and treadmill stays a setting for those who'd rather keep a controller in either hand than move windows with the 3D mouse.
|
||||
|
||||
### The cursor
|
||||
|
||||
Mouse motion turns into yaw and pitch around an anchor, the head position at the last recenter. A ray from the anchor is tested against every visible overlay with `ComputeOverlayIntersection`. On a hit, the cursor sits on that surface; otherwise it floats at `POINTER_DISTANCE`. Since the anchor isn't your current eye position, a second test runs along your line of sight to the cursor point, and anything nearer wins, so the cursor always lands on what you see under it. Overlays in `POINTER_IGNORE` are left out of both tests. A display-only panel, like a performance overlay locked to your view, has no input method, so SteamVR's laser passes through it, but `ComputeOverlayIntersection` still hits it, and the cursor stuck to it. The laser starts just before the cursor point, so an ignored panel nearer to you doesn't catch it either. Both tests ask SteamVR about every visible overlay, so a frame where nothing moved (the mouse, the anchor, the eye by more than 5 mm, which overlays show) reuses the last result, for up to 100 ms, since overlays can also move on their own. The dots' overlay settings go to SteamVR only when they change, and the pose goes to the driver, which keeps the last one, only when the laser would land 0.1 mm or more elsewhere, and at least every 100 ms.
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ A mouse drives SteamVR the way a controller's laser does, but shows up as a smal
|
||||
|
||||
- The input relay, in pointer mode (`POINTER=1`), sends mouse motion, clicks, and scrolling to the helper. A deliberate movement or a click wakes the pointer, and 30 seconds without mouse input releases it.
|
||||
- The helper, `pointer/helper/ft-pointer`, runs in the `dev` container as `frametop-pointer.service` and starts with SteamVR. It keeps the cursor, tests it against every visible overlay, draws the dot, and sends the driver an exact pose.
|
||||
- The driver, `pointer/driver/` (`ft_pointer`), is loaded by SteamVR. It's an invisible virtual controller whose laser follows the cursor. It takes the treadmill role, no hand, so the real controllers keep theirs and one can be used beside it (`POINTER_ROLE` and the driver's `role` setting; see docs/design.md, "Treadmill role").
|
||||
- The driver, `pointer/driver/` (`ft_pointer`), is loaded by SteamVR. It's an invisible virtual controller whose laser follows the cursor. It takes the right hand's role. A Frame controller held in that hand takes the role back, so use the pointer on the other hand then (`POINTER_ROLE` and the driver's `role` setting; see docs/design.md, "Treadmill role").
|
||||
|
||||
Whichever device you used last wins. Picking up a controller hands the laser back at once, and moving the mouse takes it again. With hand interaction (`POINTER_HANDS=1`), this goes per hand: a controller used recently wins its hand, and the other hand can stay bare. When the headset comes off, the pointer lets go, so the displays can sleep, and it stays off until you're wearing the headset again.
|
||||
|
||||
|
||||
@@ -32,9 +32,10 @@
|
||||
// that hand, so the real controller would never get it back otherwise.
|
||||
//
|
||||
// Settings (steamvr.vrsettings section "driver_ft_pointer", defaults in
|
||||
// ft_pointer/resources/settings/default.vrsettings): role (int, 2 = right hand, 4 = treadmill, the
|
||||
// default, 5 = stylus). A treadmill is hinted from Activate on, shown or not: SteamVR gives it
|
||||
// ft_pointer/resources/settings/default.vrsettings): role (int, 2 = right hand, the default,
|
||||
// 4 = treadmill, 5 = stylus). A treadmill is hinted from Activate on, shown or not: SteamVR gives it
|
||||
// /user/treadmill only when the device is added. Only a hand role switches to OptOut while hidden.
|
||||
// A treadmill can't move SteamVR's windows by their grab bars (docs/design.md, "Treadmill role").
|
||||
#include <openvr_driver.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"driver_ft_pointer": {
|
||||
"enable": true,
|
||||
"role": 4
|
||||
"role": 2
|
||||
}
|
||||
}
|
||||
@@ -230,14 +230,14 @@
|
||||
// the dot hides and a press answers the panel instead of clicking: a left click, gaze_left, or
|
||||
// a gaze_precision or gaze_drag press sends "calaccept" to @ft_gazed (take this dot now), a right
|
||||
// click or gaze_right "calquit" (calpanel.h).
|
||||
// POINTER_ROLE (treadmill, the default, or right, left, stylus): the role our device takes while
|
||||
// connected. Treadmill is no hand, so the real controllers keep both hand roles and a controller
|
||||
// can be used beside the pointer (or a bare hand's ray). The driver must hint it from the start
|
||||
// (its default.vrsettings role 4, or driver_ft_pointer/role; a SteamVR restart to change), so this
|
||||
// setting can't make a device added as a hand into a treadmill. With a hand role, a Frame controller
|
||||
// in your hand counts as used through its touch sensors and takes its hand's role back, and then
|
||||
// no click lands (see "no hand role" in the main loop): with a controller held in the right hand,
|
||||
// the pointer needs the left hand, or the stylus role. docs/design.md, "Treadmill role".
|
||||
// POINTER_ROLE (right, the default, or left, stylus, treadmill): the role our device takes while
|
||||
// connected. With a hand role, a Frame controller in your hand counts as used through its touch
|
||||
// sensors and takes its hand's role back, and then no click lands (see "no hand role" in the main
|
||||
// loop): with a controller held in the right hand, the pointer needs the left hand, or the stylus
|
||||
// role. Treadmill is no hand, so the real controllers keep both hand roles, but SteamVR's windows
|
||||
// don't move by their grab bars for it. The driver must hint it from the start (its
|
||||
// default.vrsettings role 4, or driver_ft_pointer/role; a SteamVR restart to change), so this
|
||||
// setting can't make a device added as a hand into a treadmill. docs/design.md, "Treadmill role".
|
||||
//
|
||||
// Hands (POINTER_HANDS, off by default; needs hand tracking, hands/): ft-hands publishes
|
||||
// pinches and grips (hands/include/fh_gestures.h), read here every frame.
|
||||
@@ -1005,7 +1005,7 @@ int main() {
|
||||
// holds back its press in gaze mode; direct: it clicks at once), POINTER_ROLE.
|
||||
bool gazeMousePrecision = true;
|
||||
bool gazeMouseHeld = true; // POINTER_GAZE_MOUSE_MOVE (see the top)
|
||||
std::string role = "treadmill";
|
||||
std::string role = "right";
|
||||
double pickupScale = 1; // POINTER_CONTROLLER_PICKUP: scales the controller-moved limits
|
||||
std::vector<std::string> ignore; // POINTER_IGNORE (see ParseIgnore)
|
||||
auto loadConfig = [&] {
|
||||
@@ -1088,9 +1088,9 @@ int main() {
|
||||
const auto mm = conf.find("POINTER_GAZE_MOUSE_MOVE");
|
||||
gazeMouseHeld = mm == conf.end() || mm->second != "free";
|
||||
const auto ro = conf.find("POINTER_ROLE");
|
||||
role = ro != conf.end() && (ro->second == "left" || ro->second == "right" || ro->second == "stylus")
|
||||
role = ro != conf.end() && (ro->second == "left" || ro->second == "stylus" || ro->second == "treadmill")
|
||||
? ro->second
|
||||
: "treadmill";
|
||||
: "right";
|
||||
pickupScale = std::clamp(ConfDouble(conf, "POINTER_CONTROLLER_PICKUP", 1), 0.5, 5.0);
|
||||
const auto ig = conf.find("POINTER_IGNORE");
|
||||
ignore = ParseIgnore(ig == conf.end() ? "" : ig->second);
|
||||
|
||||
@@ -498,17 +498,16 @@ def check_vr_socket():
|
||||
except (OSError, ValueError) as e:
|
||||
report("FAIL", label, f"/input/getstate.json didn't answer ({e}); the pause gesture can't read the controllers")
|
||||
return
|
||||
# The 3D mouse's virtual controller as a treadmill (the driver's default role 4): SteamVR gives it
|
||||
# /user/treadmill, where its compositor binding has its laser entries (docs/design.md, "Treadmill role").
|
||||
# The 3D mouse's virtual controller on a hand (the driver's default role 2). As a treadmill (role 4)
|
||||
# SteamVR's windows don't move by their grab bars for it (docs/design.md, "Treadmill role").
|
||||
for d in devices:
|
||||
if d.get("controller_type") == "ft_pointer":
|
||||
path = d.get("root_path") or "?"
|
||||
if path == "/user/treadmill":
|
||||
report("ok", "pointer device role", "the virtual controller is at /user/treadmill")
|
||||
report("warn", "pointer device role", "the virtual controller is at /user/treadmill: its laser "
|
||||
"clicks but can't move SteamVR's windows; set the driver's role to 2 (right hand)")
|
||||
else:
|
||||
report("warn", "pointer device role", f"the virtual controller is at {path}, not /user/treadmill: "
|
||||
"an older driver (role 2), or SteamVR moved the treadmill path. Retest the laser with "
|
||||
"pointer/probe/build/lasertest (frame-hands/notes/hand-interaction.md, probe P1)")
|
||||
report("ok", "pointer device role", f"the virtual controller is at {path}")
|
||||
found = [d for d in devices if d.get("controller_type") == "frame_controller" and d.get("root_path")
|
||||
and d.get("side") in ("left", "right")]
|
||||
if found and not all(any(str(c.get("path", "")).endswith("/touch") for c in d.get("components", [])) for d in found):
|
||||
|
||||
@@ -16,7 +16,7 @@ POINTER_IDLE=30 # seconds without mouse use before the controllers get their
|
||||
POINTER_WAKE_COUNTS=40 # mouse counts within 1 s needed to wake or re-claim the laser (ignores desk jitter)
|
||||
POINTER_CONTROLLER_PICKUP=1 # how hard a controller must move (x 0.35 m/s or 2 rad/s, for 100 ms) to take the laser back; raise it if resting controllers do (0.5 to 5)
|
||||
POINTER_DISTANCE=1.5 # metres to the cursor when it isn't on a panel
|
||||
POINTER_ROLE=treadmill # the role the pointer's controller takes: treadmill (no hand: controllers keep theirs; needs the driver's default role 4 and a SteamVR restart) | right | left | stylus
|
||||
POINTER_ROLE=right # the role the pointer's controller takes: right | left | stylus | treadmill (no hand: controllers keep theirs, but SteamVR's windows don't move by their grab bars; needs the driver's role 4 and a SteamVR restart)
|
||||
POINTER_CURSOR_DEG=0.4 # size of the free-space dot, in degrees
|
||||
POINTER_ORIGIN_FRACTION=0.95 # laser starts this far along eye->cursor: its beam is a few cm, SteamVR's hit dot tiny
|
||||
POINTER_LASER_WIDTH=0.8 # controller beam width (dashboard.laserRayWidthScale), set once at helper start
|
||||
|
||||
Reference in new issue
Block a user