Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions Assets/Tests/InputSystem/CoreTests_Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5895,4 +5895,163 @@ public unsafe void Devices_DoesntErrorOutOnMaxTouchCount()
BeginTouch(i, new Vector2(i * 1.0f, i * 2.0f), time: 0);
}, Throws.Nothing);
}

#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES
private unsafe void AnswerCapabilityQuery(FourCC type, InputCapabilitySupport answer)
{
runtime.SetDeviceCommandCallback(NativeInputCapabilities.systemDeviceId,
(id, command) =>
{
if (command->type != type)
return InputDeviceCommand.GenericFailure;

*(InputCapabilitySupport*)((byte*)command + InputDeviceCommand.kBaseCommandSize) = answer;
return InputDeviceCommand.GenericSuccess;
});
}

[Test]
[Category("Devices")]
[TestCase(InputCapabilitySupport.Supported, true)]
[TestCase(InputCapabilitySupport.NotSupported, false)]
// Unknown collapses to false: the platform has not answered, and a maybe is not something a
// bool property can express.
[TestCase(InputCapabilitySupport.Unknown, false)]
public void Devices_PenIsSupported_ReflectsWhatThePlatformAnswers(InputCapabilitySupport answer, bool expected)
{
AnswerCapabilityQuery(QueryPenSupportedCommand.Type, answer);

Assert.That(Pen.isSupported, Is.EqualTo(expected));
}

[Test]
[Category("Devices")]
[TestCase(InputCapabilitySupport.Supported, true)]
[TestCase(InputCapabilitySupport.NotSupported, false)]
[TestCase(InputCapabilitySupport.Unknown, false)]
public void Devices_MouseIsSupported_ReflectsWhatThePlatformAnswers(InputCapabilitySupport answer, bool expected)
{
AnswerCapabilityQuery(QueryMouseSupportedCommand.Type, answer);

Assert.That(Mouse.isSupported, Is.EqualTo(expected));
}

[Test]
[Category("Devices")]
[TestCase(InputCapabilitySupport.Supported, true)]
[TestCase(InputCapabilitySupport.NotSupported, false)]
[TestCase(InputCapabilitySupport.Unknown, false)]
public void Devices_TouchscreenIsPressureSupported_ReflectsWhatThePlatformAnswers(InputCapabilitySupport answer, bool expected)
{
AnswerCapabilityQuery(QueryTouchPressureSupportedCommand.Type, answer);

Assert.That(Touchscreen.isPressureSupported, Is.EqualTo(expected));
}

// The properties describe the platform, not a device, so they must answer without one. This is
// the case that separates them from Device.current != null.
[Test]
[Category("Devices")]
public void Devices_CapabilityQueries_AreAnsweredWithNoDeviceAdded()
{
AnswerCapabilityQuery(QueryPenSupportedCommand.Type, InputCapabilitySupport.Supported);

Assert.That(InputSystem.devices, Is.Empty);
Assert.That(Pen.isSupported, Is.True);
Assert.That(Pen.current, Is.Null);
}

// The endpoint is addressed by a reserved id. A capability query must not be delivered to a
// real device, which would let a device answer a question about the platform.
[Test]
[Category("Devices")]
public unsafe void Devices_CapabilityQueries_AreNotDeliveredToDevices()
{
var pen = InputSystem.AddDevice<Pen>();
var receivedByDevice = 0;
runtime.SetDeviceCommandCallback(pen,
(id, command) =>
{
if (command->type == QueryPenSupportedCommand.Type)
++receivedByDevice;
return InputDeviceCommand.GenericFailure;
});
AnswerCapabilityQuery(QueryPenSupportedCommand.Type, InputCapabilitySupport.Supported);

Assert.That(Pen.isSupported, Is.True);
Assert.That(receivedByDevice, Is.Zero);
}

