diff --git a/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs b/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs index 7a513aa16b..f3e7284e78 100644 --- a/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs +++ b/Assets/Tests/InputSystem/Plugins/DeviceSimulatorTests.cs @@ -8,6 +8,7 @@ using UnityEngine.InputSystem; using UnityEngine.InputSystem.Editor; using UnityEngine.InputSystem.EnhancedTouch; +using UnityEngine.InputSystem.Layouts; using UnityEngine.TestTools; using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; using TouchPhase = UnityEngine.InputSystem.TouchPhase; @@ -64,6 +65,84 @@ public void TouchscreenAddedAndRemoved() Assert.IsFalse(touchscreen.added); } + [Test] + [Category("Device Simulator")] + public void ConflictingDevicesAreNotDisabledOnCreate() + { + var mouse = AddNativeMouse(); + 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 focused (bypasses the panel-based OnUpdate). + plugin.SetConflictingDevicesDisabled(true); + + var mouse = AddNativeMouse(); + + 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 ConflictingDevicesReenabledWhenSimulatorLosesFocus() + { + var mouse = AddNativeMouse(); + + var plugin = new InputSystemPlugin(); + plugin.OnCreate(); + + plugin.SetConflictingDevicesDisabled(true); // Simulator gained focus + Assert.That(mouse.enabled, Is.False); + + plugin.SetConflictingDevicesDisabled(false); // Simulator lost focus + Assert.That(mouse.enabled, Is.True); + + plugin.OnDestroy(); + } + + [Test] + [Category("Device Simulator")] + public void ConflictingDeviceAddedWhileSimulatorNotFocused_StaysEnabled() + { + var plugin = new InputSystemPlugin(); + plugin.OnCreate(); + // m_ConflictingDevicesDisabled defaults to false (Simulator not focused). + + var mouse = AddNativeMouse(); + + Assert.That(mouse.enabled, Is.True); + + plugin.OnDestroy(); + } + + // Reports a native Mouse through the test runtime (device.native == true, which the plugin's + // disable logic requires) and returns the resolved device rather than relying on Mouse.current. + private Mouse AddNativeMouse() + { + var deviceId = runtime.ReportNewInputDevice( + new InputDeviceDescription { deviceClass = "Mouse", interfaceName = "Test" }); + InputSystem.Update(); + return (Mouse)InputSystem.GetDeviceById(deviceId); + } + 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..c3a18612ec 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,72 @@ 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) + { + if (m_ConflictingDevicesDisabled) + { + SetConflictingDevicesDisabled(false); + m_LastFocusedWindow = null; + } + 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; + + SetConflictingDevicesDisabled(simulatorFocused); + } + + // Exposed internally so tests can drive the focus transition without a live SimulatorWindow. + // OnUpdate itself can't run in a unit test: it needs play mode and a real panel to compare against. + internal void SetConflictingDevicesDisabled(bool disabled) + { + if (disabled == m_ConflictingDevicesDisabled) + return; + + if (disabled) + { + // 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); + } + else + { + 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(); + } + + m_ConflictingDevicesDisabled = disabled; + } + private void DisableConflictingDevice(InputDevice device) { if (device.native && (device is Mouse || device is Pen) && device.enabled) @@ -67,6 +136,10 @@ private void DisableConflictingDevice(InputDevice device) 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 +173,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); - } + + SetConflictingDevicesDisabled(false); + m_RootElement = null; } }