Skip to content

Commit 6b99df4

Browse files
authored
FIX: [XR-10725] Fixed regression with initial focus causing input devices to always be disabled (#2447)
1 parent d2d0b1a commit 6b99df4

5 files changed

Lines changed: 131 additions & 105 deletions

File tree

Packages/com.unity.inputsystem/InputSystem/Runtime/IInputRuntime.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ internal unsafe interface IInputRuntime
7474
/// command sent to the device.</returns>
7575
long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr);
7676

77+
/// <summary>
78+
/// Initialize the focus state based on current conditions of the application.
79+
/// </summary>
80+
/// <seealso cref="focusState"/>
81+
void InitializeFocusState();
82+
7783
/// <summary>
7884
/// Set delegate to be called on input updates.
7985
/// </summary>
@@ -113,11 +119,15 @@ internal unsafe interface IInputRuntime
113119
/// <summary>
114120
/// Set delegate to call when the application changes focus.
115121
/// </summary>
116-
/// <seealso cref="Application.onFocusChanged"/>
122+
/// <seealso cref="Application.focusChanged"/>
117123
Action<bool> onPlayerFocusChanged { get; set; }
118124
#endif
119125

126+
/// <summary>
127+
/// Flags indicating various focus states for the application and editor.
128+
/// </summary>
120129
FocusFlags focusState { get; set; }
130+
121131
/// <summary>
122132
/// Is true when the player or game view has focus.
123133
/// </summary>

Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.LegacyFocusHandling.cs

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -22,107 +22,120 @@ internal enum FocusFlags : ushort
2222
/// <summary>
2323
/// In editor this means the GameView has focus. In a built player this means the player has focus.
2424
/// </summary>
25-
ApplicationFocus = (1 << 0)
25+
ApplicationFocus = (1 << 0),
2626
};
2727

