Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 137 additions & 26 deletions CustomHeadsetOpenVR/src/Driver/DeviceProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,25 @@ static bool InputPathInteresting(const std::string &lower){
|| lower.find("pinch") != std::string::npos;
}

static bool NativeHandSerial(const char* serial){
if(!serial){ return false; }
return strcmp(serial, "VRLINKQ_Hand_Left") == 0
|| strcmp(serial, "VRLINKQ_Hand_Right") == 0;
}

static bool PhysicalGalaxyControllerSerial(const char* serial){
if(!serial){ return false; }
std::string value = serial;
return value.rfind("SamsungVST-Controller", 0) == 0
|| (value.rfind("VRLINK", 0) == 0
&& value.find("Controller") != std::string::npos);
}

static bool NativeHandDiagnosticPath(const std::string &lower){
return lower.find("index_pinch") != std::string::npos
|| lower.find("/input/grip") != std::string::npos;
}

// classify a component path into a distortion tuner control role. exact
// suffix matches against the confirmed vrlink surface (session log): joystick
// x/y scalars + joystick/a/b/x/y click booleans + grip value scalars.
Expand All @@ -407,17 +426,24 @@ static int TunerRoleForPath(const std::string &lower, bool isScalar){
return 0;
}

void CustomHeadsetDeviceProvider::OnInputComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle){
if(!name || handle == vr::k_ulInvalidInputComponentHandle){
void CustomHeadsetDeviceProvider::OnInputComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle, vr::EVRInputError error){
if(!name || error != vr::VRInputError_None || handle == vr::k_ulInvalidInputComponentHandle){
if(name && error != vr::VRInputError_None){
DriverLog("HandInputDiag: boolean create FAILED container=%llu path=%s error=%d",
(unsigned long long)container, name, (int)error);
}
return;
}
InputComponentInfo info;
info.container = container;
info.openVRID = ResolveContainerId(container);
info.name = name;
std::string lower = info.name;
for(auto &c : lower){ c = (char)tolower(c); }
info.interesting = InputPathInteresting(lower);
info.tunerRole = TunerRoleForPath(lower, false);
info.nativeHand = info.openVRID != vr::k_unTrackedDeviceIndexInvalid && IsNativeHand(info.openVRID);
info.diagnostic = info.nativeHand && NativeHandDiagnosticPath(lower);
// hand classification from the quest layout: x/y buttons exist only on
// the left controller, a/b only on the right. once known, resolve the
// openVR id too so pose updates can be routed per hand.
Expand All @@ -427,7 +453,7 @@ void CustomHeadsetDeviceProvider::OnInputComponentCreated(vr::PropertyContainerH
// lock itself, and std::mutex is non-recursive — nesting it here
// deadlocked vrserver at the first x/click creation and tripped a
// SteamVR safe-mode block (session 24 regression)
uint32_t id = ResolveContainerId(container);
uint32_t id = info.openVRID;
std::lock_guard<std::mutex> handGuard(poseLogLock);
containerHand[container] = hand;
if(id != vr::k_unTrackedDeviceIndexInvalid){
Expand All @@ -436,32 +462,42 @@ void CustomHeadsetDeviceProvider::OnInputComponentCreated(vr::PropertyContainerH
}
// always log creates: component names are the map of vrlink's input
// surface, and not having them cost a session
DriverLog("InputTap: boolean component container=%llu path=%s handle=%llu%s",
DriverLog("InputTap: boolean component container=%llu path=%s handle=%llu id=%u%s%s",
(unsigned long long)container, name, (unsigned long long)handle,
info.interesting ? " [watched]" : "");
info.openVRID, info.interesting ? " [watched]" : "",
info.nativeHand ? " [native-hand passthrough]" : "");
std::lock_guard<std::mutex> guard(poseLogLock);
inputComponents[handle] = info;
}

void CustomHeadsetDeviceProvider::OnScalarComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle){
if(!name || handle == vr::k_ulInvalidInputComponentHandle){
void CustomHeadsetDeviceProvider::OnScalarComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle, vr::EVRInputError error){
if(!name || error != vr::VRInputError_None || handle == vr::k_ulInvalidInputComponentHandle){
if(name && error != vr::VRInputError_None){
DriverLog("HandInputDiag: scalar create FAILED container=%llu path=%s error=%d",
(unsigned long long)container, name, (int)error);
}
return;
}
InputComponentInfo info;
info.container = container;
info.openVRID = ResolveContainerId(container);
info.name = name;
info.isScalar = true;
std::string lower = info.name;
for(auto &c : lower){ c = (char)tolower(c); }
info.interesting = InputPathInteresting(lower);
info.tunerRole = TunerRoleForPath(lower, true);
DriverLog("InputTap: scalar component container=%llu path=%s handle=%llu%s",
info.nativeHand = info.openVRID != vr::k_unTrackedDeviceIndexInvalid && IsNativeHand(info.openVRID);
info.diagnostic = info.nativeHand && NativeHandDiagnosticPath(lower);
DriverLog("InputTap: scalar component container=%llu path=%s handle=%llu id=%u%s%s",
(unsigned long long)container, name, (unsigned long long)handle,
info.interesting ? " [watched]" : "");
info.openVRID, info.interesting ? " [watched]" : "",
info.nativeHand ? " [native-hand passthrough]" : "");
// grip capacitive touch: vrlink never creates /input/grip/touch for these
// controllers; synthesize it next to grip/value (before taking the lock:
// the create call re-enters our own hook)
bool wantGripTouch = driverConfig.galaxyXr.nativeInputProfile && driverConfig.galaxyXr.synthesizeGripTouch
bool wantGripTouch = !info.nativeHand
&& driverConfig.galaxyXr.nativeInputProfile && driverConfig.galaxyXr.synthesizeGripTouch
&& lower.size() >= 17 && lower.compare(lower.size() - 17, 17, "/input/grip/value") == 0;
if(wantGripTouch && vr::VRDriverInput()){
vr::VRInputComponentHandle_t touchHandle = vr::k_ulInvalidInputComponentHandle;
Expand All @@ -477,7 +513,33 @@ void CustomHeadsetDeviceProvider::OnScalarComponentCreated(vr::PropertyContainer
inputComponents[handle] = info;
}

void CustomHeadsetDeviceProvider::OnScalarComponentUpdated(vr::VRInputComponentHandle_t handle, float value){
void CustomHeadsetDeviceProvider::OnScalarComponentUpdated(vr::VRInputComponentHandle_t handle, float value, double timeOffset, vr::EVRInputError error){
{
double now = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000000.0;
bool doLog = false;
std::string name;
uint32_t id = vr::k_unTrackedDeviceIndexInvalid;
{
std::lock_guard<std::mutex> guard(poseLogLock);
auto found = inputComponents.find(handle);
if(found != inputComponents.end() && found->second.diagnostic){
InputComponentInfo &info = found->second;
doLog = error != vr::VRInputError_None || !info.diagHaveScalar
|| std::fabs(value - info.diagLastScalar) >= 0.10f
|| now - info.diagLastLogTime >= 0.5;
info.diagHaveScalar = true;
info.diagLastScalar = value;
if(doLog){ info.diagLastLogTime = now; }
name = info.name;
id = info.openVRID;
}
}
if(doLog){
DriverLog("HandInputDiag: scalar id=%u path=%s value=%.3f timeOffset=%.4f result=%d",
id, name.c_str(), value, timeOffset, (int)error);
}
}
// grip touch from grip value (hysteresis 0.03 / 0.015); updated outside the lock
{
vr::VRInputComponentHandle_t touchHandle = vr::k_ulInvalidInputComponentHandle;
Expand Down Expand Up @@ -554,7 +616,28 @@ void CustomHeadsetDeviceProvider::OnScalarComponentUpdated(vr::VRInputComponentH
}
}

void CustomHeadsetDeviceProvider::OnBooleanComponentUpdated(vr::VRInputComponentHandle_t handle, bool value){
void CustomHeadsetDeviceProvider::OnBooleanComponentUpdated(vr::VRInputComponentHandle_t handle, bool value, double timeOffset, vr::EVRInputError error){
{
bool doLog = false;
std::string name;
uint32_t id = vr::k_unTrackedDeviceIndexInvalid;
{
std::lock_guard<std::mutex> guard(poseLogLock);
auto found = inputComponents.find(handle);
if(found != inputComponents.end() && found->second.diagnostic){
InputComponentInfo &info = found->second;
doLog = error != vr::VRInputError_None || !info.diagHaveBool || info.diagLastBool != value;
info.diagHaveBool = true;
info.diagLastBool = value;
name = info.name;
id = info.openVRID;
}
}
if(doLog){
DriverLog("HandInputDiag: boolean id=%u path=%s value=%d timeOffset=%.4f result=%d",
id, name.c_str(), (int)value, timeOffset, (int)error);
}
}
if(tunerInputActive.load(std::memory_order_relaxed)){
std::lock_guard<std::mutex> tunerGuard(poseLogLock);
auto found = inputComponents.find(handle);
Expand Down Expand Up @@ -915,11 +998,21 @@ static std::map<vr::VRInputComponentHandle_t, int> skeletonTapHands;
void CustomHeadsetDeviceProvider::OnSkeletonComponentCreated(vr::PropertyContainerHandle_t container, const char *name, const char *skeletonPath, vr::VRInputComponentHandle_t handle){
std::string path = skeletonPath ? skeletonPath : "";
int hand = path.find("right") != std::string::npos ? 1 : 0;
uint32_t id = ResolveContainerId(container);
bool physicalController = id != vr::k_unTrackedDeviceIndexInvalid && IsStreamedController(id);
DriverLog("SkeletonTap: component %s (%s) hand=%s handle=%llu id=%u mode=%s",
name ? name : "?", path.c_str(), hand ? "right" : "left",
(unsigned long long)handle, id,
physicalController ? "physical-controller-adjustable" : "passthrough");
// Native hands (and anything not positively identified as a physical
// Galaxy XR controller) keep their original skeleton data unchanged.
if(!physicalController){
return;
}
{
std::lock_guard<std::mutex> lock(skeletonTapMutex);
skeletonTapHands[handle] = hand;
}
DriverLog("SkeletonTap: component %s (%s) hand=%s handle=%llu", name ? name : "?", path.c_str(), hand ? "right" : "left", (unsigned long long)handle);
}

bool CustomHeadsetDeviceProvider::HandleSkeletonUpdate(vr::VRInputComponentHandle_t handle, const vr::VRBoneTransform_t *bones, uint32_t count, vr::VRBoneTransform_t *outBones){
Expand Down Expand Up @@ -1080,6 +1173,12 @@ static void CaInit(double P[6], double p0Var, double v0Var, double a0Var){
}

bool CustomHeadsetDeviceProvider::HandleDevicePoseUpdated(uint32_t openVRID, vr::DriverPose_t &pose){
// Native hand devices are published by vrlink with Controller class but
// are not physical Galaxy XR controllers. Their pose, tracking state,
// velocity and timing must reach SteamVR byte-for-byte unchanged.
if(openVRID != vr::k_unTrackedDeviceIndex_Hmd && IsNativeHand(openVRID)){
return true;
}
// raw tracking status, captured BEFORE forceTracking can launder it.
// the estimators gate on these: forceTracking's job is keeping
// devices alive for SteamVR, not feeding fake-OK into filters.
Expand Down Expand Up @@ -4438,12 +4537,12 @@ bool CustomHeadsetDeviceProvider::HandleDevicePoseUpdated(uint32_t openVRID, vr:
return true;
}

bool CustomHeadsetDeviceProvider::IsStreamedController(uint32_t openVRID){
CustomHeadsetDeviceProvider::StreamedDeviceKind CustomHeadsetDeviceProvider::GetStreamedDeviceKind(uint32_t openVRID){
{
std::lock_guard<std::mutex> guard(streamedIdentityLock);
auto found = streamedControllerCache.find(openVRID);
if(found != streamedControllerCache.end()){
return found->second != 0;
auto found = streamedDeviceKindCache.find(openVRID);
if(found != streamedDeviceKindCache.end()){
return found->second;
}
}
// property query with NO lock held (concurrency law: never call out
Expand All @@ -4452,20 +4551,33 @@ bool CustomHeadsetDeviceProvider::IsStreamedController(uint32_t openVRID){
vr::ETrackedPropertyError propError = vr::TrackedProp_Success;
char serial[128] = {};
vr::VRProperties()->GetStringProperty(container, vr::Prop_SerialNumber_String, serial, sizeof(serial), &propError);
bool streamed = false;
StreamedDeviceKind kind = StreamedDeviceKind::Other;
if(propError == vr::TrackedProp_Success){
streamed = strncmp(serial, "VRLINK", 6) == 0 || strncmp(serial, "SamsungVST", 10) == 0;
if(NativeHandSerial(serial)){
kind = StreamedDeviceKind::NativeHand;
}else if(PhysicalGalaxyControllerSerial(serial)){
kind = StreamedDeviceKind::PhysicalController;
}
}else{
// property not readable yet: do not cache, do not touch
return false;
return StreamedDeviceKind::Other;
}
{
std::lock_guard<std::mutex> guard(streamedIdentityLock);
streamedControllerCache[openVRID] = streamed ? 1 : 0;
streamedDeviceKindCache[openVRID] = kind;
}
DriverLog("VelocityFix: id=%u serial=%s streamed=%d%s", openVRID, serial, streamed ? 1 : 0,
streamed ? "" : " (native velocity, never touched)");
return streamed;
const char* kindName = kind == StreamedDeviceKind::PhysicalController ? "physical-controller"
: (kind == StreamedDeviceKind::NativeHand ? "native-hand-passthrough" : "other-passthrough");
DriverLog("DeviceClassifier: id=%u serial=%s kind=%s", openVRID, serial, kindName);
return kind;
}

bool CustomHeadsetDeviceProvider::IsNativeHand(uint32_t openVRID){
return GetStreamedDeviceKind(openVRID) == StreamedDeviceKind::NativeHand;
}

bool CustomHeadsetDeviceProvider::IsStreamedController(uint32_t openVRID){
return GetStreamedDeviceKind(openVRID) == StreamedDeviceKind::PhysicalController;
}

// direction secant over the full derive ring: raw displacement newest-oldest
Expand Down Expand Up @@ -4964,8 +5076,7 @@ bool CustomHeadsetDeviceProvider::HandleDeviceAdded(const char *&pchDeviceSerial
std::string serial = pchDeviceSerialNumber ? pchDeviceSerialNumber : "";
// SamsungVST-Controller-* on the patched APK, VRLINKQ2_Controller_* on
// the stock one. "Controller" excludes the VRLINKQ_Hand_* hand trackers.
bool streamedController = (serial.rfind("SamsungVST-Controller", 0) == 0)
|| (serial.rfind("VRLINK", 0) == 0 && serial.find("Controller") != std::string::npos);
bool streamedController = PhysicalGalaxyControllerSerial(serial.c_str());
if(streamedController){
GalaxyXRControllerShim* controllerShim = new GalaxyXRControllerShim(serial);
shims.insert(controllerShim);
Expand Down
37 changes: 27 additions & 10 deletions CustomHeadsetOpenVR/src/Driver/DeviceProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,20 @@ class CustomHeadsetDeviceProvider : public vr::IServerTrackedDeviceProvider
static void ComputeRingSecant(const VelFixState &state, bool useSmoothed, double secantVel[3], double secantAng[3]);
// cached device classes (Prop_DeviceClass_Int32), resolved on first pose
std::map<uint32_t, int> deviceClasses = {};
// streamed-controller identity cache (serial prefix VRLINK*/SamsungVST*
// = vrlink device). the velocity fix must never touch lighthouse
// devices: their native velocity is correct and mixed sessions
// (knuckles + playspace override) are a supported setup. queried once
// per id OUTSIDE any lock, then cached.
std::map<uint32_t, int> streamedControllerCache = {};
// Strict vrlink device classification. A VRLINK prefix alone is not
// enough: native hand devices use VRLINKQ_Hand_* serials and must never
// enter any physical-controller pose/filter path. Unknown devices pass
// through untouched. Queried outside locks and cached only after the
// serial property is readable.
enum class StreamedDeviceKind : int {
Other = 0,
PhysicalController = 1,
NativeHand = 2,
};
std::map<uint32_t, StreamedDeviceKind> streamedDeviceKindCache = {};
std::mutex streamedIdentityLock;
StreamedDeviceKind GetStreamedDeviceKind(uint32_t openVRID);
bool IsNativeHand(uint32_t openVRID);
bool IsStreamedController(uint32_t openVRID);
// derive-mode adaptive smoothing state (pure math under its own lock;
// never calls out — lock discipline)
Expand Down Expand Up @@ -570,6 +577,7 @@ class CustomHeadsetDeviceProvider : public vr::IServerTrackedDeviceProvider
// throw died (snap back? dropout? zero?) with direct evidence.
struct InputComponentInfo {
vr::PropertyContainerHandle_t container = 0;
uint32_t openVRID = vr::k_unTrackedDeviceIndexInvalid;
std::string name;
bool lastValue = false;
bool haveValue = false;
Expand All @@ -593,6 +601,15 @@ class CustomHeadsetDeviceProvider : public vr::IServerTrackedDeviceProvider
int tunerRole = 0;
float tunerScalar = 0;
bool tunerBool = false;
// Temporary native-hand diagnostics. These are observational only:
// the original IVRDriverInput call is made before this tap.
bool nativeHand = false;
bool diagnostic = false;
bool diagHaveBool = false;
bool diagLastBool = false;
bool diagHaveScalar = false;
float diagLastScalar = 0;
double diagLastLogTime = 0;
};
std::map<vr::VRInputComponentHandle_t, InputComponentInfo> inputComponents = {};
// gate for tuner input capture on the hot component-update path
Expand Down Expand Up @@ -683,10 +700,10 @@ class CustomHeadsetDeviceProvider : public vr::IServerTrackedDeviceProvider
std::atomic<bool> alignerGripActive {false};
double alignerGripCm[2][3] = {};
public:
void OnInputComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle);
void OnBooleanComponentUpdated(vr::VRInputComponentHandle_t handle, bool value);
void OnScalarComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle);
void OnScalarComponentUpdated(vr::VRInputComponentHandle_t handle, float value);
void OnInputComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle, vr::EVRInputError error);
void OnBooleanComponentUpdated(vr::VRInputComponentHandle_t handle, bool value, double timeOffset, vr::EVRInputError error);
void OnScalarComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle, vr::EVRInputError error);
void OnScalarComponentUpdated(vr::VRInputComponentHandle_t handle, float value, double timeOffset, vr::EVRInputError error);
void OnPoseComponentCreated(vr::PropertyContainerHandle_t container, const char* name, vr::VRInputComponentHandle_t handle);
void OnSkeletonComponentCreated(vr::PropertyContainerHandle_t container, const char* name, const char* skeletonPath, vr::VRInputComponentHandle_t handle);
bool HandleSkeletonUpdate(vr::VRInputComponentHandle_t handle, const vr::VRBoneTransform_t* bones, uint32_t count, vr::VRBoneTransform_t* outBones);
Expand Down
Loading