Skip to content

FIX: [XR-10725] Fixed regression with initial focus causing input devices to always be disabled#2447

Open
chris-massie wants to merge 9 commits into
developfrom
bugfix/XR-10725-initial-focus-desync
Open

FIX: [XR-10725] Fixed regression with initial focus causing input devices to always be disabled#2447
chris-massie wants to merge 9 commits into
developfrom
bugfix/XR-10725-initial-focus-desync

Conversation

@chris-massie

@chris-massie chris-massie commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixed regression caused by #2365 where the focus state of the application was not being synced to the input runtime correctly, causing all input devices to be initially disabled until the game window was clicked away and then back.

Testing status & QA

I tested with an XR project with a PC standalone build. Launching the built .exe now causes the hand devices (<MetaAimHand> and <XRHandDevice>) to update correctly without needing to refresh focus by clicking away and back to the game. Since those currently do not have the run in background device flags (and thus canDeviceRunInBackground is false), they correctly stop updating when the game window loses focus by clicking away. Tested with 6000.0.79f1 and 6000.5.0f1 to test both paths of UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS.

I also tested in the Unity Editor with 6.0 and 6000.5.0f1 and the hand devices update correctly. However due to how the Input System handles canRunInBackground vs canDeviceRunInBackground while in the Editor, the hand devices continue to update even while the game is not focused (Application.isFocused), but that has been the case for many years.

Note that I did see a difference in behavior between 6.0 and 6.5 in the Editor where Application.isFocused would become False after clicking Play only on Unity 6.5 and only when the XR subsystem is initialized even though "Play Focused" was enabled in the Game view. That may be an existing problem though.

Overall Product Risks

  • Complexity: Low complexity, and the changes mostly make it simpler since the InputManager.focusState no longer has its own state different from the IInputRuntime.focusState.
  • Halo Effect: Changing focus logic is scary. However, this is mostly tweaks to how focus state was synced between the internal Input Manager and the IInputRuntime. This does not affect input tests since the InputTestRuntime initializes m_FocusState = FocusFlags.ApplicationFocus and thus is independent of the Application.isFocused property. This could also affect InputSystemUIInputModule and UI Toolkit since the isPlayerFocused is now initialized/updated differently, however the 1.19.0 version of that property was always using Application.isFocused for that property so it should be functionally the same as compared to that version.

Comments to reviewers

There does seem to be a difference in the order in which the native runtime and the test runtime invokes the onPlayerFocusChanged action. The NativeInputRuntime invokes the callback first and then sets the focusState field, but the InputTestRuntime sets the field first and then invokes the callback. When I tried updating the test to order it the same as the real native input runtime, the UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButtonClickBehaviorShouldDependOnIfDeviceCanRunInBackground(false) test in UITests.cs started failing. This needs to be investigated if the test is at fault with a faulty setup or if the difference in runtimes is intentional. Addressed in #2447 (comment)

I also fixed some incorrect setting of the focusState by just setting or clearing the ApplicationFocus flag instead of setting the entire value. This has no current difference because the flags enum only has two values, but makes it future-proof and makes it consistent in this codebase.

No changelog entry is needed since this is a fix to a regression in develop's 1.20.0 which has not yet been released.

Checklist

Before review:

  • Changelog entry added.
    • Explains the change in Changed, Fixed, Added sections.
    • For API change contains an example snippet and/or migration example.
    • JIRA ticket linked, example (case %%). If it is a private issue, just add the case ID without a link.
    • Jira port for the next release set as "Resolved".
  • Tests added/changed, if applicable.
    • Functional tests Area_CanDoX, Area_CanDoX_EvenIfYIsTheCase, Area_WhenIDoX_AndYHappens_ThisIsTheResult.
    • Performance tests.
    • Integration tests.
  • Docs for new/changed API's.
    • Xmldoc cross references are set correctly.
    • Added explanation how the API works.
    • Usage code examples added.
    • The manual is updated, if needed.

During merge:

  • Commit message for squash-merge is prefixed with one of the list:
    • NEW: ___.
    • FIX: ___.
    • DOCS: ___.
    • CHANGE: ___.
    • RELEASE: 1.1.0-preview.3.

… disabled

