Skip to content

Commit 1680513

Browse files
Merge branch 'develop' into input/uum-137124-input-debugger-window-naming
2 parents 72fea98 + d0c70db commit 1680513

4 files changed

Lines changed: 263 additions & 0 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#if UNITY_2023_2_OR_NEWER // UnityEngine.InputForUI Module unavailable in earlier releases
2+
using System.Collections.Generic;
3+
using NUnit.Framework;
4+
using UnityEngine;
5+
using UnityEngine.InputForUI;
6+
using UnityEngine.InputSystem;
7+
using UnityEngine.InputSystem.LowLevel;
8+
using UnityEngine.InputSystem.Plugins.InputForUI;
9+
using Event = UnityEngine.InputForUI.Event;
10+
using EventProvider = UnityEngine.InputForUI.EventProvider;
11+
12+
// Regression tests for UUM-139662:
13+
// "UI Elements cannot be interacted with in a build when using a touchscreen monitor after using Alt+Tab"
14+
//
15+
// Root cause: InputSystemProvider.OnFocusChanged() does not reset pointer state (m_TouchState, m_MouseState,
16+
// m_PenState, m_SeenTouchEvents, m_SeenPenEvents, m_ResetSeenEventsOnUpdate, m_Events) on focus loss.
17+
// After an Alt+Tab cycle, stale values corrupt event processing for subsequent touch and mouse input.
18+
//
19+
// Fix: Reset all pointer state in OnFocusChanged(false), matching how InputManagerProvider behaves
20+
// (it polls fresh state every frame so stale state is never possible there).
21+
[Category("InputForUI")]
22+
public class InputForUIFocusRegressionTests : InputTestFixture
23+
{
24+
readonly List<Event> m_RecordedEvents = new List<Event>();
25+
InputSystemProvider m_Provider;
26+
27+
[SetUp]
28+
public override void Setup()
29+
{
30+
base.Setup();
31+
m_Provider = new InputSystemProvider();
32+
EventProvider.SetMockProvider(m_Provider);
33+
EventProvider.Subscribe(OnEvent);
34+
}
35+
36+
[TearDown]
37+
public override void TearDown()
38+
{
39+
EventProvider.Unsubscribe(OnEvent);
40+
EventProvider.ClearMockProvider();
41+
m_RecordedEvents.Clear();
42+
base.TearDown();
43+
}
44+
45+
bool OnEvent(in Event ev)
46+
{
47+
m_RecordedEvents.Add(ev);
48+
return true;
49+
}
50+
51+
static void FlushEvents()
52+
{
53+
// Process pending input events first, then let the provider dispatch them.
54+
// Mirrors the Update() helper in InputForUITests.
55+
EventProvider.NotifyUpdate();
56+
InputSystem.Update();
57+
}
58+
59+
// ─── Stale ClickCount ─────────────────────────────────────────────────────
60+
//
61+
// Scenario: user taps the touchscreen, Alt+Tabs away and back, then taps again.
62+
// BackgroundBehavior resets the device on focus loss, but the InputSystemProvider's
63+
// internal m_TouchState is never cleared — OnFocusChanged(false) only delegates to
64+
// m_InputEventPartialProvider and touches nothing else.
65+
//
66+
// PointerState.Reset() clears ButtonsState and LastPosition but intentionally
67+
// preserves ClickCount for within-session double-tap detection. After an Alt+Tab
68+
// that ClickCount is stale: the tap before focus loss left it at 1, so the first
69+
// tap after focus regain calls OnButtonChange(false→true) starting from 1, producing
70+
// clickCount=2 in the ButtonPressed event. UIToolkit uses clickCount to distinguish
71+
// single/double/triple taps — a wrong value causes the element to receive the wrong
72+
// interaction type and ignore the input.
73+
//
74+
// Expected (after fix): OnFocusChanged(false) assigns m_TouchState = default,
75+
// zeroing every field including ClickCount. The first post-regain tap starts from 0
76+
// and correctly produces clickCount == 1.
77+
[Test]
78+
[Description("Verifies that m_TouchState is reset on focus loss so that the first " +
79+
"touch press after an Alt+Tab cycle generates a clean ButtonPressed event " +
80+
"rather than one with stale (stuck-pressed) button state.")]
81+
public void AfterAltTab_FirstTouchPress_GeneratesCleanButtonPressedEvent()
82+
{
83+
InputSystem.settings.backgroundBehavior =
84+
InputSettings.BackgroundBehavior.ResetAndDisableNonBackgroundDevices;
85+
86+
InputSystem.AddDevice<Touchscreen>();
87+
88+
// ── Phase 1: complete touch tap (press + release) ────────────────────────
89+
// Use a full tap so the device is in a clean state (no active touch) before the
90+
// focus cycle. The only accumulated state we care about is ClickCount, which
91+
// PointerState.Reset() does NOT clear — that is the stale value the fix must address.
92+
BeginTouch(1, new Vector2(100f, 100f));
93+
EndTouch(1, new Vector2(100f, 100f));
94+
FlushEvents();
95+
96+
// Confirm the tap was recorded so we know the provider is wired up.
97+
Assert.That(m_RecordedEvents.Count >= 1 &&
98+
m_RecordedEvents[0] is
99+
{
100+
type: Event.Type.PointerEvent,
101+
asPointerEvent: { type: PointerEvent.Type.ButtonPressed, eventSource: EventSource.Touch }
102+
}, "Pre-condition failed: expected initial ButtonPressed from Touch");
103+
104+
m_RecordedEvents.Clear();
105+
106+
// ── Phase 2: Alt+Tab cycle ────────────────────────────────────────────────
107+
// ScheduleFocusChangedEvent triggers the InputManager's BackgroundBehavior
108+
// (device disable/reset/re-enable) but does NOT call IEventProviderImpl.OnFocusChanged —
109+
// that is called by the UnityEngine.InputForUI EventProvider module in response to
110+
// Application.focusChanged, which cannot be triggered from a unit test. We call it
111+
// directly on the provider here to reflect what actually happens at runtime.
112+
m_Provider.OnFocusChanged(false);
113+
114+
ScheduleFocusChangedEvent(applicationHasFocus: false);
115+
currentTime += 0.5f;
116+
ScheduleFocusChangedEvent(applicationHasFocus: true);
117+
currentTime += 0.5f;
118+
119+
#if UNITY_EDITOR
120+
// InputUpdateType.Editor is only valid inside the editor; in a player build
121+
// the focus events are processed through the Dynamic update path.
122+
InputSystem.Update(InputUpdateType.Editor);
123+
#endif
124+
InputSystem.Update(InputUpdateType.Dynamic);
125+
EventProvider.NotifyUpdate();
126+
127+
m_Provider.OnFocusChanged(true);
128+
129+
m_RecordedEvents.Clear();
130+
131+
// ── Phase 3: new touch at a different position after focus regain ─────────
132+
var freshTouchPosition = new Vector2(200f, 200f);
133+
BeginTouch(1, freshTouchPosition);
134+
EndTouch(1, freshTouchPosition);
135+
FlushEvents();
136+
137+
// Expect exactly one ButtonPressed event followed by one ButtonReleased event,
138+
// both with EventSource.Touch and clickCount == 1 (a fresh single tap).
139+
var pressEvents = m_RecordedEvents.FindAll(e =>
140+
e.type == Event.Type.PointerEvent &&
141+
e.asPointerEvent.type == PointerEvent.Type.ButtonPressed &&
142+
e.asPointerEvent.eventSource == EventSource.Touch);
143+
144+
Assert.AreEqual(1, pressEvents.Count,
145+
"Expected exactly one Touch ButtonPressed event after focus regain. " +
146+
"If stale ButtonsState shows the button as already pressed, OnButtonChange(true,true) " +
147+
"produces no transition and UIToolkit will not recognise this as a new press.");
148+
149+
Assert.AreEqual(1, pressEvents[0].asPointerEvent.clickCount,
150+
"clickCount should be 1 for a fresh single tap after focus regain. " +
151+
"A stale clickCount indicates m_TouchState was not reset on focus loss.");
152+
}
153+
}
154+
#endif

Assets/Tests/InputSystem/Plugins/InputForUIFocusRegressionTests.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/InputForUI/InputSystemProvider.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,39 @@ static int SortEvents(Event a, Event b)
281281
public void OnFocusChanged(bool focus)
282282
{
283283
m_InputEventPartialProvider.OnFocusChanged(focus);
284+
285+
if (!focus)
286+
{
287+
// Preserve the last known cursor/pen positions before resetting.
288+
// Mouse and pen positions are needed so that a click-to-refocus
289+
// (where the pointer hasn't moved and OnPointerPerformed won't fire)
290+
// still dispatches to the correct screen location via OnClickPerformed.
291+
// Touch positions are NOT preserved — every new touch always arrives
292+
// with a fresh position from OnPointerPerformed.
293+
var mouseLastValid = m_MouseState.LastPositionValid;
294+
var mouseLastPos = m_MouseState.LastPosition;
295+
var mouseLastDisplay = m_MouseState.LastDisplayIndex;
296+
297+
var penLastValid = m_PenState.LastPositionValid;
298+
var penLastPos = m_PenState.LastPosition;
299+
var penLastDisplay = m_PenState.LastDisplayIndex;
300+
301+
// Use default assignment rather than Reset() because Reset() intentionally
302+
// preserves ClickCount for within-session double-tap tracking.
303+
// After a focus loss that ClickCount is stale and must be zeroed.
304+
m_MouseState = default;
305+
m_TouchState = default;
306+
m_PenState = default;
307+
308+
if (mouseLastValid)
309+
m_MouseState.OnMove(m_CurrentTime, mouseLastPos, mouseLastDisplay);
310+
if (penLastValid)
311+
m_PenState.OnMove(m_CurrentTime, penLastPos, penLastDisplay);
312+
313+
ResetSeenEvents();
314+
m_ResetSeenEventsOnUpdate = false;
315+
m_Events.Clear();
316+
}
284317
}
285318

