-
Notifications
You must be signed in to change notification settings - Fork 328
FIX: Incorrect HID Stick values for LogicalMinimum with negative values #2246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
7cb59ce
9a5f397
bd804af
8ddd35e
637e5eb
c3ab660
7bba345
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,27 +218,9 @@ | |
|
||
// 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,120 @@ | |
////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) | ||
jfreire-unity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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")] | ||
|
||
// 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to pass errorMargin into test case? Isn't this given by 1 / (numerical logical range) + float.epsilon? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (If reinterpreted as floating point) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to have an odd test case above 8 bit boundary, e.g. 13 bits? |
||
{ | ||
// 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) | ||
0xA1, 0x01, // Collection (Application) | ||
0x85, 0x01, // Report ID (1) | ||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls) | ||
0x09, 0x30, // Usage (X) | ||
}; | ||
|
||
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(); | ||
|
||
// Callback to return the desired report descriptor. | ||
SetDeviceCommandCallbackToReturnReportDescriptor(deviceId, reportDescriptor); | ||
|
||
// Report device. | ||
runtime.ReportNewInputDevice( | ||
new InputDeviceDescription | ||
{ | ||
interfaceName = HID.kHIDInterface, | ||
manufacturer = "TestLogicalMinMaxParsing", | ||
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<Joystick>()); | ||
|
||
var parsedDescriptor = JsonUtility.FromJson<HID.HIDDeviceDescriptor>(device.description.capabilities); | ||
|
||
// 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"); | ||
} | ||
|
||
// 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] | ||
[Category("Devices")] | ||
public void Devices_CanCreateGenericHID_FromDeviceWithParsedReportDescriptor() | ||
|
@@ -1026,7 +1122,7 @@ | |
} | ||
|
||
[StructLayout(LayoutKind.Explicit)] | ||
struct SimpleJoystickLayout : IInputStateTypeInfo | ||
struct SimpleJoystickLayoutWithStick : IInputStateTypeInfo | ||
{ | ||
[FieldOffset(0)] public byte reportId; | ||
[FieldOffset(1)] public ushort x; | ||
|
@@ -1069,7 +1165,7 @@ | |
Assert.That(device, Is.TypeOf<Joystick>()); | ||
Assert.That(device["Stick"], Is.TypeOf<StickControl>()); | ||
|
||
InputSystem.QueueStateEvent(device, new SimpleJoystickLayout { 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(), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand the "mock out" part mentioned in comment here? Isn't this just a refactor of the previous code with a dedicated unsafe method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's just a refactor into a unsafe method since we're now using it multiple times. I can change the language if "mock" is not ideal but at least is the way I see it, since we're not actually getting anything from the platform layer in these tests.