Skip to content

Commit 67515cf

Browse files
author
Rene Damm
authored
CHANGE: Overhaul focus logic to fix problems such as stuck keys on alt-tabbing (case 1206199, #1007).
1 parent 5958185 commit 67515cf

23 files changed

+676
-74
lines changed

Assets/Tests/InputSystem/CoreTests_Actions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ public void Actions_DoNotGetTriggeredByEditorUpdates()
5252
public void Actions_TimeoutsDoNotGetTriggeredInEditorUpdates()
5353
{
5454
var gamepad = InputSystem.AddDevice<Gamepad>();
55+
56+
// Devices get reset when losing focus so when we switch from the player to the editor,
57+
// actions would get cancelled anyway. To avoid, create a device that runs in the background
58+
// and enabled runInBackground.
59+
SetCanRunInBackground(gamepad, true);
60+
runtime.runInBackground = true;
61+
5562
var action = new InputAction(binding: "<Gamepad>/buttonSouth", interactions: "hold");
5663
action.Enable();
5764

Assets/Tests/InputSystem/CoreTests_Controls.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using UnityEngine;
66
using UnityEngine.InputSystem;
77
using UnityEngine.InputSystem.Controls;
8-
using UnityEngine.InputSystem.Layouts;
98
using UnityEngine.InputSystem.LowLevel;
109
using UnityEngine.InputSystem.Processors;
1110
using UnityEngine.InputSystem.Utilities;

Assets/Tests/InputSystem/CoreTests_Devices.cs

Lines changed: 229 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,37 @@ public void Devices_CanTellIfDeviceHasNoisyControls()
791791
Assert.That(device.noisy, Is.True);
792792
}
793793

794+
[Test]
795+
[Category("Devices")]
796+
public unsafe void Devices_NoisyControlsAreToggledOffInNoiseMask()
797+
{
798+
InputSystem.AddDevice<Mouse>(); // Noise.
799+
800+
const string layout = @"
801+
{
802+
""name"" : ""TestDevice"",
803+
""extend"" : ""Gamepad"",
804+
""controls"" : [
805+
{ ""name"" : ""noisyControl"", ""layout"" : ""axis"", ""noisy"" : true }
806+
]
807+
}
808+
";
809+
810+
InputSystem.RegisterLayout(layout);
811+
var device = (Gamepad)InputSystem.AddDevice("TestDevice");
812+
813+
var noiseMaskPtr = (byte*)device.noiseMaskPtr;
814+
815+
const int kNumButtons = 14; // Buttons without left and right trigger which aren't stored in the buttons field.
816+
817+
// All the gamepads buttons should have the flag on as they aren't noise. However, the leftover
818+
// bits in the "buttons" field should be marked as noise as they are not actively used by any control.
819+
Assert.That(*(uint*)(noiseMaskPtr + device.stateBlock.byteOffset), Is.EqualTo((1 << kNumButtons) - 1));
820+
821+
// The noisy control we added should be flagged as noise by having their bits off.
822+
Assert.That(*(uint*)(noiseMaskPtr + device["noisyControl"].stateBlock.byteOffset), Is.Zero);
823+
}
824+
794825
[Test]
795826
[Category("Devices")]
796827
public void Devices_CanLookUpDeviceByItsIdAfterItHasBeenAdded()
@@ -3624,26 +3655,11 @@ public void TODO_Devices_CanHijackEventStreamOfDevice()
36243655
Assert.Fail();
36253656
}
36263657

3627-
//This could be the first step towards being able to simulate input well.
3628-
[Test]
3629-
[Category("Devices")]
3630-
[Ignore("TODO")]
3631-
public void TODO_Devices_CanCreateVirtualDevices()
3632-
{
3633-
//layout has one or more binding paths on controls instead of associated memory
3634-
//sets up state monitors
3635-
//virtual device is of a device type determined by base template (e.g. Gamepad)
3636-
//can associate additional processing logic with controls
3637-
//state changes for virtual devices are accumulated as separate buffer of events that is flushed out in a post-step
3638-
//performed as a loop so virtual devices can feed into other virtual devices
3639-
//virtual devices are marked with flag
3640-
Assert.Fail();
3641-
}
3658+
// NOTE: The focus handling logic will also implicitly take care of canceling and restarting actions.
36423659

