mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 12:00:11 +02:00
Gaze first: plan, and tools for the headset tests
docs/gaze-first.md is the plan agreed on 2026-09-30: with gaze on and no game, the gaze drives the 3D pointer everywhere, either trigger clicks where you look (tap, precision by hand movement, hold to drag), and the controllers keep everything but their lasers. For its four tests: - The driver takes "role treadmill", and its compositor bindings repeat the right hand's under /user/treadmill. They're additive, so nothing changes while the device is a hand. POINTER_ROLE accepts treadmill. - pointer/probe/lasertest follows who has the dashboard laser and the controllers' roles, and with --snapback takes the laser back. - input/vrws.py reads vrserver's web socket with the standard library (the host's Python has no websockets module) and prints component changes and update rates. Checked: it connects, and lists the controllers' bumper, thumbstick axes, and Steam button, and the headset's /proximity, which flickers off for 0.3-0.5 s while worn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d5c2ec65d7
commit
bd06207db9
7 files changed
+575
-10
No files matched your search
@@ -0,0 +1,136 @@
|
||||
// Watch who has SteamVR's laser mouse, for the gaze-first tests (docs/gaze-first.md, tests 1-3).
|
||||
// Prints the dashboard's primary device and every controller's role whenever either changes,
|
||||
// and the dashboard and role events. With --snapback, when the laser goes to any device but
|
||||
// ours (the ft_pointer driver's), it presses our /input/a (switchlaserhand) to take it back,
|
||||
// at most every 100 ms, and prints how long that took.
|
||||
// Runs as a background OpenVR client (in the dev container).
|
||||
// Usage: lasertest [--snapback] [--seconds N]
|
||||
#include <openvr.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
static volatile std::sig_atomic_t g_stop = 0;
|
||||
|
||||
static void Send(const char *msg) {
|
||||
static int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
sockaddr_un a{};
|
||||
a.sun_family = AF_UNIX;
|
||||
const char name[] = "ft_pointer"; // the driver's abstract socket
|
||||
std::memcpy(a.sun_path + 1, name, sizeof name - 1);
|
||||
sendto(fd, msg, std::strlen(msg), 0, reinterpret_cast<sockaddr *>(&a),
|
||||
socklen_t(offsetof(sockaddr_un, sun_path) + 1 + sizeof name - 1));
|
||||
}
|
||||
|
||||
static std::string Describe(vr::IVRSystem *sys, vr::TrackedDeviceIndex_t i) {
|
||||
if (i == vr::k_unTrackedDeviceIndexInvalid) return "none";
|
||||
static const char *roles[] = {"none", "left", "right", "optout", "treadmill", "stylus"};
|
||||
char type[64] = "", serial[64] = "";
|
||||
sys->GetStringTrackedDeviceProperty(i, vr::Prop_ControllerType_String, type, sizeof type);
|
||||
sys->GetStringTrackedDeviceProperty(i, vr::Prop_SerialNumber_String, serial, sizeof serial);
|
||||
const auto role = sys->GetControllerRoleForTrackedDeviceIndex(i);
|
||||
const int hint = sys->GetInt32TrackedDeviceProperty(i, vr::Prop_ControllerRoleHint_Int32);
|
||||
char out[200];
|
||||
std::snprintf(out, sizeof out, "%u %s %s role=%s hint=%d", i, type, serial, role < 6 ? roles[role] : "?", hint);
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bool snapback = false;
|
||||
double seconds = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!std::strcmp(argv[i], "--snapback")) snapback = true;
|
||||
else if (!std::strcmp(argv[i], "--seconds") && i + 1 < argc) seconds = std::atof(argv[++i]);
|
||||
else {
|
||||
std::fprintf(stderr, "usage: %s [--snapback] [--seconds N]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
std::signal(SIGINT, [](int) { g_stop = 1; });
|
||||
std::signal(SIGTERM, [](int) { g_stop = 1; });
|
||||
vr::EVRInitError err = vr::VRInitError_None;
|
||||
vr::IVRSystem *sys = vr::VR_Init(&err, vr::VRApplication_Background);
|
||||
if (err != vr::VRInitError_None) {
|
||||
std::printf("VR_Init failed: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err));
|
||||
return 1;
|
||||
}
|
||||
const auto start = Clock::now();
|
||||
auto ms = [&] { return std::chrono::duration<double, std::milli>(Clock::now() - start).count(); };
|
||||
std::string lastPrimary, lastRoles;
|
||||
Clock::time_point lostAt{}, pressedAt{};
|
||||
bool pressed = false;
|
||||
while (!g_stop && (seconds <= 0 || ms() < seconds * 1000)) {
|
||||
vr::VREvent_t ev;
|
||||
while (sys->PollNextEvent(&ev, sizeof ev)) {
|
||||
const char *name = sys->GetEventTypeNameFromEnum(vr::EVREventType(ev.eventType));
|
||||
switch (ev.eventType) {
|
||||
case vr::VREvent_DashboardActivated:
|
||||
case vr::VREvent_DashboardDeactivated:
|
||||
case vr::VREvent_TrackedDeviceRoleChanged:
|
||||
case vr::VREvent_TrackedDeviceActivated:
|
||||
case vr::VREvent_TrackedDeviceDeactivated:
|
||||
case vr::VREvent_ButtonPress:
|
||||
case vr::VREvent_ButtonUnpress:
|
||||
std::printf("%9.1f ms event %s device %u button %u\n", ms(), name, ev.trackedDeviceIndex,
|
||||
ev.data.controller.button);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
vr::TrackedDeviceIndex_t ours = vr::k_unTrackedDeviceIndexInvalid;
|
||||
std::string roles;
|
||||
for (vr::TrackedDeviceIndex_t i = 1; i < vr::k_unMaxTrackedDeviceCount; ++i) {
|
||||
if (sys->GetTrackedDeviceClass(i) != vr::TrackedDeviceClass_Controller) continue;
|
||||
char type[64] = "";
|
||||
sys->GetStringTrackedDeviceProperty(i, vr::Prop_ControllerType_String, type, sizeof type);
|
||||
if (!std::strcmp(type, "ft_pointer")) ours = i;
|
||||
roles += " [" + Describe(sys, i) + " connected=" + std::to_string(sys->IsTrackedDeviceConnected(i)) + "]";
|
||||
}
|
||||
if (roles != lastRoles) {
|
||||
std::printf("%9.1f ms controllers:%s\n", ms(), roles.c_str());
|
||||
lastRoles = roles;
|
||||
}
|
||||
const auto primary = vr::VROverlay()->GetPrimaryDashboardDevice();
|
||||
const std::string p = Describe(sys, primary);
|
||||
if (p != lastPrimary) {
|
||||
std::printf("%9.1f ms laser: %s%s\n", ms(), p.c_str(), primary == ours ? " (ours)" : "");
|
||||
if (primary == ours && lostAt != Clock::time_point{}) {
|
||||
std::printf("%9.1f ms back on ours after %.0f ms\n", ms(),
|
||||
std::chrono::duration<double, std::milli>(Clock::now() - lostAt).count());
|
||||
lostAt = {};
|
||||
}
|
||||
if (primary != ours && ours != vr::k_unTrackedDeviceIndexInvalid && primary != vr::k_unTrackedDeviceIndexInvalid)
|
||||
lostAt = Clock::now();
|
||||
lastPrimary = p;
|
||||
}
|
||||
if (snapback) {
|
||||
const auto now = Clock::now();
|
||||
if (pressed && now - pressedAt > std::chrono::milliseconds(40)) {
|
||||
Send("btn a 0");
|
||||
pressed = false;
|
||||
}
|
||||
if (!pressed && lostAt != Clock::time_point{} && now - pressedAt > std::chrono::milliseconds(100)) {
|
||||
Send("btn a 1");
|
||||
pressed = true;
|
||||
pressedAt = now;
|
||||
std::printf("%9.1f ms snapback: pressed our a\n", ms());
|
||||
}
|
||||
}
|
||||
std::fflush(stdout);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
if (pressed) Send("btn a 0");
|
||||
vr::VR_Shutdown();
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user