diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs
index 86b9886612..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.
///
@@ -113,11 +119,15 @@ internal unsafe interface IInputRuntime
///
/// Set delegate to call when the application changes focus.
///
- ///
+ ///
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 f4c781bdf4..6c4b8d9fbb 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs
@@ -22,107 +22,120 @@ 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
{
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();
+ var shouldClearCurrentUpdateInFinally = false;
+#endif
- if (!m_Runtime.isInPlayMode)
+ try
{
- focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None;
- return;
- }
+#if UNITY_EDITOR
+ SyncAllDevicesWhenEditorIsActivated();
+
+ 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
- var backgroundBehavior = m_Settings.backgroundBehavior;
- if (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.
- focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None;
- 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();
+ shouldClearCurrentUpdateInFinally = true;
#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;
+
+ // 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);
+ // 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);
+ }
}
}
-
+ finally
+ {
#if UNITY_EDITOR
- m_CurrentUpdate = InputUpdateType.None;
+ if (shouldClearCurrentUpdateInFinally)
+ m_CurrentUpdate = InputUpdateType.None;
#endif
- // We set this *after* the block above as defaultUpdateType is influenced by the setting.
- focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None;
+ m_IsHandlingFocusChange = false;
+ }
}
///
@@ -150,8 +163,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 d396770da4..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();
}
@@ -264,27 +264,6 @@ public InputSettings.ScrollDeltaBehavior scrollDeltaBehavior
}
}
- public FocusFlags focusState
- {
- get
- {
-#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
- return m_Runtime.focusState;
-#else
- return m_FocusState;
-#endif
- }
- set
- {
-#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
- if (m_Runtime != null)
- m_Runtime.focusState = value;
-#else
- m_FocusState = value;
-#endif
- }
- }
-
public float pollingFrequency
{
get
@@ -557,7 +536,17 @@ internal static void StopEditorEventPassthrough()
#else
true;
#endif
- private bool applicationHasFocus => (focusState & FocusFlags.ApplicationFocus) != FocusFlags.None;
+ 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
@@ -2027,11 +2016,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 +2261,7 @@ internal void InstallRuntime(IInputRuntime runtime)
#endif
m_Runtime.pollingFrequency = pollingFrequency;
- focusState = m_Runtime.isPlayerFocused
- ? focusState | FocusFlags.ApplicationFocus
- : focusState & ~FocusFlags.ApplicationFocus;
+ m_Runtime.InitializeFocusState();
// We only hook NativeInputSystem.onBeforeUpdate if necessary.
if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers)
@@ -2451,9 +2433,14 @@ 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;
+ // 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;
@@ -3938,8 +3925,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();
diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs
index be296bc800..7a0bdb68d0 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs
@@ -316,11 +316,22 @@ 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_FocusState = focus
+ ? m_FocusState | FocusFlags.ApplicationFocus
+ : m_FocusState & ~FocusFlags.ApplicationFocus;
+
m_FocusChangedMethod(focus);
}
diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs
index 91fa3e4bb6..70348143e6 100644
--- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs
+++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs
@@ -234,11 +234,19 @@ 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
? m_FocusState | FocusFlags.ApplicationFocus
: m_FocusState & ~FocusFlags.ApplicationFocus;
+
onPlayerFocusChanged?.Invoke(newFocusState);
}