Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
af12382
mvp focus event
VeraMommersteeg Jan 28, 2026
00f9a33
Fixed tests to schedule input events. Fixed code to make device focus…
VeraMommersteeg Feb 9, 2026
431a40f
refactored onfocusevent and cleaned up
VeraMommersteeg Feb 10, 2026
07b6445
changed focus state to be flags, fixed tests, clean up
VeraMommersteeg Feb 18, 2026
4533fbf
cleaned up code, moved editor early out to later in update
VeraMommersteeg Feb 20, 2026
e1e5c26
if focus event, dont do anything with keyboard/mouse processing
VeraMommersteeg Feb 26, 2026
687cd5a
Changed tests, as the current architecture relies on focus changes pr…
VeraMommersteeg Mar 2, 2026
2adf1d3
fixed edge case for editor/game view swapping when reset and disable …
VeraMommersteeg Mar 2, 2026
21c13f2
fixed profiler marker scopes. fixed compilation for player
VeraMommersteeg Mar 3, 2026
84a848d
cleanup
VeraMommersteeg Mar 3, 2026
48b2e53
Merge branch 'develop' into input/add-focus-events-to-queue
VeraMommersteeg Mar 3, 2026
1f99472
disabling test for now, will make jira ticket
VeraMommersteeg Mar 4, 2026
c527198
formatting and cleanup/refactoring code
VeraMommersteeg Mar 4, 2026
e63d74d
fixed compilation and failing tests for 6.4 and below
VeraMommersteeg Mar 9, 2026
700c8dd
split legacy behaviour
VeraMommersteeg Mar 9, 2026
a97d931
fixed define
VeraMommersteeg Mar 9, 2026
45a64eb
add property to interface, re-enabled test
VeraMommersteeg Mar 10, 2026
df8c990
formatting
VeraMommersteeg Mar 10, 2026
5b92fe7
cleanup
VeraMommersteeg Mar 10, 2026
2aad9f2
Merge branch 'develop' into input/add-focus-events-to-queue
VeraMommersteeg Mar 10, 2026
eb257b0
fixed merge issue
VeraMommersteeg Mar 11, 2026
eeb7e1c
fixed failing tests, processed feedback
VeraMommersteeg Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions Assets/Tests/InputSystem/CoreTests_Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,9 @@ public void Actions_DoNotGetTriggeredByEditorUpdates()

using (var trace = new InputActionTrace(action))
{
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Set(gamepad.leftTrigger, 0.123f, queueEventOnly: true);
InputSystem.Update(InputUpdateType.Editor);

Expand Down Expand Up @@ -661,13 +663,13 @@ public void Actions_DoNotGetTriggeredByOutOfFocusEventInEditor(InputSettings.Bac
// could just rely on order of event. Which means this test work for a fixed timestamp and it should
// changed accordingly.
currentTime += 1.0f;
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
currentTime += 1.0f;
// Queuing an event like it would be in the editor when the GameView is out of focus.
Set(mouse.position, new Vector2(0.234f, 0.345f) , queueEventOnly: true);
currentTime += 1.0f;
// Gaining focus like it would happen in the editor when the GameView regains focus.
runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
currentTime += 1.0f;
// This emulates a device sync that happens when the player regains focus through an IOCTL command.
// That's why it also has it's time incremented.
Expand Down Expand Up @@ -720,14 +722,15 @@ public void Actions_TimeoutsDoNotGetTriggeredInEditorUpdates()

trace.Clear();

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);
currentTime = 10;

InputSystem.Update(InputUpdateType.Editor);

Assert.That(trace, Is.Empty);

runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
InputSystem.Update(InputUpdateType.Dynamic);

actions = trace.ToArray();
Expand Down
38 changes: 25 additions & 13 deletions Assets/Tests/InputSystem/CoreTests_Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using UnityEngine.Scripting;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
using UnityEngineInternal.Input;
using Gyroscope = UnityEngine.InputSystem.Gyroscope;
using UnityEngine.TestTools.Constraints;
using Is = NUnit.Framework.Is;
Expand Down Expand Up @@ -1522,7 +1523,7 @@ public void Devices_CanReconnectDevice_WhenDisconnectedWhileAppIsOutOfFocus()
Assert.That(device, Is.Not.Null);

// Loose focus.
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update();

// Disconnect.
Expand All @@ -1534,7 +1535,7 @@ public void Devices_CanReconnectDevice_WhenDisconnectedWhileAppIsOutOfFocus()
Assert.That(InputSystem.devices, Is.Empty);

// Regain focus.
runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
InputSystem.Update();

var newDeviceId = runtime.ReportNewInputDevice(deviceDesc);
Expand Down Expand Up @@ -4604,7 +4605,13 @@ void DeviceChangeCallback(InputDevice device, InputDeviceChange change)
InputSystem.onDeviceChange += DeviceChangeCallback;

var eventCount = 0;
InputSystem.onEvent += (eventPtr, _) => ++ eventCount;
InputSystem.onEvent += (eventPtr, _) =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nitpick: I have no problem with this approach or filtering at all. The first thing that strikes me though is that it sounds like the test is based on observing an exact number of events. This means that the test is very brittle and would brake down when any new (or user plugin injected) event is added. To avoid that, maybe it would make sense to instead only count the events relevant to the test to make it future proof? The system should be resilient to new events being added IMO. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That whole test is a mess IMO, it's the 800 line test and needs to be completely broken up, and yes having the test depend on event count makes it brittle. That is the reason I had to do the check there so it only counts the tests that it should for that test. I would say we leave it for now and instead just do a full refactor of this test as a separate pr

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll make a jira card for this as something that we need to do at some point. Which is looking at all the tests there and see if they make sense

{
// Focus events will always be processed no matter the state
// Since the test relies on counting events based on state, dont count focus events
if (eventPtr.data->type != (FourCC)FocusConstants.kEventType)
++eventCount;
};

Assert.That(trackedDevice.enabled, Is.True);
Assert.That(mouse.enabled, Is.True);
Expand Down Expand Up @@ -4647,7 +4654,8 @@ void DeviceChangeCallback(InputDevice device, InputDeviceChange change)
}