3643-
// NOTE: The focus logic will also implicitly take care of canceling and restarting actions.
36443660
[Test]
36453661
[Category("Devices")]
3646-
public unsafe void Devices_WhenFocusChanges_AllConnectedDevicesAreResetOnce()
3662+
public unsafe void Devices_WhenFocusIsLost_DevicesAreForciblyReset()
36473663
{
36483664
var keyboard = InputSystem.AddDevice<Keyboard>();
36493665
var keyboardDeviceReset = false;
@@ -3694,13 +3710,208 @@ public unsafe void Devices_WhenFocusChanges_AllConnectedDevicesAreResetOnce()
36943710
return InputDeviceCommand.GenericFailure;
36953711
});
36963712

3697-
runtime.InvokePlayerFocusChanged(true);
3713+
// Put devices in non-default states.
3714+
Press(keyboard.aKey);
3715+
Press(gamepad.buttonSouth);
3716+
Set(pointer.position, new Vector2(123, 234));
3717+
3718+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.False);
3719+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.False);
3720+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.False);
3721+
3722+
// Focus loss should result in reset.
3723+
runtime.PlayerFocusLost();
36983724

36993725
Assert.That(keyboardDeviceReset, Is.True);
37003726
Assert.That(gamepadDeviceReset, Is.True);
37013727
Assert.That(pointerDeviceReset, Is.True);
3728+
3729+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.True);
3730+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.True);
3731+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.True);
3732+
Assert.That(keyboard.aKey.isPressed, Is.False);
3733+
Assert.That(gamepad.buttonSouth.isPressed, Is.False);
3734+
Assert.That(pointer.position.ReadValue(), Is.EqualTo(default(Vector2)));
3735+
3736+
keyboardDeviceReset = false;
3737+
gamepadDeviceReset = false;
3738+
pointerDeviceReset = false;
3739+
3740+
// Focus gain should not result in a reset.
3741+
runtime.PlayerFocusGained();
3742+
3743+
Assert.That(keyboardDeviceReset, Is.False);
3744+
Assert.That(gamepadDeviceReset, Is.False);
3745+
Assert.That(pointerDeviceReset, Is.False);
3746+
3747+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.True);
3748+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.True);
3749+
Assert.That(keyboard.CheckStateIsAtDefault(), Is.True);
3750+
Assert.That(keyboard.aKey.isPressed, Is.False);
3751+
Assert.That(gamepad.buttonSouth.isPressed, Is.False);
3752+
Assert.That(pointer.position.ReadValue(), Is.EqualTo(default(Vector2)));
3753+
}
3754+
3755+
[Test]
3756+
[Category("Devices")]
3757+
public void Devices_WhenFocusIsLost_DevicesAreForciblyReset_ExceptForNoisyControls()
3758+
{
3759+
InputSystem.AddDevice<Mouse>(); // Noise.
3760+
3761+
const string json = @"
3762+
{
3763+
""name"" : ""NoisyGamepad"",
3764+
""extend"" : ""Gamepad"",
3765+
""controls"" : [
3766+
{ ""name"" : ""noisyControl"", ""noisy"" : true, ""layout"" : ""Axis"" }
3767+
]
3768+
}
3769+
";
3770+
3771+
InputSystem.RegisterLayout(json);
3772+
var gamepad = (Gamepad)InputSystem.AddDevice("NoisyGamepad");
3773+
3774+
Press(gamepad.buttonSouth);
3775+
Set(gamepad.leftStick, new Vector2(123, 234));
3776+
Set((AxisControl)gamepad["noisyControl"], 345.0f);
3777+
3778+
runtime.PlayerFocusLost();
3779+
3780+
Assert.That(gamepad.CheckStateIsAtDefault(), Is.False);
3781+
Assert.That(gamepad.CheckStateIsAtDefaultIgnoringNoise(), Is.True);
3782+
Assert.That(gamepad.buttonSouth.isPressed, Is.False);
3783+
Assert.That(gamepad.leftStick.ReadValue(), Is.EqualTo(default(Vector2)));
3784+
Assert.That(((AxisControl)gamepad["noisyControl"]).ReadValue(), Is.EqualTo(345.0).Within(0.00001));
3785+
}
3786+
3787+
[Test]
3788+
[Category("Devices")]
3789+
public void Devices_WhenFocusIsLost_DevicesAreForciblyReset_AndResetsAreObservableStateChanges()
3790+
{
3791+
var gamepad = InputSystem.AddDevice<Gamepad>();
3792+
3793+
var changeMonitorTriggered = false;
3794+
InputState.AddChangeMonitor(gamepad.buttonSouth,
3795+
(control, d, arg3, arg4) => changeMonitorTriggered = true);
3796+
3797+
Press(gamepad.buttonSouth);
3798+
3799+
Assert.That(changeMonitorTriggered, Is.True);
3800+
3801+
changeMonitorTriggered = false;
3802+
3803+
runtime.PlayerFocusLost();
3804+
3805+
Assert.That(changeMonitorTriggered, Is.True);
37023806
}
37033807