// A platform capability cannot change while the application runs, so reading the property
// repeatedly must not keep issuing commands.
[Test]
[Category("Devices")]
public unsafe void Devices_CapabilityQueries_AreOnlyIssuedOnce()
{
var queryCount = 0;
runtime.SetDeviceCommandCallback(NativeInputCapabilities.systemDeviceId,
(id, command) =>
{
if (command->type != QueryPenSupportedCommand.Type)
return InputDeviceCommand.GenericFailure;

++queryCount;
*(InputCapabilitySupport*)((byte*)command + InputDeviceCommand.kBaseCommandSize) =
InputCapabilitySupport.Supported;
return InputDeviceCommand.GenericSuccess;
});

Assert.That(Pen.isSupported, Is.True);
Assert.That(Pen.isSupported, Is.True);
Assert.That(Pen.isSupported, Is.True);

Assert.That(queryCount, Is.EqualTo(1));
}

// Nothing answers, which is what an engine without the endpoint looks like. The property must
// report false rather than throwing, and must not retry on every read.
[Test]
[Category("Devices")]
public void Devices_CapabilityQueries_ReportFalseWhenNothingAnswers()
{
Assert.That(Pen.isSupported, Is.False);
Assert.That(Mouse.isSupported, Is.False);
Assert.That(Touchscreen.isPressureSupported, Is.False);
}

// Nothing generates the mirror of the engine's enum, and a reordering would silently invert
// Supported and NotSupported across the boundary. The engine pins the same values from its side.
[Test]
[Category("Devices")]
public void Devices_CapabilitySupport_MatchesTheEngineWireValues()
{
Assert.That((byte)InputCapabilitySupport.Unknown, Is.EqualTo((byte)CapabilityState.Unknown));
Assert.That((byte)InputCapabilitySupport.NotSupported, Is.EqualTo((byte)CapabilityState.NotSupported));
Assert.That((byte)InputCapabilitySupport.Supported, Is.EqualTo((byte)CapabilityState.Supported));
}

// Same reasoning for the codes: the package spells them as FourCC characters, matching every
// other command in the Commands folder, while the engine declares them as integer constants.
[Test]
[Category("Devices")]
public void Devices_CapabilityQueryCodes_MatchTheEngineCodes()
{
Assert.That((int)QueryPenSupportedCommand.Type, Is.EqualTo(NativeInputCapabilities.queryPenSupported));
Assert.That((int)QueryMouseSupportedCommand.Type, Is.EqualTo(NativeInputCapabilities.queryMouseSupported));
Assert.That((int)QueryTouchPressureSupportedCommand.Type,
Is.EqualTo(NativeInputCapabilities.queryTouchPressureSupported));
}

// The payload the package sends must be exactly the one byte the engine's payload validation
// accepts. The base command header is stripped before it reaches native.
[Test]
[Category("Devices")]
public void Devices_CapabilityQueryPayload_IsOneByteAfterTheCommandHeader()
{
Assert.That(QueryPenSupportedCommand.kSize - InputDeviceCommand.kBaseCommandSize, Is.EqualTo(1));
Assert.That(QueryMouseSupportedCommand.kSize - InputDeviceCommand.kBaseCommandSize, Is.EqualTo(1));
Assert.That(QueryTouchPressureSupportedCommand.kSize - InputDeviceCommand.kBaseCommandSize, Is.EqualTo(1));
}

#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES
}
5 changes: 5 additions & 0 deletions Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@
"name": "Unity",
"expression": "6000.5.0a8",
"define": "UNITY_INPUTSYSTEM_SUPPORTS_FOCUS_EVENTS"
},
{
"name": "Unity",
"expression": "6000.7.0a6",
"define": "UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES"
}
],
"noEngineReferences": false
Expand Down
4 changes: 4 additions & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased] - yyyy-mm-dd

### Added

- Added `Mouse.isSupported`, `Pen.isSupported` and `Touchscreen.isPressureSupported`, which report what the current platform is capable of rather than which devices are connected. Refer to [Corresponding old and new APIs](xref:input-system-old-new-apis). [ISX-2046] [ISX-2079]

