Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
164 changes: 164 additions & 0 deletions Assets/Tests/InputSystem/CoreTests_Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5895,4 +5895,168 @@ 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
// Platform capability queries. These are addressed to the engine's system endpoint rather than
// to a device, because they answer "can this platform do X" rather than "is an X connected".
// What a given platform actually answers is asserted natively in the engine repository, from
// PlatformDependent, where the file location gates the test to that platform.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"What a given platform actually answers is asserted natively in the engine repository, from PlatformDependent"
The way I read it it makes it sound like we have platfromdependent native test, but I don't think thats the case looking at the native PR.

@ekcoh ekcoh Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You read it right, and the comment was wrong. There are no per-platform tests in the engine's platform code at all. The native branch touches 13 platform files and not one of them is a test. The native tests live with the input module, and what they assert is that whichever platform they happen to run on returns a valid state, plus the endpoint contract itself: payload size validation, device codes rejected, and the enum values the managed mirror assumes.

So no test anywhere asserts the answer a named platform gives. A wrong per-platform answer is caught by cross-platform compilation and review, not by CI. Reworded the comment to say exactly that in e46584a.

Additional related cleanup in 3068ef5.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better, but I still think this comment is very verbose, do we need to say that:
"The engine's own tests assert that whichever platform they run on returns a
// valid state and that the endpoint rejects malformed payloads. No test asserts the answer a
// named platform gives, so a wrong per-platform answer is caught by review, not by CI."

Its a bit weird to comment what we are not doing and the reasoning is not that strong to justify it...

@ekcoh ekcoh Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely dropped this slop in 0951ea0 - value was zero


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
19 changes: 19 additions & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased] - yyyy-mm-dd

### Added

- Added `Pen.isSupported`, `Mouse.isSupported` and `Touchscreen.isPressureSupported`, reporting what the current platform is capable of rather than what is connected right now. Use them to decide whether to offer device-specific functionality at all. These are not drop-in replacements for the legacy `UnityEngine.Input.stylusTouchSupported` and `Input.mousePresent`: those conflated capability with presence, and on platforms where legacy did real hardware detection the new properties report capability instead, so they can be `true` where legacy was `false`. To act on input, check `Device.current != null && Device.current.enabled` rather than `Device.current != null` alone; a non-null `current` means a device object is registered, which several platforms do unconditionally, and `enabled` is what tells you it is active. The properties require an Editor version that can answer the query and are not compiled in on older versions. `Pen.isSupported` and `Touchscreen.isPressureSupported` come from [ISX-2046](https://jira.unity3d.com/browse/ISX-2046); `Mouse.isSupported` is the short-term scope of [ISX-2079](https://jira.unity3d.com/browse/ISX-2079), whose remaining half is a real presence primitive rather than a capability one
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated

```csharp
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated
// Before, legacy input. Reported true on any iPad new enough to pair a Pencil, whether or
// not one was paired, so the two questions could not be told apart.
if (Input.stylusTouchSupported) { }
if (Input.mousePresent) { }

// After. Capability is its own question with its own answer.
if (Pen.isSupported) { } // could a pen ever work on this platform
if (Mouse.isSupported) { } // could a mouse ever work on this platform

// Acting on input is a different question, and needs both parts. A non-null current means a
// device object is registered, not that hardware is attached; enabled is what says it is active.
if (Pen.current != null && Pen.current.enabled) { }
```

### 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,29 @@ 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 old Input Manager properties, such as `Input.mousePresent` and `Input.stylusTouchSupported`, 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:

- **Can this platform do it 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 never change while the application runs, so read them once and decide whether to offer device-specific
functionality.
- **Is a device available to read from right now?** Use `Device.current != null && Device.current.enabled`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see this causing a lot of trial and error for users, especially the enabled part. I would suggest adding a new Device.isAvailable to do that check, which would go well with isSupported from a semantic perspective.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and why I mentioned this in the PR description, there called device.IsUsable(), that is doable as an extension method which scales better than adding strongly coupled API - since it goes for all device types. However, its a simple addition and not load bearing but if you think I should fold that in and increase the API surface we commit to I am happy to, regardless, the parity it solved without it IMO.

@ekcoh ekcoh Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be - as you say - root class is sufficient. Leaving this open until you have a chance to respond @MorganHoarau

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense, not a fan of "usable" as a term as I don't think I've ever seen any Unity API use it. I'm not sure to understand why an extension method scales better than adding a new property on Device though. In both case we are exposing a new API. But this could be move as a separate ticket to improve the clarity around the topic.

However, I fear this would never be tackled on the side, so I would still add it here. It is a small addition and I don't see the concept going away anytime soon.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of "usable" either, and not a fan of "available" either since I would assume its available as soon as its connected, but this also gates on device being enabled.

I take the extension method thing back since I kind of ignored the fact that current design uses OO so having it on the base makes sense in this case instead of doing constrained generics.

I intererpret your reply as YES - add it? (Ignoring the open naming headache)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I built it, as InputDevice.isActive returning added && enabled, and then took it back out. Writing the tests made it clear it does not earn its place, at least not for the pattern we are documenting.

Past a current != null guard, isActive is exactly enabled. Removal resets current to null, see Mouse.OnRemoved, so added is always true on that path. It is also the same number of characters, so current != null && current.isActive buys nothing over current != null && current.enabled.

Where it genuinely differs is a cached device reference. RemoveDevice only clears m_DeviceIndex and never touches the Disabled flags, so a device that has been removed still reports enabled as true. That is a real trap, and worth having a name for, but it is one that documentation fixes as well as new API does.

So instead of the property, I documented the trap in the migration doc in 40f5b7d, with an example contrasting a cached reference against reading current. If we still think it pays for itself once that is written down, I am happy to revisit it on the core-package recreated PR, where the API review happens in the engine repo anyway.

Two shapes I ruled out on the way, in case they come up:

  • current?.enabled does not compile as a condition, since ?. on a bool member yields bool?. It needs == true, which is not shorter, and we use ?. nowhere else in the docs.
    An extension method that absorbs the null check is considered an anti-pattern generally so its dismissed since it looks like an instance call that should throw on null and silently does not.

You were right that the extension method does not scale better than a property when the design is already OO, so that part of my earlier answer was wrong regardless of where this lands.

OK to continue this discussion offline or in repurposed PR?

Both parts matter. A non-null `current` only means a device object is registered, which some platforms do
unconditionally regardless of whether hardware is attached, and `enabled` is what tells you the device is
active. The Device Simulator is a good example of 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.

Because the old properties mixed these two meanings, the capability properties are **not drop-in replacements**.
On platforms where the old property performed real detection, the new property answers the capability question
instead, so it can be `true` where the old one was `false`. The tables below note this per API.

## 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 +100,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 [`isSupported`](xref:UnityEngine.InputSystem.Mouse) to check whether the platform supports mouse input at all.<br/>Example: `if (Mouse.isSupported) ShowMouseSettings();`<br/>**Note:** Not a drop-in replacement; see [Device capability and device availability](#device-capability-and-device-availability) above. 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 [`isSupported`](xref:UnityEngine.InputSystem.Pen) to check whether the platform supports pen input at all.<br/>Example: `if (Pen.isSupported) ShowPenSettings();`<br/>**Note:** Not a drop-in replacement; see [Device capability and device availability](#device-capability-and-device-availability) above. 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 [`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
Loading
Loading