mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 02:00:06 +02:00
Settle head follow back on where you look; let the mouse reach 70 degrees
The leash only moved its reference when the head reached the leash's end, so after turning back it sat up to the leash off, and recentring meant overshooting with the head. The reference now eases toward the head's facing (POINTER_LEASH_RETURN, 0.2 s) and never lags more than the leash, so the cursor settles back to its place in the view once the head stops. The mouse can now move the cursor up to POINTER_FOLLOW_REACH (70 degrees) from the middle of the view, up from a fixed 40. Both are sliders on the Pointer page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
137705cbfd
commit
9f4b61729d
6 files changed
+35
-19
No files matched your search
@@ -53,7 +53,7 @@ If you work in the desktop for long stretches, stop Steam from putting the heads
|
||||
| Meta+Shift+H in the desktop | Hides or shows all screens (also in the menu as Hide/Show Screens, and mappable). The Visibility & wrist tab of Frametop Display Settings can instead show them only with the dashboard open, or while you look at your wrist |
|
||||
| Play a VR game | The screens hide and your controllers stay in the game. Open the SteamVR dashboard, or press Meta+Shift+H, to see and use them. To keep them visible over games, change During VR games on the Visibility & wrist tab; the controllers still stay in the game, and you use the screens with the mouse or the dashboard |
|
||||
|
||||
You can map the mouse's extra buttons to actions such as Toggle SteamVR dashboard, Recenter pointer, or Head follow on/off on the Buttons page of Frametop Input Settings. Pointer speed, dot size, and the rest are on its Pointer page and take effect immediately. Head follow, off by default, makes the pointer come along when you turn your head: it stays put until your head has turned past the leash angle, and a leash of 0 keeps it fixed in your view.
|
||||
You can map the mouse's extra buttons to actions such as Toggle SteamVR dashboard, Recenter pointer, or Head follow on/off on the Buttons page of Frametop Input Settings. Pointer speed, dot size, and the rest are on its Pointer page and take effect immediately. Head follow, off by default, makes the pointer come along when you turn your head: small head movements barely move it, it settles back to its place in your view when your head stops, and a leash of 0 keeps it fixed in your view.
|
||||
|
||||
Restarting the desktop (Restart desktop in Frametop Display Settings) closes its windows, but background work you started in it, such as servers, tmux sessions, or builds, keeps running.
|
||||
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ A few overlays need special handling:
|
||||
- Just off a panel, the cursor stays on that panel's plane within `POINTER_EDGE_REACH`, so resize margins and window controls just outside the panel are reachable.
|
||||
- While the left button is held, the cursor keeps the distance it had at the press and stops re-testing collisions, so dragging past a panel's edge doesn't make it jump.
|
||||
|
||||
Head follow is off by default. With it on (`POINTER_FOLLOW=1`, or a mouse button mapped to Head follow on/off), the cursor rides on a reference direction that's on a leash `POINTER_LEASH_DEG` from where you're facing. Turn your head within the leash and the cursor stays where it is in the room. Turn further and the reference is dragged along, and the cursor turns with it, keeping its offset. A leash of 0 makes the reference your facing direction, so the cursor is locked to your view, and mouse movement shifts it within the view. Head roll is ignored, so tilting your head doesn't swing the cursor around, and the cursor can't be pushed more than 40 degrees from the reference, so it stays in view. While the left button is held the cursor stays put in the room, so your head can't nudge a click or a drag. When you let go, it carries on from where it is instead of jumping.
|
||||
Head follow is off by default. With it on (`POINTER_FOLLOW=1`, or a mouse button mapped to Head follow on/off), the cursor rides on a reference direction that follows where you're facing, and keeps its offset from it. The reference eases toward your facing (time constant `POINTER_LEASH_RETURN`, 0.2 s), so small head movements barely move the cursor, but it never lags more than `POINTER_LEASH_DEG` behind, and once your head settles the cursor is back where it was in your view. The first version only moved the reference when your head reached the end of the leash, which left it up to the leash off after you turned back, and getting it centred again meant overshooting with your head. A leash of 0 makes the reference your facing direction, so the cursor is locked to your view, and mouse movement shifts it within the view. Head roll is ignored, so tilting your head doesn't swing the cursor around, and the mouse can move the cursor up to `POINTER_FOLLOW_REACH` (70 degrees) from the reference. While the left button is held the cursor stays put in the room, so your head can't nudge a click or a drag. When you let go, it carries on from where it is instead of jumping.
|
||||
|
||||
Replacing a loaded driver's files, as re-running the installer used to do, leaves SteamVR honoring the virtual controller's hand role but not its laser claim: the dashboard pointer stays unassigned until SteamVR restarts. The driver installer now leaves an unchanged driver in place.
|
||||
|
||||
|
||||
+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`, and the head follow settings `POINTER_FOLLOW` and `POINTER_LEASH_DEG`. 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_DISTANCE`, `POINTER_CURSOR_DEG`, `POINTER_ORIGIN_FRACTION`, `POINTER_ORIGIN_MARGIN`, `POINTER_SCENE_RADIUS`, `POINTER_EDGE_REACH`, `POINTER_LASER_WIDTH`, and the head follow settings `POINTER_FOLLOW`, `POINTER_LEASH_DEG`, `POINTER_LEASH_RETURN`, and `POINTER_FOLLOW_REACH`. 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
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ POINTER_SETTINGS = [
|
||||
("POINTER_SCENE_RADIUS", "Dock / window-control reach", 0.5, 0.1, 1.5, 0.05, "m"),
|
||||
("POINTER_EDGE_REACH", "Panel edge reach", 0.3, 0.0, 1.0, 0.05, "m"),
|
||||
("POINTER_LEASH_DEG", "Head follow leash", 10, 0, 60, 1, "°"),
|
||||
("POINTER_LEASH_RETURN", "Head follow catch-up", 0.2, 0.05, 2.0, 0.05, "s"),
|
||||
("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"),
|
||||
]
|
||||
@@ -358,7 +360,7 @@ class Backend(QObject):
|
||||
|
||||
@Slot(str, float)
|
||||
def setPointerSetting(self, key, value):
|
||||
integer = key in ("POINTER_WAKE_COUNTS", "POINTER_IDLE", "POINTER_LEASH_DEG")
|
||||
integer = key in ("POINTER_WAKE_COUNTS", "POINTER_IDLE", "POINTER_LEASH_DEG", "POINTER_FOLLOW_REACH")
|
||||
write_conf_value(key, str(int(round(value))) if integer else f"{value:.3f}".rstrip("0").rstrip("."))
|
||||
self.reload_timer.start() # debounce slider drags
|
||||
self.pointerChanged.emit()
|
||||
|
||||
@@ -87,13 +87,16 @@
|
||||
// SteamVR's resize snap back.
|
||||
//
|
||||
// Head follow (off by default; POINTER_FOLLOW=1, or the relay's "follow toggle"): the cursor
|
||||
// is carried by a reference direction on a leash, POINTER_LEASH_DEG (10) from where the head
|
||||
// faces. While the head turns within the leash, the reference and the cursor stay put in the
|
||||
// room; past it, the reference is dragged along at the leash's end, and the cursor turns with
|
||||
// it, keeping its offset (mouse movement changes the offset). At 0 the reference is the head's
|
||||
// facing, so the cursor is head-locked. Head roll is ignored (the frames have no roll), so
|
||||
// tilting the head doesn't swing the cursor. The cursor stays within kFollowReachDeg of the
|
||||
// reference, so it can't be pushed out of view. The ray starts at the eye. While the left
|
||||
// is carried by a reference direction that follows where the head faces, and the cursor turns
|
||||
// with it, keeping its offset (mouse movement changes the offset). The reference eases toward
|
||||
// the head's facing with a time constant of POINTER_LEASH_RETURN (0.2 s), so small head
|
||||
// movements barely move the cursor, and it never lags more than POINTER_LEASH_DEG (10) behind:
|
||||
// a fast turn drags it along at the leash's end. Once the head settles, the reference is back
|
||||
// on its facing, so the cursor is back where it was in the view (a leash that only moved at its
|
||||
// end left the reference up to the leash off, and getting it back meant overshooting). At 0 the
|
||||
// reference is the head's facing, so the cursor is head-locked. Head roll is ignored (the frames
|
||||
// have no roll), so tilting the head doesn't swing the cursor. The cursor stays within
|
||||
// POINTER_FOLLOW_REACH (70) of the reference. The ray starts at the eye. While the left
|
||||
// button is held (and the drop hold after it), the leash still moves the reference but the
|
||||
// cursor stays put in the room, so a click or a drag can't be nudged by the head; the offset
|
||||
// is taken up from where the cursor is when the hold ends, so it doesn't jump.
|
||||
@@ -131,7 +134,8 @@
|
||||
// but never closer than POINTER_ORIGIN_MARGIN (0.15 m) to the cursor point: SteamVR's
|
||||
// small controls (undock, frame buttons) float a few centimetres in front of their
|
||||
// panel, and a laser that starts behind them can't hit them. POINTER_FOLLOW (0) and
|
||||
// POINTER_LEASH_DEG (10): head follow, above.
|
||||
// POINTER_LEASH_DEG (10), POINTER_LEASH_RETURN (0.2 s), POINTER_FOLLOW_REACH (70 deg): head
|
||||
// follow, above.
|
||||
#include <openvr.h>
|
||||
|
||||
#include "vrmath.h"
|
||||
@@ -322,13 +326,11 @@ Vec3 LimitPitch(Vec3 d, double maxDeg) {
|
||||
|
||||
// Placement speeds (see "Placement" at the top).
|
||||
constexpr double kPlaceDegPerSec = 60; // tested: 40 deg/s is applied exactly
|
||||
// Head follow (see the top): the farthest the cursor can be from the reference direction.
|
||||
constexpr double kFollowReachDeg = 40;
|
||||
|
||||
int main() {
|
||||
double freeDistance = 1.5, cursorDeg = 0.4, originFraction = 0.95, originMargin = 0.15, sceneRadius = 0.5,
|
||||
edgeReach = 0.3, grabOffset = 0.075,
|
||||
slideSpeed = 0.5, leashDeg = 10;
|
||||
slideSpeed = 0.5, leashDeg = 10, leashReturn = 0.2, followReach = 70;
|
||||
// Head follow (see the top). followConf is POINTER_FOLLOW as last read: a reload only
|
||||
// overrides a "follow" command when the setting itself changed.
|
||||
bool follow = false, followConf = false, followReset = true;
|
||||
@@ -343,6 +345,8 @@ int main() {
|
||||
grabOffset = std::clamp(ConfDouble(conf, "LAYOUT_GRAB_OFFSET", 0.075), 0.0, 1.0);
|
||||
slideSpeed = std::clamp(ConfDouble(conf, "LAYOUT_SLIDE_SPEED", 0.5), 0.02, 2.0);
|
||||
leashDeg = std::clamp(ConfDouble(conf, "POINTER_LEASH_DEG", 10), 0.0, 90.0);
|
||||
leashReturn = std::clamp(ConfDouble(conf, "POINTER_LEASH_RETURN", 0.2), 0.0, 5.0);
|
||||
followReach = std::clamp(ConfDouble(conf, "POINTER_FOLLOW_REACH", 70), 10.0, 89.0);
|
||||
const bool wantFollow = ConfDouble(conf, "POINTER_FOLLOW", 0) != 0;
|
||||
if (wantFollow != followConf) follow = followConf = wantFollow, followReset = true;
|
||||
};
|
||||
@@ -429,6 +433,7 @@ int main() {
|
||||
Vec3 anchor;
|
||||
double yaw = 0, pitch = 0;
|
||||
Vec3 followRef{0, 0, -1}; // head follow's reference direction (see the top)
|
||||
auto followAt = std::chrono::steady_clock::now(); // its last update, for the easing
|
||||
vr::TrackedDeviceIndex_t ours = vr::k_unTrackedDeviceIndexInvalid;
|
||||
auto lastSlow = std::chrono::steady_clock::now() - std::chrono::seconds(10);
|
||||
|
||||
@@ -805,21 +810,28 @@ int main() {
|
||||
followReset = true;
|
||||
}
|
||||
|
||||
// Head follow (see the top): drag the reference along on its leash, and the cursor with it.
|
||||
// Head follow (see the top): ease the reference toward the head's facing, never more than
|
||||
// the leash behind, and turn the cursor with it.
|
||||
if (follow && active && anchored && hmd.bPoseIsValid) {
|
||||
const Vec3 head = LimitPitch(Vec3{-hm[0][2], -hm[1][2], -hm[2][2]}, 85);
|
||||
if (followReset) followRef = head, followReset = false;
|
||||
const Vec3 ref = LimitPitch(PullWithin(followRef, head, leashDeg * M_PI / 180), 85);
|
||||
const double dt = std::min(0.1, std::chrono::duration<double>(tnow - followAt).count());
|
||||
double lag = std::acos(std::clamp(Dot(followRef, head), -1.0, 1.0)) *
|
||||
(leashReturn > 0 ? std::exp(-dt / leashReturn) : 0.0);
|
||||
if (lag < 0.05 * M_PI / 180) lag = 0; // settled: land exactly on the facing
|
||||
const Vec3 ref =
|
||||
LimitPitch(PullWithin(followRef, head, std::min(lag, leashDeg * M_PI / 180)), 85);
|
||||
if (!leftHeld && tnow >= dropHoldUntil) {
|
||||
// The cursor keeps its offset from the reference (frames without roll).
|
||||
Vec3 d = FromBasis(AimBasis(ref), ToBasis(AimBasis(followRef), Direction(yaw, pitch)));
|
||||
d = PullWithin(Normalize(d), ref, kFollowReachDeg * M_PI / 180);
|
||||
d = PullWithin(Normalize(d), ref, followReach * M_PI / 180);
|
||||
yaw = std::atan2(-d.x, -d.z) * 180 / M_PI;
|
||||
pitch = std::clamp(std::asin(std::clamp(d.y, -1.0, 1.0)) * 180 / M_PI, -85.0, 85.0);
|
||||
anchor = eye;
|
||||
}
|
||||
followRef = ref;
|
||||
}
|
||||
followAt = tnow;
|
||||
|
||||
// Slow work, once a second: overlay handles, our device index, laser width.
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
@@ -19,7 +19,9 @@ POINTER_ORIGIN_MARGIN=0.15 # the laser starts at least this far (m) in front o
|
||||
POINTER_SCENE_RADIUS=0.5 # texture-less dashboard overlays (dock, window controls): hit radius (m) around their origin
|
||||
POINTER_EDGE_REACH=0.3 # just off a panel, the cursor stays on its plane this far (m), to reach its resize edges and window controls
|
||||
POINTER_FOLLOW=0 # 1 = the cursor follows your head on a leash (a mouse button mapped to Head follow on/off toggles it)
|
||||
POINTER_LEASH_DEG=10 # head follow: how far (degrees) your head can turn before the cursor comes along; 0 locks it to your view
|
||||
POINTER_LEASH_DEG=10 # head follow: the most (degrees) the cursor can lag behind your head; 0 locks it to your view
|
||||
POINTER_LEASH_RETURN=0.2 # head follow: how quickly (s) the cursor settles back to its place in your view
|
||||
POINTER_FOLLOW_REACH=70 # head follow: how far (degrees) from the middle of your view the mouse can move the cursor
|
||||
META_DASHBOARD=0 # 1 = a Meta tap on a pass-through keyboard toggles the SteamVR dashboard (pointer mode only)
|
||||
SHARE_KEYS=0 # 1 = keys of keyboards grabbed for the desktop also go to @frametop_keys, for hotkey tools (any local process can listen)
|
||||
LAYOUT_GRAB_OFFSET=0.075 # arranging screens: where the floating window's grab bar is, in metres below the screen
|
||||
|
||||
Reference in new issue
Block a user