From 0f183f3af50c61b87107eeae45aba3505d9c59b4 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 01/10] 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 20fe0b807025f7365dea861edf4405660a371df7 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 02/10] 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 50bf200fe00b3f0d448befa0cf7f116146d8aeb1 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 03/10] 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 e723cb21fcc2205720d87f3381e6027a173d3b5b 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 04/10] 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 2f57bfdb241d18377e1e341f151241ea34192637 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 05/10] 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 38b2f72a590adeaab3f6a3b7d547a76e17c1710c 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 06/10] 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 461811eefb00bac7df12f41cb8e6a1b2b60ad11b 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 07/10] 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 5c31f56b43d752733a925dfef41c458bde7ebbd0 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 08/10] 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 02e42ffe3392c71facf152e7476e1bf382a4d628 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 09/10] 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 From 6859f7e5eac5fb55689e22a9a71bb6f878b38721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freire?= Date: Tue, 21 Oct 2025 12:55:45 +0300 Subject: [PATCH 10/10] Added test case and remove control reading to narrow down testing to parsing --- Assets/Tests/InputSystem/Plugins/HIDTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Assets/Tests/InputSystem/Plugins/HIDTests.cs b/Assets/Tests/InputSystem/Plugins/HIDTests.cs index 081ac3105b..9b51d0aa7e 100644 --- a/Assets/Tests/InputSystem/Plugins/HIDTests.cs +++ b/Assets/Tests/InputSystem/Plugins/HIDTests.cs @@ -342,10 +342,14 @@ unsafe void SetDeviceCommandCallbackToReturnReportDescriptor(int deviceId, byte[ [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 + // Logical min -72, logical max -35 + [TestCase(8, new byte[] {0x15, 0xB8}, new byte[] {0x25, 0xDD}, -72, -35)] + // Logical min 30, logical max 78 + [TestCase(8, new byte[] {0x15, 0x1E}, new byte[] {0x25, 0x4E}, 30, 78)] + public void Devices_CanParseHIDDescriptor_WithSignedLogicalMinAndMaxValues(byte reportSizeBits, byte[] logicalMinBytes, byte[] logicalMaxBytes, int logicalMinExpected, int logicalMaxExpected) + { + // Dynamically create HID report descriptor for one X-axis with parameterized logical min/max var reportDescriptorStart = new byte[] { 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) @@ -393,9 +397,8 @@ public void Devices_CanParseHIDDescritpor_WithSignedLogicalMinAndMaxSticks(byte InputSystem.Update(); - var device = (Joystick)InputSystem.GetDeviceById(deviceId); + var device = InputSystem.GetDeviceById(deviceId); Assert.That(device, Is.Not.Null); - Assert.That(device, Is.TypeOf()); var parsedDescriptor = JsonUtility.FromJson(device.description.capabilities); @@ -408,11 +411,8 @@ public void Devices_CanParseHIDDescritpor_WithSignedLogicalMinAndMaxSticks(byte Assert.That(element.logicalMax, Is.EqualTo(logicalMaxExpected)); } else - Assert.Fail("Could not find X and Y elements in descriptor"); + Assert.Fail("Could not find X element in descriptor"); } - - // 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]