From ea8728cde7b0ff04882374c90091251df1ebf3d0 Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Tue, 14 Jul 2026 18:40:51 -0700 Subject: [PATCH 1/7] Fixed regression with initial focus causing added input devices to be disabled - Fixed regression caused by https://github.com/Unity-Technologies/InputSystem/pull/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. --- .../InputSystem/Runtime/IInputRuntime.cs | 2 +- .../InputManager.LegacyFocusHandling.cs | 10 ++--- .../InputSystem/Runtime/InputManager.cs | 42 ++++++++++--------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs index 86b9886612..1df8d35c4a 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs @@ -113,7 +113,7 @@ internal unsafe interface IInputRuntime /// /// Set delegate to call when the application changes focus. /// - /// + /// Action onPlayerFocusChanged { get; set; } #endif diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs index f4c781bdf4..9e0d6fa7c0 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs @@ -22,7 +22,7 @@ internal enum FocusFlags : ushort /// /// In editor this means the GameView has focus. In a built player this means the player has focus. /// - ApplicationFocus = (1 << 0) + ApplicationFocus = (1 << 0), }; internal partial class InputManager @@ -34,7 +34,7 @@ internal void OnFocusChanged(bool focus) if (!m_Runtime.isInPlayMode) { - focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None; + SetRuntimeFocusState(focus); return; } @@ -59,7 +59,7 @@ internal void OnFocusChanged(bool focus) { // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. - focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None; + SetRuntimeFocusState(focus); return; } @@ -122,7 +122,7 @@ internal void OnFocusChanged(bool focus) #endif // We set this *after* the block above as defaultUpdateType is influenced by the setting. - focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None; + SetRuntimeFocusState(focus); } /// @@ -150,8 +150,6 @@ private bool ShouldFlushEventBuffer() /// /// Determines if we should exit early from event processing without handling events. /// - /// The current event buffer - /// Whether the buffer can be flushed /// The current update type /// True if we should exit early, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index dc89828362..72ee08c50c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -268,20 +268,15 @@ public FocusFlags focusState { get { -#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - return m_Runtime.focusState; -#else - return m_FocusState; -#endif + if (m_Runtime != null) + return m_Runtime.focusState; + + return Application.isFocused ? FocusFlags.ApplicationFocus : FocusFlags.None; } set { -#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS if (m_Runtime != null) m_Runtime.focusState = value; -#else - m_FocusState = value; -#endif } } @@ -557,11 +552,11 @@ internal static void StopEditorEventPassthrough() #else true; #endif - private bool applicationHasFocus => (focusState & FocusFlags.ApplicationFocus) != FocusFlags.None; + private bool applicationHasFocus => m_Runtime != null ? m_Runtime.isPlayerFocused : Application.isFocused; private bool gameHasFocus => #if UNITY_EDITOR - m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isEditorEventPassthroughActive; + m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isEditorEventPassthroughActive; #else applicationHasFocus || gameShouldGetInputRegardlessOfFocus; #endif @@ -2027,11 +2022,6 @@ internal void InitializeData() // we don't know which one the user is going to use. The user // can manually turn off one of them to optimize operation. m_UpdateMask = InputUpdateType.Dynamic | InputUpdateType.Fixed; - #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - m_FocusState = Application.isFocused - ? m_FocusState | FocusFlags.ApplicationFocus - : m_FocusState & ~FocusFlags.ApplicationFocus; - #endif #if UNITY_EDITOR m_EditorIsActive = true; m_UpdateMask |= InputUpdateType.Editor; @@ -2277,9 +2267,7 @@ internal void InstallRuntime(IInputRuntime runtime) #endif m_Runtime.pollingFrequency = pollingFrequency; - focusState = m_Runtime.isPlayerFocused - ? focusState | FocusFlags.ApplicationFocus - : focusState & ~FocusFlags.ApplicationFocus; + SetRuntimeFocusState(Application.isFocused); // We only hook NativeInputSystem.onBeforeUpdate if necessary. if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers) @@ -2451,7 +2439,6 @@ internal struct AvailableDevice private bool m_NativeBeforeUpdateHooked; private bool m_HaveDevicesWithStateCallbackReceivers; #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - private FocusFlags m_FocusState = FocusFlags.ApplicationFocus; private bool m_DiscardOutOfFocusEvents; private double m_FocusRegainedTime; #endif @@ -4059,6 +4046,21 @@ private bool ShouldDropStatusEvents(InputEventBuffer eventBuffer) #endif // UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS + /// + /// Set the focus state of the runtime, converting from the given boolean to either setting or clearing the focus flag. + /// + /// The application focus state. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetRuntimeFocusState(bool applicationFocus) + { + if (m_Runtime != null) + { + m_Runtime.focusState = applicationFocus + ? m_Runtime.focusState | FocusFlags.ApplicationFocus + : m_Runtime.focusState & ~FocusFlags.ApplicationFocus; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void FinalizeUpdate(InputUpdateType updateType) { From b231937614655a619ff9440ea674474bd102e7e9 Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Tue, 14 Jul 2026 19:31:11 -0700 Subject: [PATCH 2/7] Whitespace fix to make formatter pass - This doesn't actually seem correctly formatted, but it's what the tool wants --- .../com.unity.inputsystem/InputSystem/Runtime/InputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 97ac950ba8..35d7d56ea8 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -556,7 +556,7 @@ internal static void StopEditorEventPassthrough() private bool gameHasFocus => #if UNITY_EDITOR - m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isEditorEventPassthroughActive; + m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isEditorEventPassthroughActive; #else applicationHasFocus || gameShouldGetInputRegardlessOfFocus; #endif From daafb57cb93700155bb1dd0cd416cf9280179ffc Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Tue, 14 Jul 2026 21:51:48 -0700 Subject: [PATCH 3/7] Updated solution to allow the test runtime to start in focus independent 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. --- .../InputSystem/Runtime/IInputRuntime.cs | 10 ++++++++++ .../Runtime/InputManager.LegacyFocusHandling.cs | 7 ------- .../InputSystem/Runtime/InputManager.cs | 17 +---------------- .../InputSystem/Runtime/NativeInputRuntime.cs | 12 ++++++++++++ .../Tests/TestFixture/InputTestRuntime.cs | 7 +++++++ 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs index 1df8d35c4a..ab95d38d40 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs @@ -74,6 +74,12 @@ internal unsafe interface IInputRuntime /// command sent to the device. long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr); + /// + /// Initialize the focus state based on current conditions of the application. + /// + /// + void InitializeFocusState(); + /// /// Set delegate to be called on input updates. /// @@ -117,7 +123,11 @@ internal unsafe interface IInputRuntime Action onPlayerFocusChanged { get; set; } #endif + /// + /// Flags indicating various focus states for the application and editor. + /// FocusFlags focusState { get; set; } + /// /// Is true when the player or game view has focus. /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs index 9e0d6fa7c0..3a6d2f3af9 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs @@ -33,10 +33,7 @@ internal void OnFocusChanged(bool focus) SyncAllDevicesWhenEditorIsActivated(); if (!m_Runtime.isInPlayMode) - { - SetRuntimeFocusState(focus); return; - } var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; #endif @@ -59,7 +56,6 @@ internal void OnFocusChanged(bool focus) { // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. - SetRuntimeFocusState(focus); return; } @@ -120,9 +116,6 @@ internal void OnFocusChanged(bool focus) #if UNITY_EDITOR m_CurrentUpdate = InputUpdateType.None; #endif - - // We set this *after* the block above as defaultUpdateType is influenced by the setting. - SetRuntimeFocusState(focus); } /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 35d7d56ea8..4b3d0cd91d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -2267,7 +2267,7 @@ internal void InstallRuntime(IInputRuntime runtime) #endif m_Runtime.pollingFrequency = pollingFrequency; - SetRuntimeFocusState(Application.isFocused); + m_Runtime.InitializeFocusState(); // We only hook NativeInputSystem.onBeforeUpdate if necessary. if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers) @@ -4046,21 +4046,6 @@ private bool ShouldDropStatusEvents(InputEventBuffer eventBuffer) #endif // UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS - /// - /// Set the focus state of the runtime, converting from the given boolean to either setting or clearing the focus flag. - /// - /// The application focus state. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetRuntimeFocusState(bool applicationFocus) - { - if (m_Runtime != null) - { - m_Runtime.focusState = applicationFocus - ? m_Runtime.focusState | FocusFlags.ApplicationFocus - : m_Runtime.focusState & ~FocusFlags.ApplicationFocus; - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void FinalizeUpdate(InputUpdateType updateType) { diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs index be296bc800..5ed84760fa 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs @@ -316,12 +316,24 @@ private bool OnWantsToShutdown() return true; } + public void InitializeFocusState() + { + m_FocusState = Application.isFocused + ? m_FocusState | FocusFlags.ApplicationFocus + : m_FocusState & ~FocusFlags.ApplicationFocus; + } + #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS private Action m_FocusChangedMethod; private void OnFocusChanged(bool focus) { m_FocusChangedMethod(focus); + + // We set this *after* the callback above as InputManager.defaultUpdateType is influenced by the property. + m_FocusState = focus + ? m_FocusState | FocusFlags.ApplicationFocus + : m_FocusState & ~FocusFlags.ApplicationFocus; } #endif diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs index 91fa3e4bb6..5c4d200854 100644 --- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs @@ -234,6 +234,13 @@ public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr) } } + public void InitializeFocusState() + { + // Testing should start in focus and individual tests can change this test runtime to lose focus + // by calling InvokePlayerFocusChanged via InputTestFixture.ScheduleFocusChangedEvent. + m_FocusState = FocusFlags.ApplicationFocus; + } + public void InvokePlayerFocusChanged(bool newFocusState) { m_FocusState = newFocusState From 67f6c048b3894d61c5b56fe4e9ba826a207a353d Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Wed, 15 Jul 2026 10:57:52 -0700 Subject: [PATCH 4/7] Removed `InputManager.focusState` property since only the setter was 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. --- .../InputSystem/Runtime/InputManager.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 4b3d0cd91d..1f15371acd 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -264,22 +264,6 @@ public InputSettings.ScrollDeltaBehavior scrollDeltaBehavior } } - public FocusFlags focusState - { - get - { - if (m_Runtime != null) - return m_Runtime.focusState; - - return Application.isFocused ? FocusFlags.ApplicationFocus : FocusFlags.None; - } - set - { - if (m_Runtime != null) - m_Runtime.focusState = value; - } - } - public float pollingFrequency { get @@ -3925,8 +3909,7 @@ private void ProcessDeviceConfigurationEvent(InputDevice device) private unsafe void ProcessFocusEvent(InputEvent* currentEventReadPtr) { var focusEventPtr = (InputFocusEvent*)currentEventReadPtr; - FocusFlags state = focusEventPtr->focusFlags; - focusState = state; + m_Runtime.focusState = focusEventPtr->focusFlags; #if UNITY_EDITOR SyncAllDevicesWhenEditorIsActivated(); From 488a7fd7897cdfdcf7a32ca6f6409f3bcbd67a38 Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Wed, 15 Jul 2026 16:21:23 -0700 Subject: [PATCH 5/7] Added temporary override of `applicationHasFocus` while handling the 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. --- .../InputManager.LegacyFocusHandling.cs | 15 ++++++++++++-- .../InputSystem/Runtime/InputManager.cs | 20 +++++++++++++++++-- .../InputSystem/Runtime/NativeInputRuntime.cs | 5 ++--- .../Tests/TestFixture/InputTestRuntime.cs | 1 + 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs index 3a6d2f3af9..021a730574 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs @@ -29,11 +29,20 @@ internal partial class InputManager { internal void OnFocusChanged(bool focus) { + // We set this to temporarily override applicationHasFocus before processing the focus change + // as defaultUpdateType is influenced by it. Before returning from this method, clear the + // bool to stop overriding to indicate this manager has finished processing the focus change. + m_IsHandlingFocusChange = true; + m_ApplicationHadFocus = !focus; + #if UNITY_EDITOR SyncAllDevicesWhenEditorIsActivated(); if (!m_Runtime.isInPlayMode) + { + m_IsHandlingFocusChange = false; return; + } var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; #endif @@ -51,11 +60,11 @@ internal void OnFocusChanged(bool focus) m_Runtime.runInBackground; #endif - var backgroundBehavior = m_Settings.backgroundBehavior; - if (backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) + if (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) { // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. + m_IsHandlingFocusChange = false; return; } @@ -116,6 +125,8 @@ internal void OnFocusChanged(bool focus) #if UNITY_EDITOR m_CurrentUpdate = InputUpdateType.None; #endif + + m_IsHandlingFocusChange = false; } /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 1f15371acd..994538ecc2 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -241,7 +241,7 @@ public InputUpdateType defaultUpdateType // The solution here would be to make update calls explicitly specify the update type and no longer use this property. if (!m_RunPlayerUpdatesInEditMode && (!gameIsPlaying || !gameHasFocus)) return InputUpdateType.Editor; - #endif +#endif return m_UpdateMask.GetUpdateTypeForPlayer(); } @@ -536,7 +536,17 @@ internal static void StopEditorEventPassthrough() #else true; #endif - private bool applicationHasFocus => m_Runtime != null ? m_Runtime.isPlayerFocused : Application.isFocused; + private bool applicationHasFocus + { + get + { +#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS + if (m_IsHandlingFocusChange) + return m_ApplicationHadFocus; +#endif + return m_Runtime != null ? m_Runtime.isPlayerFocused : Application.isFocused; + } + } private bool gameHasFocus => #if UNITY_EDITOR @@ -2425,6 +2435,12 @@ internal struct AvailableDevice #if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS private bool m_DiscardOutOfFocusEvents; private double m_FocusRegainedTime; + // These two bools are used for overriding applicationHasFocus which affects gameHasFocus + // and thus defaultUpdateType. See comments in defaultUpdateType. While processing the + // focus change in the OnFocusChanged method, these are used to temporarily override + // those properties. + private bool m_IsHandlingFocusChange; + private bool m_ApplicationHadFocus; #endif private InputEventStream m_InputEventStream; diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs index 5ed84760fa..7a0bdb68d0 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs @@ -328,12 +328,11 @@ public void InitializeFocusState() private void OnFocusChanged(bool focus) { - m_FocusChangedMethod(focus); - - // We set this *after* the callback above as InputManager.defaultUpdateType is influenced by the property. m_FocusState = focus ? m_FocusState | FocusFlags.ApplicationFocus : m_FocusState & ~FocusFlags.ApplicationFocus; + + m_FocusChangedMethod(focus); } #endif diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs index 5c4d200854..70348143e6 100644 --- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs +++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs @@ -246,6 +246,7 @@ public void InvokePlayerFocusChanged(bool newFocusState) m_FocusState = newFocusState ? m_FocusState | FocusFlags.ApplicationFocus : m_FocusState & ~FocusFlags.ApplicationFocus; + onPlayerFocusChanged?.Invoke(newFocusState); } From 53336a8f6793175505f2177aa337c144dd900c72 Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Wed, 15 Jul 2026 17:27:08 -0700 Subject: [PATCH 6/7] Updated to use `try`/`finally` block to ensure `m_IsHandlingFocusChange` is cleared before returning --- .../InputManager.LegacyFocusHandling.cs | 139 +++++++++--------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs index 021a730574..5dea2777ed 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs @@ -35,98 +35,103 @@ internal void OnFocusChanged(bool focus) m_IsHandlingFocusChange = true; m_ApplicationHadFocus = !focus; + try + { #if UNITY_EDITOR - SyncAllDevicesWhenEditorIsActivated(); + SyncAllDevicesWhenEditorIsActivated(); - if (!m_Runtime.isInPlayMode) - { - m_IsHandlingFocusChange = false; - return; - } + if (!m_Runtime.isInPlayMode) + return; - var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; + var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode; #endif - var runInBackground = + var runInBackground = #if UNITY_EDITOR - // In the editor, the player loop will always be run even if the Game View does not have focus. This - // amounts to runInBackground being always true in the editor, regardless of what the setting in - // the Player Settings window is. - // - // If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same - // path as in the player. - gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground; + + // In the editor, the player loop will always be run even if the Game View does not have focus. This + // amounts to runInBackground being always true in the editor, regardless of what the setting in + // the Player Settings window is. + // + // If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same + // path as in the player. + gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground; #else - m_Runtime.runInBackground; + m_Runtime.runInBackground; #endif - if (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) - { - // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. - // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. - m_IsHandlingFocusChange = false; - return; - } + if (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground) + { + // If runInBackground is true, no device changes should happen, even when focus is gained. So early out. + // If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further. + return; + } #if UNITY_EDITOR - // Set the current update type while we process the focus changes to make sure we - // feed into the right buffer. No need to do this in the player as it doesn't have - // the editor/player confusion. - m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); + + // Set the current update type while we process the focus changes to make sure we + // feed into the right buffer. No need to do this in the player as it doesn't have + // the editor/player confusion. + m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); #endif - if (!focus) - { - // We only react to loss of focus when we will keep running in the background. If not, - // we'll do nothing and just wait for focus to come back (where we then try to sync all devices). - if (runInBackground) + if (!focus) { - for (var i = 0; i < m_DevicesCount; ++i) + // We only react to loss of focus when we will keep running in the background. If not, + // we'll do nothing and just wait for focus to come back (where we then try to sync all devices). + if (runInBackground) { - // Determine whether to run this device in the background. - var device = m_Devices[i]; - if (!device.enabled || ShouldRunDeviceInBackground(device)) - continue; + for (var i = 0; i < m_DevicesCount; ++i) + { + // Determine whether to run this device in the background. + var device = m_Devices[i]; + if (!device.enabled || ShouldRunDeviceInBackground(device)) + continue; - // Disable the device. This will also soft-reset it. - EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + // Disable the device. This will also soft-reset it. + EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); - // In case we invoked a callback that messed with our device array, adjust our index. - var index = m_Devices.IndexOfReference(device, m_DevicesCount); - if (index == -1) - --i; - else - i = index; + // In case we invoked a callback that messed with our device array, adjust our index. + var index = m_Devices.IndexOfReference(device, m_DevicesCount); + if (index == -1) + --i; + else + i = index; + } } } - } - else - { - m_DiscardOutOfFocusEvents = true; - m_FocusRegainedTime = m_Runtime.currentTime; - // On focus gain, reenable and sync devices. - for (var i = 0; i < m_DevicesCount; ++i) + else { - var device = m_Devices[i]; + m_DiscardOutOfFocusEvents = true; + m_FocusRegainedTime = m_Runtime.currentTime; - // Re-enable the device if we disabled it on focus loss. This will also issue a sync. - if (device.disabledWhileInBackground) - EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); - // Try to sync. If it fails and we didn't run in the background, perform - // a reset instead. This is to cope with backends that are unable to sync but - // may still retain state which now may be outdated because the input device may - // have changed state while we weren't running. So at least make the backend flush - // its state (if any). - else if (device.enabled && !runInBackground && !device.RequestSync() && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus) - ResetDevice(device); + // On focus gain, reenable and sync devices. + for (var i = 0; i < m_DevicesCount; ++i) + { + var device = m_Devices[i]; + + // Re-enable the device if we disabled it on focus loss. This will also issue a sync. + if (device.disabledWhileInBackground) + EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground); + + // Try to sync. If it fails and we didn't run in the background, perform + // a reset instead. This is to cope with backends that are unable to sync but + // may still retain state which now may be outdated because the input device may + // have changed state while we weren't running. So at least make the backend flush + // its state (if any). + else if (device.enabled && !runInBackground && !device.RequestSync() && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus) + ResetDevice(device); + } } - } #if UNITY_EDITOR - m_CurrentUpdate = InputUpdateType.None; + m_CurrentUpdate = InputUpdateType.None; #endif - - m_IsHandlingFocusChange = false; + } + finally + { + m_IsHandlingFocusChange = false; + } } /// From 9d921342954671248a49352f413841bb9d6da75d Mon Sep 17 00:00:00 2001 From: Chris Massie Date: Wed, 15 Jul 2026 17:34:33 -0700 Subject: [PATCH 7/7] Address PR feedback to ensure the `m_CurrentUpdate` is cleared in the finally block --- .../Runtime/InputManager.LegacyFocusHandling.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs index 5dea2777ed..6c4b8d9fbb 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs @@ -35,6 +35,10 @@ internal void OnFocusChanged(bool focus) m_IsHandlingFocusChange = true; m_ApplicationHadFocus = !focus; +#if UNITY_EDITOR + var shouldClearCurrentUpdateInFinally = false; +#endif + try { #if UNITY_EDITOR @@ -48,7 +52,6 @@ internal void OnFocusChanged(bool focus) var runInBackground = #if UNITY_EDITOR - // In the editor, the player loop will always be run even if the Game View does not have focus. This // amounts to runInBackground being always true in the editor, regardless of what the setting in // the Player Settings window is. @@ -68,11 +71,11 @@ internal void OnFocusChanged(bool focus) } #if UNITY_EDITOR - // Set the current update type while we process the focus changes to make sure we // feed into the right buffer. No need to do this in the player as it doesn't have // the editor/player confusion. m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer(); + shouldClearCurrentUpdateInFinally = true; #endif if (!focus) @@ -123,13 +126,14 @@ internal void OnFocusChanged(bool focus) ResetDevice(device); } } - -#if UNITY_EDITOR - m_CurrentUpdate = InputUpdateType.None; -#endif } finally { +#if UNITY_EDITOR + if (shouldClearCurrentUpdateInFinally) + m_CurrentUpdate = InputUpdateType.None; +#endif + m_IsHandlingFocusChange = false; } }