@@ -218,27 +218,9 @@ public void Devices_CanCreateGenericHID_FromDeviceWithBinaryReportDescriptor()
218218
219219 // The HID report descriptor is fetched from the device via an IOCTL.
220220 var deviceId = runtime . AllocateDeviceId ( ) ;
221- unsafe
222- {
223- runtime . SetDeviceCommandCallback ( deviceId ,
224- ( id , commandPtr ) =>
225- {
226- if ( commandPtr ->type == HID . QueryHIDReportDescriptorSizeDeviceCommandType )
227- return reportDescriptor . Length ;
228221
229- if ( commandPtr ->type == HID . QueryHIDReportDescriptorDeviceCommandType
230- && commandPtr ->payloadSizeInBytes >= reportDescriptor . Length )
231- {
232- fixed( byte * ptr = reportDescriptor )
233- {
234- UnsafeUtility . MemCpy ( commandPtr ->payloadPtr , ptr , reportDescriptor . Length ) ;
235- return reportDescriptor . Length ;
236- }
237- }
222+ SetDeviceCommandCallbackToReturnReportDescriptor ( deviceId , reportDescriptor ) ;
238223
239- return InputDeviceCommand . GenericFailure ;
240- } ) ;
241- }
242224 // Report device.
243225 runtime . ReportNewInputDevice (
244226 new InputDeviceDescription
@@ -309,6 +291,130 @@ public void Devices_CanCreateGenericHID_FromDeviceWithBinaryReportDescriptor()
309291 ////TODO: check hat switch
310292 }
311293
294+ // This is used to mock out the IOCTL the HID device driver would use to return
295+ // the report descriptor and its size.
296+ unsafe void SetDeviceCommandCallbackToReturnReportDescriptor ( int deviceId , byte [ ] reportDescriptor )
297+ {
298+ runtime . SetDeviceCommandCallback ( deviceId ,
299+ ( id , commandPtr ) =>
300+ {
301+ if ( commandPtr == null )
302+ return InputDeviceCommand . GenericFailure ;
303+
304+ if ( commandPtr ->type == HID . QueryHIDReportDescriptorSizeDeviceCommandType )
305+ return reportDescriptor . Length ;
306+
307+ if ( commandPtr ->type == HID . QueryHIDReportDescriptorDeviceCommandType
308+ && commandPtr ->payloadSizeInBytes >= reportDescriptor . Length )
309+ {
310+ fixed( byte * ptr = reportDescriptor )
311+ {
312+ UnsafeUtility . MemCpy ( commandPtr ->payloadPtr , ptr , reportDescriptor . Length ) ;
313+ return reportDescriptor . Length ;
314+ }
315+ }
316+
317+ return InputDeviceCommand . GenericFailure ;
318+ } ) ;
319+ }
320+
321+ [ Test ]
322+ [ Category ( "HID Devices" ) ]
323+ // These descriptor values were generated with the Microsoft HID Authoring descriptor tool in
324+ // https://github.com/microsoft/hidtools for the expected value:
325+ // Logical min 0, logical max 65535
326+ [ TestCase ( 16 , new byte [ ] { 0x16 , 0x00 , 0x00 } , new byte [ ] { 0x27 , 0xFF , 0xFF , 0x00 , 0x00 } , 0 , 65535 ) ]
327+ // Logical min -32768, logical max 32767
328+ [ TestCase ( 16 , new byte [ ] { 0x16 , 0x00 , 0x80 } , new byte [ ] { 0x26 , 0xFF , 0x7F } , - 32768 , 32767 ) ]
329+ // Logical min 0, logical max 255
330+ [ TestCase ( 8 , new byte [ ] { 0x15 , 00 } , new byte [ ] { 0x26 , 0xFF , 0x00 } , 0 , 255 ) ]
331+ // Logical min -128, logical max 127
332+ [ TestCase ( 8 , new byte [ ] { 0x15 , 0x80 } , new byte [ ] { 0x25 , 0x7F } , - 128 , 127 ) ]
333+ // Logical min -16, logical max 15 (below 8 bit boundary)
334+ [ TestCase ( 5 , new byte [ ] { 0x15 , 0xF0 } , new byte [ ] { 0x25 , 0x0F } , - 16 , 15 ) ]
335+ // Logical min 0, logical max 31 (below 8 bit boundary)
336+ [ TestCase ( 5 , new byte [ ] { 0x15 , 0x00 } , new byte [ ] { 0x25 , 0x1F } , 0 , 31 ) ]
337+ // Logical min -4096, logical max 4095 (crosses byte boundary)
338+ [ TestCase ( 13 , new byte [ ] { 0x16 , 0x00 , 0xF0 } , new byte [ ] { 0x26 , 0xFF , 0x0F } , - 4096 , 4095 ) ]
339+ // Logical min 0, logical max 8191 (crosses byte boundary)
340+ [ TestCase ( 13 , new byte [ ] { 0x15 , 0x00 } , new byte [ ] { 0x26 , 0xFF , 0x1F } , 0 , 8191 ) ]
341+ // Logical min 0, logical max 16777215 (24 bit)
342+ [ TestCase ( 24 , new byte [ ] { 0x15 , 0x00 } , new byte [ ] { 0x27 , 0xFF , 0xFF , 0xFF , 0x00 } , 0 , 16777215 ) ]
343+ // Logical min -8388608, logical max 8388607 (24 bit)
344+ [ TestCase ( 24 , new byte [ ] { 0x17 , 0x00 , 0x00 , 0x80 , 0xFF } , new byte [ ] { 0x27 , 0xFF , 0xFF , 0x7F , 0x00 } , - 8388608 , 8388607 ) ]
345+ // Logical min -72, logical max -35
346+ [ TestCase ( 8 , new byte [ ] { 0x15 , 0xB8 } , new byte [ ] { 0x25 , 0xDD } , - 72 , - 35 ) ]
347+ // Logical min 30, logical max 78
348+ [ TestCase ( 8 , new byte [ ] { 0x15 , 0x1E } , new byte [ ] { 0x25 , 0x4E } , 30 , 78 ) ]
349+
350+ public void Devices_CanParseHIDDescriptor_WithSignedLogicalMinAndMaxValues ( byte reportSizeBits , byte [ ] logicalMinBytes , byte [ ] logicalMaxBytes , int logicalMinExpected , int logicalMaxExpected )
351+ {
352+ // Dynamically create HID report descriptor for one X-axis with parameterized logical min/max
353+ var reportDescriptorStart = new byte [ ]
354+ {
355+ 0x05 , 0x01 , // Usage Page (Generic Desktop Ctrls)
356+ 0x09 , 0x05 , // Usage (Game Pad)
357+ 0xA1 , 0x01 , // Collection (Application)
358+ 0x85 , 0x01 , // Report ID (1)
359+ 0x05 , 0x01 , // Usage Page (Generic Desktop Ctrls)
360+ 0x09 , 0x30 , // Usage (X)
361+ } ;
362+
363+ var reportDescriptorEnd = new byte [ ]
364+ {
365+ 0x75 , reportSizeBits , // Report Size (8)
366+ 0x95 , 0x01 , // Report Count (1)
367+ 0x81 , 0x02 , // Input (Data,Var,Abs)
368+ 0xC0 , // End Collection
369+ } ;
370+
371+ // Concatenate to form final descriptor based on test parameters where logical min/max bytes
372+ // are inserted in the middle.
373+ var reportDescriptor = reportDescriptorStart . Concat ( logicalMinBytes ) .
374+ Concat ( logicalMaxBytes ) .
375+ Concat ( reportDescriptorEnd ) .
376+ ToArray ( ) ;
377+
378+ // The HID report descriptor is fetched from the device via an IOCTL.
379+ var deviceId = runtime . AllocateDeviceId ( ) ;
380+
381+ // Callback to return the desired report descriptor.
382+ SetDeviceCommandCallbackToReturnReportDescriptor ( deviceId , reportDescriptor ) ;
383+
384+ // Report device.
385+ runtime . ReportNewInputDevice (
386+ new InputDeviceDescription
387+ {
388+ interfaceName = HID . kHIDInterface ,
389+ manufacturer = "TestLogicalMinMaxParsing" ,
390+ product = "TestHID" ,
391+ capabilities = new HID . HIDDeviceDescriptor
392+ {
393+ vendorId = 0x321 ,
394+ productId = 0x432
395+ } . ToJson ( )
396+ } . ToJson ( ) , deviceId ) ;
397+
398+ InputSystem . Update ( ) ;
399+
400+ var device = InputSystem . GetDeviceById ( deviceId ) ;
401+ Assert . That ( device , Is . Not . Null ) ;
402+
403+ var parsedDescriptor = JsonUtility . FromJson < HID . HIDDeviceDescriptor > ( device . description . capabilities ) ;
404+
405+ // Check we parsed the values as expected
406+ foreach ( var element in parsedDescriptor . elements )
407+ {
408+ if ( element . usage == ( int ) HID . GenericDesktop . X )
409+ {
410+ Assert . That ( element . logicalMin , Is . EqualTo ( logicalMinExpected ) ) ;
411+ Assert . That ( element . logicalMax , Is . EqualTo ( logicalMaxExpected ) ) ;
412+ }
413+ else
414+ Assert . Fail ( "Could not find X element in descriptor" ) ;
415+ }
416+ }
417+
312418 [ Test ]
313419 [ Category ( "Devices" ) ]
314420 public void Devices_CanCreateGenericHID_FromDeviceWithParsedReportDescriptor ( )
@@ -1026,7 +1132,7 @@ public void Devices_GenericHIDConvertsXAndYUsagesToStickControl()
10261132 }
10271133
10281134 [ StructLayout ( LayoutKind . Explicit ) ]
1029- struct SimpleJoystickLayout : IInputStateTypeInfo
1135+ struct SimpleJoystickLayoutWithStick : IInputStateTypeInfo
10301136 {
10311137 [ FieldOffset ( 0 ) ] public byte reportId ;
10321138 [ FieldOffset ( 1 ) ] public ushort x ;
@@ -1069,7 +1175,7 @@ public void Devices_GenericHIDXAndYDrivesStickControl()
10691175 Assert . That ( device , Is . TypeOf < Joystick > ( ) ) ;
10701176 Assert . That ( device [ "Stick" ] , Is . TypeOf < StickControl > ( ) ) ;
10711177
1072- InputSystem . QueueStateEvent ( device , new SimpleJoystickLayout { reportId = 1 , x = ushort . MaxValue , y = ushort . MinValue } ) ;
1178+ InputSystem . QueueStateEvent ( device , new SimpleJoystickLayoutWithStick { reportId = 1 , x = ushort . MaxValue , y = ushort . MinValue } ) ;
10731179 InputSystem . Update ( ) ;
10741180
10751181 Assert . That ( device [ "stick" ] . ReadValueAsObject ( ) ,
0 commit comments