2828
internal partial class InputManager
2929
{
3030
internal void OnFocusChanged(bool focus)
3131
{
32+
// We set this to temporarily override applicationHasFocus before processing the focus change
33+
// as defaultUpdateType is influenced by it. Before returning from this method, clear the
34+
// bool to stop overriding to indicate this manager has finished processing the focus change.
35+
m_IsHandlingFocusChange = true;
36+
m_ApplicationHadFocus = !focus;
37+
3238
#if UNITY_EDITOR
33-
SyncAllDevicesWhenEditorIsActivated();
39+
var shouldClearCurrentUpdateInFinally = false;
40+
#endif
3441

35-
if (!m_Runtime.isInPlayMode)
42+
try
3643
{
37-
focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None;
38-
return;
39-
}
44+
#if UNITY_EDITOR
45+
SyncAllDevicesWhenEditorIsActivated();
46+
47+
if (!m_Runtime.isInPlayMode)
48+
return;
4049

41-
var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode;
50+
var gameViewFocus = m_Settings.editorInputBehaviorInPlayMode;
4251
#endif
4352

44-
var runInBackground =
53+
var runInBackground =
4554
#if UNITY_EDITOR
46-
// In the editor, the player loop will always be run even if the Game View does not have focus. This
47-
// amounts to runInBackground being always true in the editor, regardless of what the setting in
48-
// the Player Settings window is.
49-
//
50-
// If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same
51-
// path as in the player.
52-
gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground;
55+
// In the editor, the player loop will always be run even if the Game View does not have focus. This
56+
// amounts to runInBackground being always true in the editor, regardless of what the setting in
57+
// the Player Settings window is.
58+
//
59+
// If, however, "Game View Focus" is set to "Exactly As In Player", we force code here down the same
60+
// path as in the player.
61+
gameViewFocus != InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView || m_Runtime.runInBackground;
5362
#else
54-
m_Runtime.runInBackground;
63+
m_Runtime.runInBackground;
5564
#endif
5665

57-
var backgroundBehavior = m_Settings.backgroundBehavior;
58-
if (backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground)
59-
{
60-
// If runInBackground is true, no device changes should happen, even when focus is gained. So early out.
61-
// If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further.
62-
focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None;
63-
return;
64-
}
66+
if (m_Settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus && runInBackground)
67+
{
68+
// If runInBackground is true, no device changes should happen, even when focus is gained. So early out.
69+
// If runInBackground is false, we still want to sync devices when focus is gained. So we need to continue further.
70+
return;
71+
}
6572

6673
#if UNITY_EDITOR
67-
// Set the current update type while we process the focus changes to make sure we
68-
// feed into the right buffer. No need to do this in the player as it doesn't have
69-
// the editor/player confusion.
70-
m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer();
74+
// Set the current update type while we process the focus changes to make sure we
75+
// feed into the right buffer. No need to do this in the player as it doesn't have
76+
// the editor/player confusion.
77+
m_CurrentUpdate = m_UpdateMask.GetUpdateTypeForPlayer();
78+
shouldClearCurrentUpdateInFinally = true;
7179
#endif
7280

73-
if (!focus)
74-
{
75-
// We only react to loss of focus when we will keep running in the background. If not,
76-
// we'll do nothing and just wait for focus to come back (where we then try to sync all devices).
77-
if (runInBackground)
81+
if (!focus)
7882
{
79-
for (var i = 0; i < m_DevicesCount; ++i)
83+
// We only react to loss of focus when we will keep running in the background. If not,
84+
// we'll do nothing and just wait for focus to come back (where we then try to sync all devices).
85+
if (runInBackground)
8086
{
81-
// Determine whether to run this device in the background.
82-
var device = m_Devices[i];
83-
if (!device.enabled || ShouldRunDeviceInBackground(device))
84-
continue;
87+
for (var i = 0; i < m_DevicesCount; ++i)
88+
{
89+
// Determine whether to run this device in the background.
90+
var device = m_Devices[i];
91+
if (!device.enabled || ShouldRunDeviceInBackground(device))
92+
continue;
8593

86-
// Disable the device. This will also soft-reset it.
87-
EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
94+
// Disable the device. This will also soft-reset it.
95+
EnableOrDisableDevice(device, false, DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
8896

89-
// In case we invoked a callback that messed with our device array, adjust our index.
90-
var index = m_Devices.IndexOfReference(device, m_DevicesCount);
91-
if (index == -1)
92-
--i;
93-
else
94-
i = index;
97+
// In case we invoked a callback that messed with our device array, adjust our index.
98+
var index = m_Devices.IndexOfReference(device, m_DevicesCount);
99+
if (index == -1)
100+
--i;
101+
else
102+
i = index;
103+
}
95104
}
96105
}
97-
}
98-
else
99-
{
100-
m_DiscardOutOfFocusEvents = true;
101-
m_FocusRegainedTime = m_Runtime.currentTime;
102-
// On focus gain, reenable and sync devices.
103-
for (var i = 0; i < m_DevicesCount; ++i)
106+
else
104107
{
105-
var device = m_Devices[i];
108+
m_DiscardOutOfFocusEvents = true;
109+
m_FocusRegainedTime = m_Runtime.currentTime;
110+
111+
// On focus gain, reenable and sync devices.
112+
for (var i = 0; i < m_DevicesCount; ++i)
113+
{
114+
var device = m_Devices[i];
106115

107-
// Re-enable the device if we disabled it on focus loss. This will also issue a sync.
108-
if (device.disabledWhileInBackground)
109-
EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
110-
// Try to sync. If it fails and we didn't run in the background, perform
111-
// a reset instead. This is to cope with backends that are unable to sync but
112-
// may still retain state which now may be outdated because the input device may
113-
// have changed state while we weren't running. So at least make the backend flush
114-
// its state (if any).
115-
else if (device.enabled && !runInBackground && !device.RequestSync() && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus)
116-
ResetDevice(device);
116+
// Re-enable the device if we disabled it on focus loss. This will also issue a sync.
117+
if (device.disabledWhileInBackground)
118+
EnableOrDisableDevice(device, true, DeviceDisableScope.TemporaryWhilePlayerIsInBackground);
119+
120+
// Try to sync. If it fails and we didn't run in the background, perform
121+
// a reset instead. This is to cope with backends that are unable to sync but
122+
// may still retain state which now may be outdated because the input device may
123+
// have changed state while we weren't running. So at least make the backend flush
124+
// its state (if any).
125+
else if (device.enabled && !runInBackground && !device.RequestSync() && m_Settings.backgroundBehavior != InputSettings.BackgroundBehavior.IgnoreFocus)
126+
ResetDevice(device);
127+
}
117128
}
118129
}
119-
130+
finally
131+
{
120132
#if UNITY_EDITOR
121-
m_CurrentUpdate = InputUpdateType.None;
133+
if (shouldClearCurrentUpdateInFinally)
134+
m_CurrentUpdate = InputUpdateType.None;
122135
#endif
123136

124-
// We set this *after* the block above as defaultUpdateType is influenced by the setting.
125-
focusState = focus ? FocusFlags.ApplicationFocus : FocusFlags.None;
137+
m_IsHandlingFocusChange = false;
138+
}
126139
}
127140

128141
/// <summary>
@@ -150,8 +163,6 @@ private bool ShouldFlushEventBuffer()
150163
/// <summary>
151164
/// Determines if we should exit early from event processing without handling events.
152165
/// </summary>
153-
/// <param name="eventBuffer">The current event buffer</param>
154-
/// <param name="canFlushBuffer">Whether the buffer can be flushed</param>
155166
/// <param name="updateType">The current update type</param>
156167
/// <returns>True if we should exit early, false otherwise.</returns>
157168
[MethodImpl(MethodImplOptions.AggressiveInlining)]

Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public InputUpdateType defaultUpdateType
241241
// The solution here would be to make update calls explicitly specify the update type and no longer use this property.
242242
if (!m_RunPlayerUpdatesInEditMode && (!gameIsPlaying || !gameHasFocus))
243243
return InputUpdateType.Editor;
244-
#endif
244+
#endif
245245

246246
return m_UpdateMask.GetUpdateTypeForPlayer();
247247
}
@@ -264,27 +264,6 @@ public InputSettings.ScrollDeltaBehavior scrollDeltaBehavior
264264
}
265265
}
266266