3808+
[Test]
3809+
[Category("Devices")]
3810+
public unsafe void Devices_WhenFocusIsLost_DevicesAreForciblyReset_ExceptThoseMarkedAsReceivingInputInBackground()
3811+
{
3812+
// TrackedDevice is all noisy controls. We need at least one non-noisy control to fully
3813+
// observe the behavior, so create a layout based on TrackedDevice that adds a button.
3814+
const string json = @"
3815+
{
3816+
""name"" : ""TestTrackedDevice"",
3817+
""extend"" : ""TrackedDevice"",
3818+
""controls"" : [
3819+
{ ""name"" : ""Button"", ""layout"" : ""Button"" }
3820+
]
3821+
}
3822+
";
3823+
3824+
InputSystem.RegisterLayout(json);
3825+
var trackedDevice = (TrackedDevice)InputSystem.AddDevice("TestTrackedDevice");
3826+
3827+
var receivedResetRequest = false;
3828+
runtime.SetDeviceCommandCallback(trackedDevice,
3829+
(id, commandPtr) =>
3830+
{
3831+
// IOCTL to return true from QueryCanRunInBackground.
3832+
if (commandPtr->type == QueryCanRunInBackground.Type)
3833+
{
3834+
((QueryCanRunInBackground*)commandPtr)->canRunInBackground = true;
3835+
return InputDeviceCommand.GenericSuccess;
3836+
}
3837+
if (commandPtr->type == RequestResetCommand.Type)
3838+
{
3839+
receivedResetRequest = true;
3840+
// We still fail it as we don't actually reset.
3841+
return InputDeviceCommand.GenericFailure;
3842+
}
3843+
3844+
return InputDeviceCommand.GenericFailure;
3845+
});
3846+
3847+
Set(trackedDevice.devicePosition, new Vector3(123, 234, 345));
3848+
Press((ButtonControl)trackedDevice["Button"]);
3849+
3850+
// First, do it without run-in-background being enabled. This should actually lead to
3851+
// a reset of the device even though run-in-background is enabled on the device itself.
3852+
// However, since the app as a whole is not set to run in the background, we still force
3853+
// a reset.
3854+
runtime.runInBackground = false;
3855+
3856+
runtime.PlayerFocusLost();
3857+
3858+
Assert.That(receivedResetRequest, Is.True);
3859+
Assert.That(trackedDevice.CheckStateIsAtDefault(), Is.False);
3860+
Assert.That(trackedDevice.CheckStateIsAtDefaultIgnoringNoise(), Is.True);
3861+
Assert.That(trackedDevice.devicePosition.ReadValue(),
3862+
Is.EqualTo(new Vector3(123, 234, 345)).Using(Vector3EqualityComparer.Instance));
3863+
Assert.That(((ButtonControl)trackedDevice["Button"]).ReadValue(), Is.Zero);
3864+
3865+
runtime.PlayerFocusGained();
3866+
receivedResetRequest = false;
3867+
3868+
// Next, do the same all over with run-in-background enabled. Now, the device shouldn't reset at all.
3869+
runtime.runInBackground = true;
3870+
Press((ButtonControl)trackedDevice["Button"]);
3871+
runtime.PlayerFocusLost();
3872+
3873+
Assert.That(receivedResetRequest, Is.False);
3874+
Assert.That(trackedDevice.CheckStateIsAtDefault(), Is.False);
3875+
Assert.That(trackedDevice.CheckStateIsAtDefaultIgnoringNoise(), Is.False);
3876+
Assert.That(trackedDevice.devicePosition.ReadValue(),
3877+
Is.EqualTo(new Vector3(123, 234, 345)).Using(Vector3EqualityComparer.Instance));
3878+
Assert.That(((ButtonControl)trackedDevice["Button"]).isPressed, Is.True);
3879+
}
3880+
3881+
[Test]
3882+
[Category("Devices")]
3883+
public void Devices_WhenFocusIsLost_OngoingTouchesGetCancelled()
3884+
{
3885+
var touchscreen = InputSystem.AddDevice<Touchscreen>();
3886+
3887+
BeginTouch(1, new Vector2(123, 234));
3888+
BeginTouch(2, new Vector2(234, 345));
3889+
3890+
Assert.That(touchscreen.primaryTouch.isInProgress, Is.True);
3891+
Assert.That(touchscreen.touches[0].isInProgress, Is.True);
3892+
Assert.That(touchscreen.touches[1].isInProgress, Is.True);
3893+
3894+
runtime.PlayerFocusLost();
3895+
3896+
Assert.That(touchscreen.primaryTouch.isInProgress, Is.False);
3897+
Assert.That(touchscreen.touches[0].isInProgress, Is.False);
3898+
Assert.That(touchscreen.touches[1].isInProgress, Is.False);
3899+
}
3900+
3901+
// Alt-tabbing is only relevant on Windows (Mac uses system key for the same command instead of an application key).
3902+
////TODO: investigate relevance on Linux
3903+
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
3904+
[Test]
3905+
[Category("Devices")]
3906+
[Ignore("TODO")]
3907+
public void TODO_Devices_AltTabbingDoesNOTAlterKeyboardState()
3908+
{
3909+
////TODO: add support for explicitly suppressing alt-tab, if enabled
3910+
Assert.Fail();
3911+
}
3912+
3913+
#endif
3914+
37043915
[Test]
37053916
[Category("Devices")]
37063917
public void Devices_CanListenForIMECompositionEvents()

