Skip to content

Fix Quest controller input and reconnect crash - #25

Merged
demonixis merged 1 commit into
demonixis:developfrom
SBudarin:quest-controller-input
Sep 1, 2026
Merged

Fix Quest controller input and reconnect crash#25
demonixis merged 1 commit into
demonixis:developfrom
SBudarin:quest-controller-input

Conversation

@SBudarin

@SBudarin SBudarin commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Related to issue #6

Controllers streamed from a Quest client were unusable by OpenXR apps that were using meta/oculus input profiles: they never appeared in Unity at all, the models rendered visibly tilted, grip never registered a press, and taking the headset off and on crashed the client.

Interaction profiles

  • xrGetCurrentInteractionProfile reported a device-specific profile the app had never suggested bindings for (e.g. meta/touch_controller_quest_2). The spec requires a profile the app actually bound, or XR_NULL_PATH, so report the first entry of the compatibility list the app suggested bindings for. Input was already routed through that same list.
  • Emit XrEventDataInteractionProfileChanged, which was never sent at all. An app queries the current profile once at startup - before any headset has connected - caches "no controller" and never asks again, so controllers that became active later stayed invisible. The profile is now re-resolved each xrSyncActions and the event pushed when it changes.

Poses

  • Serve /input/aim/pose from a distinct streamed aim pose instead of reusing the grip pose. Games place the controller model and ray from aim, which sits 45-60 degrees away from grip on Touch, which is what made controllers look rotated. The Quest client already computed the aim pose and now sends it.

Buttons

  • Threshold squeeze/value and trigger/value for boolean actions. The oculus/touch profile has no squeeze/click - grip is analog-only - so a boolean grip action bound to the value path always read false and grip never clicked.

Protocol

  • The aim pose fields are appended to TrackingPacket, and the receiver accepts packets down to the pre-aim size and zero-fills the rest, so existing clients keep working and a missing aim pose falls back to the grip pose. Protocol.swift is updated in step because the packet is sent as a raw struct copy, and the layout test pins the new size and field offsets.

Quest client

  • Fix a SIGSEGV on reconnect. The client applied the server's foveation preset on the discovery thread, racing the render thread's session teardown inside the Meta VR driver. Applying it is deferred to RunFrame and made idempotent, so an unchanged preset no longer tears down and rebuilds a live foveation profile.

Testing

Meta Quest 2 hardware streaming from a Mac M5 Pro, same unity project settings as on a windows machine.

…ks, reconnect crash

Controllers streamed from a Quest client were unusable by OpenXR apps: they never
appeared in Unity at all, the models rendered visibly tilted, grip never registered
a press, and taking the headset off and on crashed the client.

Interaction profiles
- xrGetCurrentInteractionProfile reported a device-specific profile the app had
  never suggested bindings for (e.g. meta/touch_controller_quest_2). The spec
  requires a profile the app actually bound, or XR_NULL_PATH, so report the first
  entry of the compatibility list the app suggested bindings for. Input was
  already routed through that same list.
- Emit XrEventDataInteractionProfileChanged, which was never sent at all. An app
  queries the current profile once at startup - before any headset has connected -
  caches "no controller" and never asks again, so controllers that became active
  later stayed invisible. The profile is now re-resolved each xrSyncActions and
  the event pushed when it changes.

Poses
- Serve /input/aim/pose from a distinct streamed aim pose instead of reusing the
  grip pose. Games place the controller model and ray from aim, which sits 45-60
  degrees away from grip on Touch, which is what made controllers look rotated.
  The Quest client already computed the aim pose and now sends it.

Buttons
- Threshold squeeze/value and trigger/value for boolean actions. The oculus/touch
  profile has no squeeze/click - grip is analog-only - so a boolean grip action
  bound to the value path always read false and grip never clicked.

Protocol
- The aim pose fields are appended to TrackingPacket, and the receiver accepts
  packets down to the pre-aim size and zero-fills the rest, so existing clients
  keep working and a missing aim pose falls back to the grip pose. Protocol.swift
  is updated in step because the packet is sent as a raw struct copy, and the
  layout test pins the new size and field offsets.

Quest client
- Fix a SIGSEGV on reconnect. The client applied the server's foveation preset on
  the discovery thread, racing the render thread's session teardown inside the
  Meta VR driver. Applying it is deferred to RunFrame and made idempotent, so an
  unchanged preset no longer tears down and rebuilds a live foveation profile.

Runtime test suite passes (480 assertions).
@demonixis
demonixis merged commit 68362bf into demonixis:develop Sep 1, 2026
2 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants