Skip to content

Fix controller disconnect after headset sleep (Generated by AI) - #198

Open
dmitam wants to merge 1 commit into
Crementif:mainfrom
dmitam:fix/resume-xr-session
Open

dmitam wants to merge 1 commit into
Crementif:mainfrom
dmitam:fix/resume-xr-session

Conversation

@dmitam

@dmitam dmitam commented Oct 11, 2026

Copy link
Copy Markdown

I don't really understand C++, but I used a local AI model to fix a very annoying bug with VR controller disconnection after the headset goes to sleep mode.
The AI added a timeout/re-initialization loop for SDL/OpenXR controller events. It works on my Quest 2. If it's a mess, feel free to close this PR, I just wanted to share.

Putting the headset down used to leave the controllers dead for good: the runtime stops the
session while the app is asleep, and READY only began a session when no renderer existed yet, so
after a wake-up no frame was ever waited on again and no input came back.

Recovery is driven by what the runtime reports instead of by cached session state:

 - STOPPING stops rendering and READY re-begins the session the runtime handed back;
 - a frame pump that stays blocked inside xrWaitFrame (Oculus can hold it for half a minute after
   it silently stopped feeding the session, and then returns XR_ERROR_RUNTIME_FAILURE) is replaced
   by a fresh one under a generational token, and once enough replacements produced no frame the
   session is rebuilt from scratch;
 - XR_SESSION_STATE_EXITING says a session can never be begun again - that is what closing the app
   in the headset sends - so Cemu follows the runtime out instead of asking for another session
   and putting the app back in front of the user.

Everything owned by a session is forgotten before xrDestroySession, including the swapchains the
layers hold, and everything owned by the instance survives it: the action sets and their suggested
bindings, which have to be suggested exactly once per instance. A replaced renderer is retired
rather than destroyed, because its frame pump may still be blocked inside xrWaitFrame, and its
ImGui overlay - there is one per process, together with the frame objects its constructor built on
the renderer's frames - is handed to the replacement instead of being rebuilt. Recoverable paths no
longer go through checkXRResult, which shows a message box and throws, and the game's hooks and
layers survive the window in which there is no renderer at all.

This branch has not been deployed

No deployments
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.

1 participant