diff --git a/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs b/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs index 7a513aa16b..ee64cf622d 100644 --- a/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs +++ b/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs @@ -64,6 +64,69 @@ public void TouchscreenAddedAndRemoved() Assert.IsFalse(touchscreen.added); } + [Test] + [Category("Device Simulator")] + public void ConflictingDevicesAreNotDisabledOnCreate() + { + runtime.ReportNewInputDevice(); + InputSystem.Update(); + var mouse = Mouse.current; + Assert.That(mouse.native, Is.True); + + var plugin = new InputSystemPlugin(); + plugin.OnCreate(); + + // Conflicting devices are only disabled once the Simulator gains focus, not on create. + Assert.That(mouse.enabled, Is.True); + + plugin.OnDestroy(); + } + + [Test] + [Category("Device Simulator")] + public void ConflictingDeviceAddedWhileSimulatorFocused_IsDisabledThenReenabledOnDestroy() + { + var plugin = new InputSystemPlugin(); + plugin.OnCreate(); + + // Simulate the Simulator window being the focused window (bypasses the panel-based OnUpdate). + SetPrivateField(plugin, "m_ConflictingDevicesDisabled", true); + + runtime.ReportNewInputDevice(); + InputSystem.Update(); + var mouse = Mouse.current; + + Assert.That(mouse.native, Is.True); + Assert.That(mouse.enabled, Is.False); // disabled via the OnDeviceChange gate + + plugin.OnDestroy(); + Assert.That(mouse.enabled, Is.True); // ReenableConflictingDevices restores it + } + + [Test] + [Category("Device Simulator")] + public void ConflictingDeviceAddedWhileSimulatorNotFocused_StaysEnabled() + { + var plugin = new InputSystemPlugin(); + plugin.OnCreate(); + // m_ConflictingDevicesDisabled defaults to false (Simulator not focused). + + runtime.ReportNewInputDevice(); + InputSystem.Update(); + var mouse = Mouse.current; + + Assert.That(mouse.enabled, Is.True); + + plugin.OnDestroy(); + } + + private static void SetPrivateField(object target, string name, object value) + { + var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field, $"Field '{name}' not found on {target.GetType().Name}"); + field.SetValue(target, value); + } + private TouchEvent CreateTouch(int touchId, Vector2 position, UnityEditor.DeviceSimulation.TouchPhase phase) { var touch = new TouchEvent(); diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index ecb98b985c..39ffdeb0a5 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed `OnMouseUpAsButton` and `OnMouseUp` being dropped in Play mode when the Game view's focus changes between a press and its release on Unity 6000.5.0a8 and newer. The legacy `SendMouseEvents` pipeline is no longer driven from `InputUpdateType.Editor` updates, which read the editor state buffer (position (0,0), not pressed) and produced a spurious mouse release that cleared the press target. - Fixed Input Debugger window's incorrect name. It is now called 'Input Debugger' instead of 'Input Debug' [UUM-137124](https://jira.unity3d.com/browse/UUM-137124). - Fixed `PoseControl.isTracked` always returning false when read through non-optimized code paths (e.g. Input Debugger) due to `sizeInBits = 8` causing the value to be normalized as `1/255` instead of `1.0`. +- Fixed the Device Simulator plugin keeping the real mouse and pen disabled while working in other Editor windows. Conflicting native `Mouse`/`Pen` devices are now only disabled while the Simulator window is focused and re-enabled as soon as focus moves elsewhere [UUM-145509](https://jira.unity3d.com/browse/UUM-145509). ### Changed - Action-level `IsPressed`, `WasPressedThisFrame`, and `WasReleasedThisFrame` for bindings to `Vector2Control` / `StickControl` no longer consult a per-control `pressPoint` on the vector (that field was removed). Use a `Press` interaction to set a custom threshold, or rely on `defaultButtonPressPoint`. diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/DeviceSimulator/InputSystemPlugin.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/DeviceSimulator/InputSystemPlugin.cs index e8cf48c263..406857807d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/DeviceSimulator/InputSystemPlugin.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/DeviceSimulator/InputSystemPlugin.cs @@ -2,8 +2,11 @@ using System; using System.Collections.Generic; +using UnityEditor; using UnityEditor.DeviceSimulation; +using UnityEditor.UIElements; using UnityEngine.InputSystem.LowLevel; +using UnityEngine.UIElements; namespace UnityEngine.InputSystem.Editor { @@ -13,6 +16,9 @@ internal class InputSystemPlugin : DeviceSimulatorPlugin private bool m_InputSystemEnabled; private bool m_Quitting; + private bool m_ConflictingDevicesDisabled; + private VisualElement m_RootElement; + private EditorWindow m_LastFocusedWindow; private List m_DisabledDevices; public override string title => "Input System"; @@ -25,6 +31,9 @@ public override void OnCreate() // Monitor whether the editor is quitting to avoid risking unsafe EnableDevice while quitting UnityEditor.EditorApplication.quitting += OnQuitting; + // Poll the active window so conflicting devices are only disabled while the simulator is focused. + UnityEditor.EditorApplication.update += OnUpdate; + m_DisabledDevices = new List(); // deviceSimulator is never null when the plugin is instantiated by a simulator window, but it can be null during unit tests @@ -32,12 +41,6 @@ public override void OnCreate() deviceSimulator.touchScreenInput += OnTouchEvent; InputSystem.onDeviceChange += OnDeviceChange; - // UGUI elements like a button don't get pressed when multiple pointers for example mouse and touchscreen are sending data at the same time - foreach (var device in InputSystem.devices) - { - DisableConflictingDevice(device); - } - SimulatorTouchscreen = InputSystem.AddDevice("Device Simulator Touchscreen"); } } @@ -56,6 +59,44 @@ internal void OnTouchEvent(TouchEvent touchEvent) }); } + public override VisualElement OnCreateUI() + { + m_RootElement = new VisualElement(); + m_RootElement.Add(new HelpBox( + L10n.Tr("Manages Input System devices while the Simulator is focused."), + HelpBoxMessageType.Info)); + return m_RootElement; + } + + private void OnUpdate() + { + if (!EditorApplication.isPlaying) + return; + + var focusedWindow = EditorWindow.focusedWindow; + if (focusedWindow == m_LastFocusedWindow) + return; + + m_LastFocusedWindow = focusedWindow; + var simulatorFocused = + m_RootElement != null + && focusedWindow != null + && focusedWindow.rootVisualElement.panel == m_RootElement.panel; + + if (simulatorFocused && !m_ConflictingDevicesDisabled) + { + // UGUI elements like a button don't get pressed when multiple pointers for example mouse and touchscreen are sending data at the same time + foreach (var device in InputSystem.devices) + DisableConflictingDevice(device); + m_ConflictingDevicesDisabled = true; + } + else if (!simulatorFocused && m_ConflictingDevicesDisabled) + { + ReenableConflictingDevices(); + m_ConflictingDevicesDisabled = false; + } + } + private void DisableConflictingDevice(InputDevice device) { if (device.native && (device is Mouse || device is Pen) && device.enabled) @@ -65,8 +106,28 @@ private void DisableConflictingDevice(InputDevice device) } } + private void ReenableConflictingDevices() + { + foreach (var device in m_DisabledDevices) + { + // Note that m_Quitting is used here to mitigate the problem reported in issue tracker: + // https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-10774. + // Enabling a device will call into IOCTL of backend which may be destroyed prior + // to this callback on Unity version. This is not a fix for the actual problem + // of shutdown order but a package fix to mitigate this problem. + // The core problem with the destruction order was still there in Unity 6.5. + if (device.added && !m_Quitting) + InputSystem.EnableDevice(device); + } + m_DisabledDevices.Clear(); + } + private void OnDeviceChange(InputDevice device, InputDeviceChange change) { + // Only disable newly added/reconnected devices while the simulator is the active window. + if (!m_ConflictingDevicesDisabled) + return; + if (change == InputDeviceChange.Added || change == InputDeviceChange.Reconnected) DisableConflictingDevice(device); } @@ -100,20 +161,13 @@ public override void OnDestroy() InputSystem.onDeviceChange -= OnDeviceChange; UnityEditor.EditorApplication.quitting -= OnQuitting; + UnityEditor.EditorApplication.update -= OnUpdate; if (SimulatorTouchscreen != null) InputSystem.RemoveDevice(SimulatorTouchscreen); - foreach (var device in m_DisabledDevices) - { - // Note that m_Quitting is used here to mitigate the problem reported in issue tracker: - // https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-10774. - // Enabling a device will call into IOCTL of backend which may be destroyed prior - // to this callback on Unity version. This is not a fix for the actual problem - // of shutdown order but a package fix to mitigate this problem. - // The core problem with the destruction order was still there in Unity 6.5. - if (device.added && !m_Quitting) - InputSystem.EnableDevice(device); - } + + ReenableConflictingDevices(); + m_RootElement = null; } }