- Fixed regression caused by #2365 where the focus state of the application was not being synced to the input runtime correctly, causing all input devices to be initially disabled until the game window was clicked away and then back.
- Fixed some incorrect setting of the `focusState` by just setting or clearing the ApplicationFocus flag instead of setting the entire value. In effect this has no current difference because the flags enum only has two values, but makes it future-proof.
@chris-massie chris-massie self-assigned this Jul 15, 2026
@chris-massie chris-massie added the bug Issues where existsing functionality misbehaves label Jul 15, 2026

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The focus-state sync change is directionally right, but the new runtime-install path now overwrites injected runtimes with the host application's focus state, which regresses a real code path in this repo.

Reviewed commit f36bf495

🤖 Helpful? 👍/👎

Comment thread Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs Outdated
@codecov-github-com

codecov-github-com Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 94.54545% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...System/Runtime/InputManager.LegacyFocusHandling.cs 95.12% 2 Missing ⚠️
...ty.inputsystem/InputSystem/Runtime/InputManager.cs 85.71% 1 Missing ⚠️
@@             Coverage Diff              @@
##           develop    #2447       +/-   ##
============================================
+ Coverage    58.58%   78.94%   +20.36%     
============================================
  Files          738      767       +29     
  Lines       135831   140741     +4910     
============================================
+ Hits         79570   111107    +31537     
+ Misses       56261    29634    -26627     
Flag Coverage Δ
inputsystem_MacOS_6000.0 5.31% <7.40%> (-0.01%) ⬇️
inputsystem_MacOS_6000.0_project 77.49% <94.44%> (+0.24%) ⬆️
inputsystem_MacOS_6000.3 5.31% <7.40%> (-0.01%) ⬇️
inputsystem_MacOS_6000.3_project 77.49% <94.44%> (+0.27%) ⬆️
inputsystem_MacOS_6000.4 5.32% <7.40%> (-0.01%) ⬇️
inputsystem_MacOS_6000.4_project 77.52% <94.44%> (+0.29%) ⬆️
inputsystem_MacOS_6000.5 5.30% <27.27%> (-0.01%) ⬇️
inputsystem_MacOS_6000.5_project 77.55% <100.00%> (+0.28%) ⬆️
inputsystem_MacOS_6000.6 5.30% <27.27%> (-0.01%) ⬇️
inputsystem_MacOS_6000.6_project 77.55% <100.00%> (+0.28%) ⬆️
inputsystem_Ubuntu_6000.0 5.31% <7.40%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.0_project 77.40% <94.44%> (+0.24%) ⬆️
inputsystem_Ubuntu_6000.3 5.31% <7.40%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.3_project 77.40% <94.44%> (+0.27%) ⬆️
inputsystem_Ubuntu_6000.4 5.32% <7.40%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.4_project 77.43% <94.44%> (+0.29%) ⬆️
inputsystem_Ubuntu_6000.5 5.30% <27.27%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.5_project 77.46% <100.00%> (+0.29%) ⬆️
inputsystem_Ubuntu_6000.6 5.30% <27.27%> (-0.01%) ⬇️
inputsystem_Ubuntu_6000.6_project 77.46% <100.00%> (+0.29%) ⬆️
inputsystem_Windows_6000.0 5.31% <7.40%> (-0.01%) ⬇️
inputsystem_Windows_6000.0_project 77.61% <94.44%> (+0.23%) ⬆️
inputsystem_Windows_6000.3 5.31% <7.40%> (-0.01%) ⬇️
inputsystem_Windows_6000.3_project 77.61% <94.44%> (+0.26%) ⬆️
inputsystem_Windows_6000.4 5.32% <7.40%> (-0.01%) ⬇️
inputsystem_Windows_6000.4_project 77.64% <94.44%> (+0.29%) ⬆️
inputsystem_Windows_6000.5 5.30% <27.27%> (-0.01%) ⬇️
inputsystem_Windows_6000.5_project 77.67% <100.00%> (+0.27%) ⬆️
inputsystem_Windows_6000.6 5.30% <27.27%> (-0.01%) ⬇️
inputsystem_Windows_6000.6_project 77.67% <100.00%> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...y.inputsystem/InputSystem/Runtime/IInputRuntime.cs 80.00% <ø> (+80.00%) ⬆️
...utsystem/InputSystem/Runtime/NativeInputRuntime.cs 64.20% <100.00%> (+45.01%) ⬆️
....inputsystem/Tests/TestFixture/InputTestRuntime.cs 91.34% <100.00%> (+4.28%) ⬆️
...ty.inputsystem/InputSystem/Runtime/InputManager.cs 89.01% <85.71%> (+68.41%) ⬆️
...System/Runtime/InputManager.LegacyFocusHandling.cs 95.04% <95.12%> (+56.50%) ⬆️