Assets/Tests/InputSystem/CoreTests_State.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -644,10 +644,7 @@ public StateWithMultiBitControl WithData(int value)
644644
return this;
645645
}
646646

647-
public FourCC format
648-
{
649-
get { return new FourCC('T', 'E', 'S', 'T'); }
650-
}
647+
public FourCC format => new FourCC('T', 'E', 'S', 'T');
651648
}
652649

653650
[InputControlLayout(stateType = typeof(StateWithMultiBitControl))]

Assets/Tests/InputSystem/Plugins/EnhancedTouchTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ public void EnhancedTouch_SupportsInputUpdateIn(InputSettings.UpdateMode updateM
128128
[TestCase(InputSettings.UpdateMode.ProcessEventsInFixedUpdate)]
129129
public void EnhancedTouch_SupportsEditorUpdates(InputSettings.UpdateMode updateMode)
130130
{
131+
// To better observe that play mode and edit mode state is indeed independent and handled
132+
// correctly, suppress resetting of the touch device when focus is lost to the player.
133+
runtime.runInBackground = true;
134+
SetCanRunInBackground(Touchscreen.current);
135+
131136
InputEditorUserSettings.lockInputToGameView = false;
132137
InputSystem.settings.updateMode = updateMode;
133138
runtime.currentTimeForFixedUpdate += Time.fixedDeltaTime;

0 commit comments

Comments
 (0)