@@ -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 ( )
0 commit comments