... and 277 files with indirect coverage changes

ℹ️ Need help interpreting these results?

- This doesn't actually seem correctly formatted, but it's what the tool wants
@chris-massie chris-massie marked this pull request as draft July 15, 2026 03:13
…ent of the Application.isFocused property

- Since the runtime is what triggers the callback that the InputManager uses to respond to the focus change, we don't need to have the InputManager set the focusState. We can just rely on the runtime itself to set the focus state after triggering the Action.
- TODO: The InputTestRuntime.InvokePlayerFocusChanged does the opposite order of NativeInputRuntime.OnFocusChanged when setting the field. However, if I update the test runtime to set the `m_FocusState` after invoking the `onPlayerFocusChanged`, the `UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButtonClickBehaviorShouldDependOnIfDeviceCanRunInBackground(false)` test fails. More investigation is needed to identify if the test is not setup correctly or if the difference in order is intended.
if (m_Runtime != null)
return m_Runtime.focusState;

return Application.isFocused ? FocusFlags.ApplicationFocus : FocusFlags.None;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My worry with this would be that if m_runtime is null and we are using focus events, that could potentially mean that whatever focus state we receive could be wrong in high noise cases, as we are processing focus events based on the Input update processing buffer, not the application focus. I know there were some prs that improved the m_runtime lifecycle that were done later, so maybe this isnt a problem anymore now

@chris-massie chris-massie Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is ever a case where there isn't a runtime installed in the InputManager now, so I don't think that fallback case would ever be hit. But anyway, there wasn't any place in the code where this getter was being used after this refactor, so I removed the property and updated the one line where the setter was being used to just directly set it in the runtime. Done in 67f6c04.

/// In editor this means the GameView has focus. In a built player this means the player has focus.
/// </summary>
ApplicationFocus = (1 << 0)
ApplicationFocus = (1 << 0),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont need comma here if we dont have more entries here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our Unity style guide says to use a trailing comma. I think the reasoning is because it keeps the diff cleaner if another entry is added since it will only be new lines instead of also being a modification to an existing line.

Trail last element in a list with ','

…used

- Replaced the one line where the setter was used with directly updating the runtime's focus state. This was done to remove questions about the fallback case for the getter if there was no runtime set.
@chris-massie

chris-massie commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

@VeraMommersteeg I addressed your feedback.

However there's still something that I wanted to call out in the PR description under Comments to reviewers:

There does seem to be a difference in the order in which the native runtime and the test runtime invokes the onPlayerFocusChanged action. The NativeInputRuntime invokes the callback first and then sets the focusState field, but the InputTestRuntime sets the field first and then invokes the callback. When I tried updating the test to order it the same as the real native input runtime, the UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButtonClickBehaviorShouldDependOnIfDeviceCanRunInBackground(false) test in UITests.cs started failing. This needs to be investigated if the test is at fault with a faulty setup or if the difference in runtimes is intentional.

The InputManager.ProcessFocusEvent under the #if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS case seems to set the focus state in the runtime first as opposed to the similar code in InputManager.LegacyFocusHandling.cs's OnFocusChanged method where that callback happens first (see NativeInputRuntime.OnFocusChanged).

I think it would be worth taking a look at these 3 cases to determine what the correct functionality should be while we're looking at all this focus stuff. The u-pr bot even called this out below: #2447 (comment)

@chris-massie chris-massie marked this pull request as ready for review July 15, 2026 19:33

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The startup focus sync looks directionally right, but the legacy focus callback path still leaves one user-visible regression on supported older Unity versions.

Reviewed commit 4c668b7c

🤖 Helpful? 👍/👎

Comment thread Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs Outdated
@chris-massie chris-massie marked this pull request as draft July 15, 2026 21:06
…focus change to restore old functionality

