From ff2c526e5f0df5312f0ef90085793862d406053e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 26 Sep 2025 13:09:03 +0100 Subject: [PATCH 1/9] Add proposed fix by PR #2245 --- .../com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs index 3e7354584b..1fdc43ad6d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs @@ -257,6 +257,7 @@ internal static unsafe HIDDeviceDescriptor ReadHIDDeviceDescriptor(ref InputDevi // Update the descriptor on the device with the information we got. deviceDescription.capabilities = hidDeviceDescriptor.ToJson(); + Debug.Log($"Parsing HID descriptor from JSON for device '{deviceDescription.capabilities}'"); } else { @@ -385,7 +386,7 @@ public InputControlLayout Build() var yElementParameters = yElement.DetermineParameters(); builder.AddControl(stickName + "/x") - .WithFormat(xElement.isSigned ? InputStateBlock.FormatSBit : InputStateBlock.FormatBit) + .WithFormat(xElement.DetermineFormat()) .WithByteOffset((uint)(xElement.reportOffsetInBits / 8 - byteOffset)) .WithBitOffset((uint)(xElement.reportOffsetInBits % 8)) .WithSizeInBits((uint)xElement.reportSizeInBits) @@ -394,7 +395,7 @@ public InputControlLayout Build() .WithProcessors(xElement.DetermineProcessors()); builder.AddControl(stickName + "/y") - .WithFormat(yElement.isSigned ? InputStateBlock.FormatSBit : InputStateBlock.FormatBit) + .WithFormat(yElement.DetermineFormat()) .WithByteOffset((uint)(yElement.reportOffsetInBits / 8 - byteOffset)) .WithBitOffset((uint)(yElement.reportOffsetInBits % 8)) .WithSizeInBits((uint)yElement.reportSizeInBits) From 697f0292aaedd8b3453a11ddf0f17645af9dc3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 26 Sep 2025 13:10:17 +0100 Subject: [PATCH 2/9] Fix parsing signed bytes and incorrect byte reading for 4 bytes --- .../InputSystem/Plugins/HID/HIDParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs index efcd1e87c0..051b9bd1d6 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs @@ -267,7 +267,7 @@ private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr) { if (currentPtr >= endPtr) return 0; - return *currentPtr; + return (sbyte)*currentPtr; } // Read short. @@ -291,7 +291,7 @@ private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr) var data3 = *(currentPtr + 2); var data4 = *(currentPtr + 3); - return (data4 << 24) | (data3 << 24) | (data2 << 8) | data1; + return (data4 << 24) | (data3 << 16) | (data2 << 8) | data1; } Debug.Assert(false, "Should not reach here"); From 117410768db05f948558680c23d1aa4690b07b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 26 Sep 2025 13:12:15 +0100 Subject: [PATCH 3/9] Add test to validate stick values Also creates a simple layout struct for a device with only a single analog stick where the HID report descriptor states that the x and y values are between -127 and 127. --- Assets/Tests/InputSystem/Plugins/HIDTests.cs | 139 ++++++++++++++++--- 1 file changed, 118 insertions(+), 21 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index a36018f22b..e744c24c8e 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -218,27 +218,9 @@ public void Devices_CanCreateGenericHID_FromDeviceWithBinaryReportDescriptor() // The HID report descriptor is fetched from the device via an IOCTL. var deviceId = runtime.AllocateDeviceId(); - unsafe - { - runtime.SetDeviceCommandCallback(deviceId, - (id, commandPtr) => - { - if (commandPtr->type == HID.QueryHIDReportDescriptorSizeDeviceCommandType) - return reportDescriptor.Length; - if (commandPtr->type == HID.QueryHIDReportDescriptorDeviceCommandType - && commandPtr->payloadSizeInBytes >= reportDescriptor.Length) - { - fixed(byte* ptr = reportDescriptor) - { - UnsafeUtility.MemCpy(commandPtr->payloadPtr, ptr, reportDescriptor.Length); - return reportDescriptor.Length; - } - } + SetDeviceCommandCallbackToReturnReportDescriptor(deviceId, reportDescriptor); - return InputDeviceCommand.GenericFailure; - }); - } // Report device. runtime.ReportNewInputDevice( new InputDeviceDescription @@ -309,6 +291,111 @@ public void Devices_CanCreateGenericHID_FromDeviceWithBinaryReportDescriptor() ////TODO: check hat switch } + // This is used to mock out the IOCTL the HID device driver would use to return + // the report descriptor and its size. + unsafe void SetDeviceCommandCallbackToReturnReportDescriptor(int deviceId, byte[] reportDescriptor) + { + runtime.SetDeviceCommandCallback(deviceId, + (id, commandPtr) => + { + if (commandPtr->type == HID.QueryHIDReportDescriptorSizeDeviceCommandType) + return reportDescriptor.Length; + + if (commandPtr->type == HID.QueryHIDReportDescriptorDeviceCommandType + && commandPtr->payloadSizeInBytes >= reportDescriptor.Length) + { + fixed(byte* ptr = reportDescriptor) + { + UnsafeUtility.MemCpy(commandPtr->payloadPtr, ptr, reportDescriptor.Length); + return reportDescriptor.Length; + } + } + + return InputDeviceCommand.GenericFailure; + }); + } + + [Test] + [Category("HID Devices")] + public void Devices_CanCrateGenericHID_WithSignedLogicalMinAndMaxSticks() + { + // This is a HID report descriptor for a simple device with two analog sticks; + // Similar to a user that reported an issue in Discussions: + // https://discussions.unity.com/t/input-system-reading-invalid-values-from-hall-effect-keyboards/1684840/3 + var reportDescriptor = new byte[] + { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // Report ID (1) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs) + 0xC0, // End Collection + }; + + // The HID report descriptor is fetched from the device via an IOCTL. + var deviceId = runtime.AllocateDeviceId(); + + // Callback to return the desired report descriptor. + SetDeviceCommandCallbackToReturnReportDescriptor(deviceId, reportDescriptor); + + // Report device. + runtime.ReportNewInputDevice( + new InputDeviceDescription + { + interfaceName = HID.kHIDInterface, + manufacturer = "TestVendor", + product = "TestHID", + capabilities = new HID.HIDDeviceDescriptor + { + vendorId = 0x321, + productId = 0x432 + }.ToJson() + }.ToJson(), deviceId); + + InputSystem.Update(); + + var device = (Joystick)InputSystem.GetDeviceById(deviceId); + Assert.That(device, Is.Not.Null); + Assert.That(device, Is.TypeOf()); + + // Stick vector 2 should be centered at (0,0). + Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0f, 0f)).Using(Vector2EqualityComparer.Instance)); + + // Queue event with stick pushed to bottom. We assume Y axis is inverted by default in HID devices. + // See HID.HIDElementDescriptor.DetermineParameters() + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 0, y = 127 }); + InputSystem.Update(); + + Assert.That(device.stick.ReadValue() , Is.EqualTo(new Vector2(0f, -1f)).Using(Vector2EqualityComparer.Instance)); + + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 0, y = -127 }); + InputSystem.Update(); + + Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0f, 1f)).Using(Vector2EqualityComparer.Instance)); + + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 127, y = 0 }); + InputSystem.Update(); + + Assert.That(device.stick.ReadValue() , Is.EqualTo(new Vector2(1f, 0f)).Using(Vector2EqualityComparer.Instance)); + + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = -127, y = 0 }); + InputSystem.Update(); + + Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(-1f, 0f)).Using(Vector2EqualityComparer.Instance)); + + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 127, y = 127 }); + InputSystem.Update(); + + Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0.7071f, -0.7071f)).Using(Vector2EqualityComparer.Instance)); + } + [Test] [Category("Devices")] public void Devices_CanCreateGenericHID_FromDeviceWithParsedReportDescriptor() @@ -1026,7 +1113,7 @@ public void Devices_GenericHIDConvertsXAndYUsagesToStickControl() } [StructLayout(LayoutKind.Explicit)] - struct SimpleJoystickLayout : IInputStateTypeInfo + struct SimpleJoystickLayoutWithStickUshort : IInputStateTypeInfo { [FieldOffset(0)] public byte reportId; [FieldOffset(1)] public ushort x; @@ -1035,6 +1122,16 @@ struct SimpleJoystickLayout : IInputStateTypeInfo public FourCC format => new FourCC('H', 'I', 'D'); } + [StructLayout(LayoutKind.Explicit)] + struct SimpleJoystickLayoutWithStickByte : IInputStateTypeInfo + { + [FieldOffset(0)] public byte reportId; + [FieldOffset(1)] public sbyte x; + [FieldOffset(2)] public sbyte y; + + public FourCC format => new FourCC('H', 'I', 'D'); + } + [Test] [Category("Devices")] public void Devices_GenericHIDXAndYDrivesStickControl() @@ -1069,7 +1166,7 @@ public void Devices_GenericHIDXAndYDrivesStickControl() Assert.That(device, Is.TypeOf()); Assert.That(device["Stick"], Is.TypeOf()); - InputSystem.QueueStateEvent(device, new SimpleJoystickLayout { reportId = 1, x = ushort.MaxValue, y = ushort.MinValue }); + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickUshort { reportId = 1, x = ushort.MaxValue, y = ushort.MinValue }); InputSystem.Update(); Assert.That(device["stick"].ReadValueAsObject(), From a11f6ef8cf15c92778d7b360f5ab4dc0c20293e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Sat, 27 Sep 2025 15:47:25 +0300 Subject: [PATCH 4/9] Cast short when concatenating bytes --- .../com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs index 051b9bd1d6..5317277ee1 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs @@ -277,7 +277,7 @@ private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr) return 0; var data1 = *currentPtr; var data2 = *(currentPtr + 1); - return (data2 << 8) | data1; + return (short)(data2 << 8) | data1; } // Read int. From 6ee49d22c09efb39a8c417920d3f1dc8a7627fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Mon, 29 Sep 2025 10:38:19 +0300 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Assets/Tests/InputSystem/Plugins/HIDTests.cs | 2 +- Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index e744c24c8e..bec950f159 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -317,7 +317,7 @@ unsafe void SetDeviceCommandCallbackToReturnReportDescriptor(int deviceId, byte[ [Test] [Category("HID Devices")] - public void Devices_CanCrateGenericHID_WithSignedLogicalMinAndMaxSticks() + public void Devices_CanCreateGenericHID_WithSignedLogicalMinAndMaxSticks() { // This is a HID report descriptor for a simple device with two analog sticks; // Similar to a user that reported an issue in Discussions: diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs index 1fdc43ad6d..0a304dc068 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HID.cs @@ -257,7 +257,6 @@ internal static unsafe HIDDeviceDescriptor ReadHIDDeviceDescriptor(ref InputDevi // Update the descriptor on the device with the information we got. deviceDescription.capabilities = hidDeviceDescriptor.ToJson(); - Debug.Log($"Parsing HID descriptor from JSON for device '{deviceDescription.capabilities}'"); } else { From 9876c1308c0ec05be3b3703fc5534ae8363a8cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Wed, 8 Oct 2025 21:59:44 +0300 Subject: [PATCH 6/9] Refactor test to only focus on parsing HID descriptor --- Assets/Tests/InputSystem/Plugins/HIDTests.cs | 89 +++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index bec950f159..a14ec2229c 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -317,12 +317,26 @@ unsafe void SetDeviceCommandCallbackToReturnReportDescriptor(int deviceId, byte[ [Test] [Category("HID Devices")] - public void Devices_CanCreateGenericHID_WithSignedLogicalMinAndMaxSticks() + + // These descriptor values were generated with the Microsoft HID Authoring descriptor tool in + // https://github.com/microsoft/hidtools for the expexted values. + // Logical min 0, logical max 65535 + [TestCase(16, new byte[] {0x16, 0x00, 0x00}, new byte[] { 0x27, 0xFF, 0xFF, 0x00, 0x00 }, 0, 65535, 0.01f)] + // Logical min -32768, logical max 32767 + [TestCase(16, new byte[] {0x16, 0x00, 0x80}, new byte[] {0x26, 0xFF, 0x7F}, -32768, 32767, 0.01f)] + // Logical min 0, logical max 255 + [TestCase(8, new byte[] {0x15, 00}, new byte[] {0x26, 0xFF, 0x00}, 0, 255, 0.01f)] + // Logical min -128, logical max 127 + [TestCase(8, new byte[] {0x15, 0x80}, new byte[] {0x25, 0x7F}, -128, 127, 0.01f)] + // Logical min -16, logical max 15 (below 8 bit boundary) + [TestCase(5, new byte[] {0x15, 0xF0}, new byte[] {0x25, 0x0F}, -16, 15, 0)] + // Logical min 0, logical max 31 (below 8 bit boundary) + [TestCase(5, new byte[] {0x15, 0x00}, new byte[] {0x25, 0x1F}, 0, 31, 0)] + public void Devices_CanParseHIDDescritpor_WithSignedLogicalMinAndMaxSticks(byte reportSizeBits, byte[] logicalMinBytes, byte[] logicalMaxBytes, int logicalMinExpected, int logicalMaxExpected, float errorMargin) { - // This is a HID report descriptor for a simple device with two analog sticks; - // Similar to a user that reported an issue in Discussions: - // https://discussions.unity.com/t/input-system-reading-invalid-values-from-hall-effect-keyboards/1684840/3 - var reportDescriptor = new byte[] + // Dynamically create HID report descriptor for two analog sticks with parameterized logical min/max + + var reportDescriptorStart = new byte[] { 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x09, 0x05, // Usage (Game Pad) @@ -330,15 +344,23 @@ public void Devices_CanCreateGenericHID_WithSignedLogicalMinAndMaxSticks() 0x85, 0x01, // Report ID (1) 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x09, 0x30, // Usage (X) - 0x09, 0x31, // Usage (Y) - 0x15, 0x81, // Logical Minimum (-127) - 0x25, 0x7F, // Logical Maximum (127) - 0x75, 0x08, // Report Size (8) - 0x95, 0x02, // Report Count (2) - 0x81, 0x02, // Input (Data,Var,Abs) - 0xC0, // End Collection }; + var reportDescriptorEnd = new byte[] + { + 0x75, reportSizeBits, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0xC0, // End Collection + }; + + // Concatenate to form final descriptor based on test parameters where logical min/max bytes + // are inserted in the middle. + var reportDescriptor = reportDescriptorStart.Concat(logicalMinBytes). + Concat(logicalMaxBytes). + Concat(reportDescriptorEnd). + ToArray(); + // The HID report descriptor is fetched from the device via an IOCTL. var deviceId = runtime.AllocateDeviceId(); @@ -350,7 +372,7 @@ public void Devices_CanCreateGenericHID_WithSignedLogicalMinAndMaxSticks() new InputDeviceDescription { interfaceName = HID.kHIDInterface, - manufacturer = "TestVendor", + manufacturer = "TestLogicalMinMaxParsing", product = "TestHID", capabilities = new HID.HIDDeviceDescriptor { @@ -365,35 +387,22 @@ public void Devices_CanCreateGenericHID_WithSignedLogicalMinAndMaxSticks() Assert.That(device, Is.Not.Null); Assert.That(device, Is.TypeOf()); - // Stick vector 2 should be centered at (0,0). - Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0f, 0f)).Using(Vector2EqualityComparer.Instance)); - - // Queue event with stick pushed to bottom. We assume Y axis is inverted by default in HID devices. - // See HID.HIDElementDescriptor.DetermineParameters() - InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 0, y = 127 }); - InputSystem.Update(); - - Assert.That(device.stick.ReadValue() , Is.EqualTo(new Vector2(0f, -1f)).Using(Vector2EqualityComparer.Instance)); - - InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 0, y = -127 }); - InputSystem.Update(); + var parsedDescriptor = JsonUtility.FromJson(device.description.capabilities); - Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0f, 1f)).Using(Vector2EqualityComparer.Instance)); - - InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 127, y = 0 }); - InputSystem.Update(); - - Assert.That(device.stick.ReadValue() , Is.EqualTo(new Vector2(1f, 0f)).Using(Vector2EqualityComparer.Instance)); - - InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = -127, y = 0 }); - InputSystem.Update(); - - Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(-1f, 0f)).Using(Vector2EqualityComparer.Instance)); - - InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickByte { reportId = 1, x = 127, y = 127 }); - InputSystem.Update(); + // Check we parsed the values as expected + foreach (var element in parsedDescriptor.elements) + { + if (element.usage == (int)HID.GenericDesktop.X) + { + Assert.That(element.logicalMin, Is.EqualTo(logicalMinExpected)); + Assert.That(element.logicalMax, Is.EqualTo(logicalMaxExpected)); + } + else + Assert.Fail("Could not find X and Y elements in descriptor"); + } - Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0.7071f, -0.7071f)).Using(Vector2EqualityComparer.Instance)); + // Stick vector 2 should be centered at (0,0) when initialized + Assert.That(device.stick.ReadValue(), Is.EqualTo(new Vector2(0f, 0f)).Using(Vector2EqualityComparer.Instance)); } [Test] From bfc7bacfc00f4b83ab721583c8feed0762875353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Wed, 8 Oct 2025 22:00:05 +0300 Subject: [PATCH 7/9] Remove unused test struct --- Assets/Tests/InputSystem/Plugins/HIDTests.cs | 14 ++------------ .../InputSystem/Plugins/HID/HIDParser.cs | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index a14ec2229c..efd0ca188f 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -1122,7 +1122,7 @@ public void Devices_GenericHIDConvertsXAndYUsagesToStickControl() } [StructLayout(LayoutKind.Explicit)] - struct SimpleJoystickLayoutWithStickUshort : IInputStateTypeInfo + struct SimpleJoystickLayoutWithStick : IInputStateTypeInfo { [FieldOffset(0)] public byte reportId; [FieldOffset(1)] public ushort x; @@ -1131,16 +1131,6 @@ struct SimpleJoystickLayoutWithStickUshort : IInputStateTypeInfo public FourCC format => new FourCC('H', 'I', 'D'); } - [StructLayout(LayoutKind.Explicit)] - struct SimpleJoystickLayoutWithStickByte : IInputStateTypeInfo - { - [FieldOffset(0)] public byte reportId; - [FieldOffset(1)] public sbyte x; - [FieldOffset(2)] public sbyte y; - - public FourCC format => new FourCC('H', 'I', 'D'); - } - [Test] [Category("Devices")] public void Devices_GenericHIDXAndYDrivesStickControl() @@ -1175,7 +1165,7 @@ public void Devices_GenericHIDXAndYDrivesStickControl() Assert.That(device, Is.TypeOf()); Assert.That(device["Stick"], Is.TypeOf()); - InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStickUshort { reportId = 1, x = ushort.MaxValue, y = ushort.MinValue }); + InputSystem.QueueStateEvent(device, new SimpleJoystickLayoutWithStick { reportId = 1, x = ushort.MaxValue, y = ushort.MinValue }); InputSystem.Update(); Assert.That(device["stick"].ReadValueAsObject(), diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs index 5317277ee1..be38227f27 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/HID/HIDParser.cs @@ -277,7 +277,7 @@ private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr) return 0; var data1 = *currentPtr; var data2 = *(currentPtr + 1); - return (short)(data2 << 8) | data1; + return (short)((data2 << 8) | data1); } // Read int. From 08d6a1257bd08b5a285a7782d5b3ee4817551e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 17 Oct 2025 13:29:45 +0300 Subject: [PATCH 8/9] Expand testing cases --- Assets/Tests/InputSystem/Plugins/HIDTests.cs | 28 +++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index efd0ca188f..081ac3105b 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -298,6 +298,9 @@ unsafe void SetDeviceCommandCallbackToReturnReportDescriptor(int deviceId, byte[ runtime.SetDeviceCommandCallback(deviceId, (id, commandPtr) => { + if (commandPtr == null) + return InputDeviceCommand.GenericFailure; + if (commandPtr->type == HID.QueryHIDReportDescriptorSizeDeviceCommandType) return reportDescriptor.Length; @@ -317,22 +320,29 @@ unsafe void SetDeviceCommandCallbackToReturnReportDescriptor(int deviceId, byte[ [Test] [Category("HID Devices")] - // These descriptor values were generated with the Microsoft HID Authoring descriptor tool in - // https://github.com/microsoft/hidtools for the expexted values. + // https://github.com/microsoft/hidtools for the expected value: // Logical min 0, logical max 65535 - [TestCase(16, new byte[] {0x16, 0x00, 0x00}, new byte[] { 0x27, 0xFF, 0xFF, 0x00, 0x00 }, 0, 65535, 0.01f)] + [TestCase(16, new byte[] {0x16, 0x00, 0x00}, new byte[] { 0x27, 0xFF, 0xFF, 0x00, 0x00 }, 0, 65535)] // Logical min -32768, logical max 32767 - [TestCase(16, new byte[] {0x16, 0x00, 0x80}, new byte[] {0x26, 0xFF, 0x7F}, -32768, 32767, 0.01f)] + [TestCase(16, new byte[] {0x16, 0x00, 0x80}, new byte[] {0x26, 0xFF, 0x7F}, -32768, 32767)] // Logical min 0, logical max 255 - [TestCase(8, new byte[] {0x15, 00}, new byte[] {0x26, 0xFF, 0x00}, 0, 255, 0.01f)] + [TestCase(8, new byte[] {0x15, 00}, new byte[] {0x26, 0xFF, 0x00}, 0, 255)] // Logical min -128, logical max 127 - [TestCase(8, new byte[] {0x15, 0x80}, new byte[] {0x25, 0x7F}, -128, 127, 0.01f)] + [TestCase(8, new byte[] {0x15, 0x80}, new byte[] {0x25, 0x7F}, -128, 127)] // Logical min -16, logical max 15 (below 8 bit boundary) - [TestCase(5, new byte[] {0x15, 0xF0}, new byte[] {0x25, 0x0F}, -16, 15, 0)] + [TestCase(5, new byte[] {0x15, 0xF0}, new byte[] {0x25, 0x0F}, -16, 15)] // Logical min 0, logical max 31 (below 8 bit boundary) - [TestCase(5, new byte[] {0x15, 0x00}, new byte[] {0x25, 0x1F}, 0, 31, 0)] - public void Devices_CanParseHIDDescritpor_WithSignedLogicalMinAndMaxSticks(byte reportSizeBits, byte[] logicalMinBytes, byte[] logicalMaxBytes, int logicalMinExpected, int logicalMaxExpected, float errorMargin) + [TestCase(5, new byte[] {0x15, 0x00}, new byte[] {0x25, 0x1F}, 0, 31)] + // Logical min -4096, logical max 4095 (crosses byte boundary) + [TestCase(13, new byte[] {0x16, 0x00, 0xF0}, new byte[] {0x26, 0xFF, 0x0F}, -4096, 4095)] + // Logical min 0, logical max 8191 (crosses byte boundary) + [TestCase(13, new byte[] {0x15, 0x00}, new byte[] {0x26, 0xFF, 0x1F}, 0, 8191)] + // Logical min 0, logical max 16777215 (24 bit) + [TestCase(24, new byte[] {0x15, 0x00}, new byte[] {0x27, 0xFF, 0xFF, 0xFF, 0x00}, 0, 16777215)] + // Logical min -8388608, logical max 8388607 (24 bit) + [TestCase(24, new byte[] {0x17, 0x00, 0x00, 0x80, 0xFF}, new byte[] {0x27, 0xFF, 0xFF, 0x7F, 0x00}, -8388608, 8388607)] + public void Devices_CanParseHIDDescritpor_WithSignedLogicalMinAndMaxSticks(byte reportSizeBits, byte[] logicalMinBytes, byte[] logicalMaxBytes, int logicalMinExpected, int logicalMaxExpected) { // Dynamically create HID report descriptor for two analog sticks with parameterized logical min/max From 775b3dd55200fccda8d4d71dc5e0042ef31ec2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Fri, 17 Oct 2025 14:17:39 +0300 Subject: [PATCH 9/9] Update CHANGELOG.md --- Packages/com.unity.inputsystem/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index aee42048d6..c3a33de107 100644 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -22,6 +22,9 @@ however, it has to be formatted properly to pass verification tests. - Fixed an issue in `DeltaStateEvent.From` where unsafe code would throw exception or crash if internal pointer `currentStatePtr` was `null`. [ISXB-1637](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637). - Fixed an issue in `InputTestFixture.Set` where attempting to change state of a device not belonging to the test fixture context would result in null pointer exception or crash. [ISXB-1637](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637). - Fixed an issue where input action parameter overrides applied via `InputActionRebindingExtensions.ApplyParameterOverride` would not be applied if the associated binding has the empty string as `InputBinding.name`and the binding mask also has the empty string as name. (ISXB-1721). +- Fixed HID parsing not handling logical minimum and maximum values correctly when they are negative. This applies for platforms that parse HID descriptors in the package, e.g. macOS at the moment. +- Fix usage of correct data format for stick axes in HID Layout Builder ([User contribution](https://github.com/Unity-Technologies/InputSystem/pull/2245)) + ## [1.15.0] - 2025-10-03