// Lose focus.
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Assert.That(sensor.enabled, Is.False);
Assert.That(disabledDevice.enabled, Is.False);
Expand Down Expand Up @@ -5068,7 +5076,8 @@ void DeviceChangeCallback(InputDevice device, InputDeviceChange change)
commands.Clear();

// Regain focus.
runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
InputSystem.Update(InputUpdateType.Dynamic);

Assert.That(sensor.enabled, Is.False);
Assert.That(disabledDevice.enabled, Is.False);
Expand Down Expand Up @@ -5275,13 +5284,10 @@ void DeviceChangeCallback(InputDevice device, InputDeviceChange change)
"Sync Gamepad", "Sync Joystick",
"Sync TrackedDevice", "Sync TrackedDevice2",
"Sync Mouse", "Sync Mouse2", "Sync Mouse3",
"Sync Keyboard", "Reset Joystick"
}));
// Enabled devices that don't support syncs get reset.
Assert.That(changes, Is.EquivalentTo(new[]
{
"SoftReset Mouse1", "SoftReset Mouse3", "HardReset Joystick", "SoftReset TrackedDevice2"
"Sync Keyboard"
}));
// Enabled devices that don't support syncs dont get reset for Ignore Focus as we do not want to cancel any actions.
Assert.That(changes, Is.Empty);
break;
}
}
Expand Down Expand Up @@ -5318,7 +5324,13 @@ public void Devices_CanSkipProcessingEventsWhileInBackground()
Assert.That(performedCount, Is.EqualTo(1));

// Lose focus
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
#if UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS
// in the new system, we have to process the focus event to update the state of the devices.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nitpick: Avoid terms like "new" and "old", instead be specific, e.g. "Since Unity 6000.5.a8, we have..." (or whatever is the appropriate version). "New" is relative to something, and it is not clear to what, so this won't age well I am afraid.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Alternatively just skip what is before the "comma" since the define should also states the change version.

// In the old system, this wouldn't work and would make the test fal
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of "In the old system" maybe just "Previously"?

InputSystem.Update();
#endif

Assert.That(gamepad.enabled, Is.False);

// Queue an event while in the background. We don't want to see this event to be processed once focus
Expand All @@ -5329,7 +5341,7 @@ public void Devices_CanSkipProcessingEventsWhileInBackground()
InputSystem.Update();

// Gain focus
runtime.PlayerFocusGained();
ScheduleFocusEvent(true);

// Run update to try process events accordingly once focus is gained
InputSystem.Update();
Expand Down
6 changes: 4 additions & 2 deletions Assets/Tests/InputSystem/CoreTests_Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2720,7 +2720,8 @@ public void Editor_CanForceKeyboardAndMouseInputToGameViewWithoutFocus()
var keyboard = InputSystem.AddDevice<Keyboard>();
var mouse = InputSystem.AddDevice<Mouse>();

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Assert.That(keyboard.enabled, Is.True);
Assert.That(mouse.enabled, Is.True);
Expand Down Expand Up @@ -3016,7 +3017,8 @@ public void Editor_LeavingPlayMode_ReenablesAllDevicesTemporarilyDisabledDueToFo
Set(mouse.position, new Vector2(123, 234));
Press(gamepad.buttonSouth);

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Assert.That(gamepad.enabled, Is.False);

Expand Down
12 changes: 9 additions & 3 deletions Assets/Tests/InputSystem/CoreTests_State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,9 @@ public void State_CanSetUpMonitorsForStateChanges_InEditor()
InputState.AddChangeMonitor(gamepad.leftStick,
(control, time, eventPtr, monitorIndex) => monitorFired = true);

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Set(gamepad.leftStick, new Vector2(0.123f, 0.234f), queueEventOnly: true);
InputSystem.Update(InputUpdateType.Editor);

Expand Down Expand Up @@ -1676,7 +1678,9 @@ public void State_RecordingHistory_ExcludesEditorInputByDefault()
{
history.StartRecording();

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Set(gamepad.leftTrigger, 0.123f, queueEventOnly: true);
InputSystem.Update(InputUpdateType.Editor);

Expand All @@ -1696,7 +1700,9 @@ public void State_RecordingHistory_CanCaptureEditorInput()
history.updateMask = InputUpdateType.Editor;
history.StartRecording();

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

Set(gamepad.leftTrigger, 0.123f, queueEventOnly: true);
InputSystem.Update(InputUpdateType.Editor);

Expand Down
28 changes: 22 additions & 6 deletions Assets/Tests/InputSystem/Plugins/EnhancedTouchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,10 @@ public void EnhancedTouch_SupportsEditorUpdates(InputSettings.UpdateMode updateM
Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));

// And make sure we're not seeing the data in the editor.
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: It is only semantics, by I personally think a test fixture helper like is should be named what it does instead of reflecting implementation details (that changing focus leads to scheduling an event). E.g. ChangeFocus(applicationHasFocus: false) is a lot clearer. The fact that this leads to a deferred change event is a detail. But the actual change happens when that line is executed.

Nitpick: When using trivial arguments like this, I think it's helpful to pass as named argument since false could be mean many things. It makes it a lot easier to read the code, especially in web-interface where there are no IDE smartness that highlights the name of the argument.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I only write this comment once even though it applies to all tests here, e.g. row 174 ScheduleFocusEvent(true) etc.

InputSystem.Update(InputUpdateType.Editor);

Assert.That(Touch.activeTouches, Is.Empty);

// Feed some data into editor state.
BeginTouch(2, new Vector2(0.234f, 0.345f), queueEventOnly: true);
InputSystem.Update(InputUpdateType.Editor);
Expand All @@ -171,8 +170,25 @@ public void EnhancedTouch_SupportsEditorUpdates(InputSettings.UpdateMode updateM
Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(2));

// Switch back to player.
runtime.PlayerFocusGained();
InputSystem.Update();
ScheduleFocusEvent(true);

// Explicitly schedule the player's configured update type rather than relying on the default.
// Without explicit scheduling, defaultUpdateType would be Editor (since focus has not yet been
// gained during update), causing the editor buffer to be used instead of the player buffer,
// which would retrieve the wrong active touch. A proper fix would require removing defaultUpdateType
// and splitting player/editor update loops into separate methods.
switch (updateMode)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would be cleaner, and separate concerns, if the type transform from InputSettings.UpdateMode to InputUpdateType would happen in its own single-purpose (internal) extension method. This whole switch statement would then effectively be part of that static method and this would instead become:
InputSystem.Update(updateMode.ToInputUpdateType());

I suspect that conversion happens in multiple places so such a conversion is likely reusable to reduce code-bloat as well.

{
case InputSettings.UpdateMode.ProcessEventsInDynamicUpdate:
InputSystem.Update(InputUpdateType.Dynamic);
break;
case InputSettings.UpdateMode.ProcessEventsInFixedUpdate:
InputSystem.Update(InputUpdateType.Fixed);
break;
case InputSettings.UpdateMode.ProcessEventsManually:
InputSystem.Update(InputUpdateType.Manual);
break;
}

Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
Assert.That(Touch.activeTouches[0].touchId, Is.EqualTo(1));
Expand Down Expand Up @@ -1160,7 +1176,7 @@ public void EnhancedTouch_ActiveTouchesGetCanceledOnFocusLoss_WithRunInBackgroun
Assert.That(Touch.activeTouches, Has.Count.EqualTo(1));
Assert.That(Touch.activeTouches[0].phase, Is.EqualTo(TouchPhase.Began));

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);

if (runInBackground)
{
Expand All @@ -1171,7 +1187,7 @@ public void EnhancedTouch_ActiveTouchesGetCanceledOnFocusLoss_WithRunInBackgroun
else
{
// When not running in the background, the same thing happens but only on focus gain.
runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
InputSystem.Update();
}

Expand Down
6 changes: 3 additions & 3 deletions Assets/Tests/InputSystem/Plugins/InputForUITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -715,19 +715,19 @@ public void UIActions_DoNotGetTriggeredByOutOfFocusEventInEditor(InputSettings.B
currentTime += 1.0f;
Update();
currentTime += 1.0f;
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
currentTime += 1.0f;
Set(mouse.position, outOfFocusPosition , queueEventOnly: true);
currentTime += 1.0f;
runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
currentTime += 1.0f;
Set(mouse.position, focusPosition, queueEventOnly: true);
currentTime += 1.0f;

// We call specific updates to simulate editor behavior when regaining focus.
InputSystem.Update(InputUpdateType.Editor);
Assert.AreEqual(0, m_InputForUIEvents.Count);
InputSystem.Update();
InputSystem.Update(InputUpdateType.Dynamic);
// Calling the event provider update after we call InputSystem updates so that we trigger InputForUI events
EventProvider.NotifyUpdate();

Expand Down
14 changes: 10 additions & 4 deletions Assets/Tests/InputSystem/Plugins/UITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4128,7 +4128,9 @@ public IEnumerator UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButto

scene.leftChildReceiver.events.Clear();

runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);

if (canRunInBackground)
Assert.That(clickCanceled, Is.EqualTo(0));
else
Expand All @@ -4139,7 +4141,9 @@ public IEnumerator UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButto
Assert.That(scene.eventSystem.hasFocus, Is.False);
Assert.That(clicked, Is.False);

runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
InputSystem.Update(InputUpdateType.Dynamic);

scene.eventSystem.SendMessage("OnApplicationFocus", true);

yield return null;
Expand Down Expand Up @@ -4168,11 +4172,13 @@ public IEnumerator UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButto

// Ensure that losing and regaining focus doesn't cause the next click to be ignored
clicked = false;
runtime.PlayerFocusLost();
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);
scene.eventSystem.SendMessage("OnApplicationFocus", false);
yield return null;

runtime.PlayerFocusGained();
ScheduleFocusEvent(true);
InputSystem.Update(InputUpdateType.Dynamic);
scene.eventSystem.SendMessage("OnApplicationFocus", true);
yield return null;

Expand Down
17 changes: 15 additions & 2 deletions Assets/Tests/InputSystem/Plugins/UserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1268,8 +1268,21 @@ public void Users_DoNotReactToEditorInput()
++InputUser.listenForUnpairedDeviceActivity;
InputUser.onUnpairedDeviceUsed += (control, eventPtr) => Assert.Fail("Should not react!");

runtime.PlayerFocusLost();

// Process the focus event before pressing the button to ensure correct update type selection.
//
// Issue: When Update() is called without an update type, it uses defaultUpdateType which checks
// focus state. However, scheduled focus events aren't processed until an update runs, so the
// focus check sees stale state and selects the wrong update type.
//
// Workaround: Run a dynamic update first to process the focus event, ensuring the subsequent
// button press correctly uses editor update type.
//
// Alternative: Queue the button press and explicitly call an editor update to process both events.
//
// Proper fix: Remove defaultUpdateType and split editor/player loops, or always specify the
// update type explicitly when calling Update().
ScheduleFocusEvent(false);
InputSystem.Update(InputUpdateType.Dynamic);
Press(gamepad.buttonSouth);

Assert.That(gamepad.buttonSouth.isPressed, Is.True);
Expand Down
5 changes: 5 additions & 0 deletions Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"name": "Unity",
"expression": "6000.3.0a6",
"define": "UNITY_INPUT_SYSTEM_PLATFORM_POLLING_FREQUENCY"
},
{
"name": "Unity",
"expression": "6000.5.0a8",
"define": "UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS"
}
],
"noEngineReferences": false
Expand Down
Loading
Loading