### Fixed

- Fixed the Inspector help button for a selected `.inputactions` asset ("Open Reference for Input Action Importer") opening a missing documentation page; it now links to the Action Assets manual page [UUM-149518](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-149518)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,52 @@ Directly reading hardware controls bypasses the new Input System's action-based
[`Input.imeIsSelected`](https://docs.unity3d.com/ScriptReference/Input-imeIsSelected.html)|Use: [`Keyboard.current.imeSelected`](xref:UnityEngine.InputSystem.Keyboard)
[`Input.inputString`](https://docs.unity3d.com/ScriptReference/Input-inputString.html)|Subscribe to the [`Keyboard.onTextInput`](xref:UnityEngine.InputSystem.Keyboard) event:<br/>`Keyboard.current.onTextInput += character => /* ... */;`

## Device capability and device availability

Several Input Manager properties, such as [`Input.mousePresent`](https://docs.unity3d.com/ScriptReference/Input-mousePresent.html) and
[`Input.stylusTouchSupported`](https://docs.unity3d.com/ScriptReference/Input-stylusTouchSupported.html), answered
two questions at once, and answered them differently depending on the platform. On some platforms they were a
hardcoded constant meaning roughly "this platform has this kind of device", and on others they performed real
hardware detection.

The new Input System separates the two:

- **Does this platform support this kind of input at all?**
Use the capability properties: [`Mouse.isSupported`](xref:UnityEngine.InputSystem.Mouse),
[`Pen.isSupported`](xref:UnityEngine.InputSystem.Pen) and
[`Touchscreen.isPressureSupported`](xref:UnityEngine.InputSystem.Touchscreen). These don't change while the
application runs, so read them once and decide whether to offer device-specific functionality.
- **Can a device deliver input right now?**
Use `Device.current != null && Device.current.enabled`. A non-null `current` only means a device object is
registered, which some platforms do whether or not hardware is attached, and
[`enabled`](xref:UnityEngine.InputSystem.InputDevice) is what tells you the device delivers input. The Device
Simulator shows the difference: while simulating a touch device it disables the native mouse and pen without
removing them, so `current` stays non-null while `enabled` becomes false.

Read `current` each time rather than caching a device reference. Removing a device doesn't disable it, so a device
that has been removed still reports `enabled` as `true`. A cached reference therefore needs
[`added`](xref:UnityEngine.InputSystem.InputDevice) as well:

```csharp
// Cached once, for example in a field holding the pad assigned to a player.
var gamepad = Gamepad.current;

// The pad is then unplugged, so the Input System removes the device.
Debug.Log(gamepad.enabled); // True. Removing a device does not disable it.
Debug.Log(gamepad.added); // False. It is no longer in InputSystem.devices.

// So a cached reference needs both checks, where reading current needs only enabled.
if (gamepad.added && gamepad.enabled)
Debug.Log(gamepad.leftStick.ReadValue());
```

Reading `current` at the point of use avoids this, because removing a device resets `current` to `null`.

The capability properties answer a different question from the Input Manager properties they replace, so the two
can report different values. On platforms where an Input Manager property performed real hardware detection, the
capability property reports what the platform supports instead, which can be `true` where the old property was
`false`. The tables below note where this applies.

## Mouse

`MonoBehaviour.OnMouse` events, such as [MonoBehaviour.OnMouseDown](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseDown.html), are supported in Unity 6.4 and later.
Expand All @@ -77,19 +123,19 @@ Directly reading hardware controls bypasses the new Input System's action-based
[`Input.GetMouseButtonDown`](https://docs.unity3d.com/ScriptReference/Input.GetMouseButtonDown.html)<br/>Example: `Input.GetMouseButtonDown(0)`|Use [`wasPressedThisFrame`](xref:UnityEngine.InputSystem.Controls.ButtonControl) on the corresponding mouse button.<br/>Example: `InputSystem.Mouse.current.leftButton.wasPressedThisFrame`
[`Input.GetMouseButtonUp`](https://docs.unity3d.com/ScriptReference/Input.GetMouseButtonUp.html)<br/>Example: `Input.GetMouseButtonUp(0)`|Use [`wasReleasedThisFrame`](xref:UnityEngine.InputSystem.Controls.ButtonControl) on the corresponding mouse button.<br/>Example: `InputSystem.Mouse.current.leftButton.wasReleasedThisFrame`
[`Input.mousePosition`](https://docs.unity3d.com/ScriptReference/Input-mousePosition.html)|Use [`Mouse.current.position.ReadValue()`](xref:UnityEngine.InputSystem.Mouse)<br/>Example: `Vector2 position = Mouse.current.position.ReadValue();`<br/> **Note:** Mouse simulation from touch isn't implemented yet.
[`Input.mousePresent`](https://docs.unity3d.com/ScriptReference/Input-mousePresent.html)|No corresponding API yet.
[`Input.mousePresent`](https://docs.unity3d.com/ScriptReference/Input-mousePresent.html)|Use [`Mouse.isSupported`](xref:UnityEngine.InputSystem.Mouse) to check whether the platform supports mouse input at all.<br/>Example: `if (Mouse.isSupported) ShowMouseSettings();`<br/>**Note:** Answers a different question from the Input Manager property. Refer to [Device capability and device availability](#device-capability-and-device-availability). Input System does not currently deliver mouse input on iOS, iPadOS or visionOS, so `Mouse.isSupported` is `false` there even though the platform itself supports indirect mice. Requires a recent Editor version.

## Touch and Pen

|Input Manager (Old)|Input System (New)|
|--|--|
[`Input.GetTouch`](https://docs.unity3d.com/ScriptReference/Input.GetTouch.html)<br/>For example:<br/>`Touch touch = Input.GetTouch(0);`<br/>`Vector2 touchPos = touch.position;`|Use [`EnhancedTouch.Touch.activeTouches[i]`](xref:UnityEngine.InputSystem.EnhancedTouch.Touch)<br/>Example: `Vector2 touchPos = EnhancedTouch.Touch.activeTouches[0].position;`<br/> **Note:** Enable enhanced touch support first by calling [`EnhancedTouch.Enable()`](xref:UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport).
[`Input.multiTouchEnabled`](https://docs.unity3d.com/ScriptReference/Input-multiTouchEnabled.html)|No corresponding API yet.
[`Input.multiTouchEnabled`](https://docs.unity3d.com/ScriptReference/Input-multiTouchEnabled.html)|There is no direct equivalent, because this is a setting rather than a hardware capability. To get the same first-touch-wins behaviour, read [`primaryTouch`](xref:UnityEngine.InputSystem.Touchscreen) instead of iterating all touches, or bind to `<Touchscreen>/primaryTouch`.<br/>Example: `if (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.isPressed)`<br/>**Note:** Two differences from setting `Input.multiTouchEnabled = false`. First, `primaryTouch` filters only itself: [`touches`](xref:UnityEngine.InputSystem.Touchscreen), the `<Touchscreen>/touch*` bindings and [`EnhancedTouch`](xref:UnityEngine.InputSystem.EnhancedTouch.Touch) still report every finger, whereas the legacy setting suppressed additional touches globally. Second, when the finger that started the primary touch lifts while other fingers are still down, the primary touch is retained rather than ended until the last finger is released, so a control bound to it stays actuated in the meantime.
[`Input.simulateMouseWithTouches`](https://docs.unity3d.com/ScriptReference/Input-multiTouchEnabled.html)|No corresponding API yet.
[`Input.stylusTouchSupported`](https://docs.unity3d.com/ScriptReference/Input-stylusTouchSupported.html)|No corresponding API yet.
[`Input.stylusTouchSupported`](https://docs.unity3d.com/ScriptReference/Input-stylusTouchSupported.html)|Use [`Pen.isSupported`](xref:UnityEngine.InputSystem.Pen) to check whether the platform supports pen input at all.<br/>Example: `if (Pen.isSupported) ShowPenSettings();`<br/>**Note:** Answers a different question from the Input Manager property. Refer to [Device capability and device availability](#device-capability-and-device-availability). Requires a recent Editor version.
[`Input.touchCount`](https://docs.unity3d.com/ScriptReference/Input-touchCount.html)|[`EnhancedTouch.Touch.activeTouches.Count`](xref:UnityEngine.InputSystem.EnhancedTouch.Touch)<br/> **Note:** Enable enhanced touch support first by calling [`EnhancedTouchSupport.Enable()`](xref:UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport)
[`Input.touches`](https://docs.unity3d.com/scriptreference/input-touches.html)|[`EnhancedTouch.Touch.activeTouches`](xref:UnityEngine.InputSystem.EnhancedTouch.Touch)<br/> **Note:** Enable enhanced touch support first by calling [`EnhancedTouch.Enable()`](xref:UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport)
[`Input.touchPressureSupported`](https://docs.unity3d.com/ScriptReference/Input-touchPressureSupported.html)|No corresponding API yet.
[`Input.touchPressureSupported`](https://docs.unity3d.com/ScriptReference/Input-touchPressureSupported.html)|Use [`Touchscreen.isPressureSupported`](xref:UnityEngine.InputSystem.Touchscreen) to check whether the platform delivers a real pressure value with touch input.<br/>Example: `if (Touchscreen.isPressureSupported) UsePressureForBrushWidth();`<br/>**Note:** When this is `false`, [`pressure`](xref:UnityEngine.InputSystem.Controls.TouchControl) reports a constant `1` while a finger is down rather than a measured value. This is a platform-wide answer rather than a per-device one. Requires a recent Editor version.
[`Input.touchSupported`](https://docs.unity3d.com/ScriptReference/Input-touchSupported.html)|[`Touchscreen.current != null`](xref:UnityEngine.InputSystem.Touchscreen)
[`Input.backButtonLeavesApp`](https://docs.unity3d.com/ScriptReference/Input-backButtonLeavesApp.html)|No corresponding API yet.
[`GetPenEvent`](https://docs.unity3d.com/ScriptReference/Input.GetPenEvent.html)<br/>[`GetLastPenContactEvent`](https://docs.unity3d.com/ScriptReference/Input.GetLastPenContactEvent.html)<br/>[`ResetPenEvents`](https://docs.unity3d.com/ScriptReference/Input.ResetPenEvents.html)<br/>[`ClearLastPenContactEvent`](https://docs.unity3d.com/ScriptReference/Input.ClearLastPenContactEvent.html)|Use: [`Pen.current`](xref:UnityEngine.InputSystem.Pen)<br/>See the [Pen, tablet and stylus support](devices-pen.md) docs for more information.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES
namespace UnityEngine.InputSystem.LowLevel
{
/// <summary>
/// Answer to a platform capability query, meaning what the platform can deliver rather than
/// what is currently connected.
/// </summary>
/// <remarks>
/// Mirrors the engine's <c>CapabilityState</c>, whose wire values are pinned by tests on both
/// sides. <see cref="Unknown"/> is zero so that an unwritten payload, or a platform that has not
/// implemented a query, reads as "we do not know" rather than as a confident
/// <see cref="NotSupported"/>.
///
/// The value space is open. Treat anything other than <see cref="Supported"/> as not supported
/// rather than rejecting it, because a newer engine may answer with a value this version of the
/// package does not know about.
/// </remarks>
internal enum InputCapabilitySupport : byte
{
/// <summary>
/// The platform has no answer, typically because it has not implemented the query yet.
/// </summary>
Unknown = 0,

/// <summary>
/// The platform definitively cannot deliver it.
/// </summary>
NotSupported = 1,

/// <summary>
/// The platform definitively can deliver it.
/// </summary>
Supported = 2
}
}
#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading