mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 04:04:09 +02:00
Don't let resting controllers take the laser from the mouse
A controller released the 3D mouse's pointer on a single pose sample faster than 0.35 m/s or 2 rad/s, once the mouse had been still for 500 ms. The helper polls about every 8 ms, so one noisy sample was enough: a knock on the desk, or a tracking jump when the headset's cameras pick a resting controller up again (#1). A controller now has to stay over the limit for 100 ms in a row, and only samples with a normal tracking result (Running_OK) count. The new POINTER_CONTROLLER_PICKUP setting (1 by default, 0.5 to 5) scales both limits; it's a slider on the Pointer page of Frametop Input Settings and applies live. A controller picked up for real still gets the laser back through SteamVR's hand role, which follows its touch sensors. The release log now records the speed and spin that triggered it, to tune the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
14879023f3
commit
0857a54fab
5 files changed
+33
-11
No files matched your search
+1
-1
@@ -91,7 +91,7 @@ Replacing a loaded driver's files, as re-running the installer used to do, leave
|
||||
|
||||
### Handing the laser back and forth
|
||||
|
||||
The dashboard follows whichever device summoned it or last pressed its trigger. Frametop adds "last used wins": moving a real controller releases the pointer at once, and the next mouse movement takes the laser back. Small movements don't count; waking needs `POINTER_WAKE_COUNTS` of mouse motion within a second, so desk jitter doesn't steal the laser. While the pointer is awake, a tiny transparent overlay with `MakeOverlaysInteractiveIfVisible` keeps SteamVR's laser mouse on, since otherwise the first click would only switch the laser on.
|
||||
The dashboard follows whichever device summoned it or last pressed its trigger. Frametop adds "last used wins": moving a real controller releases the pointer, and the next mouse movement takes the laser back. Moving means faster than 0.35 m/s or 2 rad/s (both times `POINTER_CONTROLLER_PICKUP`, 1 by default) for 100 ms in a row, while the controller is tracked normally. A single sample over the limit used to be enough, and controllers resting on a desk took the laser back on a knock or a tracking jump while the mouse was in use. Small movements don't count; waking needs `POINTER_WAKE_COUNTS` of mouse motion within a second, so desk jitter doesn't steal the laser. While the pointer is awake, a tiny transparent overlay with `MakeOverlaysInteractiveIfVisible` keeps SteamVR's laser mouse on, since otherwise the first click would only switch the laser on.
|
||||
|
||||
When the headset comes off, SteamVR reports its activity level as idle at once and turns the displays off 5 seconds later (`power.turnOffScreensTimeout`), unless something keeps it awake. An awake pointer did, and so did the helper's `vrcmd` runs: each is a new SteamVR client, and a new client every second kept SteamVR out of standby. The helper now releases the pointer as soon as the headset is idle, ignores the mouse until you're wearing it again, and pauses the overlay list whenever the pointer is off.
|
||||
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ pointer/helper/run.sh status | log | restart
|
||||
pointer/driver/install.sh probe # devices, hand roles, who owns the dashboard pointer
|
||||
```
|
||||
|
||||
The pointer settings are in `~/.config/frametop.conf`: `POINTER_SENSITIVITY`, `POINTER_IDLE`, `POINTER_WAKE_COUNTS`, `POINTER_DISTANCE`, `POINTER_CURSOR_DEG`, `POINTER_ORIGIN_FRACTION`, `POINTER_ORIGIN_MARGIN`, `POINTER_SCENE_RADIUS`, `POINTER_EDGE_REACH`, `POINTER_LASER_WIDTH`, the head follow settings `POINTER_FOLLOW`, `POINTER_LEASH_DEG`, `POINTER_LEASH_DELAY`, `POINTER_LEASH_RETURN`, and `POINTER_FOLLOW_REACH`, and the gaze mode settings `POINTER_GAZE`, `POINTER_GAZE_RETAKE`, `POINTER_GAZE_NUDGE_MAX`, `POINTER_GAZE_HOLD`, and `POINTER_GAZE_SHOW`. The example config explains each. Frametop Input Settings changes them live; after editing the file by hand, restart the relay or the helper.
|
||||
The pointer settings are in `~/.config/frametop.conf`: `POINTER_SENSITIVITY`, `POINTER_IDLE`, `POINTER_WAKE_COUNTS`, `POINTER_CONTROLLER_PICKUP`, `POINTER_DISTANCE`, `POINTER_CURSOR_DEG`, `POINTER_ORIGIN_FRACTION`, `POINTER_ORIGIN_MARGIN`, `POINTER_SCENE_RADIUS`, `POINTER_EDGE_REACH`, `POINTER_LASER_WIDTH`, the head follow settings `POINTER_FOLLOW`, `POINTER_LEASH_DEG`, `POINTER_LEASH_DELAY`, `POINTER_LEASH_RETURN`, and `POINTER_FOLLOW_REACH`, and the gaze mode settings `POINTER_GAZE`, `POINTER_GAZE_RETAKE`, `POINTER_GAZE_NUDGE_MAX`, `POINTER_GAZE_HOLD`, and `POINTER_GAZE_SHOW`. The example config explains each. Frametop Input Settings changes them live; after editing the file by hand, restart the relay or the helper.
|
||||
|
||||
## Frametop Input Settings
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ POINTER_SETTINGS = [
|
||||
("POINTER_FOLLOW_REACH", "Head follow reach", 70, 20, 85, 1, "°"),
|
||||
("POINTER_WAKE_COUNTS", "Movement to wake", 40, 5, 200, 5, "counts"),
|
||||
("POINTER_IDLE", "Release after idle", 30, 5, 120, 5, "s"),
|
||||
("POINTER_CONTROLLER_PICKUP", "Controller movement to take over", 1.0, 0.5, 5.0, 0.1, "×"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -37,8 +37,12 @@
|
||||
// second kept SteamVR from going to standby.
|
||||
//
|
||||
// Last used wins: when a real controller moves (picked up), the pointer is released
|
||||
// at once (driver "hide", which also drops its hand role hint), so the controller gets
|
||||
// (driver "hide", which also drops its hand role hint), so the controller gets
|
||||
// its role and laser back. The next mouse input reconnects and claims the laser again.
|
||||
// Moving means faster than 0.35 m/s or 2 rad/s, both times POINTER_CONTROLLER_PICKUP,
|
||||
// for 100 ms in a row with the controller tracked normally: a single sample over the
|
||||
// limit was enough before, and controllers resting on a desk released the pointer on a
|
||||
// knock or a tracking jump.
|
||||
// SteamVR gives a contested hand role to the most recently used device, and a held
|
||||
// Frame controller counts as used (touch sensors). If our device hasn't got the hand
|
||||
// role within a second of waking, the pointer is released (no orphan white dot) and
|
||||
@@ -190,7 +194,8 @@
|
||||
// POINTER_LEASH_DEG (10), POINTER_LEASH_DELAY (0.2 s), POINTER_LEASH_RETURN (0.2 s),
|
||||
// POINTER_FOLLOW_REACH (70 deg): head follow, above. POINTER_GAZE (0), POINTER_GAZE_RETAKE
|
||||
// (5 deg), POINTER_GAZE_NUDGE_MAX (8 deg), POINTER_GAZE_HOLD (0.5 s), POINTER_GAZE_SHOW (1 s):
|
||||
// gaze mode, above.
|
||||
// gaze mode, above. POINTER_CONTROLLER_PICKUP (1, 0.5 to 5): how hard a controller must
|
||||
// move to take the laser back, above.
|
||||
#include <openvr.h>
|
||||
|
||||
#include "vrbuttons.h"
|
||||
@@ -426,6 +431,7 @@ int main() {
|
||||
// Gaze mode (see the top); gazeConf is POINTER_GAZE as last read, like followConf.
|
||||
bool gazeOn = false, gazeConf = false;
|
||||
double gazeRetake = 5, gazeNudgeMax = 8, gazeHold = 0.5, gazeShow = 1;
|
||||
double pickupScale = 1; // POINTER_CONTROLLER_PICKUP: scales the controller-moved limits
|
||||
auto loadConfig = [&] {
|
||||
const auto conf = ReadConfig();
|
||||
freeDistance = std::clamp(ConfDouble(conf, "POINTER_DISTANCE", 1.5), 0.3, 10.0);
|
||||
@@ -448,6 +454,7 @@ int main() {
|
||||
gazeShow = std::clamp(ConfDouble(conf, "POINTER_GAZE_SHOW", 1), 0.0, 30.0);
|
||||
const bool wantGaze = ConfDouble(conf, "POINTER_GAZE", 0) != 0;
|
||||
if (wantGaze != gazeConf) gazeOn = gazeConf = wantGaze;
|
||||
pickupScale = std::clamp(ConfDouble(conf, "POINTER_CONTROLLER_PICKUP", 1), 0.5, 5.0);
|
||||
};
|
||||
loadConfig();
|
||||
const float laserWidth = float(ConfDouble(ReadConfig(), "POINTER_LASER_WIDTH", 0.8));
|
||||
@@ -515,6 +522,8 @@ int main() {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
Clock::time_point lastMouse{}, claimAt{}, claimRelease{}, wokeAt{}, noWakeUntil{};
|
||||
bool claimPending = false, claimHeld = false;
|
||||
// Last used wins: since when each controller has been moving (zero: it isn't).
|
||||
Clock::time_point movingSince[vr::k_unMaxTrackedDeviceCount] = {};
|
||||
// Tilt mode (see top of file).
|
||||
bool leftHeld = false, tilting = false, tiltStart = false, swallowedRight = false;
|
||||
double tiltYaw = 0, tiltPitch = 0;
|
||||
@@ -952,8 +961,9 @@ int main() {
|
||||
recenter = true;
|
||||
} else if (std::strncmp(buf, "reload", 6) == 0) {
|
||||
loadConfig();
|
||||
std::printf("reloaded: free distance %.2f m, dot %.2f deg, origin %.2f, head follow %s, leash %.0f deg\n",
|
||||
freeDistance, cursorDeg, originFraction, follow ? "on" : "off", leashDeg);
|
||||
std::printf("reloaded: free distance %.2f m, dot %.2f deg, origin %.2f, head follow %s, leash %.0f deg, "
|
||||
"controller pickup %.1fx\n",
|
||||
freeDistance, cursorDeg, originFraction, follow ? "on" : "off", leashDeg, pickupScale);
|
||||
std::fflush(stdout);
|
||||
} else if (std::strncmp(buf, "follow", 6) == 0) {
|
||||
const char *arg = buf + 6;
|
||||
@@ -1019,26 +1029,36 @@ int main() {
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
// Last used wins: a real controller being moved releases the pointer.
|
||||
// Last used wins: a real controller being moved releases the pointer (see the top).
|
||||
if (active && tnow - lastMouse > std::chrono::milliseconds(500)) {
|
||||
for (vr::TrackedDeviceIndex_t i = 1; i < vr::k_unMaxTrackedDeviceCount; ++i) {
|
||||
if (i == ours || !all[i].bPoseIsValid) continue;
|
||||
if (sys->GetTrackedDeviceClass(i) != vr::TrackedDeviceClass_Controller) continue;
|
||||
if (i == ours || !all[i].bPoseIsValid || all[i].eTrackingResult != vr::TrackingResult_Running_OK ||
|
||||
sys->GetTrackedDeviceClass(i) != vr::TrackedDeviceClass_Controller) {
|
||||
movingSince[i] = {};
|
||||
continue;
|
||||
}
|
||||
const auto &v = all[i].vVelocity.v, &w = all[i].vAngularVelocity.v;
|
||||
const double speed = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
const double spin = std::sqrt(w[0] * w[0] + w[1] * w[1] + w[2] * w[2]);
|
||||
if (speed > 0.35 || spin > 2.0) {
|
||||
if (speed <= 0.35 * pickupScale && spin <= 2.0 * pickupScale) {
|
||||
movingSince[i] = {};
|
||||
continue;
|
||||
}
|
||||
if (movingSince[i] == Clock::time_point{}) movingSince[i] = tnow;
|
||||
if (tnow - movingSince[i] >= std::chrono::milliseconds(100)) {
|
||||
active = false;
|
||||
claimPending = claimHeld = false;
|
||||
overlay->HideOverlay(cursor);
|
||||
overlay->HideOverlay(marker);
|
||||
SendTo(out, "ft_pointer", "btn a 0");
|
||||
SendTo(out, "ft_pointer", "hide");
|
||||
std::printf("controller %u moved: pointer released\n", i);
|
||||
std::printf("controller %u moved (%.2f m/s, %.1f rad/s): pointer released\n", i, speed, spin);
|
||||
std::fflush(stdout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::fill(std::begin(movingSince), std::end(movingSince), Clock::time_point{});
|
||||
}
|
||||
const auto &hm = hmd.mDeviceToAbsoluteTracking.m;
|
||||
const Vec3 eye{hm[0][3], hm[1][3], hm[2][3]};
|
||||
|
||||
@@ -11,6 +11,7 @@ POINTER=0 # 1 = the mouse drives the universal 3D pointer (ft_pointer d
|
||||
POINTER_SENSITIVITY=0.03 # degrees per mouse count
|
||||
POINTER_IDLE=30 # seconds without mouse use before the controllers get their laser back
|
||||
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_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
|
||||
|
||||
Reference in new issue
Block a user