267-
public FocusFlags focusState
268-
{
269-
get
270-
{
271-
#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
272-
return m_Runtime.focusState;
273-
#else
274-
return m_FocusState;
275-
#endif
276-
}
277-
set
278-
{
279-
#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
280-
if (m_Runtime != null)
281-
m_Runtime.focusState = value;
282-
#else
283-
m_FocusState = value;
284-
#endif
285-
}
286-
}
287-
288267
public float pollingFrequency
289268
{
290269
get
@@ -557,7 +536,17 @@ internal static void StopEditorEventPassthrough()
557536
#else
558537
true;
559538
#endif
560-
private bool applicationHasFocus => (focusState & FocusFlags.ApplicationFocus) != FocusFlags.None;
539+
private bool applicationHasFocus
540+
{
541+
get
542+
{
543+
#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
544+
if (m_IsHandlingFocusChange)
545+
return m_ApplicationHadFocus;
546+
#endif
547+
return m_Runtime != null ? m_Runtime.isPlayerFocused : Application.isFocused;
548+
}
549+
}
561550

562551
private bool gameHasFocus =>
563552
#if UNITY_EDITOR
@@ -2027,11 +2016,6 @@ internal void InitializeData()
20272016
// we don't know which one the user is going to use. The user
20282017
// can manually turn off one of them to optimize operation.
20292018
m_UpdateMask = InputUpdateType.Dynamic | InputUpdateType.Fixed;
2030-
#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
2031-
m_FocusState = Application.isFocused
2032-
? m_FocusState | FocusFlags.ApplicationFocus
2033-
: m_FocusState & ~FocusFlags.ApplicationFocus;
2034-
#endif
20352019
#if UNITY_EDITOR
20362020
m_EditorIsActive = true;
20372021
m_UpdateMask |= InputUpdateType.Editor;
@@ -2277,9 +2261,7 @@ internal void InstallRuntime(IInputRuntime runtime)
22772261
#endif
22782262
m_Runtime.pollingFrequency = pollingFrequency;
22792263

2280-
focusState = m_Runtime.isPlayerFocused
2281-
? focusState | FocusFlags.ApplicationFocus
2282-
: focusState & ~FocusFlags.ApplicationFocus;
2264+
m_Runtime.InitializeFocusState();
22832265

22842266
// We only hook NativeInputSystem.onBeforeUpdate if necessary.
22852267
if (m_BeforeUpdateListeners.length > 0 || m_HaveDevicesWithStateCallbackReceivers)
@@ -2451,9 +2433,14 @@ internal struct AvailableDevice
24512433
private bool m_NativeBeforeUpdateHooked;
24522434
private bool m_HaveDevicesWithStateCallbackReceivers;
24532435
#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
2454-
private FocusFlags m_FocusState = FocusFlags.ApplicationFocus;
24552436
private bool m_DiscardOutOfFocusEvents;
24562437
private double m_FocusRegainedTime;
2438+
// These two bools are used for overriding applicationHasFocus which affects gameHasFocus
2439+
// and thus defaultUpdateType. See comments in defaultUpdateType. While processing the
2440+
// focus change in the OnFocusChanged method, these are used to temporarily override
2441+
// those properties.
2442+
private bool m_IsHandlingFocusChange;
2443+
private bool m_ApplicationHadFocus;
24572444
#endif
24582445
private InputEventStream m_InputEventStream;
24592446

@@ -3938,8 +3925,7 @@ private void ProcessDeviceConfigurationEvent(InputDevice device)
39383925
private unsafe void ProcessFocusEvent(InputEvent* currentEventReadPtr)
39393926
{
39403927
var focusEventPtr = (InputFocusEvent*)currentEventReadPtr;
3941-
FocusFlags state = focusEventPtr->focusFlags;
3942-
focusState = state;
3928+
m_Runtime.focusState = focusEventPtr->focusFlags;
39433929

39443930
#if UNITY_EDITOR
39453931
SyncAllDevicesWhenEditorIsActivated();

Packages/com.unity.inputsystem/InputSystem/Runtime/NativeInputRuntime.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,22 @@ private bool OnWantsToShutdown()
316316
return true;
317317
}
318318

319+
public void InitializeFocusState()
320+
{
321+
m_FocusState = Application.isFocused
322+
? m_FocusState | FocusFlags.ApplicationFocus
323+
: m_FocusState & ~FocusFlags.ApplicationFocus;
324+
}
325+
319326
#if !UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
320327
private Action<bool> m_FocusChangedMethod;
321328

322329
private void OnFocusChanged(bool focus)
323330
{
331+
m_FocusState = focus
332+
? m_FocusState | FocusFlags.ApplicationFocus
333+
: m_FocusState & ~FocusFlags.ApplicationFocus;
334+
324335
m_FocusChangedMethod(focus);
325336
}
326337

Packages/com.unity.inputsystem/Tests/TestFixture/InputTestRuntime.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,19 @@ public unsafe long DeviceCommand(int deviceId, InputDeviceCommand* commandPtr)
234234
}
235235
}
236236

237+
public void InitializeFocusState()
238+
{
239+
// Testing should start in focus and individual tests can change this test runtime to lose focus
240+
// by calling InvokePlayerFocusChanged via InputTestFixture.ScheduleFocusChangedEvent.
241+
m_FocusState = FocusFlags.ApplicationFocus;
242+
}
243+
237244
public void InvokePlayerFocusChanged(bool newFocusState)
238245
{
239246
m_FocusState = newFocusState
240247
? m_FocusState | FocusFlags.ApplicationFocus
241248
: m_FocusState & ~FocusFlags.ApplicationFocus;
249+
242250
onPlayerFocusChanged?.Invoke(newFocusState);
243251
}
244252

0 commit comments

Comments
 (0)