diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index ed90efe605..ca0932c8f2 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -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(); + 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 } diff --git a/Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef b/Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef index 7c7a31cd0a..cf1d4ef1b2 100644 --- a/Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef +++ b/Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef @@ -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 diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 9a6d3514da..0bd9242b03 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -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) diff --git a/Packages/com.unity.inputsystem/Documentation~/corresponding-old-new-api.md b/Packages/com.unity.inputsystem/Documentation~/corresponding-old-new-api.md index dcad91390f..3a2817294d 100644 --- a/Packages/com.unity.inputsystem/Documentation~/corresponding-old-new-api.md +++ b/Packages/com.unity.inputsystem/Documentation~/corresponding-old-new-api.md @@ -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:
`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. @@ -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)
Example: `Input.GetMouseButtonDown(0)`|Use [`wasPressedThisFrame`](xref:UnityEngine.InputSystem.Controls.ButtonControl) on the corresponding mouse button.
Example: `InputSystem.Mouse.current.leftButton.wasPressedThisFrame` [`Input.GetMouseButtonUp`](https://docs.unity3d.com/ScriptReference/Input.GetMouseButtonUp.html)
Example: `Input.GetMouseButtonUp(0)`|Use [`wasReleasedThisFrame`](xref:UnityEngine.InputSystem.Controls.ButtonControl) on the corresponding mouse button.
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)
Example: `Vector2 position = Mouse.current.position.ReadValue();`
**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.
Example: `if (Mouse.isSupported) ShowMouseSettings();`
**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)
For example:
`Touch touch = Input.GetTouch(0);`
`Vector2 touchPos = touch.position;`|Use [`EnhancedTouch.Touch.activeTouches[i]`](xref:UnityEngine.InputSystem.EnhancedTouch.Touch)
Example: `Vector2 touchPos = EnhancedTouch.Touch.activeTouches[0].position;`
**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 `/primaryTouch`.
Example: `if (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.isPressed)`
**Note:** Two differences from setting `Input.multiTouchEnabled = false`. First, `primaryTouch` filters only itself: [`touches`](xref:UnityEngine.InputSystem.Touchscreen), the `/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.
Example: `if (Pen.isSupported) ShowPenSettings();`
**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)
**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)
**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.
Example: `if (Touchscreen.isPressureSupported) UsePressureForBrushWidth();`
**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)
[`GetLastPenContactEvent`](https://docs.unity3d.com/ScriptReference/Input.GetLastPenContactEvent.html)
[`ResetPenEvents`](https://docs.unity3d.com/ScriptReference/Input.ResetPenEvents.html)
[`ClearLastPenContactEvent`](https://docs.unity3d.com/ScriptReference/Input.ClearLastPenContactEvent.html)|Use: [`Pen.current`](xref:UnityEngine.InputSystem.Pen)
See the [Pen, tablet and stylus support](devices-pen.md) docs for more information. diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/InputCapabilitySupport.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/InputCapabilitySupport.cs new file mode 100644 index 0000000000..ba81c91eab --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/InputCapabilitySupport.cs @@ -0,0 +1,36 @@ +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES +namespace UnityEngine.InputSystem.LowLevel +{ + /// + /// Answer to a platform capability query, meaning what the platform can deliver rather than + /// what is currently connected. + /// + /// + /// Mirrors the engine's CapabilityState, whose wire values are pinned by tests on both + /// sides. 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 + /// . + /// + /// The value space is open. Treat anything other than 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. + /// + internal enum InputCapabilitySupport : byte + { + /// + /// The platform has no answer, typically because it has not implemented the query yet. + /// + Unknown = 0, + + /// + /// The platform definitively cannot deliver it. + /// + NotSupported = 1, + + /// + /// The platform definitively can deliver it. + /// + Supported = 2 + } +} +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/InputCapabilitySupport.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/InputCapabilitySupport.cs.meta new file mode 100644 index 0000000000..a929bf2129 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/InputCapabilitySupport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02d29d523ce34539874c1f75cf4e7093 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryMouseSupportedCommand.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryMouseSupportedCommand.cs new file mode 100644 index 0000000000..bd67c63312 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryMouseSupportedCommand.cs @@ -0,0 +1,45 @@ +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES +using System.Runtime.InteropServices; +using UnityEngine.InputSystem.Utilities; + +namespace UnityEngine.InputSystem.LowLevel +{ + /// + /// Queries whether the platform can deliver mouse input at all, as opposed to whether a mouse is + /// connected right now. + /// + /// + /// Addressed to the engine's system endpoint rather than to a device, so it is sent through + /// rather than + /// . + /// + /// The FourCC must match kInputFourCCIOCTLQueryMouseSupported in the engine's + /// input module. + /// + /// + [StructLayout(LayoutKind.Explicit, Size = kSize)] + internal struct QueryMouseSupportedCommand : IInputDeviceCommandInfo + { + public static FourCC Type => new FourCC('Q', 'M', 'O', 'U'); + + internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(byte); + + [FieldOffset(0)] + public InputDeviceCommand baseCommand; + + [FieldOffset(InputDeviceCommand.kBaseCommandSize)] + public InputCapabilitySupport isSupported; + + public FourCC typeStatic => Type; + + public static QueryMouseSupportedCommand Create() + { + return new QueryMouseSupportedCommand + { + baseCommand = new InputDeviceCommand(Type, kSize), + isSupported = InputCapabilitySupport.Unknown + }; + } + } +} +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryMouseSupportedCommand.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryMouseSupportedCommand.cs.meta new file mode 100644 index 0000000000..3990362cc1 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryMouseSupportedCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e57d51102344b9ba77c44776fb91b3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryPenSupportedCommand.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryPenSupportedCommand.cs new file mode 100644 index 0000000000..1267228799 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryPenSupportedCommand.cs @@ -0,0 +1,45 @@ +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES +using System.Runtime.InteropServices; +using UnityEngine.InputSystem.Utilities; + +namespace UnityEngine.InputSystem.LowLevel +{ + /// + /// Queries whether the platform can deliver pen input at all, as opposed to whether a pen is + /// connected right now. + /// + /// + /// Addressed to the engine's system endpoint rather than to a device, so it is sent through + /// rather than + /// . + /// + /// The FourCC must match kInputFourCCIOCTLQueryPenSupported in the engine's + /// input module. + /// + /// + [StructLayout(LayoutKind.Explicit, Size = kSize)] + internal struct QueryPenSupportedCommand : IInputDeviceCommandInfo + { + public static FourCC Type => new FourCC('Q', 'P', 'E', 'N'); + + internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(byte); + + [FieldOffset(0)] + public InputDeviceCommand baseCommand; + + [FieldOffset(InputDeviceCommand.kBaseCommandSize)] + public InputCapabilitySupport isSupported; + + public FourCC typeStatic => Type; + + public static QueryPenSupportedCommand Create() + { + return new QueryPenSupportedCommand + { + baseCommand = new InputDeviceCommand(Type, kSize), + isSupported = InputCapabilitySupport.Unknown + }; + } + } +} +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryPenSupportedCommand.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryPenSupportedCommand.cs.meta new file mode 100644 index 0000000000..4bc5f97fca --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryPenSupportedCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37e5432c8c304ccf8ff9d9ee9d2f5945 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryTouchPressureSupportedCommand.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryTouchPressureSupportedCommand.cs new file mode 100644 index 0000000000..e510d28162 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryTouchPressureSupportedCommand.cs @@ -0,0 +1,45 @@ +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES +using System.Runtime.InteropServices; +using UnityEngine.InputSystem.Utilities; + +namespace UnityEngine.InputSystem.LowLevel +{ + /// + /// Queries whether the platform delivers a real pressure value with touch input, as opposed to + /// the constant 1.0 reported by platforms that have touch but no pressure. + /// + /// + /// Addressed to the engine's system endpoint rather than to a device, so it is sent through + /// rather than + /// . + /// + /// The FourCC must match kInputFourCCIOCTLQueryTouchPressureSupported in the engine's + /// input module. + /// + /// + [StructLayout(LayoutKind.Explicit, Size = kSize)] + internal struct QueryTouchPressureSupportedCommand : IInputDeviceCommandInfo + { + public static FourCC Type => new FourCC('Q', 'T', 'P', 'S'); + + internal const int kSize = InputDeviceCommand.kBaseCommandSize + sizeof(byte); + + [FieldOffset(0)] + public InputDeviceCommand baseCommand; + + [FieldOffset(InputDeviceCommand.kBaseCommandSize)] + public InputCapabilitySupport isSupported; + + public FourCC typeStatic => Type; + + public static QueryTouchPressureSupportedCommand Create() + { + return new QueryTouchPressureSupportedCommand + { + baseCommand = new InputDeviceCommand(Type, kSize), + isSupported = InputCapabilitySupport.Unknown + }; + } + } +} +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryTouchPressureSupportedCommand.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryTouchPressureSupportedCommand.cs.meta new file mode 100644 index 0000000000..a874646728 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Commands/QueryTouchPressureSupportedCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e7f6f16acbb4932bcd1cd9a52ccf843 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Mouse.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Mouse.cs index 7cbfd52515..5ce28744c8 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Mouse.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Mouse.cs @@ -257,6 +257,39 @@ public class Mouse : Pointer, IInputStateCallbackReceiver /// public new static Mouse current { get; private set; } +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// + /// Whether the current platform can deliver mouse input at all, regardless of whether a + /// mouse is connected right now. + /// + /// + /// Use this to decide whether to offer mouse-specific functionality, such as a sensitivity + /// setting. It is true on a platform where a mouse can work even when none is connected, and + /// it doesn't change while the application runs. + /// + /// To find out whether a mouse is connected and delivering input, use + /// and instead. + /// + /// A false value means the platform doesn't support mouse input, or that it couldn't determine + /// an answer. The two cases aren't distinguished. + /// + /// + /// + /// + /// // True on a desktop platform with no mouse plugged in. + /// if (Mouse.isSupported) + /// ShowMouseSensitivitySetting(); + /// + /// if (Mouse.current != null && Mouse.current.enabled) + /// Debug.Log(Mouse.current.position.ReadValue()); + /// + /// + /// + /// + /// + public static bool isSupported => InputSystem.manager.IsMouseSupported(); +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// /// Called when the mouse becomes the current mouse. /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Pen.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Pen.cs index 9c0f8e5a07..47828bec52 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Pen.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Pen.cs @@ -322,6 +322,54 @@ public class Pen : Pointer /// public new static Pen current { get; internal set; } +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// + /// Whether the current platform can deliver pen input at all, regardless of whether a pen is + /// connected right now. + /// + /// + /// Use this to decide whether to offer pen-specific functionality, such as a pressure or tilt + /// setting. It is true on a platform where a pen can work even when none is connected, and it + /// doesn't change while the application runs. + /// + /// To find out whether a pen is connected and delivering input, use and + /// instead. + /// + /// A false value means the platform doesn't support pen input, or that it couldn't determine an + /// answer. The two cases aren't distinguished. + /// + /// + /// + /// + /// using UnityEngine; + /// using UnityEngine.InputSystem; + /// + /// public class ExampleScript : MonoBehaviour + /// { + /// private bool m_ShowPenSettings; + /// + /// void Start() + /// { + /// // True on a platform that can deliver pen input, even when no pen is connected yet. + /// m_ShowPenSettings = Pen.isSupported; + /// } + /// + /// void Update() + /// { + /// if (Pen.current != null && Pen.current.enabled && Pen.current.tip.wasPressedThisFrame) + /// { + /// // handle the pen tip being pressed + /// } + /// } + /// } + /// + /// + /// + /// + /// + public static bool isSupported => InputSystem.manager.IsPenSupported(); +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// /// Return the given pen button. /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Touchscreen.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Touchscreen.cs index 7a82e6a177..33cba6fbea 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Touchscreen.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Touchscreen.cs @@ -520,6 +520,42 @@ protected TouchControl[] touchControlArray /// Current touch screen. public new static Touchscreen current { get; internal set; } +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// + /// Whether the current platform delivers a real pressure value with touch input. + /// + /// + /// Use this to decide whether to treat as an analog signal. + /// When it is false, reports a constant 1 while a finger is + /// down instead of a measured value. It doesn't change while the application runs. + /// + /// The answer covers the platform, not an individual touchscreen. Platforms with no touchscreen + /// at all also report false. + /// + /// A false value means the platform doesn't deliver touch pressure, or that it couldn't + /// determine an answer. The two cases aren't distinguished. On some platforms the OS supplies a + /// pressure value whether or not the attached digitizer measures one, and there is no per-device + /// query to tell a real reading from a constant. + /// + /// To find out whether a touchscreen is connected and delivering input, use + /// and instead. + /// + /// + /// + /// + /// var brushWidth = Touchscreen.isPressureSupported + /// && Touchscreen.current != null && Touchscreen.current.enabled + /// ? Touchscreen.current.primaryTouch.pressure.ReadValue() * maxBrushWidth + /// : defaultBrushWidth; + /// + /// + /// + /// + /// + /// + public static bool isPressureSupported => InputSystem.manager.IsTouchPressureSupported(); +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// /// The current global settings for Touchscreen devices. /// diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 994538ecc2..bc6b67452c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -3125,14 +3125,68 @@ internal void ApplyActions() DelegateHelpers.InvokeCallbacksSafe(ref m_ActionsChangedListeners, k_InputOnActionsChangeMarker, "InputSystem.onActionsChange"); } - internal unsafe long ExecuteGlobalCommand(ref TCommand command) +#if UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + /// + /// Sends a command to the engine's system endpoint, which answers questions about the + /// platform rather than about any one device. + /// + internal unsafe long ExecuteSystemCommand(ref TCommand command) where TCommand : struct, IInputDeviceCommandInfo { var ptr = (InputDeviceCommand*)UnsafeUtility.AddressOf(ref command); - // device id is irrelevant as we route it based on fourcc internally - return InputRuntime.s_Instance.DeviceCommand(0, ptr); + return m_Runtime.DeviceCommand(NativeInputCapabilities.systemDeviceId, ptr); + } + + // Platform capabilities cannot change while the process runs, so each is queried at most + // once. These are instance fields rather than statics on purpose: a domain reload or a test + // installing a different runtime builds a new InputManager, which discards the cache without + // needing an explicit reset hook. A failed query caches as Unknown so that an engine which + // cannot answer is asked once rather than on every read. + private InputCapabilitySupport? m_PenSupported; + private InputCapabilitySupport? m_MouseSupported; + private InputCapabilitySupport? m_TouchPressureSupported; + + internal bool IsPenSupported() + { + if (!m_PenSupported.HasValue) + { + var command = QueryPenSupportedCommand.Create(); + m_PenSupported = ExecuteSystemCommand(ref command) >= 0 + ? command.isSupported + : InputCapabilitySupport.Unknown; + } + + return m_PenSupported.Value == InputCapabilitySupport.Supported; + } + + internal bool IsMouseSupported() + { + if (!m_MouseSupported.HasValue) + { + var command = QueryMouseSupportedCommand.Create(); + m_MouseSupported = ExecuteSystemCommand(ref command) >= 0 + ? command.isSupported + : InputCapabilitySupport.Unknown; + } + + return m_MouseSupported.Value == InputCapabilitySupport.Supported; } + internal bool IsTouchPressureSupported() + { + if (!m_TouchPressureSupported.HasValue) + { + var command = QueryTouchPressureSupportedCommand.Create(); + m_TouchPressureSupported = ExecuteSystemCommand(ref command) >= 0 + ? command.isSupported + : InputCapabilitySupport.Unknown; + } + + return m_TouchPressureSupported.Value == InputCapabilitySupport.Supported; + } + +#endif // UNITY_INPUTSYSTEM_SUPPORTS_CAPABILITY_QUERIES + internal void AddAvailableDevicesThatAreNowRecognized() { for (var i = 0; i < m_AvailableDeviceCount; ++i) diff --git a/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef b/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef index 84e2b9faeb..d8f5257253 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef +++ b/Packages/com.unity.inputsystem/InputSystem/Unity.InputSystem.asmdef @@ -101,6 +101,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