286319
public bool RequestCurrentState(Event.Type type)

Tools/CI/CI-CHANGES.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# CI Changes
2+
3+
This file documents notable changes made to the CI cookbook (`Tools/CI/`), including the motivation, what was changed, and any follow-up actions required.
4+
5+
---
6+
7+
## Wrench 2.12.0 Migration & CI Fixes
8+
**Branch:** `bugfix/fix-ci-failure-due-to-warnings`
9+
**Date:** June 2026
10+
**Author:** Darren Kelly
11+
12+
### Summary
13+
Migrated the CI cookbook from `RecipeEngine.Modules.Wrench` 2.2.1 to 2.12.0 and fixed several CI failures that surfaced as a result.
14+
15+
---
16+
17+
### 1. Wrench Version Bump
18+
**File:** `InputSystem.Cookbook.csproj`
19+
20+
Bumped `RecipeEngine.Modules.Wrench` from `2.2.1` to `2.12.0`. The version in `wrench_config.json` is updated automatically by the regenerate script.
21+
22+
---
23+
24+
### 2. BaseRecipe — Migrated to Wrench 2.x Job Iteration Pattern
25+
**File:** `Recipes/BaseRecipe.cs`
26+
27+
`GetJobs()` was still using the Wrench 1.x pattern, reading platforms only from `UnityEditors[0]` and iterating `SupportedEditorVersions` as a flat list. This meant per-editor platform customisations were silently ignored for all editor versions except the first.
28+
29+
Migrated to the 2.x pattern: iterates `package.UnityEditors` directly, using `unityEditor.Version.Version` for the version string and `unityEditor.EditorPlatforms.Items` for the platform set. Job names now use `EditorPlatformType` (e.g. `MacOs13`, `MacOs13Arm`) instead of `SystemType` (e.g. `MacOS`) to produce unique names — necessary because Wrench 2.12.0 adds `MacOs13Arm` by default and both `MacOs13` and `MacOs13Arm` share `SystemType.MacOS`.
30+
31+
---
32+
33+
### 3. Mac ARM64 Fix for Unity 6.6
34+
**File:** `Settings/InputSystemSettings.cs`
35+
36+
`OverridePackagePlatform` was unconditionally forcing `MacOs13` (Intel) onto all editor versions including Unity 6.6. Wrench 2.12.0 defaults Unity 6.6 to `MacOs13Arm` (Apple Silicon), so this override was reverting 6.6 back to an Intel agent and causing `Bad CPU type in executable` failures on standalone tests.
37+
38+
Fixed by skipping the `MacOs13` override for Unity 6.6, allowing Wrench's default `MacOs13Arm` agent to take effect.
39+
40+
**Jobs fixed:**
41+
- `StandaloneFunctionalTests - 6000.6 - MacOs13`
42+
- `StandaloneIl2CppFunctionalTests - 6000.6 - MacOs13`
43+
- All other custom recipe jobs for Unity 6.6 on Mac
44+
45+
---
46+
47+
### 4. Ubuntu Standalone Tests — Switched to GPU VM
48+
**File:** `Settings/InputSystemSettings.cs`
49+
50+
The default Ubuntu2204 agent is a CPU-only VM (`Unity::VM`), which uses software rendering. Standalone player tests run graphical processes that are 2–3× slower under software rendering, causing frequent timeouts and flaky failures.
51+
52+
Overrode the Ubuntu2204 agent to use `ResourceType.VmGpu` (`Unity::VM::GPU`) with `package-ci/ubuntu-22.04:v4`. This is consistent with how other Unity teams (Burst, HDRP, UX Engineering) run Linux standalone tests. The GPU VM provisions an NVIDIA RTX 2080 Ti by default.
53+
54+
**Jobs fixed:**
55+
- `StandaloneFunctionalTests - 6000.0 - Ubuntu2204`
56+
- `StandaloneFunctionalTests - 6000.3 - Ubuntu2204`
57+
- All other standalone recipe jobs on Ubuntu2204
58+
59+
---
60+
61+
### Known Outstanding Issues
62+
63+
- **`parentSpanId` deserialization error** — Unity 6.6 editor builds (from `6000.6.0a8` onwards) include a test framework change (PR #102587 in `unity/unity`) that emits `parentSpanId: ""` instead of `null`, which UTR cannot deserialize. This is a known upstream issue being tracked in PR #109932 in `unity/unity`. No action required on our side; monitor that PR for a fix.
64+
65+
- **`Ubuntu2004 packAndPromotePlatformType` warning** — Wrench logs a warning that `Ubuntu2004` is set as the `packAndPromotePlatformType` but is not in the editor platforms. This is pre-existing and non-blocking (generation still succeeds). Investigate whether `packAndPromotePlatformType` should be updated to `Ubuntu2204`.

0 commit comments

Comments
 (0)