- Since the `applicationHasFocus` property was updated with this branch to return the runtime's value instead of having the duplicate field, it now caused the defaultUpdateType to be different during the method. The goal of this change is to restore the old timing of when that property changes until after the `OnFocusChanged` method finishes executing.
@chris-massie chris-massie marked this pull request as ready for review July 15, 2026 23:59

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The focus-state ownership cleanup is directionally sound, but the legacy focus handler now has a cleanup hole that can leave the manager stuck in the wrong focus/update state after an exception.

Reviewed commit 488a7fd7

🤖 Helpful? 👍/👎

@Pauliusd01 Pauliusd01 self-requested a review July 16, 2026 04:00
@Pauliusd01

This comment was marked as off-topic.

@u-pr

u-pr Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Test Plan

  • Test plan approved by PR author
  • Test execution in progress
  • Test execution complete
  • Ready for merge

Summary of Changes & Risk Assessment

Summary of Changes

This PR fixes a regression (XR-10725) where input devices (specifically XR hand devices) remained disabled on application startup because the initial focus state was not correctly synced to the input runtime. The fix moves the source of truth for focusState into the IInputRuntime and ensures it is initialized and updated correctly across both native and test runtimes. It also introduces logic in InputManager to handle focus transitions without desyncing the defaultUpdateType.

Risk Assessment

  • High Risk Areas: XR input device availability on startup (Standalone builds).
  • Medium Risk Areas: Application focus transitions (Alt-Tab behavior) and Editor vs. Player focus parity.
  • Low Risk Areas: Internal test fixture updates (InputTestRuntime).

Test Scenarios

Functional Testing

  • Initial Focus Sync: Launch a Standalone XR project. Verify that <MetaAimHand> and <XRHandDevice> (which do not run in background) are active immediately without requiring a manual window focus cycle.
  • Runtime Focus Transitions: In a built app, click away from the game window and back. Verify devices disable/re-enable based on their canDeviceRunInBackground status.
  • Initialization Order: Verify IInputRuntime.InitializeFocusState is called during InputManager.InstallRuntime (InputManager.cs:L2264).

Regression Testing

  • Legacy Focus Path: Test on a Unity version where UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS is not defined. Ensure OnFocusChanged in InputManager.LegacyFocusHandling.cs:L30 still correctly disables/enables devices.
  • Editor Focus Parity: In the Unity Editor, verify that hand devices continue to update even when the Game view is not focused (preserving the long-standing Editor behavior mentioned in the PR).
  • UI Focus Regression: Run existing UI tests, specifically UI_WhenAppLosesAndRegainsFocus_... in UITests.cs, to ensure the callback order discrepancy noted by the author doesn't break button press states.
🔍 Regression Deep Dive (additional risks identified)
  • Update Type Desync: Verify that defaultUpdateType returns the correct value (Player vs Editor) during the exact moment m_IsHandlingFocusChange is true (InputManager.cs:L543).
  • XR Subsystem Initialization: Verify focus behavior on Unity 6.5+ when XR initializes, checking if Application.isFocused stays true when "Play Focused" is enabled.

Edge Cases

  • Start Minimized: Launch the application in a minimized state (if platform supports it). Verify devices remain disabled until the window is first restored/focused.
  • Rapid Focus Toggle: Rapidly Alt-Tab in and out of the application to ensure the m_IsHandlingFocusChange flag correctly resets in the finally block (InputManager.LegacyFocusHandling.cs:L130).

💡 This test plan updates automatically when /test_plan is run on new commits. If you have any feedback, please reach out in #ai-qa


🤖 Helpful? Please react with 👍/👎 | Questions❓Please reach out in Slack #ask-u-pr

@Pauliusd01 Pauliusd01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've ran the /test_plan suggested tests (without XR devices) as well as some from the original focus PR in playmode and player looking for oddities with different focus settings. Saw improvements in device discoverability for the player only, otherwise it was the same as develop. But I have to be honest that the whole focus setting matrix was very confusing and I relied heavily on passing it to claude and constantly asking it whether x or y is expected.

Adding the test scenes I used below if you want to double check them:

InputPRTests.zip
FocusEvents.zip

The InputPRTests one has some of the matrix documented in the scene itself and you can double check if what you're seeing is what you're supposed to be getting (it's split into tabs for each /test_plan test, pick one and toggle it on)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Issues where existsing functionality misbehaves

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants